Compare commits

...

14 Commits

Author SHA1 Message Date
joakimp f08480182a Bump opencode to v1.4.6
Publish Docker Image / build-omos (push) Successful in 1h19m28s
Publish Docker Image / build-base (push) Successful in 1h30m47s
Publish Docker Image / update-description (push) Successful in 13s
2026-04-15 12:21:29 +02:00
joakimp 5ec47fdf4b Add AGENTS.md with project-specific guidance for opencode sessions 2026-04-14 19:28:26 +02:00
joakimp 210cb7d1a1 Document Python 3.13 included by default in Trixie base image 2026-04-14 13:01:49 +02:00
joakimp 0a3e142b8f Document locale and editor override in README and DOCKER_HUB 2026-04-14 08:37:08 +02:00
joakimp 158e1590a6 Generate all UTF-8 locales, allow locale override via env vars
Users can set LANG, LANGUAGE, LC_ALL in .env to override the default
en_US.UTF-8 locale (e.g. sv_SE.UTF-8 for Swedish).
2026-04-14 08:35:42 +02:00
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
8 changed files with 201 additions and 20 deletions
+5
View File
@@ -31,6 +31,11 @@ WORKSPACE_PATH=~/projects
# Path to SSH keys on host
SSH_KEY_PATH=~/.ssh
# ── Locale (defaults to en_US.UTF-8) ─────────────────────────────────
# LANG=sv_SE.UTF-8
# LANGUAGE=sv_SE:sv
# LC_ALL=sv_SE.UTF-8
# ── oh-my-opencode-slim (multi-agent orchestration) ──────────────────
# Requires image built with INSTALL_OMOS=true
# ENABLE_OMOS=false
+47
View File
@@ -0,0 +1,47 @@
# AGENTS.md
## Project overview
Docker image packaging [opencode](https://opencode.ai) into a production-ready dev container. Two image variants (base and omos) are published to Docker Hub via Gitea Actions CI. Not a library or application — this is infrastructure (Dockerfile, entrypoint scripts, docker-compose, documentation).
## File roles
- `Dockerfile` — single multi-stage build for both variants. OMOS variant is controlled by `INSTALL_OMOS=true` build arg. All GitHub-sourced binaries are pinned with version ARGs.
- `entrypoint.sh` — runs as root: UID/GID adjustment, SSH permissions, volume ownership fixes. Then drops to developer via gosu.
- `entrypoint-user.sh` — runs as developer: git config, opencode.json generation from env vars, OMOS setup.
- `DOCKER_HUB.md` — pushed to Docker Hub description via CI API call. Must stay under 25KB. Short description field must be ≤100 bytes.
- `README.md` — source repo documentation. Must stay in sync with DOCKER_HUB.md (both describe the same features but for different audiences).
- `.gitea/workflows/docker-publish.yml` — CI pipeline: three parallel jobs (build-base, build-omos, update-description). Triggered by tag push only.
## Versioning scheme
Tags follow `v{opencode_version}{letter}` — e.g. `v1.4.3k`. The number matches the opencode npm version. The letter suffix increments for container-level changes (tooling, docs, CVE fixes) on the same opencode version. CI produces four Docker Hub tags per release: `vX.Y.Zn`, `latest`, `vX.Y.Zn-omos`, `latest-omos`.
## Critical conventions
- **entrypoint.sh volume ownership loop** — when adding a new named volume mount point, add it to the `for dir in ...` loop in `entrypoint.sh` so root-owned volumes get chowned on startup.
- **Three docs to keep in sync** — Dockerfile changes that add tools or features must be reflected in `README.md`, `DOCKER_HUB.md`, and `.env.example`. The docker-compose examples in both docs must match the source `docker-compose.yml` pattern.
- **GitHub-sourced binaries** — fzf, gosu, git-lfs, neovim, bat, eza, zoxide, uv, rustup are installed from upstream releases (not apt) with pinned versions. Use the same `ARCH` case-switch pattern for multi-arch support (amd64/arm64).
- **Shell scripts use `set -euo pipefail`** — both entrypoints are strict. Errors in volume chown or SSH permission operations are intentionally suppressed with `|| true`.
- **Docker Hub description update** — uses `/v2/auth/token` endpoint (not the deprecated `/v2/users/login`). Auth uses `identifier`/`secret` fields, returns `access_token`, sent as `Bearer`. Short description must be ≤100 bytes.
## CI quirks
- Both build jobs include an IPv4 preference step (`gai.conf` + `driver-opts: network=host` for buildx) to work around intermittent IPv6 failures on the Gitea runners.
- `update-description` job runs only when both builds succeed (`needs: [build-base, build-omos]`).
- Tags must be pushed to trigger CI. Pushing to `main` alone does not build images.
## Testing changes
No test suite. Verify by:
1. Building locally: `docker compose build`
2. Running: `docker compose run --rm devbox bash`
3. Checking tool availability inside container: `nvim --version`, `bat --version`, `uv --version`, etc.
4. For entrypoint changes: test with a non-1000 UID workspace to verify UID adjustment and volume ownership fixes.
## Commit style
Imperative mood, first line summarizes the change. Multi-line body explains "why" when non-obvious. Examples from history:
- `Fix ownership of named volume mount points in entrypoint`
- `Add uv package manager to base image for on-demand Python support`
- `Upgrade base image from Debian bookworm to trixie (current stable)`
+69 -9
View File
@@ -114,12 +114,43 @@ The entrypoint automatically detects the owner of `/workspace` and adjusts the c
| `USER_UID` | Container user UID | Auto-detect from `/workspace` owner |
| `USER_GID` | Container user GID | Auto-detect from `/workspace` owner |
## Initial Setup
### Locale and Editor
### 1. Create a project directory
The container defaults to English (`en_US.UTF-8`) and neovim as the editor. Override via environment variables:
| Variable | Description | Default |
|---|---|---|
| `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` |
All common UTF-8 locales are pre-generated in the image. Example for Swedish:
```bash
LANG=sv_SE.UTF-8
LANGUAGE=sv_SE:sv
LC_ALL=sv_SE.UTF-8
```
## Initial Setup
### 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 +176,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
@@ -187,6 +218,7 @@ Understanding what survives container restarts and what doesn't:
| `/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
@@ -224,13 +256,13 @@ docker run -it --rm \
## 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:
The image includes Python 3.13 (from Debian Trixie) and [uv](https://docs.astral.sh/uv/), a fast Python package manager that replaces pip, venv, and pyenv:
```bash
# Install Python (persists across restarts with devbox-uv volume)
uv python install 3.12
# Python 3.13 is available out of the box
python3 --version
# Create a virtual environment and install dependencies
# Use uv for package management
uv venv
uv pip install -r requirements.txt
@@ -242,6 +274,9 @@ 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
```
To persist Python installs across container restarts, add a named volume:
@@ -305,6 +340,28 @@ 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
Create a directory with a `docker-compose.yml` and a `.env` file:
@@ -317,7 +374,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
@@ -347,6 +404,8 @@ services:
# 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 opencode config directory (persists config changes across restarts)
@@ -361,6 +420,7 @@ volumes:
# 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.
@@ -390,7 +450,7 @@ 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
+5 -4
View File
@@ -1,11 +1,11 @@
# 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
ARG OPENCODE_VERSION=1.4.3
ARG OPENCODE_VERSION=1.4.6
LABEL maintainer="joakimp"
LABEL description="Portable opencode developer container"
@@ -107,12 +107,13 @@ RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64"
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
# Set locale — generate common UTF-8 locales (override via LANG/LC_ALL env vars)
RUN sed -i '/\.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
+50 -6
View File
@@ -27,7 +27,7 @@ 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
@@ -42,6 +42,18 @@ docker compose run --rm devbox
## 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:
@@ -105,6 +117,10 @@ docker compose exec -u developer devbox aws --version
| `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` |
@@ -141,13 +157,13 @@ volumes:
### 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:
The image includes Python 3.13 (from Debian Trixie) and [uv](https://docs.astral.sh/uv/), a fast Python package manager that replaces pip, venv, and pyenv:
```bash
# Install Python (persists across restarts with devbox-uv volume)
uv python install 3.12
# Python 3.13 is available out of the box
python3 --version
# Create a virtual environment and install dependencies
# Use uv for package management
uv venv
uv pip install -r requirements.txt
@@ -159,6 +175,9 @@ 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`:
@@ -228,6 +247,30 @@ 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:
@@ -397,7 +440,7 @@ 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)
├── AWS CLI v2 (SSO + Bedrock auth)
@@ -420,6 +463,7 @@ Container (Debian bookworm)
| `/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 persist config changes and use custom settings, mount the config directory from the host (see Custom opencode config above).
+4
View File
@@ -54,6 +54,9 @@ services:
# - 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
@@ -62,3 +65,4 @@ volumes:
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 "$@"