joakimp f210d533eb Fix root-owned parent dirs left behind by nested volume mounts
When a named volume is mounted at a nested path like
/home/developer/.local/state/opencode, Docker creates the parent
directory (.local/state) as root:root. The existing chown loop only
fixed the leaf mount points, leaving parents unwritable by the
developer user.

Add a non-recursive pre-pass that fixes ownership of the common
parent dirs (.local, .local/share, .local/state, .config) so that
anything creating new subdirs beneath them works correctly after a
fresh container recreate. Regression introduced by commit 967ce7d
(devbox-state volume) and only partially addressed by a06dc5f.
2026-04-20 22:12:14 +02:00
2026-04-20 20:26:24 +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

# Clone
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)
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

# If mounting opencode config (recommended for persistent settings)
mkdir -p ~/.config/opencode

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
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

Custom opencode config

For full control over opencode settings (MCP servers, custom models, and — on the OMOS variant — oh-my-opencode-slim agents), mount the entire config directory from the host:

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

This persists all configuration changes across container restarts, including opencode.json, skills, and (on the OMOS variant) oh-my-opencode-slim.json. When an existing opencode.json is found, the OPENCODE_PROVIDER auto-config is skipped.

Portability note: The mounted config runs inside a Linux container. Any absolute paths inside opencode.json (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

Mount agent skills from the host:

volumes:
  - ~/.agents/skills:/home/developer/.agents/skills:ro

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

Shared machine setup (multiple users, single OS account)

For machines where multiple users share one OS account (e.g. a common garage user), a separate compose file isolates each user's config and data using a SIGNUM variable.

Each user creates their own directory and setup:

# 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

# Create per-user config directory
mkdir -p ~/<signum>/.config/opencode

# Edit .env with your signum, provider, keys, etc.
vim .env

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

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

  • Container name: devbox-<signum> (no collisions)
  • Named volumes: prefixed with the project directory name (automatic per-user isolation)
  • Opencode config: ~/<signum>/.config/opencode/ (per-user settings, OMOS config, etc.)

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 or pin a specific opencode version:

docker compose build --build-arg INSTALL_PYTHON=true --build-arg INSTALL_GO=true
docker compose build --build-arg OPENCODE_VERSION=1.5.0
Arg Default Description
INSTALL_PYTHON false Python 3 + pip + venv
INSTALL_GO false Go toolchain
INSTALL_OMOS false oh-my-opencode-slim multi-agent orchestration (installs Bun and plugin)
OMOS_VERSION latest Pin a specific oh-my-opencode-slim version

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.

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.

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, 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/.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 Host bind mount (if configured) Yes opencode.json, skills, plus oh-my-opencode-slim.json on the OMOS variant

opencode config (opencode.json) 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, mount the config directory from the host (see Custom opencode config above).

License

MIT

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