A security-hardened Docker environment for running the OpenCode CLI in complete isolation. Features a read-only container with dropped Linux capabilities (principle of least privilege), seccomp profile preventing privilege escalation, and file-based secrets management.
- Docker installed on your machine.
- Make
curlandjq(required formake build-latest)
# Build the image
make build
# Set up your secrets (one-time)
mkdir -p ~/.opencode-docker/secrets
echo "your-api-key" > ~/.opencode-docker/secrets/context7_api_key
chmod 600 ~/.opencode-docker/secrets/*
# Run using the wrapper script (recommended)
bin/opencode-dockerThe bin/opencode-docker script is the recommended way to run OpenCode Docker. It:
- Persists all data to
~/.opencode-docker/(sessions, cache, settings) - Reads secrets from
~/.opencode-docker/secrets/ - Uses the current directory as the workspace
- Works from any directory once added to your PATH
Add to your ~/.bashrc:
export PATH="$HOME/git/opencode-docker/bin:$PATH"Then reload:
source ~/.bashrcRun the following command to add opencode-docker to your path. Replace the Path with the correct path for your environment.
fish_add_path $HOME/git/opencode-docker/binOnce added to your PATH, you can run from any directory:
# Run in current directory
opencode-docker
# Continue a session
opencode-docker -s ses_2d068fdfaffefxNTts5doK0upT
# Override workspace directory
OPENCODE_WORKSPACE=/path/to/project opencode-dockerThis container implements defense-in-depth with multiple security layers:
- Complete isolation: Distroless base image with no shell or package manager
- Read-only filesystem: Root filesystem is immutable; only
/tmp(tmpfs) and mounted volumes are writable - Dropped capabilities:
--cap-drop ALLremoves all Linux capabilities (principle of least privilege) - Privilege escalation prevention:
--security-opt no-new-privilegesblocks setuid/setgid exploits - Unprivileged user: Runs as non-root UID 1000 (configurable at build time)
- Resource limits: Memory (2GB) and CPU (2 cores) constraints prevent resource exhaustion
- File-based secrets: Secrets loaded from files (not environment variables) to avoid exposure in process listings or logs
This project uses file-based secrets instead of environment variables for improved security. Secrets stored in files are not visible in docker inspect, process listings, error messages, or logs.
-
Create your secrets directory:
mkdir -p ~/.opencode-docker/secrets chmod 700 ~/.opencode-docker/secrets
-
Add your API keys as individual files:
echo "your-api-key" > ~/.opencode-docker/secrets/anthropic_api_key echo "your-api-key" > ~/.opencode-docker/secrets/openai_api_key echo "your-api-key" > ~/.opencode-docker/secrets/context7_api_key chmod 600 ~/.opencode-docker/secrets/*
-
The runtime bootstrap (
bootstrap.py) automatically loads all files from/run/secretsas environment variables:- Filenames are converted to uppercase
- Dashes and dots are replaced with underscores
- Example:
anthropic_api_keybecomesANTHROPIC_API_KEY
| Filename | Environment Variable | Provider |
|---|---|---|
anthropic_api_key |
ANTHROPIC_API_KEY |
Anthropic |
openai_api_key |
OPENAI_API_KEY |
OpenAI |
context7_api_key |
CONTEXT7_API_KEY |
Context7 MCP |
google_application_credentials |
GOOGLE_APPLICATION_CREDENTIALS |
Vertex AI |
aws_access_key_id |
AWS_ACCESS_KEY_ID |
AWS Bedrock |
aws_secret_access_key |
AWS_SECRET_ACCESS_KEY |
AWS Bedrock |
Note: Environment variables can still be passed directly if needed, but file-based secrets are strongly recommended for security.
When using the wrapper script (bin/opencode-docker):
| Data | Location | Description |
|---|---|---|
| Home Directory | ~/.opencode-docker/ |
OpenCode cache, plugins, settings, sessions |
| Secrets | ~/.opencode-docker/secrets/ |
API keys and credentials |
| Config | ./config/ (this repo) |
OpenCode configuration, MCP servers, custom skills |
| Workspace | Current directory | Your project files |
When using make run (development only):
| Data | Location | Description |
|---|---|---|
| Home Directory | ./homebase/ |
Local persistent home |
| Secrets | ./secrets/ |
Local secrets |
| Config | ./config/ |
OpenCode config |
| Workspace | ./workspace/ |
Local workspace |
The Superpowers plugin includes a visual brainstorming companion that serves mockups, diagrams, and design options in your browser.
The brainstorming server uses a randomly assigned port in the range 49152-65535:
- Wrapper script: Use the
-bor--brainstormflag withbin/opencode-docker - Development:
make runandmake shellautomatically configure and expose the port
When the brainstorming skill starts, it will display the assigned port. Access it at the URL shown (e.g., http://localhost:XXXXX).
make build # Build with auto-detected UID/GID
make build VERSION=1.3.17 # Build with specific version tag
make build-latest # Build latest OpenCode version
make tag-latest VERSION=1.3.17 # Tag a version as latest
make run # Dev run (uses ./homebase, ./workspace, ./secrets)
make shell # Debug shell (builder-tools stage with bash)
make clean # Remove imageFor advanced users who need custom container configuration:
docker run --rm -it \
--read-only \
--tmpfs /tmp:exec,size=512m \
--cap-drop ALL \
--security-opt=no-new-privileges \
--memory=2g \
--cpus=2 \
-v ~/.opencode-docker:/app:rw \
-v /path/to/opencode-docker/config:/app/.config/opencode:rw \
-v $(pwd):/workspace:rw \
-v ~/.opencode-docker/secrets:/run/secrets:ro \
opencode-docker /workspace# Default UID/GID (1000)
docker build -t opencode-docker .
# With custom UID/GID (recommended)
docker build --build-arg USER_UID=$(id -u) --build-arg USER_GID=$(id -g) -t opencode-docker .
# With version tag
docker build --build-arg USER_UID=$(id -u) --build-arg USER_GID=$(id -g) -t opencode-docker:1.3.17 .The image uses a multi-stage build with distroless runtime:
- Base:
gcr.io/distroless/base-debian12(no shell, no package manager) - Node.js: Node 24 from NodeSource, runtime dependencies extracted via
collect-runtime-deps.sh - Python: Python 3 with venv support from Debian 12
- OpenCode: Installed via official installer in build stage
- Bootstrap:
bootstrap.pyloads secrets from/run/secrets, starts Xvfb, then execs OpenCode