Isolated coding environments with AI tools (Claude Code, OpenCode, Crush, Qwen Code) on Debian 13, powered by Incus.
Three modes of operation via two scripts:
-
Container mode (
coding-sandbox)- Needs to build the images before the first run (requires sudo)
- Ephemeral container that mounts your current directory from the host.
- The configuration (the sandbox users home directory) is stored in the host users
~/.coding-sandboxdirectory.
-
VM mode (
coding-sandbox --vm)- Needs the same build step as the containert above (that build creates images for the VM an the container)
- It starts a Persistent VM once where you check out and work on projects directly.
- Every subsequent access is a new shell inside the same VM
- All data code and configuration is isolated on the VM disk, not the host.
- Keep in mind that a rebuild of the VM will destroy all data inside!
- The idea is to checkout projects into the VM, work on them directly and push all work back into the repo.
-
Quick sandbox (
quick-coding-sandbox)- This lightweight variant skips the custom image build and uses stock Debian images from Incus.
- On first run or setup (
quick-coding-sandbox setup) you can select which tool(s) to install. - Ephemeral container that mounts your current directory from the host.
- The configuration (the sandbox users home directory) is stored in the host users
~/.quick-coding-sandboxdirectory.
-
AI Coding tools (the unified installation scripts for these tools are in the
toolssubdirectory)- Claude Code (default)
- OpenCode
- Crush
- Qwen Code
- This project is Beta quality. It's quite stable for me and my current work style, but expect rough edges for other usages.
- I'm open to feedback and suggestions.
- I'll continue to experiment, the different sandbox modes might change in the future.
- I'm happy to hear about your use cases.
# Build the package
make deb
sudo dpkg -i coding-sandbox_*.deb# Just run directly from the repo
./coding-sandbox build
./coding-sandbox shell# Build the image (once, requires root for distrobuilder)
coding-sandbox build
# Option A: Ephemeral container — mounts $PWD, destroyed on exit
cd ~/projects/my-repo
coding-sandbox claude
# Option B: Persistent VM — clone repos inside, work directly
coding-sandbox --vm shell
git clone git@github.com:you/project.git
cd project && claude- Incus installed and initialized (
incus init) - User is member of the incus group
- distrobuilder (for image builds, requires root)
- debootstrap (for Debian rootfs bootstrapping during build)
- APT proxy (apt-cacher-ng) recommended for fast repeated builds
For faster repeated builds, install apt-cacher-ng and configure it on your host:
sudo apt install apt-cacher-ngThe build script automatically detects if Acquire::https::Proxy is configured and uses it. No further configuration needed.
If you skip this, builds will work but download all packages from Debian mirrors each time.
VM (--vm) |
Container (default) | |
|---|---|---|
| Startup | 30–180s | 1–5s |
| Persistence | Survives reboots, lives until destroy |
Destroyed on exit |
| Workspace | Clone projects inside the VM | $PWD mounted from host |
| Home directory | On VM disk | Persistent on host (~/.coding-sandbox/home) |
| Isolation | Full kernel (own kernel, GRUB, ACPI) | Shared host kernel |
| Host sync | Timezone, locale, APT proxy, git config | Git config (first run) |
| Use case | Long-running dev environment | Quick one-off tasks, CI-like runs |
coding-sandbox [command] [--vm]
shell Interactive shell (default)
build Build container and VM images
start Start the VM (VM only)
stop Stop the VM (VM only)
destroy Delete the VM and images [--purge to delete build cache]
rebuild Destroy and rebuild from scratch [--purge to force full rebuild]
status Show instance and image status (with version freshness)
doctor System diagnostics and dependency check
version Show version
help Show this help
Add --vm to use VM mode instead of container mode.
All settings are configurable via environment variables:
| Variable | Default | Description |
|---|---|---|
CODING_SANDBOX_VM |
coding-sandbox |
VM instance name |
CODING_SANDBOX_IMAGE |
coding-sandbox/debian-13 |
VM image alias |
CODING_SANDBOX_CONTAINER_IMAGE |
coding-sandbox/debian-13/container |
Container image alias |
CODING_SANDBOX_BUILDDIR |
~/.cache/coding-sandbox (dev) / /var/cache/coding-sandbox (.deb) |
Build artifact directory |
CODING_SANDBOX_TOOLS |
claude-code opencode crush |
Tools to install during build |
CODING_SANDBOX_PORTS |
(empty) | Space-separated ports to forward (localhost-only) |
CODING_SANDBOX_USER |
sandbox |
User inside VM/container |
CODING_SANDBOX_CPU |
1 |
VM vCPU count |
CODING_SANDBOX_RAM |
2GiB |
VM RAM allocation |
CODING_SANDBOX_DISK |
20GiB |
VM root disk size |
CODING_SANDBOX_HOME |
~/.coding-sandbox/home |
Container persistent home |
- OS: Debian 13 (Trixie) amd64 with systemd
- AI Tools: Claude Code, OpenCode, Crush, Qwen Code (configurable via
CODING_SANDBOX_TOOLS) - Dev Tools: jq, gh (GitHub CLI), glow, bat, ncdu, ripgrep, fzf, shellcheck
- Essentials: git, vim, nano, curl, wget, htop, SSH server
- User:
sandboxwith passwordless sudo
Both AI tools need to be configured on first use (in either VM or container):
- Claude Code: Run
claudeand follow the login prompt, or runclaude loginbeforehand. See Claude Code docs for details. - OpenCode: Requires a configuration file (
~/.config/opencode/config.json) with your provider and API key. See OpenCode README for setup instructions. - Crush: See Crush README for configuration.
- Qwen Code: Run
qwenand follow the setup prompt, or set API key via environment variable.
In containers, credentials persist in the shared home directory (~/.coding-sandbox/home) across runs. In the VM, they persist on the VM disk.
Each tool generates an uninstaller script at /usr/local/lib/uninstallers/<tool-name> inside the VM. To manually uninstall a tool:
# Inside the VM (via coding-sandbox --vm shell)
sudo /usr/local/lib/uninstallers/claude-code
# Or from the host
incus exec coding-sandbox -- /usr/local/lib/uninstallers/claude-codeRunning coding-sandbox --vm update automatically uses uninstallers for clean tool updates.
For lightweight containers without custom image builds:
quick-coding-sandbox setup # install tools (one-time)
quick-coding-sandbox shell # launch containerUses stock Debian images from Incus image server — no root, no distrobuilder needed. A sandbox user (UID 1000, passwordless sudo) is created inside the container at launch, matching the behavior of coding-sandbox.
If you are not in the incus-admin group and use Incus 6.0.x LTS (Debian Trixie/Sid) with an incus-user restricted project, container launches will fail because shift=true is blocked in restricted projects. This affects coding-sandbox without --vm and quick-coding-sandbox. VM mode is not affected.
Users in the incus-admin group are not affected — they operate in the unrestricted default project where shift=true works directly.
Workaround 1: Configure the restricted project (recommended)
Ask your administrator (or run with sudo yourself) to allow UID mapping in your project:
# Replace user-1000 with your project name (incus project list to check)
sudo incus project set user-1000 restricted.containers.lowlevel=allow
sudo incus project set user-1000 restricted.idmap.uid=1000
sudo incus project set user-1000 restricted.idmap.gid=1000
# Allow the UID mapping at OS level (if not already present)
echo "root:1000:1" | sudo tee -a /etc/subuid
echo "root:1000:1" | sudo tee -a /etc/subgidThis allows the scripts to fall back to raw.idmap when shift=true is blocked. The /etc/subuid and /etc/subgid entries permit the kernel to map host UID/GID 1000 into the container namespace. This is a minimal, targeted entry — it does not change host permissions or grant new privileges to any user. The security impact is comparable to shift=true (container processes with the mapped UID can access the host user's mounted files, which is the intended behavior).
Workaround 2: Use the incus-admin group
sudo usermod -aG incus-admin $USER
# Log out and back in for group change to take effectMembers of incus-admin operate in the unrestricted default project where shift=true works without issues. This is the simpler option but grants full Incus administrative privileges.
Future: Once Debian stable ships an Incus version that handles UID mapping in restricted projects without these workarounds, this section and the related fallback code can be removed.
Forward ports from container/VM to host (localhost-only):
CODING_SANDBOX_PORTS="8080 3000" coding-sandbox shell- Images: First build takes 10-30 minutes. Subsequent rebuilds reuse debootstrap cache.
- Chrome integration: Use
--remote-debugging-port=9222inside the sandbox, forward port 9222. - Custom tools: Set
CODING_SANDBOX_TOOLS="claude-code crush"to control which tools are installed (default: all).
# Run unit tests
bash tests/run.sh
# Run unit + integration tests (requires Incus, creates/destroys instances)
bash tests/run.sh --integration