pi 053dac5308
Validate / base-change-warning (push) Successful in 7s
Validate / docs-check (push) Successful in 14s
Validate / validate-omos (push) Successful in 4m23s
Validate / validate-with-pi (push) Failing after 4m38s
Publish Docker Image / base-decide (push) Successful in 11s
Publish Docker Image / resolve-versions (push) Successful in 5s
Publish Docker Image / build-base (push) Has been skipped
Validate / validate-base (push) Successful in 5m27s
Validate / validate-pi-only (push) Failing after 3m59s
Publish Docker Image / smoke-base (push) Successful in 3m33s
Publish Docker Image / smoke-omos (push) Successful in 7m4s
Publish Docker Image / smoke-with-pi (push) Successful in 4m35s
Publish Docker Image / smoke-omos-with-pi (push) Successful in 5m31s
Validate / validate-omos-with-pi (push) Failing after 15m20s
Publish Docker Image / smoke-pi-only (push) Successful in 6m16s
Publish Docker Image / build-variant-base (push) Successful in 14m24s
Publish Docker Image / build-variant-omos (push) Successful in 19m13s
Publish Docker Image / build-variant-with-pi (push) Successful in 25m46s
Publish Docker Image / build-variant-pi-only (push) Successful in 15m39s
Publish Docker Image / build-variant-omos-with-pi (push) Successful in 27m57s
Publish Docker Image / promote-base-latest (push) Has been skipped
Publish Docker Image / update-description (push) Successful in 7s
docs: promote CHANGELOG Unreleased -> v1.15.13c
2026-06-03 21:46:37 +02:00

opencode-devbox

Portable AI developer environment in a Docker container. Run opencode on any Docker-capable machine with configurable LLM providers, dev tools, and host filesystem access.

Why?

The official ghcr.io/anomalyco/opencode image (now archived) was Alpine-based and minimal — no git, no dev tools, broken PTY support due to musl/glibc incompatibility. This project provides a Debian-based, production-ready alternative using the current v1.x release.

Quick Start

Just want to run it? No git clone needed — grab the two template files:

mkdir -p ~/opencode-devbox && cd ~/opencode-devbox

# Pull docker-compose.yml and the .env template
curl -O https://gitea.jordbo.se/joakimp/opencode-devbox/raw/branch/main/docker-compose.yml
curl -fsSL https://gitea.jordbo.se/joakimp/opencode-devbox/raw/branch/main/.env.example -o .env

# Edit .env — at minimum: OPENCODE_PROVIDER, the matching API key,
# WORKSPACE_PATH, GIT_USER_NAME, GIT_USER_EMAIL.
$EDITOR .env

# Pull and run
docker compose run --rm devbox

This pulls joakimp/opencode-devbox:latest from Docker Hub, mounts WORKSPACE_PATH at /workspace, and drops you straight into opencode. Use bash instead of (no command) to land in a shell first — useful for aws sso login, pi, omos, etc.

Want to hack on the image itself, follow upstream changes, or rebuild from source? Clone the repo:

git clone ssh://gitea.jordbo.se:2222/joakimp/opencode-devbox.git
cd opencode-devbox

# Configure
cp .env.example .env
# Edit .env with your provider, API key, workspace path, git config

# Install git hooks (secret scanning) before committing
brew install gitleaks  # macOS / Linuxbrew
./setup-hooks.sh

# Build and run
docker compose run --rm devbox

Features

  • Debian trixie base — glibc, full PTY/terminal support
  • Configurable providers — Anthropic, OpenAI, AWS Bedrock via env vars
  • Host filesystem access — bind mount any directory as /workspace
  • SSH key forwarding — git push/pull to private repos
  • MCP server support — Node.js included for npx-based MCP servers
  • Non-root user — runs as developer with UID auto-matched to workspace owner (sudo available)
  • Python via uvuv package manager included; install Python on demand with uv python install
  • Rust via rustuprustup-init included; bootstrap Rust on demand with rustup-init -y
  • Optional runtimes — Python (apt), Go via build args (Node.js always included — required for opencode v1.x)
  • Multi-agent orchestration — optional oh-my-opencode-slim integration via build arg
  • AWS CLI v2 — built-in SSO/Bedrock authentication with headless device-code flow
  • Multi-arch — amd64 and arm64

Usage

Prerequisites

Bind-mounted directories must exist on the host before starting the container. Docker creates missing directories as root-owned, which causes permission issues.

# Required: workspace for your projects
mkdir -p ~/projects

Connecting to the container

From your laptop, SSH into the remote server where Docker is running, then start the container:

# 1. SSH into the remote server
ssh user@remote-server

# 2. Navigate to the project
cd opencode-devbox

# 3. Start the container with an interactive shell
docker compose run --rm devbox bash

# You're now inside the container — run commands here
aws sso login --sso-session <your-sso-session> --use-device-code
opencode

Running modes

Interactive shell — enter the container, run multiple commands:

docker compose run --rm devbox bash

Direct to opencode — skips the shell, launches opencode immediately:

docker compose run --rm devbox

Background container — keep it running, attach when needed:

# Start in background
docker compose up -d

# Attach a shell to the running container
docker compose exec -u developer devbox bash

# Or run a single command inside it
docker compose exec -u developer devbox aws --version

run creates a new container (cleaned up with --rm). exec attaches to an already running one.

Configuration

Environment Variables

Variable Description Default
OPENCODE_PROVIDER LLM provider (anthropic, openai, amazon-bedrock) anthropic
OPENCODE_MODEL Model override Provider default
ANTHROPIC_API_KEY Anthropic API key
OPENAI_API_KEY OpenAI API key
AWS_REGION AWS region for Bedrock us-east-1
AWS_PROFILE AWS SSO profile name default
GIT_USER_NAME Git commit author name
GIT_USER_EMAIL Git commit author email
WORKSPACE_PATH Host path to mount .
SSH_KEY_PATH Host SSH key directory ~/.ssh
DEVBOX_LAN_ACCESS LAN-access mode: auto (jump only on VM-backed hosts), jump (always), off auto
HOST_SSH_USER Username to SSH into the host as (required for the LAN jump)
DEVBOX_HOST_ALIAS Hostname used to reach the container host host.docker.internal
USER_UID Override container user UID Auto-detect from /workspace
USER_GID Override container user GID Auto-detect from /workspace
LANG System locale en_US.UTF-8
LANGUAGE Language priority list en_US:en
LC_ALL Override all locale settings en_US.UTF-8
EDITOR Default text editor nvim
ENABLE_OMOS Enable oh-my-opencode-slim multi-agent orchestration false
OMOS_TMUX Enable tmux pane integration for OMOS false
OMOS_SKILLS Install OMOS recommended skills on first run true
OMOS_RESET Force regenerate OMOS config on next start false
SKILLSET_CONTAINER_PATH Path to skillset repo inside container (for auto-deploy when not at /workspace/skillset) Auto-detect

Reaching your LAN from the container

The devbox works the same way whether the host is native Linux Docker or a VM-backed runtime (macOS OrbStack / Docker Desktop, or Docker Desktop on Windows) — but their networking differs:

  • Native Linux Docker: the host NATs container egress onto its LAN, so other devices on your LAN are reachable directly. Nothing to configure.
  • VM-backed (macOS / Docker Desktop): the container runs in a Linux VM behind the host's network stack. The host's directly-attached LAN peers are not bridged into the container by default — only the host itself and routed subnets are reachable.

On every start the entrypoint detects which case applies. On VM-backed hosts it generates a writable ~/.ssh-local/config that uses the host as an SSH jump to reach LAN peers; on native Linux it does nothing.

To enable it on a VM-backed host:

  1. Set HOST_SSH_USER=<your host username> in .env.
  2. Start the container once. The entrypoint prints a public key — append it to your host's ~/.ssh/authorized_keys.
  3. Ensure the host's SSH server is on (on macOS: System Settings → General → Sharing → Remote Login).
  4. Reach the host with dssh host, and reach LAN peers by adding ProxyJump host to their entries in your bind-mounted ~/.ssh/config:
# in your host ~/.ssh/config (mounted read-only into the container)
Host my-nas
    HostName 192.168.1.50
    User admin
    ProxyJump host

Then dssh my-nas routes container → host → LAN peer. (dssh/dscp wrap ssh -F ~/.ssh-local/config; the host config is pulled in via Include.)

This ships the mechanism only — your specific target hosts live in your own ~/.ssh/config, never baked into the image. Set DEVBOX_LAN_ACCESS=off to disable, or =jump to force it (e.g. native Linux with extra_hosts: ["host.docker.internal:host-gateway"]).

Custom opencode config

Opencode configuration is persisted automatically via the named volume devbox-opencode-config. This volume is mounted at /home/developer/.config/opencode by default — no host directory setup required. All changes to opencode.jsonc, skills, and (on the OMOS variant) oh-my-opencode-slim.json survive container recreation.

When an existing opencode.jsonc is found in the volume, the OPENCODE_PROVIDER auto-config is skipped.

Alternative: host bind-mount — if you specifically want to share config from the host (e.g. to version-control it or sync across machines), replace the named volume with a bind mount:

volumes:
  - ~/.config/opencode:/home/developer/.config/opencode

Portability note: The mounted config runs inside a Linux container. Any absolute paths inside opencode.jsonc (for example, host-specific plugin entries like file:///usr/local/lib/node_modules/... or file:///opt/homebrew/...) will not resolve inside the container. Prefer bare package specifiers (e.g. "oh-my-opencode-slim") that resolve via node_modules lookup, which works on both macOS and Linux hosts.

Custom skills

Skills are deployed automatically from a skillset repo on container start. The entrypoint detects the skillset location in this order:

  1. SKILLSET_CONTAINER_PATH env var (explicit path to skillset repo inside container)
  2. ~/skillset mount (if present)
  3. /workspace/skillset fallback (if your workspace contains a skillset/ directory)

When a skillset repo is detected, its skills are symlinked into ~/.agents/skills/ automatically. No manual configuration needed.

Warning: Do not bind-mount a host ~/.agents/skills directory directly into the container. This conflicts with the symlink-based auto-deploy mechanism and causes broken skill references.

Neovim configuration

The image includes neovim 0.12 with EDITOR=nvim set by default. To use your own neovim config (and have plugins auto-install via lazy.nvim on first start), mount it from the host:

volumes:
  - ~/.config/nvim:/home/developer/.config/nvim:ro

Python development with uv

The image includes Python 3.13 (from Debian Trixie) and uv, a fast Python package manager that replaces pip, venv, and pyenv:

# Python 3.13 is available out of the box
python3 --version

# Use uv for package management
uv venv
uv pip install -r requirements.txt

# Or use uv's project workflow (reads pyproject.toml)
uv sync

# Run a Python script
uv run python script.py

# Install standalone Python tools
uvx ruff check .

# Install a newer Python version (persists with devbox-uv volume)
uv python install 3.14

Python installations are stored in ~/.local/share/uv/. To persist them across container restarts, add the devbox-uv named volume to your docker-compose.yml:

volumes:
  - devbox-uv:/home/developer/.local/share/uv

volumes:
  devbox-uv:

Project virtual environments (.venv) are stored in your workspace directory and persist automatically via the /workspace bind mount.

Rust development with rustup

The image includes rustup-init, the Rust toolchain installer. Rust is not pre-installed but can be bootstrapped on demand:

# One-time setup: install Rust toolchain (~300MB, persists with volumes)
rustup-init -y
source ~/.cargo/env

# Now use Rust normally
cargo new my-project
cargo build
cargo run

To persist Rust toolchains and cargo data across container restarts, add named volumes to your docker-compose.yml:

volumes:
  - devbox-rustup:/home/developer/.rustup
  - devbox-cargo:/home/developer/.cargo

volumes:
  devbox-rustup:
  devbox-cargo:

JavaScript and TypeScript

The base image includes Node.js 22 and npm — sufficient for most JavaScript and TypeScript development:

# Initialize a new project
npm init -y

# Install dependencies
npm install

# Run TypeScript (via tsx, ts-node, etc.)
npx tsx src/index.ts

# Use npx for one-off tools
npx tsc --init

The OMOS image variant also includes Bun, a faster JavaScript runtime and package manager:

bun init
bun install
bun run src/index.ts

Node modules are stored in your project directory under /workspace and persist automatically.

VS Code integration

VS Code can connect directly to a running opencode-devbox container for a full IDE experience with IntelliSense, debugging, and extensions running inside the container.

Local Docker (Docker running on your workstation):

  1. Install the Dev Containers extension
  2. Start the container: docker compose up -d
  3. In VS Code: Ctrl+Shift+P → "Dev Containers: Attach to Running Container" → select opencode-devbox

Remote Docker (Docker running on a remote server, e.g. via SSH):

  1. Install the Remote - SSH and Dev Containers extensions
  2. Connect to the remote host: Ctrl+Shift+P → "Remote-SSH: Connect to Host"
  3. On the remote host, start the container: docker compose up -d
  4. In VS Code (now connected to the remote): Ctrl+Shift+P → "Dev Containers: Attach to Running Container"

VS Code extensions installed inside the container persist as long as the container exists (not removed with docker compose down). For persistent extension storage across container recreations, add a named volume:

volumes:
  - devbox-vscode:/home/developer/.vscode-server

Multi-user setup

The shared-machine compose file (docker-compose.shared.yml) supports two modes:

Own-account mode (each user has their own OS login — the common case): Leave SIGNUM unset in .env. The project name defaults to devbox-$USER, so each OS user automatically gets isolated container names and named volumes with zero configuration.

Shared-account mode (everyone logs in as the same OS user, e.g. garage): Each user sets SIGNUM=<unique-id> in .env to get isolation.

Setup per user:

# Replace <signum> with your username/identifier
mkdir -p ~/<signum>/opencode-devbox
cd ~/<signum>/opencode-devbox

# Copy the shared-machine compose and env files
cp /path/to/opencode-devbox/docker-compose.shared.yml docker-compose.yml
cp /path/to/opencode-devbox/.env.shared.example .env

# Edit .env — set SIGNUM only if you're in shared-account mode
vim .env

# Start
docker compose up -d
docker compose exec -u developer devbox opencode

Each user's container, config, and named volumes are fully isolated:

  • Container name: devbox-<signum> (or devbox-$USER in own-account mode)
  • Named volumes: prefixed with the project name (devbox-<signum>_devbox-data, etc.) — the Docker daemon is system-wide, so directory-name prefixing alone is NOT sufficient for isolation
  • Opencode config: persisted via per-user named volume (devbox-<signum>_devbox-opencode-config)

See docker-compose.shared.yml and .env.shared.example for the full configuration.

Rebuilding the Image

docker compose run and docker compose up use the existing image — they do not rebuild when you change the Dockerfile or build args (e.g. updating OPENCODE_VERSION). Rebuild explicitly:

# Rebuild then run
docker compose build
docker compose run --rm devbox

# Or rebuild and run in one step
docker compose run --rm --build devbox

Build Args

Enable optional language runtimes, pin a specific opencode version, or lock any of the tooling components:

docker compose build --build-arg INSTALL_GO=true
docker compose build --build-arg OPENCODE_VERSION=1.5.0
docker compose build --build-arg NVIM_VERSION=0.12.1   # pin to a specific version
Arg Default Description
INSTALL_GO false Go toolchain (resolves latest stable from go.dev when GO_VERSION=latest)
INSTALL_MEMPALACE true MemPalace local AI memory system (~300 MB — disable to shrink image if you don't need MCP memory)
INSTALL_MEMPALACE_TOOLKIT true mempalace-toolkit bash wrappers (mempalace-session, mempalace-docs). Cloned at build time from MEMPALACE_TOOLKIT_REF (default main). Requires INSTALL_MEMPALACE=true.
INSTALL_OMOS false oh-my-opencode-slim multi-agent orchestration (installs Bun and plugin)
INSTALL_OPENCODE true Install opencode. Set false to build a pi-only image (still includes Bun if INSTALL_OMOS=true; for a fully stripped pi-only image see the pi-devbox repo).
INSTALL_PI false Install pi as alternative/complementary harness. Both clones pi-toolkit (~5 MB) and pi-extensions (~1 MB) into /opt/; entrypoint deploys them on container start. ~150 MB total image growth.
PI_VERSION latest npm version of @earendil-works/pi-coding-agent. Floats by default (image rebuild = pi update).
PI_TOOLKIT_REF, PI_EXTENSIONS_REF main Git refs for the toolkit/extensions clones. Pin to a tag/commit for reproducibility.
OPENCODE_VERSION (pinned per release) opencode npm version. Drives the image tag and is intentionally not floated.
NODE_VERSION 22 Node.js major version. Pinned to protect against upstream breaking changes across majors.
GOSU_VERSION, FZF_VERSION, GIT_LFS_VERSION, NVIM_VERSION, BAT_VERSION, EZA_VERSION, ZOXIDE_VERSION, UV_VERSION, GITEA_MCP_VERSION, GO_VERSION, OMOS_VERSION latest All GitHub/Gitea/go.dev-hosted binaries resolve to the newest upstream release at build time. Override with a specific version to pin. Resolved versions are logged in CI output.

Reproducibility note: With latest defaults, two builds of the same v{opencode} tag may embed different tool versions if upstream releases have happened in between. This is intentional — it means every rebuild picks up upstream CVE fixes automatically. If you need a bit-for-bit reproducible build, pass explicit *_VERSION args. The CI smoke test logs the resolved versions for every release build.

oh-my-opencode-slim (Multi-Agent Orchestration)

oh-my-opencode-slim adds a multi-agent layer on top of opencode — an Orchestrator delegates tasks to specialized agents (Explorer, Oracle, Librarian, Designer, Fixer), each configurable with different models and providers.

Setup

A pre-built OMOS image is available on Docker Hub as joakimp/opencode-devbox:latest-omos. Alternatively, build from source:

1. Build the image with OMOS support:

docker compose build --build-arg INSTALL_OMOS=true

This installs Bun and the oh-my-opencode-slim package into the image.

2. Enable in .env:

ENABLE_OMOS=true

3. Run as normal:

docker compose run --rm devbox

On first start, the entrypoint runs the oh-my-opencode-slim installer in non-interactive mode. It generates agent configuration at ~/.config/opencode/oh-my-opencode-slim.json inside the container. The default preset uses OpenAI models — edit the generated config or mount your own to customize.

OMOS Environment Variables

Variable Default Description
ENABLE_OMOS false Activate oh-my-opencode-slim on container start
OMOS_TMUX false Enable tmux pane integration (tmux is included in the base image)
OMOS_SKILLS true Install recommended skills (simplify, agent-browser, cartography)
OMOS_RESET false Force regenerate config on next start (backs up existing config)

Custom Configuration

If you mount the opencode config directory (see Custom opencode config above), the oh-my-opencode-slim.json file is included and persists across restarts. Edit it directly to control which models power each agent, fallback chains, council setup, and more.

See the oh-my-opencode-slim configuration docs for the full reference.

Verifying Agents

After starting opencode with OMOS enabled, run inside the opencode session:

ping all agents

All six agents should respond if your provider authentication is working.

pi (alternative/complementary harness)

pi is a lightweight TUI coding-agent that can run alongside opencode in the same container. Both harnesses share the mempalace install and palace data — wing/diary entries created by one are visible to the other.

Setup

Pre-built pi-enabled images are available on Docker Hub as joakimp/opencode-devbox:latest-with-pi (base + pi) and joakimp/opencode-devbox:latest-omos-with-pi (OMOS + pi). Pulling one of those tags is the fastest path. If you want pi without opencode, use the separate, leaner joakimp/pi-devbox image instead (it's built from the same Dockerfile.variant with INSTALL_OPENCODE=false, published in its own repo so an opencode-devbox tag never ships without opencode). Alternatively, build from source:

Build

docker compose build --build-arg INSTALL_PI=true
# Or: pin a pi version
docker compose build --build-arg INSTALL_PI=true --build-arg PI_VERSION=0.73.0
# Or: pi-only image (no opencode, smaller)
docker compose build --build-arg INSTALL_PI=true --build-arg INSTALL_OPENCODE=false

Run

The default compose run --rm devbox invocation drops to a login bash so you can choose:

docker compose run --rm devbox       # bash, then `pi` or `opencode` or `aws sso login`
docker compose run --rm devbox pi     # launch pi directly
docker compose run --rm devbox opencode

For an attached compose up -d container, both harnesses are reachable via compose exec:

docker compose exec -u developer devbox pi
docker compose exec -u developer devbox opencode
docker compose exec -u developer devbox bash

What gets installed

  • pi CLI — npm-installed globally at build time. Version pinned by PI_VERSION.
  • pi-toolkit — keybindings.json (mosh/tmux newline fixes), pi-env.zsh (AWS env loader), settings.json template. Cloned to /opt/pi-toolkit; deployed to ~/.pi/agent/ on first container start.
  • pi-extensions — 7 extensions, cloned to /opt/pi-extensions and symlinked into ~/.pi/agent/extensions/:
    • confirm-destructive — confirm-prompt before dangerous bash commands and session actions.
    • ext-toggle/ext slash command to list and enable/disable extensions at runtime (rename-to-disable; survives /reload).
    • git-checkpoint — per-turn git stash checkpoint, restorable on /fork.
    • mcp-loader — generic MCP server loader. Reads an mcp block from ~/.pi/agent/settings.json (same shape as opencode and Claude Desktop) and connects to each declared server, exposing the tools as native pi tools. Supports both local stdio subprocesses (uvx mcp-searxng, gitea-mcp, …) and remote streamable-HTTP servers per MCP spec 2025-03-26 (e.g. https://mcp.context7.com/mcp). Adds a /mcp slash command for runtime status / toggle (same UX as /ext). See pi-extensions/AGENTS.md for transport details and the headers config for auth tokens.
    • notify — native terminal notification when the agent finishes.
    • ssh-controlmaster — transparent SSH remote execution via persistent ControlMaster socket (when pi is launched with --ssh user@host).
    • todotodo tool for the agent + /todos for the user.
  • mempalace bridge — separate mempalace.ts extension symlinked from the cloned mempalace-toolkit. Provides pi's MCP tools for palace search/diary/knowledge-graph with bespoke agent-identity injection from $MEMPALACE_AGENT_NAME. Coexists with mcp-loader rather than replacing it — don't list mempalace in settings.json's mcp block too, or you'll get duplicate tool registrations.
  • MCP servers (none baked in beyond mempalace) — the loader registers nothing by default. Add servers by editing ~/.pi/agent/settings.json and /reload. Examples (mcp-searxng for web search, context7 for live library docs) are in the pi-extensions README.

Persistence

~/.pi/ is mounted on the devbox-pi-config named volume. Everything below survives container recreate and image rebuilds:

  • ~/.pi/agent/settings.json (provider/model, theme selection, the mcp block, and the packages array tracking installed pi packages).
  • ~/.pi/agent/extensions/ (hand-placed extensions and the symlinks deployed by pi-extensions/install.sh).
  • ~/.pi/agent/sessions/, ~/.pi/agent/auth.json.
  • ~/.pi/agent/git/<host>/<path>/ (pi packages installed via pi install git:...).
  • ~/.pi/npm-global/ (pi packages installed via pi install npm:..., plus any npm install -g invoked as the developer user). NPM_CONFIG_PREFIX is pre-set in the image, the prefix's bin/ is on PATH, and the directory itself lives on the volume — so user-installed themes, skills, and extensions survive everything short of docker compose down -v.

The baked pi binary (and pi-toolkit / pi-extensions repos under /opt/) live on the image filesystem, not the volume. Image rebuild is the upgrade path for those — same contract as OPENCODE_VERSION. If you npm install -g @earendil-works/pi-coding-agent yourself, the user-installed copy on the volume wins via PATH order and survives image rebuilds.

Configuration

The entrypoint copies pi-toolkit/settings.example.json to ~/.pi/agent/settings.json on first start. Edit it to set provider/model:

docker compose exec -u developer devbox $EDITOR ~/.pi/agent/settings.json

The AWS env loader (pi-env.zsh) reads ~/.config/pi/.env if you bind-mount one; otherwise pi uses container env vars passed via .env.

AWS Bedrock Authentication

When using AWS Bedrock as your LLM provider, you need:

1. AWS config on the host

The container needs access to your ~/.aws/config with SSO session configuration. If you already have this on another machine, copy it:

scp -r user@other-machine:~/.aws ~/.aws

Or configure from scratch on the host:

aws configure sso

2. Mount ~/.aws into the container

Uncomment the AWS volume mount in docker-compose.yml:

- ~/.aws:/home/developer/.aws

Note: do not use :ro — SSO writes token cache files to this directory.

3. Authenticate inside the container

Since the container runs headless (no browser), use the device-code flow:

# Start the container
docker compose up -d
docker compose exec -u developer devbox bash

# Authenticate — prints a URL and code you open in your local browser
aws sso login --sso-session <your-sso-session> --use-device-code

# Once approved in the browser, start opencode
opencode

The --use-device-code flag outputs a URL and short code instead of trying to open a browser. Copy the URL into any browser (on your laptop, phone, etc.), enter the code, and complete the 2FA flow. The CLI in the container picks up the session automatically.

SSO sessions typically last 812 hours before requiring re-authentication. Since ~/.aws is mounted from the host, tokens persist across container restarts.

MemPalace — persistent AI memory

The image includes MemPalace, a local-first AI memory system that stores conversation history verbatim and retrieves it via semantic search. Nothing leaves your machine.

MemPalace adds ~300 MB to the image (chromadb, embedding model deps). If you don't use it, rebuild with --build-arg INSTALL_MEMPALACE=false to shrink the image.

Enabling persistence

Uncomment the palace volume in docker-compose.yml:

- devbox-palace:/home/developer/.mempalace

Without the volume, palace data lives in the container's writable layer and is lost on --force-recreate.

MCP integration with opencode

Add mempalace as an MCP server in your opencode.jsonc (inside ~/.config/opencode/):

{
  "mcp": {
    "mempalace": {
      "type": "local",
      "command": ["mempalace-mcp"]
    }
  }
}

The image installs mempalace into an isolated uv tool venv at /opt/uv-tools/mempalace/. uv tool install places mempalace-mcp on PATH as a shim whose shebang points at the venv's Python, so MCP clients can invoke it as a normal binary without worrying about the venv. Do not use ["python3", "-m", "mempalace.mcp_server"] — the system Python cannot import from the uv-managed venv and you'll get ModuleNotFoundError / MCP error -32000: connection closed.

This gives opencode access to 29 MCP tools for searching memory, querying the knowledge graph, managing wings/rooms/drawers, and agent diaries.

Basic usage

# Mine project files into the palace
mempalace mine /workspace

# Mine conversation transcripts
mempalace mine ~/.local/share/opencode/ --mode convos

# Search memory
mempalace search "why did we switch to eno1"

# Load context for a new session
mempalace wake-up

Each workspace gets its own isolated "wing" — memories never leak between projects.

Scheduled mining (mempalace-toolkit)

The image bakes in mempalace-toolkit, a small set of bash wrappers that pair with mempalace for two common routines:

# Mine opencode session history (reads ~/.local/share/opencode/opencode.db, stages JSONL, mines into wing_conversations)
mempalace-session

# Mine a project's docs into a dedicated wing
mempalace-docs /workspace/my-project

Both wrappers are idempotent and dedup-aware — re-running them on unchanged input is a cheap no-op.

For weekly automated runs, the toolkit ships ready-to-use scheduler templates (systemd user timer, launchd user agent, cron) in its contrib/ directory. The *-devbox variants are designed for this container: host-side schedulers that docker exec into the running opencode-devbox.

Disable the toolkit (keeps mempalace itself) with --build-arg INSTALL_MEMPALACE_TOOLKIT=false. Pin to a specific ref with --build-arg MEMPALACE_TOOLKIT_REF=v0.3.0 once tagged releases exist.

Storage

Two separate named volumes keep different data classes apart:

  • Palace data (~/.mempalace/): ChromaDB vectors, SQLite knowledge graph, drawers. This is your memory — back it up, treat it as precious. Persists via the devbox-palace named volume.
  • Embedding model cache (~/.cache/chroma/): ONNX model (~79 MB), downloaded automatically on first search. Disposable — blow it away and it re-downloads in ~4 seconds. Persists via the devbox-chroma-cache named volume so you don't re-download on every container recreation.
  • No API keys required for core functionality (local embeddings via ONNX).

Both volumes are commented out by default in docker-compose.yml — uncomment to enable:

- devbox-palace:/home/developer/.mempalace
- devbox-chroma-cache:/home/developer/.cache/chroma

Air-gapped environments: pre-populate the devbox-chroma-cache volume with the all-MiniLM-L6-v2/ model contents. The palace volume needs no pre-population.

Gitea MCP server

The image includes the official Gitea MCP server (gitea-mcp), providing 50+ MCP tools for interacting with self-hosted Gitea instances — repositories, issues, pull requests, releases, branches, wiki, and Actions.

Setup

  1. Create a Personal Access Token on your Gitea instance (Settings → Applications → Generate Token, scopes: repo, read:user).

  2. Add to your .env:

    GITEA_HOST=https://your-gitea-instance.example.com
    GITEA_ACCESS_TOKEN=your_token_here
    
  3. Enable the gitea MCP server in your opencode.jsonc:

    {
      "mcp": {
        "gitea": {
          "type": "local",
          "command": ["gitea-mcp", "-t", "stdio", "--host", "{env:GITEA_HOST}"],
          "environment": {
            "GITEA_ACCESS_TOKEN": "{env:GITEA_ACCESS_TOKEN}"
          },
          "enabled": true
        }
      }
    }
    

The server is installed but disabled by default — it requires authentication to be useful.

Context7 MCP server

The image auto-registers a Context7 MCP server, which provides up-to-date library documentation and code examples to LLMs at query time. This is a remote MCP server at mcp.context7.com/mcp — no local binary is needed.

  • Auto-registered in the generated opencode.jsonc (no manual setup required)
  • Provides documentation for any programming library/framework on demand
  • Requires internet access — useless in air-gapped/offline environments

Shell defaults

The image ships a baked .bash_aliases and .inputrc with quality-of-life defaults. On first container start they are copied from /etc/skel-devbox/ into /home/developer/ only if the target file does not already exist — so host bind-mounts and any version you've customized inside the container are never overwritten on upgrade.

Defaults you get out of the box:

  • Prefix history search on Up/Down arrows (type git , press Up, walk back through prior git ... commands only). Ctrl-Up / Ctrl-Down still step through full history.
  • Persistent history$HISTFILE points at ~/.cache/bash/history, backed by the devbox-shell-history named volume so history survives container recreation. Timestamps, 100 000 entries, dedup.
  • Case-insensitive tab completion, coloured completion lists, show-all-if-ambiguous.
  • Aliasesls/ll/la use eza, cat uses bat, gs/gd/gl for git, safe rm/mv/cp.
  • Integrationszoxide (z <fragment> to jump), fzf Ctrl-R / Ctrl-T key bindings.
  • Prompt marker[devbox] prefix so it's always obvious you're inside the container.

Overriding the defaults

Option A — bind-mount host files. Uncomment the bind-mount lines in docker-compose.yml:

- ~/.bash_aliases:/home/developer/.bash_aliases:ro
- ~/.inputrc:/home/developer/.inputrc:ro

Single-file bind-mount caveat (all platforms): Docker bind-mounts the file's inode, not its path. When editors like vim, nvim, VS Code, or sed -i save a file, they write to a temp file and rename() it over the original — creating a new inode. The container stays pinned to the old (now unlinked) inode and never sees the update. This is a kernel limitation (Docker #15793), not fixable by Docker. Append-only writes (echo "alias foo=bar" >> file) are safe because they modify the same inode. Workaround: mount the parent directory instead of the single file (e.g. ~/.config/devbox-shell:/home/developer/.config/devbox-shell:ro) and source files from there.

Option B — customize inside the container. Just edit ~/.bash_aliases or ~/.inputrc as normal. Pair this with a bind-mount or named volume on the home dir if you want the edits to survive container recreation.

Restoring or diffing defaults

The skel files remain available inside every container at /etc/skel-devbox/. Useful commands:

# See what the image currently ships
cat /etc/skel-devbox/.bash_aliases

# Diff your current config against the upstream defaults
diff ~/.bash_aliases /etc/skel-devbox/.bash_aliases

# Reset to the baked defaults
cp /etc/skel-devbox/.bash_aliases ~/.bash_aliases

# …or delete the file and recreate the container — the entrypoint
# copies from /etc/skel-devbox/ on next start if the target is absent
rm ~/.bash_aliases

Secret Scanning

A gitleaks pre-commit hook prevents accidentally committing API keys, passwords, or other secrets.

Setup

# macOS / Linuxbrew
brew install gitleaks

# Debian/Ubuntu (download binary)
curl -sSL https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_$(uname -s)_$(uname -m).tar.gz | sudo tar -xz -C /usr/local/bin gitleaks

The hook runs automatically on every git commit. If gitleaks isn't installed, the hook prints a warning and allows the commit (no hard dependency on collaborators).

Bypass

For legitimate cases (test data, documentation with example keys):

git commit --no-verify -m "Add test fixtures"

Configuration

Allowlisted paths and rules are in .gitleaks.toml. The defaults extend gitleaks' built-in rules and allow .env.example and documentation files.

Architecture

Host Machine
├── ~/projects/my-app  ──bind mount──▶  /workspace (container)
├── ~/.ssh             ──bind mount──▶  /home/developer/.ssh (ro)
├── ~/.aws             ──bind mount──▶  /home/developer/.aws (Bedrock SSO)
└── .env               ──env vars───▶  provider config + API keys

Container (Debian trixie)
├── opencode binary
├── oh-my-opencode-slim (optional — multi-agent orchestration plugin, includes Bun)
├── AWS CLI v2 (SSO + Bedrock auth)
├── neovim 0.12, tmux, htop, bat, eza, zoxide, uv, rustup, make, gcc, g++, rsync
├── git, git-crypt, age, gitleaks, ssh, ripgrep, fd, fzf, jq, curl, tree
├── Node.js (for MCP servers)
├── Bun (optional — included with oh-my-opencode-slim)
├── entrypoint.sh (UID adjustment, git config, provider setup)
└── /workspace ← your code lives here

Data persistence

Path in container Source Survives --rm? Contains
/workspace Host bind mount Yes Your project files
/home/developer/.ssh Host bind mount (ro) Yes SSH keys
/home/developer/.aws Host bind mount (if configured) Yes AWS credentials/SSO cache
/home/developer/.local/share/opencode Named volume devbox-data Yes Session history, memory
/home/developer/.local/state/opencode Named volume devbox-state Yes TUI settings (theme, toggles)
/home/developer/.cache/bash Named volume devbox-shell-history Yes Bash history ($HISTFILE), survives container recreate
/home/developer/.local/share/zoxide Named volume devbox-zoxide Yes Zoxide directory history (z <fragment> jump targets)
/home/developer/.local/share/nvim Named volume devbox-nvim-data Yes Neovim plugins, Mason LSP installs, Lazy plugin cache
/home/developer/.local/share/uv Named volume devbox-uv (if configured) Yes Python installs, uv tool installs
/home/developer/.rustup Named volume devbox-rustup (if configured) Yes Rust toolchains
/home/developer/.cargo Named volume devbox-cargo (if configured) Yes Cargo binaries, registry cache
/home/developer/.vscode-server Named volume devbox-vscode (if configured) Yes VS Code server and extensions
/home/developer/.config/opencode Named volume devbox-opencode-config Yes opencode.jsonc, skills, plus oh-my-opencode-slim.json on the OMOS variant

opencode config (opencode.jsonc) is auto-generated from OPENCODE_PROVIDER on each start. It sets provider and model only — no MCP servers. To persist config changes and use custom settings, use the named volume (default) or bind-mount from host (see Custom opencode config above).

License

MIT

S
Description
No description provided
Readme 3.6 MiB
Languages
Shell 79.6%
Python 20.4%