Skip to main content

Safe YOLO with AI code tools: universal isolated development environment using kernel overlayfs

Project description

boq

Isolated development environment using Linux kernel overlayfs.

Why boq?

boq allows:

  • You to use yolo or --dangerously-skip-permissions mode without worrying about AI screwing up your Linux system.
  • You to instantly create an isolated environment that runs exactly like your familiar system.
    • All your familiar commands and AI tools ready to use - no need to re-install, login, authenticate, or configure.
    • Zero learning curve - same shell, same muscle memory, same dotfiles. It just feels like home.
    • Full access to your entire $HOME - all your projects, references, and personal scripts. Not trapped in a single project directory.
    • Your existing compilers, interpreters, and build cache ready to use - no re-downloading, no re-building.
  • You to run multiple isolated environments simultaneously (dev, experiment, feature-x) - each with independent file changes, but sharing the same build toolchain.
  • You to let multiple AI agents work in parallel on the same project, each in its own isolated environment.
  • You to experiment fearlessly - don't like the result? boq destroy and start fresh. No leftover config files, broken dependencies, or system pollution.
  • No Dockerfile to write, no image to build, instant start.

Note: Files unchanged by boq are read from the host in real-time. If you git pull on the host, unmodified files will update inside the boq. Best practice: keep host files untouched while AI is working, and sync periodically.

Features

  • Configurable overlay directories (default: $HOME, /usr, /opt, /home/linuxbrew)
  • Passthrough paths that bypass overlay and share with host
  • Full file locking support (unlike fuse-overlayfs)
  • TOML-based configuration with 3-tier override system

Networking

  • In default bridge mode, each boq gets a static IP and a host alias boq-<name>.
  • From host, you can access services inside boq directly, for example: curl http://boq-dev:8080.
  • Legacy rootless running boqs do not provide this host-access alias; restart them with boq stop <name> then boq enter <name> to upgrade.
  • For docker-backed boqs (create --runtime docker), boq auto-manages a user-defined network boq-docker-net. Its subnet is auto-detected once and persisted in ~/.boq/.docker-subnet.
  • With docker runtime, container.network only supports host / none (or unset). Any other configured network name is rejected.

Installation

Requires Python 3.11+ and one container runtime: podman or docker.

# Install one runtime (choose one)
sudo apt install podman
# or
sudo apt install docker.io

# Install boq (choose one)
pipx install boq          # Recommended: isolated global install
uv tool install boq       # Alternative: using uv
pip install boq           # Or: install to current environment

# For development
git clone https://github.com/zhengbuqian/boq.git
cd boq
uv pip install -e .               # Editable install

Optionally, add the following to your ~/.bashrc or ~/.zshrc to get auto completion and auto yolo mode:

if [ -z "$BOQ_NAME" ]; then
    # add auto completion only in host.
    eval "$(boq completion -s zsh)"
else
    echo "In boq env, using yolo mode by default for AI cli tools."
    alias claude="claude --dangerously-skip-permissions"
    alias codex="codex --dangerously-bypass-approvals-and-sandbox"
    alias gemini="gemini --yolo"
    # if you use zsh and see error like `zsh: can't rename ~/.zsh_history.new to $HISTFILE`,
    # you can suppress this error by unsetting `HIST_SAVE_BY_COPY` option.
    unsetopt HIST_SAVE_BY_COPY
fi

# I personally also use this to know where am I, in host or in some boq.
PROMPT="[%m] ${PROMPT}"

Note: Requires sudo for mounting kernel overlayfs.

Quick Start

# Create a boq and enter it (exit shell to detach, container keeps running)
boq create dev

# Re-enter existing boq
boq enter dev

# See what changed
boq diff dev

# Run a command in boq
boq run dev "make test"

# Stop boq
boq stop dev

# Remove boq
boq destroy dev

Commands

Command Description
create <name> Create a new boq and enter it (use --no-enter to skip, use --runtime to pick backend, and --docker-sudo/--no-docker-sudo for docker mode)
enter [name] Attach shell to boq (starts if not running; use --migrate-to-docker to migrate a stopped boq to docker)
run <name> <cmd> Run a command in boq (must be running; may be interrupted by stop/destroy)
stop [name] Stop a boq immediately (may interrupt active sessions)
destroy <name> Destroy a boq immediately (stops if running; may interrupt active sessions)
diff [name] [path] Show changes made in boq
status [name] Show boq status
list [--size] List all boq instances (--size also shows disk usage)
version Show current boq version
completion -s <shell> Output shell completion script

Default name is default for commands that accept [name].

diff options

boq diff dev                      # Show all content changes
boq diff dev ~/project            # Filter by path (respects .gitignore)
boq diff dev --no-gitignore       # Include gitignored files
boq diff dev --include-metadata   # Include metadata-only changes

Shell Completion

# Bash: add to ~/.bashrc
eval "$(boq completion -s bash)"

# Zsh: add to ~/.zshrc
eval "$(boq completion -s zsh)"

Configuration

TOML-based configuration with 3-tier override system:

  1. defaults.toml (shipped with package) - base defaults
  2. ~/.boq/config.toml (user global) - override defaults
  3. ~/.boq/<name>/config.toml (per-boq) - override for specific boq

Higher priority overrides lower. Lists append by default (use <key>_replace to fully replace).

Example ~/.boq/config.toml

[container]
# Change default shell
shell = "/bin/zsh"

# Change base image
image = "ubuntu:24.04"

[runtime]
# auto: prefer docker when available, else podman
default = "auto"

[docker]
# default for create when runtime is docker
use_sudo = false

[container.env]
# Add custom environment variables
MY_VAR = "value"

[overlays]
# Add additional overlay directory
"/data" = "data"

[passthrough]
# Add paths that bypass overlay (appends to default list)
paths = [
    "$HOME/.my-tool",
]

# Or replace the entire list
paths_replace = [
    "$HOME/.zsh_history",
    "$HOME/.claude",
]

[mounts]
# Custom mounts: mount a file/directory to a different location in the container
# Each entry specifies: src (host path), dest (container path), mode ("ro" or "rw")
custom = [
    { src = "/path/on/host", dest = "/path/in/container", mode = "ro" },
]

Default Configuration

[container]
image = "ubuntu:22.04"
shell = "/bin/bash"
capabilities = ["SYS_PTRACE"]

[runtime]
default = "auto"

[docker]
use_sudo = false

[overlays]
"$HOME" = "home"
"/usr" = "usr"
"/opt" = "opt"
"/home/linuxbrew" = "linuxbrew"

[passthrough]
# sharing shell history and AI cli tools config/history bewteen boq and host by default
paths = [
    "$HOME/.zsh_history",
    "$HOME/.bash_history",
    "$HOME/.claude",
    "$HOME/.gemini",
    "$HOME/.codex",
    "$HOME/.factory",
]

[mounts]
readonly = ["/bin", "/lib", "/lib64", "/lib32", "/sbin"]
direct = []
custom = []

Environment variable expansion ($HOME, $USER, etc.) is supported in all string values.

How It Works

  • create sets up overlays, starts container, and enters shell (use --no-enter to skip)
  • enter attaches a shell; exiting detaches but container stays running
  • create picks runtime automatically by default (prefer docker when available, else podman)
  • create --runtime docker|podman explicitly selects runtime
  • create --docker-sudo/--no-docker-sudo controls docker command prefix for that boq
  • enter --migrate-to-docker can migrate a stopped podman boq to docker backend
  • run executes a single command (container must be running)
  • stop immediately stops container and unmounts overlays (active sessions may be interrupted)
  • destroy immediately stops/removes container and deletes boq files (active sessions may be interrupted)
  • Container manages its own /proc, /sys, /dev, /tmp

Overlay Directories

Multiple directories are overlayed (copy-on-write) using kernel overlayfs. Changes are stored in ~/.boq/<name>/<overlay>/upper/.

Read-only Mounts

  • /bin, /lib, /lib64, /lib32, /sbin - essential system directories, read-only from host

Known Limitations

Host file changes visible in running boq

Symptom: If you run git pull on the host while boq is running, new files appear inside the boq.

Cause: Overlayfs lowerdir is live, not a snapshot.

Workaround: Do NOT modify files on the host while boq is running.

  • To update code: run git pull inside the boq, OR
  • Stop the boq first, update on host, then re-enter

Troubleshooting

Symlinks to external/mounted drives don't work

Symptom: You have a symlink like ~/.ccache -> /mnt/nvme1n1p1/.ccache, but inside boq the symlink is broken:

$ ls -la ~/.ccache
lrwxrwxrwx 1 user user 22 Feb 21 2024 .ccache -> /mnt/nvme1n1p1/.ccache
$ ls ~/.ccache/
ls: cannot access '/home/user/.ccache/': No such file or directory

Cause: By default, /mnt (or other mount points for external drives) is not mounted inside boq. The symlink exists in the $HOME overlay, but its target doesn't exist.

Solutions: There are three approaches, each with different trade-offs:

Solution 1: Direct mount (shared read-write)

Mount the entire parent directory directly:

[mounts]
direct = ["/mnt"]
  • Symlink resolves correctly (~/.ccache/mnt/nvme1n1p1/.ccache)
  • Read-write: changes in boq are immediately visible on host
  • Use case: You want to share build cache between host and boq (e.g., ccache, pip cache)

Solution 2: Passthrough the symlink target (shared read-write)

Add the symlink path to passthrough:

[passthrough]
paths = ["$HOME/.ccache"]
  • ~/.ccache becomes accessible (podman resolves the symlink and mounts the target directly)
  • Note: Inside boq, ~/.ccache appears as a directory, not a symlink
  • Read-write: changes in boq are immediately visible on host
  • Use case: You only need one specific path shared, not the entire /mnt

Solution 3: Overlay individual mount points (isolated copy-on-write)

Overlay each mounted drive separately:

[overlays]
"/mnt/nvme1n1p1" = "nvme1n1p1"
"/mnt/nvme2n1p1" = "nvme2n1p1"
  • Symlink resolves correctly (~/.ccache/mnt/nvme1n1p1/.ccache)
  • Copy-on-write: changes in boq are isolated, host doesn't see them
  • Changes stored in ~/.boq/<name>/nvme1n1p1/upper/
  • Use case: You want to experiment with cached data without affecting host cache

Important: You cannot overlay /mnt directly if it contains nested mount points (e.g., /mnt/nvme1n1p1, /mnt/nvme2n1p1). Overlayfs cannot see through mount points - you'll only see empty directories. You must overlay each mount point individually.

Summary

Solution Access via symlink Host sees changes Use case
direct = ["/mnt"] ✅ Yes Share cache with host
passthrough = ["$HOME/.ccache"] ✅ (as directory) ✅ Yes Share specific path only
overlays per mount point ❌ No (isolated) Experiment without affecting host

DNS resolution fails inside container

Error: "Temporary failure in name resolution"

Cause: systemd-resolved uses a stub resolver at an address that doesn't work inside the container.

Solution: boq auto-detects your DNS configuration:

  1. If systemd-resolved is in use, it mounts /run/systemd/resolve/resolv.conf (with actual upstream DNS servers)
  2. Otherwise, it falls back to /etc/resolv.conf

If auto-detection doesn't work for your system, use a custom mount to override:

[mounts]
custom = [
    { src = "/path/to/your/resolv.conf", dest = "/etc/resolv.conf", mode = "ro" },
]

Using VS Code / Cursor / Antigravity / Windsurf and more with boq

Since boq isolates files via overlayfs, editors on the host can't directly see changes made inside boq. Here are three approaches:

Option 1: Attach editor to the container (Recommended)

Use the Dev Containers extension to attach to the running boq container:

  1. Install "Dev Containers" extension
  2. Ctrl+Shift+P → "Dev Containers: Attach to Running Container..."
  3. Select boq-<name>

You get full editor experience inside the container with all your build cache and intermediate results. Changes stay isolated.

Note (rootful vs rootless):

  • If your boq container is running with rootful podman, Dev Containers may not see it via plain podman.
  • Create a wrapper command and point Dev Containers to it:
mkdir -p ~/.local/bin
cat > ~/.local/bin/sudopodman <<'EOF'
#!/usr/bin/env bash
exec sudo -n /usr/bin/podman "$@"
EOF
chmod +x ~/.local/bin/sudopodman

Then set Dev > Containers: Docker Path to sudopodman.

If you're still using rootless containers, keep using podman.

Option 2: Use git worktree with passthrough

Create a separate working copy that's shared between host and boq:

# On host
git worktree add ../my-project-boq feature-branch
# ~/.boq/config.toml
[passthrough]
paths = ["$HOME/projects/my-project-boq"]

Edit on host, build inside boq. Multiple boqs can work on different worktrees without interference. Downside: no shared build cache—each worktree builds from scratch.

Option 3: Make project directory passthrough

[passthrough]
paths = ["$HOME/projects/my-project"]

Simplest setup—edit on host, run in boq. But only one boq can safely work on this project at a time (changes are shared).

Summary

Approach Build cache Multiple boqs Edit from host
Dev Containers ✅ Yes ✅ Yes ❌ No (edit in container)
Git worktree + passthrough ❌ No ✅ Yes ✅ Yes
Project passthrough ✅ Yes ❌ No (conflicts) ✅ Yes

Design Notes

Why kernel overlayfs instead of fuse-overlayfs?

  • fuse-overlayfs mounts are only accessible by the user who created them (permission issues with podman)
  • fuse-overlayfs doesn't fully support POSIX file locking
  • Kernel overlayfs requires sudo but provides full compatibility

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

boq-0.0.7.tar.gz (30.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

boq-0.0.7-py3-none-any.whl (33.0 kB view details)

Uploaded Python 3

File details

Details for the file boq-0.0.7.tar.gz.

File metadata

  • Download URL: boq-0.0.7.tar.gz
  • Upload date:
  • Size: 30.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for boq-0.0.7.tar.gz
Algorithm Hash digest
SHA256 d6a3ef4821af910825b0175814280650610ea399602274e0a6620643e2ed375e
MD5 156f436ebd0297c6c31ecc711900d504
BLAKE2b-256 37aa84190a3817de3ca608862fc7bfeb05d23512e008c86aa036261248adfb96

See more details on using hashes here.

Provenance

The following attestation bundles were made for boq-0.0.7.tar.gz:

Publisher: publish.yml on zhengbuqian/boq

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file boq-0.0.7-py3-none-any.whl.

File metadata

  • Download URL: boq-0.0.7-py3-none-any.whl
  • Upload date:
  • Size: 33.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for boq-0.0.7-py3-none-any.whl
Algorithm Hash digest
SHA256 ec8dbedabf617ded3eb50742673c28cf93dd9edef6d7eb282b787975267ab5c9
MD5 8114a35f37a1999ee95abf5e37ce25fd
BLAKE2b-256 9fa5a20786288b4c5fbe8e5e3f3ef983645f4cf9e409c0632b3b966edddab6cd

See more details on using hashes here.

Provenance

The following attestation bundles were made for boq-0.0.7-py3-none-any.whl:

Publisher: publish.yml on zhengbuqian/boq

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page