Compare commits

...

23 Commits

Author SHA1 Message Date
joakimp 56f98da914 Add error handling to Docker Hub description update step
Publish Docker Image / build-base (push) Successful in 31m21s
Publish Docker Image / build-omos (push) Successful in 32m22s
Publish Docker Image / update-description (push) Failing after 15s
2026-04-11 22:16:09 +02:00
joakimp 078c095116 Parallelize base and omos image builds into separate CI jobs
Publish Docker Image / build-base (push) Successful in 30m52s
Publish Docker Image / build-omos (push) Successful in 35m17s
Publish Docker Image / update-description (push) Successful in 14s
2026-04-11 18:20:33 +02:00
joakimp e50617d9b9 Clarify tmux is independently useful in OMOS variant documentation
Publish Docker Image / build-and-push (push) Has been cancelled
2026-04-11 16:36:15 +02:00
joakimp 986fed9e05 Add oh-my-opencode-slim to DOCKER_HUB.md as optional build-from-source feature 2026-04-11 16:21:11 +02:00
joakimp 4729131e4e Add optional oh-my-opencode-slim multi-agent orchestration support
Integrate oh-my-opencode-slim as an opt-in feature via INSTALL_OMOS build arg.
A single build arg installs Bun, tmux, and the plugin; runtime activation is
controlled by ENABLE_OMOS and related env vars in the entrypoint.
2026-04-11 16:15:47 +02:00
joakimp 973e727acb Fix CVEs: install git-lfs from GitHub (Go 1.25), document Go versions for gosu/fzf
Publish Docker Image / build-and-push (push) Successful in 28m34s
2026-04-10 23:30:14 +02:00
joakimp 9c8a2c0169 Fix CVEs: install gosu 1.19 and fzf 0.71.0 from GitHub releases instead of Debian packages
Publish Docker Image / build-and-push (push) Successful in 28m17s
2026-04-10 22:18:42 +02:00
joakimp 18e55aeb18 Fix CVEs: install gosu from GitHub release instead of Debian package (Go 1.19.8 → current)
Publish Docker Image / build-and-push (push) Successful in 28m38s
2026-04-10 20:20:12 +02:00
joakimp 73e94c7e54 Overhaul docs: fix docker-compose to use env_file, add AWS SSO setup guide, clarify exec -u developer 2026-04-10 20:10:18 +02:00
joakimp fa4739e061 Fix exec examples: specify -u developer since container starts as root 2026-04-10 19:40:19 +02:00
joakimp b72079f9fa Add AWS mount to docker-compose examples, remove :ro for SSO token writes 2026-04-10 19:31:32 +02:00
joakimp ba41187e21 Fix entrypoint crash on read-only SSH mount
Publish Docker Image / build-and-push (push) Successful in 28m27s
2026-04-10 17:24:37 +02:00
joakimp 4112d30a3e Add Linux install instructions for gitleaks 2026-04-10 15:49:10 +02:00
joakimp 8d06a737f1 Add OPENCODE_MODEL and AWS_PROFILE to .env.example 2026-04-10 15:46:53 +02:00
joakimp aaf7d66157 Update non-root user description to reflect UID auto-matching 2026-04-10 15:35:51 +02:00
joakimp bc1dceeaa1 Bump opencode to 1.4.3
Publish Docker Image / build-and-push (push) Successful in 28m47s
2026-04-10 14:14:08 +02:00
joakimp 7685facb37 Add runtime UID/GID adjustment to match host workspace owner 2026-04-10 13:57:45 +02:00
joakimp 94b64db751 Add .env setup instructions to Docker Hub docker-compose section 2026-04-10 13:45:41 +02:00
joakimp 9b1f7d1028 Add optional skill directory mounts for host-based opencode skills 2026-04-10 13:06:58 +02:00
joakimp 476d9fb4f5 Add commented-out config mount to docker-compose example in Docker Hub docs 2026-04-10 13:00:51 +02:00
joakimp ca5efe1007 Document data persistence and storage locations 2026-04-10 10:00:51 +02:00
joakimp 4b7b8a0c4b Add multiple shells section to Docker Hub docs 2026-04-10 08:56:07 +02:00
joakimp c0b887791f Add Docker Hub description update step to CI workflow 2026-04-10 08:47:27 +02:00
9 changed files with 682 additions and 130 deletions
+8 -1
View File
@@ -10,7 +10,7 @@ OPENCODE_PROVIDER=anthropic
# OPENCODE_MODEL=anthropic/claude-sonnet-4-5
# ── API Keys (set the one matching your provider) ────────────────────
ANTHROPIC_API_KEY=
# ANTHROPIC_API_KEY=
# OPENAI_API_KEY=
# GEMINI_API_KEY=
@@ -30,3 +30,10 @@ WORKSPACE_PATH=~/projects
# Path to SSH keys on host
SSH_KEY_PATH=~/.ssh
# ── oh-my-opencode-slim (multi-agent orchestration) ──────────────────
# Requires image built with INSTALL_OMOS=true
# ENABLE_OMOS=false
# OMOS_TMUX=false # Enable tmux multiplexer integration
# OMOS_SKILLS=true # Install recommended skills (simplify, agent-browser, cartography)
# OMOS_RESET=false # Force regenerate oh-my-opencode-slim config on next start
+74 -2
View File
@@ -6,7 +6,7 @@ on:
- 'v*'
jobs:
build-and-push:
build-base:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
@@ -32,7 +32,7 @@ jobs:
VERSION=${GITHUB_REF#refs/tags/}
echo "version=${VERSION}" >> $GITHUB_OUTPUT
- name: Build and push
- name: Build and push (base)
uses: docker/build-push-action@v7
with:
context: .
@@ -41,3 +41,75 @@ jobs:
tags: |
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:${{ steps.version.outputs.version }}
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:latest
build-omos:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract version from tag
id: version
run: |
VERSION=${GITHUB_REF#refs/tags/}
echo "version=${VERSION}" >> $GITHUB_OUTPUT
- name: Build and push (omos)
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
build-args: |
INSTALL_OMOS=true
tags: |
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:${{ steps.version.outputs.version }}-omos
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:latest-omos
update-description:
runs-on: ubuntu-latest
needs: [build-base, build-omos]
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Update Docker Hub description
run: |
TOKEN=$(curl -s -X POST https://hub.docker.com/v2/users/login/ \
-H "Content-Type: application/json" \
-d '{"username":"${{ vars.DOCKERHUB_USERNAME }}","password":"${{ secrets.DOCKERHUB_TOKEN }}"}' \
| jq -r .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." \
'{"full_description": $full, "description": $short}' | \
curl -s -o /dev/null -w "%{http_code}" -X PATCH \
"https://hub.docker.com/v2/repositories/${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox/" \
-H "Authorization: JWT $TOKEN" \
-H "Content-Type: application/json" \
-d @-)
echo "Docker Hub API returned: $HTTP_CODE"
if [ "$HTTP_CODE" != "200" ]; then
echo "::error::Docker Hub description update failed with HTTP $HTTP_CODE"
exit 1
fi
+237 -26
View File
@@ -2,6 +2,17 @@
Portable AI developer environment for [opencode](https://opencode.ai). Debian-based, with git, SSH, Node.js, AWS CLI v2, and common dev tools pre-installed.
## Image Variants
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 |
Both variants support `linux/amd64` and `linux/arm64`.
## Quick Start
```bash
@@ -32,8 +43,35 @@ docker run -it --rm \
Then run `opencode` when ready.
## Running Multiple Shells
Once opencode is running it takes over the terminal. To have a separate shell for `aws`, `git`, or other commands, run the container in the background and attach multiple times:
```bash
# Start in background
docker run -d --name devbox \
-e ANTHROPIC_API_KEY=your-key \
-e OPENCODE_PROVIDER=anthropic \
-v ~/projects:/workspace \
-v ~/.ssh:/home/developer/.ssh:ro \
joakimp/opencode-devbox:latest sleep infinity
# Shell 1: run opencode
docker exec -it -u developer devbox opencode
# Shell 2 (separate terminal): aws, git, etc.
docker exec -it -u developer devbox bash
# When done
docker rm -f devbox
```
> **Note:** Always use `-u developer` with `docker exec` — the container starts as root for UID adjustment, then drops to `developer`. Without `-u developer`, exec runs as root.
## Environment Variables
All configuration is done via environment variables, typically stored in a `.env` file.
### Provider Configuration
| Variable | Description | Default |
@@ -56,14 +94,7 @@ Set the key matching your provider:
| Variable | Description | Default |
|---|---|---|
| `AWS_REGION` | AWS region | `us-east-1` |
| `AWS_PROFILE` | AWS profile name | `default` |
For SSO authentication, start with `bash` and run:
```bash
aws sso login --sso-session <your-session> --use-device-code
opencode
```
| `AWS_PROFILE` | AWS SSO profile name | `default` |
### Git
@@ -72,26 +103,97 @@ opencode
| `GIT_USER_NAME` | Git commit author name |
| `GIT_USER_EMAIL` | Git commit author email |
## Volumes
### User ID Mapping
| Host Path | Container Path | Purpose |
The container runs as user `developer` (UID 1000 by default). If your host user has a different UID, file permission mismatches can occur on mounted volumes.
The entrypoint automatically detects the owner of `/workspace` and adjusts the container user's UID/GID to match. You can also set it explicitly:
| Variable | Description | Default |
|---|---|---|
| Your project directory | `/workspace` | Code you want to work on |
| `~/.ssh` | `/home/developer/.ssh:ro` | SSH keys for git (read-only) |
| (optional) `~/.aws` | `/home/developer/.aws:ro` | AWS credentials/config |
| (optional) Custom config | `/home/developer/.config/opencode/opencode.json:ro` | Full opencode config with MCP servers, etc. |
| `USER_UID` | Container user UID | Auto-detect from `/workspace` owner |
| `USER_GID` | Container user GID | Auto-detect from `/workspace` owner |
### Persisting opencode data
## Initial Setup
To keep opencode state (session history, memory) between runs, add a named volume:
### 1. Create a project directory
```bash
docker run -it --rm \
-v opencode-data:/home/developer/.local/share/opencode \
... \
joakimp/opencode-devbox:latest
mkdir -p ~/projects
```
### 2. Create a `.env` file
Create a `.env` file with your configuration. Examples for each provider:
**Anthropic:**
```bash
OPENCODE_PROVIDER=anthropic
ANTHROPIC_API_KEY=sk-ant-...
GIT_USER_NAME=Your Name
GIT_USER_EMAIL=you@example.com
```
**OpenAI:**
```bash
OPENCODE_PROVIDER=openai
OPENAI_API_KEY=sk-...
GIT_USER_NAME=Your Name
GIT_USER_EMAIL=you@example.com
```
**AWS Bedrock (SSO):**
```bash
OPENCODE_PROVIDER=amazon-bedrock
OPENCODE_MODEL=amazon-bedrock/anthropic.claude-sonnet-4-5-v1
AWS_REGION=eu-west-1
AWS_PROFILE=your-profile-name
GIT_USER_NAME=Your Name
GIT_USER_EMAIL=you@example.com
```
### 3. AWS SSO setup (Bedrock users only)
AWS SSO requires a `~/.aws/config` file on the host with your SSO session configuration. If you already have this on another machine, copy it:
```bash
scp -r user@other-machine:~/.aws ~/.aws
```
Or configure from scratch:
```bash
aws configure sso
```
You'll be prompted for:
- SSO session name
- SSO start URL
- SSO region
- Registration scopes (typically `sso:account:access`)
The `~/.aws` directory must be mounted into the container (see docker-compose example below).
## Data Storage and Persistence
Understanding what survives container restarts and what doesn't:
| Path in container | Source | Survives restart? | Contains |
|---|---|---|---|
| `/workspace` | Host bind mount | ✅ Yes — lives on host | Your project files |
| `/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 |
### 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 data** (session history, memory) is lost with `--rm` unless you add a named volume.
- **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:
@@ -107,44 +209,153 @@ When a config file is mounted, the `OPENCODE_PROVIDER` auto-config is skipped.
## Using docker-compose
Create a `docker-compose.yml`:
Create a directory with a `docker-compose.yml` and a `.env` file:
```bash
mkdir opencode-devbox && cd opencode-devbox
```
`.env` — your settings (never commit this):
```bash
OPENCODE_PROVIDER=amazon-bedrock
OPENCODE_MODEL=amazon-bedrock/anthropic.claude-sonnet-4-5-v1
AWS_REGION=eu-west-1
AWS_PROFILE=your-profile-name
GIT_USER_NAME=Your Name
GIT_USER_EMAIL=you@example.com
```
`docker-compose.yml`:
```yaml
services:
devbox:
image: joakimp/opencode-devbox:latest
# For multi-agent orchestration, use the omos variant instead:
# image: joakimp/opencode-devbox:latest-omos
stdin_open: true
tty: true
env_file:
- .env
environment:
- TERM=xterm-256color
- OPENCODE_PROVIDER=anthropic
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- GIT_USER_NAME=${GIT_USER_NAME}
- GIT_USER_EMAIL=${GIT_USER_EMAIL}
volumes:
- ~/projects:/workspace
- ~/.ssh:/home/developer/.ssh:ro
- devbox-data:/home/developer/.local/share/opencode
# 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
# - ~/.agents/skills:/home/developer/.agents/skills:ro
volumes:
devbox-data:
```
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.
Then:
```bash
# Start in background
docker compose up -d
# Open a shell (always use -u developer with exec)
docker compose exec -u developer devbox bash
# For Bedrock: authenticate, then start opencode
aws sso login --sso-session <your-session> --use-device-code
opencode
# Or run opencode directly (if no SSO needed)
docker compose exec -u developer devbox opencode
# One-shot mode (creates and removes container)
docker compose run --rm devbox # direct to opencode
docker compose run --rm devbox bash # interactive shell
```
## What's Included
### Base image (`latest`)
- **Debian bookworm-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
- **Non-root user** — runs as `developer` (UID 1000) with sudo access
- **Non-root user** — runs as `developer` with UID auto-matched to workspace owner (sudo available)
### OMOS image (`latest-omos`)
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)
When [building from source](https://gitea.jordbo.se/joakimp/opencode-devbox), additional runtimes are available via build args:
- **Python 3** (`INSTALL_PYTHON=true`) — Python 3 + pip + venv
- **Go** (`INSTALL_GO=true`) — Go toolchain
## oh-my-opencode-slim (OMOS variant)
The `-omos` image variant includes [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim), which adds a multi-agent layer on top of opencode. An Orchestrator delegates tasks to specialized agents, each configurable with different models and providers.
### Quick start with OMOS
```bash
docker run -it --rm \
-e OPENAI_API_KEY=your-key \
-e OPENCODE_PROVIDER=openai \
-e ENABLE_OMOS=true \
-v ~/projects:/workspace \
-v ~/.ssh:/home/developer/.ssh:ro \
joakimp/opencode-devbox:latest-omos
```
On first start, the entrypoint configures oh-my-opencode-slim automatically. The default preset uses OpenAI models.
### OMOS environment variables
| Variable | Default | Description |
|---|---|---|
| `ENABLE_OMOS` | `false` | Activate oh-my-opencode-slim on container start |
| `OMOS_TMUX` | `false` | Enable tmux pane integration (watch agents in split panes) |
| `OMOS_SKILLS` | `true` | Install recommended skills (simplify, agent-browser, cartography) |
| `OMOS_RESET` | `false` | Force regenerate config on next start (backs up existing config) |
### 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
```
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
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.
## Source
+43 -6
View File
@@ -5,7 +5,7 @@ ARG DEBIAN_VERSION=bookworm-slim
FROM debian:${DEBIAN_VERSION} AS base
ARG TARGETARCH
ARG OPENCODE_VERSION=1.4.2
ARG OPENCODE_VERSION=1.4.3
LABEL maintainer="joakimp"
LABEL description="Portable opencode developer container"
@@ -20,13 +20,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
wget \
git \
git-lfs \
openssh-client \
gnupg \
jq \
ripgrep \
fd-find \
fzf \
tree \
less \
vim-tiny \
@@ -37,6 +35,30 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
&& ln -s /usr/bin/fdfind /usr/local/bin/fd \
&& rm -rf /var/lib/apt/lists/*
# ── Go-compiled tools (install from GitHub to avoid CVEs in Debian's old Go builds)
# gosu — privilege de-escalation (built with Go 1.24.6)
ARG GOSU_VERSION=1.19
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \
curl -fsSL "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-${ARCH}" -o /usr/local/bin/gosu && \
chmod +x /usr/local/bin/gosu && \
gosu --version
# fzf — fuzzy finder (built with Go 1.23.12)
ARG FZF_VERSION=0.71.0
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \
curl -fsSL "https://github.com/junegunn/fzf/releases/download/v${FZF_VERSION}/fzf-${FZF_VERSION}-linux_${ARCH}.tar.gz" | tar -xz -C /usr/local/bin fzf && \
fzf --version
# git-lfs — Git Large File Storage (built with Go 1.25)
ARG GIT_LFS_VERSION=3.7.1
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \
curl -fsSL "https://github.com/git-lfs/git-lfs/releases/download/v${GIT_LFS_VERSION}/git-lfs-linux-${ARCH}-v${GIT_LFS_VERSION}.tar.gz" | tar -xz -C /tmp && \
install /tmp/git-lfs-${GIT_LFS_VERSION}/git-lfs /usr/local/bin/git-lfs && \
rm -rf /tmp/git-lfs-${GIT_LFS_VERSION} && \
git lfs install --system && \
git-lfs --version
# Set locale
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
ENV LANG=en_US.UTF-8
@@ -84,6 +106,19 @@ RUN if [ "${INSTALL_GO}" = "true" ]; then \
ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt; \
fi
# ── Optional: oh-my-opencode-slim (multi-agent orchestration) ────────
# Installs Bun runtime, tmux, 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}; \
fi
# ── Non-root user ────────────────────────────────────────────────────
ARG USER_NAME=developer
ARG USER_UID=1000
@@ -95,16 +130,18 @@ RUN groupadd --gid ${USER_GID} ${USER_NAME} && \
# Create standard directories
RUN mkdir -p /workspace \
/home/${USER_NAME}/.config/opencode \
/home/${USER_NAME}/.config/opencode/skills \
/home/${USER_NAME}/.agents/skills \
/home/${USER_NAME}/.local/share/opencode \
/home/${USER_NAME}/.ssh && \
chown -R ${USER_NAME}:${USER_NAME} /workspace /home/${USER_NAME}
# ── Entrypoint ────────────────────────────────────────────────────────
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
COPY entrypoint-user.sh /usr/local/bin/entrypoint-user.sh
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/entrypoint-user.sh
USER ${USER_NAME}
# Start as root — entrypoint adjusts UID/GID then drops to developer
WORKDIR /workspace
ENTRYPOINT ["entrypoint.sh"]
+140 -11
View File
@@ -18,7 +18,7 @@ cp .env.example .env
# Edit .env with your provider, API key, workspace path, git config
# Install git hooks (secret scanning)
brew install gitleaks # one-time
brew install gitleaks # macOS / Linuxbrew
./setup-hooks.sh
# Build and run
@@ -32,8 +32,9 @@ docker compose run --rm devbox
- **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` (UID 1000) with sudo
- **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)
- **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
@@ -76,10 +77,10 @@ docker compose run --rm devbox
docker compose up -d
# Attach a shell to the running container
docker compose exec devbox bash
docker compose exec -u developer devbox bash
# Or run a single command inside it
docker compose exec devbox aws --version
docker compose exec -u developer devbox aws --version
```
> `run` creates a new container (cleaned up with `--rm`). `exec` attaches to an already running one.
@@ -95,10 +96,17 @@ docker compose exec devbox aws --version
| `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` |
| `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
@@ -109,6 +117,16 @@ volumes:
- ./my-opencode.json:/home/developer/.config/opencode/opencode.json:ro
```
### Custom skills
Mount your host's opencode skills into the container:
```yaml
volumes:
- ~/.config/opencode/skills:/home/developer/.config/opencode/skills:ro
- ~/.agents/skills:/home/developer/.agents/skills:ro
```
### 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:
@@ -135,14 +153,105 @@ 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) |
| `OMOS_VERSION` | `latest` | Pin a specific oh-my-opencode-slim version |
## oh-my-opencode-slim (Multi-Agent Orchestration)
[oh-my-opencode-slim](https://github.com/alvinunreal/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:**
```bash
docker compose build --build-arg INSTALL_OMOS=true
```
This installs Bun, tmux, and the oh-my-opencode-slim package into the image.
**2. Enable in `.env`:**
```bash
ENABLE_OMOS=true
```
**3. Run as normal:**
```bash
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 with `INSTALL_OMOS`) |
| `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:
```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.
### 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 to authenticate via AWS SSO from inside the container. Since the container runs headless (no browser), use the device-code flow:
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:
```bash
# Start the container interactively
docker compose run --rm devbox bash
scp -r user@other-machine:~/.aws ~/.aws
```
Or configure from scratch on the host:
```bash
aws configure sso
```
### 2. Mount `~/.aws` into the container
Uncomment the AWS volume mount in `docker-compose.yml`:
```yaml
- ~/.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:
```bash
# 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
@@ -153,7 +262,7 @@ 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.
SSO sessions typically last 812 hours before requiring re-authentication. Since `~/.aws` is mounted from the host, tokens persist across container restarts.
## Secret Scanning
@@ -162,8 +271,11 @@ A [gitleaks](https://github.com/gitleaks/gitleaks) pre-commit hook prevents acci
### Setup
```bash
brew install gitleaks # one-time install
./setup-hooks.sh # installs the pre-commit hook
# 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).
@@ -186,17 +298,34 @@ Allowlisted paths and rules are in `.gitleaks.toml`. The defaults extend gitleak
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 bookworm)
├── opencode binary
├── oh-my-opencode-slim (optional — multi-agent orchestration plugin, includes Bun + tmux)
├── AWS CLI v2 (SSO + Bedrock auth)
├── git, ssh, ripgrep, fd, jq, curl, fzf
├── Node.js (for MCP servers)
├── entrypoint.sh (SSH perms, git config, provider setup)
├── 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
```
### 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/.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 |
**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).
## License
MIT
+13 -5
View File
@@ -3,7 +3,7 @@
# Usage:
# cp .env.example .env # configure your provider and keys
# docker compose up -d
# docker compose exec devbox opencode
# docker compose exec -u developer devbox opencode
#
# Or for interactive one-shot:
# docker compose run --rm devbox
@@ -15,6 +15,7 @@ services:
args:
INSTALL_PYTHON: "false"
INSTALL_GO: "false"
INSTALL_OMOS: "false"
image: opencode-devbox:latest
container_name: opencode-devbox
stdin_open: true
@@ -30,14 +31,21 @@ services:
# SSH keys (read-only) — for git push/pull
- ${SSH_KEY_PATH:-~/.ssh}:/home/developer/.ssh:ro
# Optional: mount your own opencode config
# - ./config/opencode.json:/home/developer/.config/opencode/opencode.json:ro
# 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
# - ~/.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: persist opencode data (auth, memory, etc.)
- devbox-data:/home/developer/.local/share/opencode
# Optional: AWS credentials for Bedrock
# - ~/.aws:/home/developer/.aws:ro
# Optional: AWS credentials/SSO config (not read-only — SSO writes token cache)
# - ~/.aws:/home/developer/.aws
volumes:
devbox-data:
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
set -euo pipefail
# ── Git config defaults ──────────────────────────────────────────────
if [ -n "${GIT_USER_NAME:-}" ] && ! git config --global user.name &>/dev/null; then
git config --global user.name "$GIT_USER_NAME"
fi
if [ -n "${GIT_USER_EMAIL:-}" ] && ! git config --global user.email &>/dev/null; then
git config --global user.email "$GIT_USER_EMAIL"
fi
# ── Generate opencode config from env vars if no config mounted ──────
CONFIG_DIR="$HOME/.config/opencode"
CONFIG_FILE="$CONFIG_DIR/opencode.json"
if [ ! -f "$CONFIG_FILE" ] && [ -n "${OPENCODE_PROVIDER:-}" ]; then
echo "Generating opencode config for provider: $OPENCODE_PROVIDER"
mkdir -p "$CONFIG_DIR"
case "$OPENCODE_PROVIDER" in
anthropic)
cat > "$CONFIG_FILE" <<EOF
{
"\$schema": "https://opencode.ai/config.json",
"model": "${OPENCODE_MODEL:-anthropic/claude-sonnet-4-5}",
"share": "disabled",
"autoupdate": false
}
EOF
;;
openai)
cat > "$CONFIG_FILE" <<EOF
{
"\$schema": "https://opencode.ai/config.json",
"model": "${OPENCODE_MODEL:-openai/gpt-4o}",
"share": "disabled",
"autoupdate": false
}
EOF
;;
amazon-bedrock)
cat > "$CONFIG_FILE" <<EOF
{
"\$schema": "https://opencode.ai/config.json",
"model": "${OPENCODE_MODEL:-amazon-bedrock/anthropic.claude-sonnet-4-5-v1}",
"share": "disabled",
"autoupdate": false,
"provider": {
"amazon-bedrock": {
"options": {
"region": "${AWS_REGION:-us-east-1}"
}
}
}
}
EOF
;;
*)
cat > "$CONFIG_FILE" <<EOF
{
"\$schema": "https://opencode.ai/config.json",
"model": "${OPENCODE_MODEL:-anthropic/claude-sonnet-4-5}",
"share": "disabled",
"autoupdate": false
}
EOF
;;
esac
fi
# ── oh-my-opencode-slim setup (multi-agent orchestration) ────────────
# Activated by ENABLE_OMOS=true. Requires the image to be built with
# INSTALL_OMOS=true (which installs bun + the oh-my-opencode-slim package).
OMOS_CONFIG="$CONFIG_DIR/oh-my-opencode-slim.json"
if [ "${ENABLE_OMOS:-false}" = "true" ]; then
if ! command -v bunx &>/dev/null; then
echo "WARNING: ENABLE_OMOS=true but bun is not installed."
echo "Rebuild with: docker compose build --build-arg INSTALL_OMOS=true"
elif [ ! -f "$OMOS_CONFIG" ]; then
echo "Setting up oh-my-opencode-slim agents..."
# Determine installer flags
OMOS_TMUX_FLAG="no"
if [ "${OMOS_TMUX:-false}" = "true" ]; then
OMOS_TMUX_FLAG="yes"
fi
OMOS_SKILLS_FLAG="yes"
if [ "${OMOS_SKILLS:-true}" = "false" ]; then
OMOS_SKILLS_FLAG="no"
fi
bunx oh-my-opencode-slim@latest install \
--no-tui \
--tmux="${OMOS_TMUX_FLAG}" \
--skills="${OMOS_SKILLS_FLAG}"
echo "oh-my-opencode-slim configured successfully."
else
echo "oh-my-opencode-slim config found at $OMOS_CONFIG (use OMOS_RESET=true to overwrite)."
# Allow reset via env var (creates backup automatically)
if [ "${OMOS_RESET:-false}" = "true" ]; then
echo "OMOS_RESET=true — regenerating oh-my-opencode-slim config..."
OMOS_TMUX_FLAG="no"
[ "${OMOS_TMUX:-false}" = "true" ] && OMOS_TMUX_FLAG="yes"
OMOS_SKILLS_FLAG="yes"
[ "${OMOS_SKILLS:-true}" = "false" ] && OMOS_SKILLS_FLAG="no"
bunx oh-my-opencode-slim@latest install \
--no-tui \
--tmux="${OMOS_TMUX_FLAG}" \
--skills="${OMOS_SKILLS_FLAG}" \
--reset
fi
fi
fi
# ── Execute command ──────────────────────────────────────────────────
exec "$@"
+44 -78
View File
@@ -1,84 +1,50 @@
#!/usr/bin/env bash
set -euo pipefail
USER_NAME="developer"
CURRENT_UID=$(id -u "$USER_NAME")
CURRENT_GID=$(id -g "$USER_NAME")
# ── UID/GID adjustment ───────────────────────────────────────────────
# Priority: env vars > auto-detect from /workspace > default (1000)
TARGET_UID="${USER_UID:-}"
TARGET_GID="${USER_GID:-}"
# Auto-detect from /workspace owner if env vars not set
if [ -z "$TARGET_UID" ] && [ -d /workspace ]; then
WORKSPACE_UID=$(stat -c '%u' /workspace 2>/dev/null || stat -f '%u' /workspace 2>/dev/null)
WORKSPACE_GID=$(stat -c '%g' /workspace 2>/dev/null || stat -f '%g' /workspace 2>/dev/null)
# Only adjust if workspace is owned by a non-root user
if [ "$WORKSPACE_UID" != "0" ] && [ "$WORKSPACE_UID" != "$CURRENT_UID" ]; then
TARGET_UID="$WORKSPACE_UID"
TARGET_GID="${TARGET_GID:-$WORKSPACE_GID}"
fi
fi
# Apply UID/GID changes if needed
if [ -n "$TARGET_GID" ] && [ "$TARGET_GID" != "$CURRENT_GID" ]; then
groupmod -g "$TARGET_GID" "$USER_NAME" 2>/dev/null || true
find /home/"$USER_NAME" -not -path "/home/$USER_NAME/.ssh/*" -group "$CURRENT_GID" -exec chgrp "$TARGET_GID" {} + 2>/dev/null || true
fi
if [ -n "$TARGET_UID" ] && [ "$TARGET_UID" != "$CURRENT_UID" ]; then
usermod -u "$TARGET_UID" "$USER_NAME" 2>/dev/null || true
find /home/"$USER_NAME" -not -path "/home/$USER_NAME/.ssh/*" -user "$CURRENT_UID" -exec chown "$TARGET_UID" {} + 2>/dev/null || true
echo "Adjusted developer UID:GID to $TARGET_UID:${TARGET_GID:-$CURRENT_GID}"
fi
# ── SSH key permissions ──────────────────────────────────────────────
# If SSH keys are mounted, fix permissions (bind mounts may have wrong perms)
if [ -d "$HOME/.ssh" ] && [ "$(ls -A "$HOME/.ssh" 2>/dev/null)" ]; then
chmod 700 "$HOME/.ssh"
find "$HOME/.ssh" -type f -name "id_*" ! -name "*.pub" -exec chmod 600 {} \; 2>/dev/null || true
find "$HOME/.ssh" -type f -name "*.pub" -exec chmod 644 {} \; 2>/dev/null || true
[ -f "$HOME/.ssh/known_hosts" ] && chmod 644 "$HOME/.ssh/known_hosts"
[ -f "$HOME/.ssh/config" ] && chmod 600 "$HOME/.ssh/config"
# If SSH keys are mounted, fix permissions (skip if read-only mount)
if [ -d "/home/$USER_NAME/.ssh" ] && [ "$(ls -A "/home/$USER_NAME/.ssh" 2>/dev/null)" ]; then
if touch "/home/$USER_NAME/.ssh/.perm_test" 2>/dev/null; then
rm -f "/home/$USER_NAME/.ssh/.perm_test"
chmod 700 "/home/$USER_NAME/.ssh"
find "/home/$USER_NAME/.ssh" -type f -name "id_*" ! -name "*.pub" -exec chmod 600 {} \; 2>/dev/null || true
find "/home/$USER_NAME/.ssh" -type f -name "*.pub" -exec chmod 644 {} \; 2>/dev/null || true
[ -f "/home/$USER_NAME/.ssh/known_hosts" ] && chmod 644 "/home/$USER_NAME/.ssh/known_hosts"
[ -f "/home/$USER_NAME/.ssh/config" ] && chmod 600 "/home/$USER_NAME/.ssh/config"
fi
fi
# ── Git config defaults ──────────────────────────────────────────────
# Set git config from env vars if not already configured via mounted .gitconfig
if [ -n "${GIT_USER_NAME:-}" ] && ! git config --global user.name &>/dev/null; then
git config --global user.name "$GIT_USER_NAME"
fi
if [ -n "${GIT_USER_EMAIL:-}" ] && ! git config --global user.email &>/dev/null; then
git config --global user.email "$GIT_USER_EMAIL"
fi
# ── Generate opencode config from env vars if no config mounted ──────
CONFIG_DIR="$HOME/.config/opencode"
CONFIG_FILE="$CONFIG_DIR/opencode.json"
if [ ! -f "$CONFIG_FILE" ] && [ -n "${OPENCODE_PROVIDER:-}" ]; then
echo "Generating opencode config for provider: $OPENCODE_PROVIDER"
mkdir -p "$CONFIG_DIR"
# Build provider-specific config
case "$OPENCODE_PROVIDER" in
anthropic)
cat > "$CONFIG_FILE" <<EOF
{
"\$schema": "https://opencode.ai/config.json",
"model": "${OPENCODE_MODEL:-anthropic/claude-sonnet-4-5}",
"share": "disabled",
"autoupdate": false
}
EOF
;;
openai)
cat > "$CONFIG_FILE" <<EOF
{
"\$schema": "https://opencode.ai/config.json",
"model": "${OPENCODE_MODEL:-openai/gpt-4o}",
"share": "disabled",
"autoupdate": false
}
EOF
;;
amazon-bedrock)
cat > "$CONFIG_FILE" <<EOF
{
"\$schema": "https://opencode.ai/config.json",
"model": "${OPENCODE_MODEL:-amazon-bedrock/anthropic.claude-sonnet-4-5-v1}",
"share": "disabled",
"autoupdate": false,
"provider": {
"amazon-bedrock": {
"options": {
"region": "${AWS_REGION:-us-east-1}"
}
}
}
}
EOF
;;
*)
cat > "$CONFIG_FILE" <<EOF
{
"\$schema": "https://opencode.ai/config.json",
"model": "${OPENCODE_MODEL:-anthropic/claude-sonnet-4-5}",
"share": "disabled",
"autoupdate": false
}
EOF
;;
esac
fi
# ── Execute command ──────────────────────────────────────────────────
exec "$@"
# ── Drop to developer user for remaining setup ──────────────────────
exec gosu "$USER_NAME" /usr/local/bin/entrypoint-user.sh "$@"
+2 -1
View File
@@ -13,7 +13,8 @@ cat > "$HOOK_DIR/pre-commit" << 'HOOK'
if ! command -v gitleaks >/dev/null 2>&1; then
echo ""
echo "⚠️ gitleaks is not installed — skipping secret scan"
echo " Install: brew install gitleaks"
echo " Install: brew install gitleaks (macOS)"
echo " Or: 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"
echo ""
exit 0
fi