Compare commits

...

19 Commits

Author SHA1 Message Date
joakimp 271dc2eb35 Fix Bedrock config: add AWS_PROFILE to generated config, add .agents/skills to volume ownership fix
Publish Docker Image / build-omos (push) Successful in 36m41s
Publish Docker Image / build-base (push) Successful in 38m37s
Publish Docker Image / update-description (push) Successful in 17s
2026-04-13 19:52:08 +02:00
joakimp 875afe0039 Add ~/.local/bin and ~/.cargo/bin to PATH for uv and rustup 2026-04-13 19:48:31 +02:00
joakimp 9e381ebe32 Fix ownership of named volume mount points in entrypoint
Named Docker volumes are created as root on first use, causing permission
denied errors for the developer user. The entrypoint now fixes ownership
of all known volume mount points after UID/GID adjustment.
2026-04-13 19:46:25 +02:00
joakimp 3e048218c3 Update Python example from 3.12 to 3.14 (current stable) 2026-04-13 19:14:33 +02:00
joakimp 6ecd65d18d Update Bedrock model example to eu.anthropic.claude-opus-4-6-v1 2026-04-13 19:04:21 +02:00
joakimp e58962a72c Upgrade base image from Debian bookworm to trixie (current stable)
Publish Docker Image / build-base (push) Successful in 32m39s
Publish Docker Image / build-omos (push) Successful in 39m41s
Publish Docker Image / update-description (push) Successful in 18s
Bookworm (Debian 12) reaches EOL June 2026. Trixie (Debian 13) has been
stable since August 2025 with support until 2028/LTS until 2030.
2026-04-13 13:57:45 +02:00
joakimp d2c0447147 Add VS Code server volume to docker-compose examples and persistence tables 2026-04-13 10:20:25 +02:00
joakimp 77a7daf67f Document VS Code Dev Containers integration for local and remote Docker 2026-04-13 10:14:27 +02:00
joakimp b3cfe641bb Document required host directories to prevent root-owned bind mount issues 2026-04-12 23:52:59 +02:00
joakimp f7bd21b9fe Add rustup for on-demand Rust support, document JS/TS development
Publish Docker Image / build-omos (push) Successful in 32m33s
Publish Docker Image / build-base (push) Successful in 32m41s
Publish Docker Image / update-description (push) Successful in 18s
Install rustup-init binary from Rust CDN. Users bootstrap Rust with
'rustup-init -y' — persists via devbox-rustup and devbox-cargo volumes.
Add JavaScript/TypeScript development docs (Node.js + npm in base, Bun in OMOS).
2026-04-12 21:36:57 +02:00
joakimp 1b97d98155 Add uv package manager to base image for on-demand Python support
Publish Docker Image / build-base (push) Successful in 30m41s
Publish Docker Image / build-omos (push) Successful in 35m39s
Publish Docker Image / update-description (push) Failing after 2s
Install uv from GitHub releases (~23MB). Users can install Python with
'uv python install 3.12' — persists across restarts via devbox-uv volume.
Eliminates need for a separate Python image variant.
2026-04-12 20:14:30 +02:00
joakimp de659fbc54 Switch to new Docker Hub /v2/auth/token API for description updates
The old /v2/users/login endpoint is deprecated and returns tokens with
insufficient permissions. Use /v2/auth/token with Bearer auth instead.
2026-04-12 19:10:55 +02:00
joakimp d651a084de Fix Docker Hub short description: trim to 100-byte limit 2026-04-12 19:00:34 +02:00
joakimp 18b4df23e5 Fix IPv6 connectivity failures: force IPv4 preference in CI builds
Publish Docker Image / build-base (push) Successful in 30m22s
Publish Docker Image / build-omos (push) Successful in 34m37s
Publish Docker Image / update-description (push) Failing after 15s
2026-04-12 17:39:25 +02:00
joakimp 60c83568cd Switch to directory mount for opencode config, update docs with make
Publish Docker Image / build-omos (push) Has been cancelled
Publish Docker Image / update-description (push) Has been cancelled
Publish Docker Image / build-base (push) Has been cancelled
Mount ~/.config/opencode as a directory instead of individual files.
This persists all config changes (opencode.json, oh-my-opencode-slim.json,
skills) across container restarts. Add make to README architecture diagram.
2026-04-12 17:33:45 +02:00
joakimp a8b5f23dba Add make to dev tools list in DOCKER_HUB.md
Publish Docker Image / build-omos (push) Failing after 10m37s
Publish Docker Image / update-description (push) Has been cancelled
Publish Docker Image / build-base (push) Has been cancelled
2026-04-12 17:05:35 +02:00
joakimp a6972becd1 Add make to base image
Publish Docker Image / build-base (push) Has been cancelled
Publish Docker Image / build-omos (push) Has been cancelled
Publish Docker Image / update-description (push) Has been cancelled
2026-04-12 17:04:50 +02:00
joakimp a183ad7ac6 Add neovim 0.12, bat, eza, zoxide, tmux, htop to base image
Publish Docker Image / update-description (push) Has been cancelled
Publish Docker Image / build-omos (push) Has been cancelled
Publish Docker Image / build-base (push) Has been cancelled
Replace vim-tiny with neovim from GitHub releases (pinned, multi-arch).
Add bat, eza, zoxide from GitHub releases and tmux, htop, patch from apt.
Move tmux from OMOS-only to base image. Set EDITOR=nvim.
Add neovim config mount option to docker-compose and docs.
2026-04-12 16:59:31 +02:00
joakimp 017f7f1343 Fix Docker Hub description update: use --rawfile and capture error response 2026-04-11 23:52:50 +02:00
7 changed files with 440 additions and 68 deletions
+23 -7
View File
@@ -14,11 +14,18 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Force IPv4 for Docker Hub
run: |
# Prefer IPv4 to avoid intermittent IPv6 connectivity failures
echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
with:
driver-opts: network=host
- name: Login to Docker Hub
uses: docker/login-action@v4
@@ -50,11 +57,18 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Force IPv4 for Docker Hub
run: |
# Prefer IPv4 to avoid intermittent IPv6 connectivity failures
echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
with:
driver-opts: network=host
- name: Login to Docker Hub
uses: docker/login-action@v4
@@ -91,25 +105,27 @@ jobs:
- name: Update Docker Hub description
run: |
TOKEN=$(curl -s -X POST https://hub.docker.com/v2/users/login/ \
TOKEN=$(curl -s -X POST https://hub.docker.com/v2/auth/token \
-H "Content-Type: application/json" \
-d '{"username":"${{ vars.DOCKERHUB_USERNAME }}","password":"${{ secrets.DOCKERHUB_TOKEN }}"}' \
| jq -r .token)
-d '{"identifier":"${{ vars.DOCKERHUB_USERNAME }}","secret":"${{ secrets.DOCKERHUB_TOKEN }}"}' \
| jq -r .access_token)
if [ "$TOKEN" = "null" ] || [ -z "$TOKEN" ]; then
echo "::error::Failed to authenticate with Docker Hub API"
exit 1
fi
HTTP_CODE=$(jq -n \
--arg full "$(cat DOCKER_HUB.md)" \
--arg short "Portable AI dev environment for opencode. Debian-based with git, Node.js, AWS CLI, and SSH support. Available in base and omos (multi-agent) variants." \
--rawfile full DOCKER_HUB.md \
--arg short "Portable AI dev environment for opencode. Debian-based with git, Node.js, AWS CLI, and SSH support." \
'{"full_description": $full, "description": $short}' | \
curl -s -o /dev/null -w "%{http_code}" -X PATCH \
curl -s -o /tmp/hub-response.txt -w "%{http_code}" -X PATCH \
"https://hub.docker.com/v2/repositories/${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox/" \
-H "Authorization: JWT $TOKEN" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @-)
echo "Docker Hub API returned: $HTTP_CODE"
if [ "$HTTP_CODE" != "200" ]; then
echo "Response body:"
cat /tmp/hub-response.txt
echo "::error::Docker Hub description update failed with HTTP $HTTP_CODE"
exit 1
fi
+162 -26
View File
@@ -9,7 +9,7 @@ Two image variants are published for each release:
| Tag | Description |
|---|---|
| `latest` / `vX.Y.Z` | Base image — opencode, Node.js, AWS CLI, dev tools |
| `latest-omos` / `vX.Y.Z-omos` | Base + [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim) multi-agent orchestration, Bun, and tmux |
| `latest-omos` / `vX.Y.Z-omos` | Base + [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim) multi-agent orchestration and Bun |
Both variants support `linux/amd64` and `linux/arm64`.
@@ -116,10 +116,22 @@ The entrypoint automatically detects the owner of `/workspace` and adjusts the c
## Initial Setup
### 1. Create a project directory
### 1. Create host directories
Bind-mounted directories must exist on the host before starting the container. Docker creates missing directories as root-owned, which causes permission issues.
```bash
# Required
mkdir -p ~/projects
# If mounting opencode config (recommended for persistent settings)
mkdir -p ~/.config/opencode
# If using AWS Bedrock
# mkdir -p ~/.aws
# If mounting neovim config
# mkdir -p ~/.config/nvim
```
### 2. Create a `.env` file
@@ -145,7 +157,7 @@ GIT_USER_EMAIL=you@example.com
**AWS Bedrock (SSO):**
```bash
OPENCODE_PROVIDER=amazon-bedrock
OPENCODE_MODEL=amazon-bedrock/anthropic.claude-sonnet-4-5-v1
OPENCODE_MODEL=amazon-bedrock/eu.anthropic.claude-opus-4-6-v1
AWS_REGION=eu-west-1
AWS_PROFILE=your-profile-name
GIT_USER_NAME=Your Name
@@ -184,28 +196,149 @@ Understanding what survives container restarts and what doesn't:
| `/home/developer/.ssh` | Host bind mount (ro) | ✅ Yes — lives on host | SSH keys |
| `/home/developer/.aws` | Host bind mount | ✅ Yes — lives on host | AWS credentials/SSO cache |
| `/home/developer/.local/share/opencode` | Named volume (if configured) | ✅ Yes — Docker volume | Session history, memory, auth tokens |
| `/home/developer/.config/opencode/opencode.json` | Generated by entrypoint | ❌ No — regenerated each start | Provider config, MCP server definitions |
| `/home/developer/.config/opencode/oh-my-opencode-slim.json` | Generated by entrypoint (OMOS variant) | ❌ No — regenerated each start | Agent/model mappings |
| `/home/developer/.local/share/uv` | Named volume (if configured) | ✅ Yes — Docker volume | Python installs, uv tool installs |
| `/home/developer/.rustup` | Named volume (if configured) | ✅ Yes — Docker volume | Rust toolchains |
| `/home/developer/.cargo` | Named volume (if configured) | ✅ Yes — Docker volume | Cargo binaries, registry cache |
| `/home/developer/.vscode-server` | Named volume (if configured) | ✅ Yes — Docker volume | VS Code server and extensions |
| `/home/developer/.config/opencode` | Host bind mount (if configured) | ✅ Yes — lives on host | opencode.json, oh-my-opencode-slim.json, skills |
### Key points
- **Project files** (`/workspace`) are always safe — they're your host filesystem.
- **opencode config** is auto-generated from `OPENCODE_PROVIDER` env var on each start. It only sets provider and model — no MCP servers. To persist MCP server config, mount your own config file (see Custom opencode Config below).
- **opencode config** is auto-generated from `OPENCODE_PROVIDER` env var on each start if no existing config is found. To persist config changes, mount the config directory from the host (see Custom opencode Config below).
- **opencode data** (session history, memory) is lost with `--rm` unless you add a named volume.
- **Python installs** via `uv python install` are lost unless you add the `devbox-uv` named volume.
- **Rust toolchains** via `rustup-init` are lost unless you add the `devbox-rustup` and `devbox-cargo` named volumes.
- **AWS SSO tokens** persist across restarts when `~/.aws` is mounted (recommended for Bedrock users).
## Custom opencode Config
For full control (MCP servers, custom models, keybindings), mount your own config:
For full control over opencode settings (MCP servers, custom models, oh-my-opencode-slim agents, etc.), mount the entire config directory from the host:
```bash
docker run -it --rm \
-v ./my-opencode.json:/home/developer/.config/opencode/opencode.json:ro \
-v ~/.config/opencode:/home/developer/.config/opencode \
... \
joakimp/opencode-devbox:latest
```
When a config file is mounted, the `OPENCODE_PROVIDER` auto-config is skipped.
This persists all configuration changes across container restarts. When an existing `opencode.json` is found, the `OPENCODE_PROVIDER` auto-config is skipped.
## 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:
```bash
docker run -it --rm \
-v ~/.config/nvim:/home/developer/.config/nvim:ro \
... \
joakimp/opencode-devbox:latest
```
## Python Development with uv
The image includes [uv](https://docs.astral.sh/uv/), a fast Python package manager that replaces pip, venv, and pyenv. Python is not pre-installed but can be installed on demand:
```bash
# Install Python (persists across restarts with devbox-uv volume)
uv python install 3.14
# Create a virtual environment and install dependencies
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 .
```
To persist Python installs across container restarts, add a named volume:
```bash
docker run -it --rm \
-v devbox-uv:/home/developer/.local/share/uv \
... \
joakimp/opencode-devbox:latest
```
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:
```bash
# 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:
```bash
docker run -it --rm \
-v devbox-rustup:/home/developer/.rustup \
-v devbox-cargo:/home/developer/.cargo \
... \
joakimp/opencode-devbox:latest
```
## JavaScript and TypeScript
The base image includes **Node.js 22** and **npm** — sufficient for most JavaScript and TypeScript development:
```bash
# Initialize a new project
npm init -y
# Install dependencies
npm install
# Run TypeScript (via tsx, ts-node, etc.)
npx tsx src/index.ts
```
The OMOS image variant also includes **Bun**, a faster JavaScript runtime and package manager:
```bash
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.
**Requirements:** Install the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension. For remote Docker hosts, also install [Remote - SSH](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh).
**Steps:**
1. Start the container: `docker compose up -d`
2. In VS Code: `Ctrl+Shift+P` → "Dev Containers: Attach to Running Container" → select `opencode-devbox`
For remote Docker hosts (e.g. connecting to a server via SSH), first connect to the remote host with Remote-SSH, then attach to the container from there.
VS Code extensions installed inside the container persist as long as the container exists. For persistent extension storage across container recreations, add a named volume:
```bash
docker run -it --rm \
-v devbox-vscode:/home/developer/.vscode-server \
... \
joakimp/opencode-devbox:latest
```
## Using docker-compose
@@ -219,7 +352,7 @@ mkdir opencode-devbox && cd opencode-devbox
```bash
OPENCODE_PROVIDER=amazon-bedrock
OPENCODE_MODEL=amazon-bedrock/anthropic.claude-sonnet-4-5-v1
OPENCODE_MODEL=amazon-bedrock/eu.anthropic.claude-opus-4-6-v1
AWS_REGION=eu-west-1
AWS_PROFILE=your-profile-name
GIT_USER_NAME=Your Name
@@ -244,16 +377,28 @@ services:
- ~/projects:/workspace
- ~/.ssh:/home/developer/.ssh:ro
- devbox-data:/home/developer/.local/share/opencode
# Optional: persist Python/uv installs across restarts
# - devbox-uv:/home/developer/.local/share/uv
# Optional: persist Rust toolchains and cargo data
# - devbox-rustup:/home/developer/.rustup
# - devbox-cargo:/home/developer/.cargo
# Optional: persist VS Code server and extensions
# - devbox-vscode:/home/developer/.vscode-server
# Mount AWS config for Bedrock SSO (required for amazon-bedrock provider)
# - ~/.aws:/home/developer/.aws
# Optional: mount your own opencode config (MCP servers, custom models, etc.)
# - ./opencode.json:/home/developer/.config/opencode/opencode.json:ro
# Optional: mount opencode skills from host
# - ~/.config/opencode/skills:/home/developer/.config/opencode/skills:ro
# Optional: mount opencode config directory (persists config changes across restarts)
# - ~/.config/opencode:/home/developer/.config/opencode
# Optional: mount opencode agent skills from host
# - ~/.agents/skills:/home/developer/.agents/skills:ro
# Optional: mount neovim config from host (plugins auto-install on first start)
# - ~/.config/nvim:/home/developer/.config/nvim:ro
volumes:
devbox-data:
# devbox-uv:
# devbox-rustup:
# devbox-cargo:
# devbox-vscode:
```
Docker Compose loads `.env` automatically from the same directory. All variables from `.env` are passed to the container via `env_file`. Do **not** hardcode provider settings in the `environment:` section — use `.env` instead.
@@ -283,11 +428,11 @@ docker compose run --rm devbox bash # interactive shell
### Base image (`latest`)
- **Debian bookworm-slim** — glibc, full terminal/PTY support
- **Debian trixie-slim** — glibc, full terminal/PTY support
- **opencode** — AI coding assistant
- **Node.js 22** — for npx-based MCP servers
- **AWS CLI v2** — SSO and Bedrock authentication
- **Dev tools** — git, git-lfs, ssh, ripgrep, fd, fzf, jq, curl, wget, vim, tree
- **Dev tools** — git, git-lfs, ssh, ripgrep, fd, fzf, bat, eza, zoxide, uv, rustup, jq, make, curl, wget, neovim 0.12, tmux, htop, tree
- **Non-root user** — runs as `developer` with UID auto-matched to workspace owner (sudo available)
### OMOS image (`latest-omos`)
@@ -296,7 +441,6 @@ Everything in the base image, plus:
- **[oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim)** — multi-agent orchestration plugin
- **Bun** — JavaScript runtime required by oh-my-opencode-slim
- **tmux** — terminal multiplexer (used by OMOS for agent pane integration, but also useful on its own for managing multiple terminal sessions)
- **6 specialized agents** — Orchestrator, Explorer, Oracle, Librarian, Designer, Fixer
### Additional runtimes (build from source)
@@ -335,15 +479,7 @@ On first start, the entrypoint configures oh-my-opencode-slim automatically. The
### Custom OMOS configuration
Mount your own config to control which models power each agent:
```bash
docker run -it --rm \
-e ENABLE_OMOS=true \
-v ./oh-my-opencode-slim.json:/home/developer/.config/opencode/oh-my-opencode-slim.json:ro \
... \
joakimp/opencode-devbox:latest-omos
```
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](https://github.com/alvinunreal/oh-my-opencode-slim/blob/master/docs/configuration.md) for the full reference.
+53 -5
View File
@@ -1,7 +1,7 @@
# opencode-devbox — portable AI dev environment
# Debian-based container with opencode and configurable dev tools
ARG DEBIAN_VERSION=bookworm-slim
ARG DEBIAN_VERSION=trixie-slim
FROM debian:${DEBIAN_VERSION} AS base
ARG TARGETARCH
@@ -27,7 +27,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
fd-find \
tree \
less \
vim-tiny \
htop \
tmux \
make \
patch \
diffutils \
sudo \
locales \
procps \
@@ -59,11 +63,57 @@ RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;;
git lfs install --system && \
git-lfs --version
# neovim — modern text editor (pre-built release from GitHub)
ARG NVIM_VERSION=0.12.1
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "arm64" ;; *) echo "x86_64" ;; esac) && \
curl -fsSL "https://github.com/neovim/neovim/releases/download/v${NVIM_VERSION}/nvim-linux-${ARCH}.tar.gz" | tar -xz -C /opt && \
ln -s /opt/nvim-linux-${ARCH}/bin/nvim /usr/local/bin/nvim && \
nvim --version | head -1
# bat — syntax-highlighted cat replacement
ARG BAT_VERSION=0.25.0
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
curl -fsSL "https://github.com/sharkdp/bat/releases/download/v${BAT_VERSION}/bat-v${BAT_VERSION}-${ARCH}-unknown-linux-musl.tar.gz" | tar -xz -C /tmp && \
install /tmp/bat-v${BAT_VERSION}-${ARCH}-unknown-linux-musl/bat /usr/local/bin/bat && \
rm -rf /tmp/bat-v${BAT_VERSION}-* && \
bat --version
# eza — modern ls replacement
ARG EZA_VERSION=0.23.4
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
curl -fsSL "https://github.com/eza-community/eza/releases/download/v${EZA_VERSION}/eza_${ARCH}-unknown-linux-gnu.tar.gz" | tar -xz -C /usr/local/bin && \
eza --version | head -1
# zoxide — smarter cd command
ARG ZOXIDE_VERSION=0.9.9
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
curl -fsSL "https://github.com/ajeetdsouza/zoxide/releases/download/v${ZOXIDE_VERSION}/zoxide-${ZOXIDE_VERSION}-${ARCH}-unknown-linux-musl.tar.gz" | tar -xz -C /usr/local/bin zoxide && \
zoxide --version
# uv — fast Python package manager (replaces pip, venv, pyenv)
ARG UV_VERSION=0.11.6
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
curl -fsSL "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${ARCH}-unknown-linux-musl.tar.gz" | tar -xz -C /tmp && \
install /tmp/uv-${ARCH}-unknown-linux-musl/uv /usr/local/bin/uv && \
install /tmp/uv-${ARCH}-unknown-linux-musl/uvx /usr/local/bin/uvx && \
rm -rf /tmp/uv-* && \
uv --version
# rustup — Rust toolchain manager
# Installs the rustup-init binary only. Users bootstrap Rust with:
# rustup-init -y && source ~/.cargo/env
# Toolchains persist via devbox-rustup and devbox-cargo volumes.
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
curl -fsSL "https://static.rust-lang.org/rustup/dist/${ARCH}-unknown-linux-gnu/rustup-init" -o /usr/local/bin/rustup-init && \
chmod +x /usr/local/bin/rustup-init
# Set locale
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8
ENV EDITOR=nvim
ENV PATH="/home/developer/.local/bin:/home/developer/.cargo/bin:${PATH}"
# ── Node.js (required for opencode v1.x install + MCP servers) ──────
ARG NODE_VERSION=22
@@ -107,13 +157,11 @@ RUN if [ "${INSTALL_GO}" = "true" ]; then \
fi
# ── Optional: oh-my-opencode-slim (multi-agent orchestration) ────────
# Installs Bun runtime, tmux, and the oh-my-opencode-slim npm package.
# Installs Bun runtime and the oh-my-opencode-slim npm package.
# Runtime activation is controlled by ENABLE_OMOS env var in entrypoint.
ARG INSTALL_OMOS=false
ARG OMOS_VERSION=latest
RUN if [ "${INSTALL_OMOS}" = "true" ]; then \
apt-get update && apt-get install -y --no-install-recommends tmux && \
rm -rf /var/lib/apt/lists/* && \
curl -fsSL https://bun.sh/install | BUN_INSTALL=/usr/local bash && \
bun --version && \
npm install -g oh-my-opencode-slim@${OMOS_VERSION}; \
+158 -23
View File
@@ -27,19 +27,33 @@ docker compose run --rm devbox
## Features
- **Debian bookworm** base — glibc, full PTY/terminal support
- **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)
- **Optional runtimes** — Python, Go via build args (Node.js always included — required for opencode v1.x)
- **Python via uv** — `uv` package manager included; install Python on demand with `uv python install`
- **Rust via rustup** — `rustup-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](https://github.com/alvinunreal/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.
```bash
# 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:
@@ -110,23 +124,146 @@ docker compose exec -u developer devbox aws --version
### Custom opencode config
Mount your own `opencode.json` for full control (MCP servers, custom models, etc.):
For full control over opencode settings (MCP servers, custom models, oh-my-opencode-slim agents, etc.), mount the entire config directory from the host:
```yaml
volumes:
- ./my-opencode.json:/home/developer/.config/opencode/opencode.json:ro
- ~/.config/opencode:/home/developer/.config/opencode
```
This persists all configuration changes across container restarts, including `opencode.json`, `oh-my-opencode-slim.json`, and skills. When an existing `opencode.json` is found, the `OPENCODE_PROVIDER` auto-config is skipped.
### Custom skills
Mount your host's opencode skills into the container:
Mount agent skills from the host:
```yaml
volumes:
- ~/.config/opencode/skills:/home/developer/.config/opencode/skills:ro
- ~/.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:
```yaml
volumes:
- ~/.config/nvim:/home/developer/.config/nvim:ro
```
### Python development with uv
The image includes [uv](https://docs.astral.sh/uv/), a fast Python package manager that replaces pip, venv, and pyenv. Python is not pre-installed but can be installed on demand:
```bash
# Install Python (persists across restarts with devbox-uv volume)
uv python install 3.14
# Create a virtual environment and install dependencies
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 .
```
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`:
```yaml
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:
```bash
# 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`:
```yaml
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:
```bash
# 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:
```bash
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](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-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](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh) and [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-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:
```yaml
volumes:
- devbox-vscode:/home/developer/.vscode-server
```
### 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:
@@ -153,7 +290,7 @@ docker compose build --build-arg OPENCODE_VERSION=1.5.0
|---|---|---|
| `INSTALL_PYTHON` | `false` | Python 3 + pip + venv |
| `INSTALL_GO` | `false` | Go toolchain |
| `INSTALL_OMOS` | `false` | [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim) multi-agent orchestration (installs Bun, tmux, and plugin) |
| `INSTALL_OMOS` | `false` | [oh-my-opencode-slim](https://github.com/alvinunreal/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)
@@ -170,7 +307,7 @@ A pre-built OMOS image is available on Docker Hub as `joakimp/opencode-devbox:la
docker compose build --build-arg INSTALL_OMOS=true
```
This installs Bun, tmux, and the oh-my-opencode-slim package into the image.
This installs Bun and the oh-my-opencode-slim package into the image.
**2. Enable in `.env`:**
@@ -191,20 +328,15 @@ On first start, the entrypoint runs the oh-my-opencode-slim installer in non-int
| Variable | Default | Description |
|---|---|---|
| `ENABLE_OMOS` | `false` | Activate oh-my-opencode-slim on container start |
| `OMOS_TMUX` | `false` | Enable tmux pane integration (tmux is included with `INSTALL_OMOS`) |
| `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
You can mount your own oh-my-opencode-slim config instead of using the auto-generated one:
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.
```yaml
volumes:
- ./oh-my-opencode-slim.json:/home/developer/.config/opencode/oh-my-opencode-slim.json:ro
```
The config file controls which models power each agent, fallback chains, council setup, and more. See the [oh-my-opencode-slim configuration docs](https://github.com/alvinunreal/oh-my-opencode-slim/blob/master/docs/configuration.md) for the full reference.
See the [oh-my-opencode-slim configuration docs](https://github.com/alvinunreal/oh-my-opencode-slim/blob/master/docs/configuration.md) for the full reference.
### Verifying Agents
@@ -301,14 +433,14 @@ Host Machine
├── ~/.aws ──bind mount──▶ /home/developer/.aws (Bedrock SSO)
└── .env ──env vars───▶ provider config + API keys
Container (Debian bookworm)
Container (Debian trixie)
├── opencode binary
├── oh-my-opencode-slim (optional — multi-agent orchestration plugin, includes Bun + tmux)
├── oh-my-opencode-slim (optional — multi-agent orchestration plugin, includes Bun)
├── AWS CLI v2 (SSO + Bedrock auth)
├── git, ssh, ripgrep, fd, jq, curl, fzf
├── neovim 0.12, tmux, htop, bat, eza, zoxide, uv, rustup, make
├── git, ssh, ripgrep, fd, fzf, jq, curl, tree
├── Node.js (for MCP servers)
├── Bun (optional — included with oh-my-opencode-slim)
├── tmux (optional — included with oh-my-opencode-slim, also useful independently)
├── entrypoint.sh (UID adjustment, git config, provider setup)
└── /workspace ← your code lives here
```
@@ -321,10 +453,13 @@ Container (Debian bookworm)
| `/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/.config/opencode/opencode.json` | Generated by entrypoint | ❌ No | Provider/model config |
| `/home/developer/.config/opencode/oh-my-opencode-slim.json` | Generated by entrypoint (if OMOS enabled) | ❌ No | Agent/model mappings |
| `/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, oh-my-opencode-slim.json, skills |
**opencode config** (`opencode.json`) is auto-generated from `OPENCODE_PROVIDER` on each start. It sets provider and model only — no MCP servers. To use MCP servers or custom settings, mount your own config file (see Custom opencode config above).
**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
+23 -6
View File
@@ -31,21 +31,38 @@ services:
# SSH keys (read-only) — for git push/pull
- ${SSH_KEY_PATH:-~/.ssh}:/home/developer/.ssh:ro
# Optional: mount your own opencode config (MCP servers, custom models, etc.)
# - ./opencode.json:/home/developer/.config/opencode/opencode.json:ro
# Optional: mount opencode config directory (persists config changes across restarts)
# Includes opencode.json, oh-my-opencode-slim.json, skills, etc.
# When mounted, OPENCODE_PROVIDER auto-config is skipped if opencode.json exists.
# - ~/.config/opencode:/home/developer/.config/opencode
# Optional: mount opencode skills from host
# - ~/.config/opencode/skills:/home/developer/.config/opencode/skills:ro
# Optional: mount opencode agent skills from host
# - ~/.agents/skills:/home/developer/.agents/skills:ro
# Optional: mount your own oh-my-opencode-slim config
# - ./oh-my-opencode-slim.json:/home/developer/.config/opencode/oh-my-opencode-slim.json:ro
# Optional: mount neovim config from host (plugins auto-install on first start)
# - ~/.config/nvim:/home/developer/.config/nvim:ro
# Optional: persist opencode data (auth, memory, etc.)
- devbox-data:/home/developer/.local/share/opencode
# Optional: persist uv data (Python installs, tool installs)
# Without this, 'uv python install' must be re-run after container removal.
- devbox-uv:/home/developer/.local/share/uv
# Optional: persist Rust toolchains and cargo data
# Without this, 'rustup-init' must be re-run after container removal.
# - devbox-rustup:/home/developer/.rustup
# - devbox-cargo:/home/developer/.cargo
# Optional: persist VS Code server and extensions across container recreations
# - devbox-vscode:/home/developer/.vscode-server
# Optional: AWS credentials/SSO config (not read-only — SSO writes token cache)
# - ~/.aws:/home/developer/.aws
volumes:
devbox-data:
devbox-uv:
# devbox-rustup:
# devbox-cargo:
# devbox-vscode:
+2 -1
View File
@@ -48,7 +48,8 @@ EOF
"provider": {
"amazon-bedrock": {
"options": {
"region": "${AWS_REGION:-us-east-1}"
"region": "${AWS_REGION:-us-east-1}",
"profile": "${AWS_PROFILE:-default}"
}
}
}
+19
View File
@@ -46,5 +46,24 @@ if [ -d "/home/$USER_NAME/.ssh" ] && [ "$(ls -A "/home/$USER_NAME/.ssh" 2>/dev/n
fi
fi
# ── Fix ownership of named volume mount points ──────────────────────
# Named volumes are created as root on first use. Fix ownership so the
# developer user can write to them.
FINAL_UID="${TARGET_UID:-$CURRENT_UID}"
FINAL_GID="${TARGET_GID:-$CURRENT_GID}"
for dir in \
/home/"$USER_NAME"/.local/share/opencode \
/home/"$USER_NAME"/.local/share/uv \
/home/"$USER_NAME"/.rustup \
/home/"$USER_NAME"/.cargo \
/home/"$USER_NAME"/.vscode-server \
/home/"$USER_NAME"/.config/opencode \
/home/"$USER_NAME"/.config/nvim \
/home/"$USER_NAME"/.agents/skills; do
if [ -d "$dir" ] && [ "$(stat -c '%u' "$dir" 2>/dev/null)" != "$FINAL_UID" ]; then
chown -R "$FINAL_UID":"$FINAL_GID" "$dir" 2>/dev/null || true
fi
done
# ── Drop to developer user for remaining setup ──────────────────────
exec gosu "$USER_NAME" /usr/local/bin/entrypoint-user.sh "$@"