Linux 05 Sierpień 2026

Distrobox — testing apps in isolation without upgrade risk

Also in:

You have a working app install where everything is fine, and a new version is on the horizon. You want to try it before overwriting the package on the host. Classic Docker is painful for GUI apps (especially Electron): X11, GPU, FUSE, login flows. Distrobox solves exactly that — a container with full graphical integration, while the host install stays untouched.

I recently hit two cases like this. Sometimes I need an older slicer build for my 3D printer — Snapmaker can make a mess on upgrade and settings go sideways. I’ve also been seeing Cursor issues: the to-do list broken in 3.12 but fine in 3.9; models noticeably slower on some task types, while an older build still feels normal. Something you can run in an isolated environment would help…

This article first explains what Distrobox is and when it makes sense, then walks through a real example: testing Cursor 3.14.x next to a stable 3.9.x install — separate home, AppImage, working-directory pitfalls, and a practical OAuth workaround.


What Distrobox is

Distrobox is a layer on top of Podman or Docker. The goal is not hard sandboxing (like Flatpak), but integration. The container gets:

  • X11 / Wayland access (GUI windows look native),
  • audio, USB, disks,
  • (by default) your home directory,
  • the ability to export apps into the host application menu.

In practice you run software from another distribution — or a separate copy of the same program — without dual-booting and without fighting full Docker under Electron. The same pattern works for an IDE, a slicer, an audio editor, or any app you don’t want to upgrade blindly.

What people use it for

Distrobox is mainly for running software “as if on another distro” while keeping your normal host desktop. Typical uses:

  1. Apps missing or painful on your distro — Fedora/Arch-only packages, old dependencies, library conflicts.
  2. Isolated dev environments — a separate stack (Node, Python, toolchain) without polluting the system.
  3. GUI from a container — a big win over plain Docker: windows, audio, and home files work without hand-wiring sockets.
  4. Older or newer versions side by side — test or roll back without upgrading the host (Cursor, 3D slicers, etc.).
  5. Exporting an app to the host menu (distrobox-export) — it shows up like a normal system application.

What it is not: a hard security sandbox. It deliberately integrates tightly with the host (home, X11, devices). If the goal is “I don’t trust this code”, prefer Flatpak or a regular container with tight mounts.

Distrobox vs plain Docker

Distrobox Classic Docker
Goal integrated GUI / dev environment on the host service isolation, CI, servers
GUI windows works out of the box (X11/Wayland) manual socket mounts, lots of friction
Home shared with the host by default usually a separate volume
Sandbox weak — don’t treat it as security isolation stronger (with sensible config)

Requirements

  • Linux with Podman or Docker (Distrobox prefers Podman when both are installed),
  • the distrobox package from your distro, or the upstream install script.

On Kubuntu / Ubuntu this is usually enough:

sudo apt install distrobox podman

Example: a new Cursor without upgrading the host

Goal: try the latest Cursor (here: AppImage 3.14.7) without touching the working .deb (e.g. 3.9.16 at /usr/bin/cursor).

Strategy:

  1. A Distrobox container with a separate home directory.
  2. Inside it — the newest AppImage.
  3. Editor data (~/.config/Cursor) lands in the container home, not the host one.

Without a separate home, Distrobox mounts your real ~/ and the new Cursor can overwrite settings / the SQLite DB of the working install. We avoid that.

Host                          Distrobox (cursor-test)
─────                         ───────────────────────
/usr/bin/cursor  (3.9.x)      Cursor.AppImage (3.14.x)
~/.config/Cursor              ~/cursor-test-home/.config/Cursor
     ↑ untouched                    ↑ isolated data

The same layout applies to other apps: separate --home, binary / AppImage / package inside the container, host left alone.


Step 1 — separate home and creating the container

mkdir -p ~/cursor-test-home

distrobox create \
  --name cursor-test \
  --image ubuntu:24.04 \
  --home ~/cursor-test-home \
  --additional-packages "libfuse2t64 libgtk-3-0 libnss3 libxss1 libasound2t64"

--home ~/cursor-test-home is the important part. Distrobox will also mount the real /home/$USER (host integration), but $HOME inside the container points at the test directory — and that’s where apps that respect $HOME / XDG should write config.

Enter the container:

distrobox enter -nw cursor-test

The -nw flag (no workdir) drops you straight into the container $HOME. Without it, Distrobox keeps the host’s current directory — if you enter from /home/you, you stay in /home/you after enter, and it looks like --home did nothing.

Check:

echo $HOME    # /home/you/cursor-test-home
pwd           # with -nw: the same
ls ~          # skel (.bashrc, .profile, …) — not your full host home

If pwd shows /home/you but echo $HOME shows .../cursor-test-home, you’re fine — just cd ~ or enter with -nw.


Step 2 — downloading Cursor (AppImage)

The endpoint https://cursor.com/api/download?platform=linux-x64&releaseTrack=stable does not return a binary. It returns JSON with downloadUrl, debUrl, version, etc.

Wrong (you get a ~600 B JSON file):

curl -L "https://cursor.com/api/download?platform=linux-x64&releaseTrack=stable" -o Cursor.AppImage

Correct:

# inside the container, in $HOME
URL=$(python3 -c "import json,urllib.request; print(json.load(urllib.request.urlopen('https://cursor.com/api/download?platform=linux-x64&releaseTrack=stable'))['downloadUrl'])")
curl -fL -o Cursor.AppImage "$URL"
chmod +x Cursor.AppImage
file Cursor.AppImage   # expect: ELF 64-bit … (~280 MB)

Launch:

./Cursor.AppImage
# on Ubuntu 24.04+ you may need:
./Cursor.AppImage --no-sandbox
# or if FUSE is missing:
./Cursor.AppImage --appimage-extract-and-run

Optional launcher:

cat > ~/run-cursor.sh <<'EOF'
#!/bin/bash
set -euo pipefail
export HOME=/home/$USER/cursor-test-home   # adjust the path
cd "$HOME"
exec ./Cursor.AppImage --no-sandbox "$@"
EOF
chmod +x ~/run-cursor.sh

Step 3 — pitfall: OAuth login in the container

Cursor signs in via OAuth in an external browser (xdg-open). In Distrobox, clicking “Log in” often does nothing.

Why

  1. Distrobox replaces xdg-open with distrobox-host-exec.
  2. distrobox-host-exec relies on host-spawn, and host-spawn needs Flatpak on the host (D-Bus interface org.freedesktop.Flatpak.Development).
  3. Without Flatpak you get Error: Cannot download host-spawn / exit code 127 — the browser never opens.

Even if the browser does open, there’s a second issue: after login, the cursor://… callback on the host usually hits the host Cursor (handler under /usr/share/applications), not the container instance.

For an AI feature smoke test, don’t fight that. It’s simpler to copy the session from the working host install.


Step 4 — OAuth workaround: copy the session from the host

Cursor stores tokens in SQLite:

~/.config/Cursor/User/globalStorage/state.vscdb

Keys include:

  • cursorAuth/accessToken
  • cursorAuth/refreshToken
  • cursorAuth/cachedEmail
  • cursorAuth/cachedTeam
  • cursorAuth/stripeMembershipType

Quit the test Cursor, then on the host:

import sqlite3, shutil, time

host = "/home/USER/.config/Cursor/User/globalStorage/state.vscdb"
test = "/home/USER/cursor-test-home/.config/Cursor/User/globalStorage/state.vscdb"
keys = [
    "cursorAuth/accessToken",
    "cursorAuth/refreshToken",
    "cursorAuth/cachedEmail",
    "cursorAuth/cachedScopedProfile",
    "cursorAuth/cachedSignUpType",
    "cursorAuth/cachedTeam",
    "cursorAuth/stripeMembershipType",
]

h = sqlite3.connect(f"file:{host}?mode=ro", uri=True)
vals = {k: h.execute("SELECT value FROM ItemTable WHERE key=?", (k,)).fetchone()[0] for k in keys}
h.close()

shutil.copy2(test, test + f".bak-{int(time.time())}")
t = sqlite3.connect(test)
for k, v in vals.items():
    t.execute(
        "INSERT INTO ItemTable(key,value) VALUES(?,?) "
        "ON CONFLICT(key) DO UPDATE SET value=excluded.value",
        (k, v),
    )
t.execute("DELETE FROM ItemTable WHERE key=?", ("glass.lastSignedInAuthId",))
t.commit()
t.close()
print("OK")

After restarting the AppImage, it should be logged into the same account as the host. That is deliberate session sharing — fine for testing, not hard identity isolation.

If you still want the browser path: install flatpak on the host (official host-spawn path) or install a browser inside the container. Then remember the cursor:// handler conflict.


Step 5 — what to test before upgrading the host

  • GUI launch, layout, opening a project,
  • Agent / Chat / terminal,
  • Git, extensions, MCP,
  • your day-to-day workflow,
  • the tools you care about, command duration.

If it looks good — upgrade the host .deb as usual. If not — delete the container and keep the old version.


Cleanup

distrobox stop cursor-test
distrobox rm cursor-test
rm -rf ~/cursor-test-home

The host Cursor stays unchanged.


Checklist — quick cheat sheet

  1. distrobox create … --home ~/cursor-test-home
  2. distrobox enter -nw cursor-test
  3. Download the real AppImage from downloadUrl (not the raw API JSON)
  4. Copy tokens from the host state.vscdb (OAuth in the container usually stalls)
  5. Exercise your typical workflow
  6. After the test: distrobox rm + optional host .deb upgrade

Summary

Distrobox is a practical “second Linux” for GUI apps without dual-boot and without Docker circus under Electron. It helps with a 3D slicer and an IDE alike: separate --home, binary in the container, host untouched.

For Cursor specifically, OAuth inside Distrobox can go completely silent — for a quick test, the easiest move is copying the session from the working install. Host upgrades stop being a leap of faith: you verify first, then overwrite what already works.