Create, maintain, and troubleshoot Dev Containers to abstract the dev environment from host dependencies. Covers Docker, Docker Compose, and devcontainer.json spec for VS Code / Claude Code compatibility. Integrates with opencode agents via docker exec. Trigger: When creating or modifying .devcontainer/, docker-compose.yml for dev environments, or configuring opencode to run agent commands inside a container.
opencode runs on the host machine. Its bash tool executes commands on the host shell.
To run agent commands inside the container, use docker exec as a bridge.
Host Machine
├── opencode (TUI / CLI) ← you interact here
│ └── bash tool ← executes on host by default
│ └── docker exec <ctr> <cmd> ← bridges into container
└── Docker
└── devcontainer ← all dev tools live here
├── node / python / go
├── linters, test runners
└── project files (bind mount)
Three integration modes (pick one per project):
| Mode | Use when | How |
|---|---|---|
docker exec wrapper | opencode runs on host, agents use container tools | Add custom commands in opencode.json |
| opencode inside container | Full isolation, all tools in container | docker exec <ctr> opencode |
| MCP server in container | Advanced: container exposes MCP endpoint | Remote MCP in opencode.json |
Always set --name (Docker) or container_name (Compose). Agents need a stable name.
# Bad: random container ID
docker run -it myimage
# Good: stable name
docker run -it --name myproject-dev myimage
Project files MUST be bind-mounted, NOT copied. This lets opencode's file tools work on the same files.
// devcontainer.json
"mounts": [
"source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached"
]
Always create a non-root user. Many tools (npm, git) break or behave unexpectedly as root.
RUN groupadd -r devuser && useradd -r -g devuser -m devuser
USER devuser
In .opencode/opencode.json (project-level), define wrappers so agents run commands in the container:
{
"$schema": "https://opencode.ai/config.json",
"command": {
"test": {
"description": "Run tests inside devcontainer",
"template": "Run this bash command and report results:\n```\ndocker exec myproject-dev bash -c 'cd /workspace && $ARGUMENTS'\n```"
}
}
}
Or configure the bash tool to always use the container via a project AGENTS.md rule:
## Dev Environment
All commands that run code (tests, linters, build, install) MUST be run inside
the devcontainer using: `docker exec myproject-dev bash -c 'cd /workspace && <cmd>'`
Never run node/python/go commands directly on the host.
Always have .devcontainer/devcontainer.json. This enables:
project/
├── .devcontainer/
│ ├── devcontainer.json # Required — VS Code / Claude Code spec
│ ├── Dockerfile # Custom image (if not using a base image)
│ └── docker-compose.yml # When project needs multiple services
├── .opencode/
│ └── opencode.json # Project opencode config (container commands)
└── AGENTS.md # Tell agents to use docker exec
{
"name": "My Project Dev",
// Option A: single container from image
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
// Option B: build from Dockerfile
"build": {
"dockerfile": "Dockerfile",
"context": ".."
},
// Option C: use docker-compose
"dockerComposeFile": "docker-compose.yml",
"service": "app", // which compose service is the dev container
"workspaceFolder": "/workspace",
// Port forwarding
"forwardPorts": [3000, 5432],
// Run after container created (once)
"postCreateCommand": "npm install",
// Run after container starts (every time)
"postStartCommand": "echo 'Container ready'",
// VS Code extensions to auto-install
"customizations": {
"vscode": {
"extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}
},
// Container user
"remoteUser": "devuser",
// Environment variables
"containerEnv": {
"NODE_ENV": "development"
}
}
# Build and start devcontainer (VS Code CLI)
devcontainer up --workspace-folder .
# Start with Docker directly
docker build -t myproject-dev .devcontainer/
docker run -d --name myproject-dev \
-v $(pwd):/workspace \
-w /workspace \
myproject-dev sleep infinity
# Execute a command inside the running container
docker exec myproject-dev bash -c 'cd /workspace && npm test'
docker exec myproject-dev bash -c 'cd /workspace && pytest'
# Open an interactive shell inside the container
docker exec -it myproject-dev bash
# With Docker Compose
docker compose -f .devcontainer/docker-compose.yml up -d
docker compose -f .devcontainer/docker-compose.yml exec app bash
# Rebuild after Dockerfile changes
docker compose -f .devcontainer/docker-compose.yml build --no-cache
# Stop and remove
docker compose -f .devcontainer/docker-compose.yml down
docker rm -f myproject-dev
# Inspect container (troubleshooting)
docker inspect myproject-dev
docker logs myproject-dev
Container won't build?
→ Check Dockerfile syntax: docker build --no-cache -t test .devcontainer/
→ Check base image exists: docker pull <image>
→ Check network (corporate proxy?): add ENV http_proxy in Dockerfile
Container starts but exits immediately?
→ Missing entrypoint: add `sleep infinity` or a long-running process
→ Check logs: docker logs <container>
Bind mount not working (files not visible inside)?
→ Check path: docker inspect <container> | grep Mounts
→ On Linux: check SELinux → add :z flag to mount
→ On Windows: Docker Desktop → Settings → File Sharing
Permission errors inside container?
→ Running as root when you shouldn't (or vice versa)
→ Match UID: add --user $(id -u):$(id -g) or use fixuid in Dockerfile
"Command not found" inside container?
→ Tool not installed in image: add to Dockerfile RUN apt-get install / npm install -g
→ PATH issue: check ENV PATH in Dockerfile
postCreateCommand fails?
→ Run it manually: docker exec -it <ctr> bash -c '<command>'
→ Check working directory: devcontainer.json workspaceFolder must match mount target
devcontainer-base.json — minimal base templatedevcontainer-node.json — Node.js / TypeScript projectdevcontainer-python.json — Python projectdocker-compose.devcontainer.yml — multi-service setupopencode-devcontainer.json — opencode.json snippet for container command routing