A Docker container running opencode — an open-source AI coding agent with a terminal UI — accessible via browser, remote TUI client, or SSH. Designed for 24/7 deployment on Dokploy (self-hosted PaaS) or any Docker host.
For Dokploy deployment, see Dokploy Deployment.
For standalone Docker usage:
# 1. Clone this repo
git clone https://github.com/Du7chManiac/agent-container.git
cd agent-container
# 2. Create the external network
docker network create dokploy-network
# 3. Start the container
docker compose up -d
# 4. Access via browser or remote TUI
# Browser (web mode): http://localhost:4096
# Remote TUI (serve mode): opencode attach http://localhost:4096Note: When running standalone, you may need to add
portsback todocker-compose.ymlor usedocker execto access the container. Seeenv.examplefor all configurable environment variables.
The container supports three access modes, controlled by the OPENCODE_MODE environment variable:
OPENCODE_MODE=serveStarts a headless HTTP API server (REST + SSE). This allows:
- Remote TUI — Connect a local opencode terminal client to the remote server:
opencode attach https://your-domain.example.com
- Multiple clients — Several browsers or TUI clients can connect simultaneously to the same server, sharing session state
- API access — Full REST API with OpenAPI 3.1 spec available at
/doc
OPENCODE_MODE=webStarts the opencode web UI — a full browser-based interface for interacting with the AI agent. Access it at:
https://your-domain.example.com
OPENCODE_MODE=openchamber
OPENCHAMBER_UI_PASSWORD=your-secure-password
OPENCHAMBER_PUBLIC_ORIGIN=https://opencode.example.com # required behind a reverse proxyStarts OpenChamber — a "control room" web UI around opencode with branching sessions, diff review, terminal management, and tool progress tracking. OpenChamber manages its own opencode subprocess internally and exposes only the OpenChamber UI on OPENCODE_PORT (default 4096), so it's a drop-in swap for web mode from the port-mapping perspective.
Differences from web mode:
- Auth: OpenChamber uses a single shared UI password set via
OPENCHAMBER_UI_PASSWORD(not HTTP Basic Auth).OPENCODE_SERVER_PASSWORD/OPENCODE_SERVER_USERNAMEare ignored in this mode — the entrypoint logs a warning if you set them. - No
opencode attach: Because OpenChamber manages opencode internally (on a private port), the external REST API is not exposed, so theopencode attachremote TUI client cannot connect. If you need remote TUI access, stay onservemode. - Unprotected without a password: If
OPENCHAMBER_UI_PASSWORDis unset, the container still starts (matching the existing "SSH with no auth" behavior) but logs a prominent warning. Only do this on trusted networks (e.g., behind Tailscale). - Reverse proxy: OpenChamber's terminal WebSocket has a strict origin check. When the container is behind Traefik, nginx, etc., set
OPENCHAMBER_PUBLIC_ORIGINto the externally-visible origin (e.g.https://opencode.example.com, no path). The entrypoint seeds it intosettings.jsonbefore OpenChamber starts. Without it, the UI loads but terminals fail with "Connection failed: Terminal stream connection error".
Under the hood the entrypoint runs openchamber --port $OPENCODE_PORT --host 0.0.0.0 --foreground, and the spawned opencode subprocess binds to a private loopback port (4097 by default, 4098 if you set OPENCODE_PORT=4097).
OPENCODE_MODE=sshTraditional SSH access — connect via SSH and run opencode interactively. See SSH Access for setup details.
Protect your web/serve endpoint with HTTP Basic Auth:
OPENCODE_SERVER_PASSWORD=your-secure-password
OPENCODE_SERVER_USERNAME=opencode # optional, defaults to "opencode"These env vars are read directly by opencode. When set, browsers will show a native login dialog.
To connect with opencode attach when a password is set, pass the credentials via flag or environment variable:
# Using the -p / --password flag (and optionally -u / --username):
opencode attach -p your-secure-password https://your-domain.example.com
# Or set the environment variable on the client side:
OPENCODE_SERVER_PASSWORD=your-secure-password opencode attach https://your-domain.example.comNote: Client-side password support for
opencode attachwas added in opencode#9095. Make sure you are running a recent version of opencode on your local machine.
The web/serve port defaults to 4096. Change it with:
OPENCODE_PORT=8080-
Create a Compose service in Dokploy:
- Go to your Dokploy dashboard
- Create a new Compose project
- Point it to this repository (or paste the
docker-compose.ymlcontents)
-
Set environment variables in Dokploy's Environment tab:
OPENCODE_SERVER_PASSWORD— secure your web/serve endpoint- Any API keys you need (see Environment Variables)
- For
OPENCODE_CONFIG_JSON, paste the JSON as a single line
-
Configure domain — In Dokploy's Domains settings:
- Add a domain (e.g.,
opencode.example.com) - Set the container port to
4096(or your customOPENCODE_PORT) - Traefik handles SSL termination and HTTP routing automatically
- Add a domain (e.g.,
-
Deploy the service
-
Connect:
# Browser (web mode) https://opencode.example.com # Remote TUI (serve mode — default) opencode attach https://opencode.example.com
For Dokploy, add a TCP port mapping in the Ports settings (e.g., host 2222 → container 22). See SSH Access for full setup.
The docker-compose.yml references dokploy-network as an external network. This network is automatically created by Dokploy. If running standalone without Dokploy, either:
- Create it manually:
docker network create dokploy-network - Or remove the
networkssection fromdocker-compose.yml
OpenCode Go ($10/month) provides access to multiple AI models without needing individual API keys.
-
Access the container (via web UI, remote TUI, or SSH)
-
Start opencode (if using SSH):
opencode
-
Run the
/connectcommand in the opencode TUI -
Select OpenCode Go as the provider
-
A URL will be displayed — open it in your local browser, complete the authentication, and paste the resulting key back into the terminal
-
Your credentials are stored in
~/.local/share/opencode/which is persisted via thecoder-homeDocker volume — they survive container restarts and redeployments.
If you prefer using your own API keys instead of OpenCode Go, set them as environment variables:
# In your .env file
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
GOOGLE_API_KEY=AI...These are passed into the container and opencode will detect them automatically.
SSH is disabled by default. There are two ways to enable it:
- SSH-only mode: Set
OPENCODE_MODE=ssh— SSH runs as the foreground process (no web/serve) - SSH alongside serve/web: Set
SSH_ENABLED=true— starts SSH in the background alongside the primary mode
The default SSH port mapping is 2222 on the host (configurable via SSH_PORT). Connect with:
ssh coder@your-server -p 2222Set SSH_PUBLIC_KEY to the contents of your public key:
SSH_PUBLIC_KEY="ssh-ed25519 AAAAC3... user@machine"When only a key is provided (no SSH_PASSWORD), password authentication is automatically disabled for improved security.
Then connect:
ssh coder@your-server -p 2222Set SSH_PASSWORD:
SSH_PASSWORD=your-secure-passwordIf both SSH_PUBLIC_KEY and SSH_PASSWORD are set, both authentication methods are enabled.
If SSH is enabled but neither SSH_PUBLIC_KEY nor SSH_PASSWORD is configured, the entrypoint generates a random password and prints it to the container logs. Check with:
docker compose logs opencodeSet GIT_REPO_URL to automatically clone a repository when the container starts:
GIT_REPO_URL=https://github.com/user/repo.git
GIT_BRANCH=main # optionalThe clone is idempotent — if the directory already exists (from a previous run), it is skipped.
Access the container and clone repos into ~/workspace/:
cd ~/workspace
git clone https://github.com/user/repo.gitThe ~/workspace directory is part of the coder-home volume and persists across restarts.
Set your commit identity via environment variables:
GIT_USER_NAME="Your Name"
GIT_USER_EMAIL="you@example.com"The container ships the official Gitea tea CLI — Gitea's counterpart to the GitHub gh CLI. When configured, the agent (or you, over SSH) can drive repositories, issues, pull requests, releases, and more from the shell.
Set these environment variables:
GITEA_URL=https://gitea.example.com # Your Gitea instance URL
GITEA_TOKEN=your-personal-access-token # Gitea PAT with appropriate scopesOn startup the entrypoint runs tea login add --name default --url $GITEA_URL --token $GITEA_TOKEN as the coder user, writing persistent auth to ~/.config/tea/config.yml. The existing default login is deleted first so rotating the token and restarting the container just works.
Both vars are also forwarded into the opencode process environment, matching how GITHUB_TOKEN is exposed.
- Go to your Gitea instance → Settings → Applications → Access Tokens
- Create a new token with the scopes you need (e.g.,
repo,issue,admin:org) - Copy the token into
GITEA_TOKEN
Once tea is authenticated, typical commands include:
tea repos list
tea issues create --repo owner/repo --title "..." --body "..."
tea pulls list --repo owner/repo
tea releases create --repo owner/repo --tag v1.0.0See tea --help or the tea docs for the full command set.
On first start, if no config exists, the container creates ~/.config/opencode/opencode.json with full permissions:
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"*": "allow"
}
}This grants opencode permission to execute all tools without prompting.
Set OPENCODE_CONFIG_JSON to a full JSON config string to override the default:
OPENCODE_CONFIG_JSON='{"$schema":"https://opencode.ai/config.json","provider":"anthropic","model":"claude-sonnet-4-20250514","permission":{"*":"allow"}}'The config file is persisted in the coder-home volume. Access the container and edit it:
nano ~/.config/opencode/opencode.jsonBy default the container runs in UTC. Set the TZ environment variable to use a different timezone:
TZ=America/New_YorkThis affects system logs, git commit timestamps, and all time-related operations. See the full list of timezone names.
The Docker image supports both amd64 (x86_64) and arm64 (aarch64) architectures. The correct Go binary is automatically selected during the build based on the target platform. To build for a specific architecture:
docker buildx build --platform linux/arm64 -t opencode-agent .| Tool | Version | Notes |
|---|---|---|
| Node.js | 22.x LTS | Via NodeSource |
| npm | Bundled with Node.js | |
| Python 3 | System (3.12) | With pip and venv |
| Go | 1.26.1 | Official binary |
| Git | System | |
| GitHub CLI (gh) | Latest | Authenticate with GITHUB_TOKEN env var |
| Gitea CLI (tea) | 0.11.0 | Pinned prebuilt binary; authenticates via GITEA_URL + GITEA_TOKEN |
| OpenChamber | Latest at build time | @openchamber/web — optional control-room UI, enabled via OPENCODE_MODE=openchamber. Pinned to latest; pass --build-arg CACHEBUST=$(date +%s) (or a commit SHA) on rebuild to force a fresh pull |
| node-gyp | Latest | Native addon build tool (global) |
| yarn | Latest | Alternative package manager (global) |
| pnpm | Latest | Fast, disk-efficient package manager (global) |
| build-essential | System | gcc, g++, make |
| pkg-config | System | Library compile/link flag helper |
| Native dev libs | System | libsqlite3, libpq, libcairo2, libjpeg, libpango, libgif, librsvg2, libpixman, libxml2, libcurl |
| ripgrep (rg) | System | Fast file search |
| fd-find (fd) | System | Fast file finder |
| jq | System | JSON processor |
| tmux | System | Terminal multiplexer |
| vim / nano | System | Text editors |
| curl / wget | System | HTTP clients |
| htop | System | Process viewer |
The coder user has passwordless sudo access, so you can install additional tools as needed:
sudo apt-get update && sudo apt-get install -y <package>Note: Packages installed via sudo are now persisted across restarts thanks to the coder-home volume. However, they will not survive a full image rebuild — to make them permanent across rebuilds, add them to the Dockerfile.
| Volume | Container Path | Purpose |
|---|---|---|
coder-home |
/home/coder |
Entire home directory — config, auth, workspace, bash history, installed tools, dotfiles |
ssh-host-keys |
/etc/ssh/host_keys |
SSH host keys — prevents "host key changed" warnings after restarts |
Both volumes are Docker named volumes, which persist data across container restarts, rebuilds, and redeployments.
On first start (when the coder-home volume is empty), the entrypoint copies a skeleton directory into /home/coder with the opencode binary and default directory structure. Subsequent starts reuse the existing home directory contents.
The container includes a Docker HEALTHCHECK that adapts to the active mode:
- serve/web: Checks the HTTP endpoint on the configured port
- ssh: Verifies the SSH daemon is accepting connections
Docker and Dokploy will report the container as healthy once the primary service is ready.
| Variable | Required | Default | Description |
|---|---|---|---|
OPENCODE_MODE |
No | serve |
Access mode: serve, web, openchamber, or ssh |
OPENCODE_PORT |
No | 4096 |
Port for web/serve/openchamber modes |
OPENCODE_AUTO_UPDATE |
No | false |
Set to true to update OpenCode on startup |
OPENCODE_SERVER_PASSWORD |
No | — | HTTP Basic Auth password for web/serve (ignored in openchamber mode) |
OPENCODE_SERVER_USERNAME |
No | opencode |
HTTP Basic Auth username for web/serve (ignored in openchamber mode) |
OPENCHAMBER_UI_PASSWORD |
No | — | UI password for OpenChamber mode (shared, no username) |
OPENCHAMBER_PUBLIC_ORIGIN |
No (but required behind a reverse proxy) | — | Public origin (https://host[:port]) for OpenChamber mode. Seeded into settings.json so the terminal WebSocket's origin check accepts requests proxied through Traefik/nginx |
SSH_ENABLED |
No | false |
Start SSH in background for serve/web modes |
SSH_PUBLIC_KEY |
No | — | SSH public key for key-based auth |
SSH_PASSWORD |
No | — | Password for SSH password auth |
TZ |
No | UTC |
Timezone (e.g., America/New_York) |
GIT_REPO_URL |
No | — | Repository URL to clone on startup |
GIT_BRANCH |
No | — | Branch to checkout (default: repo default) |
GIT_USER_NAME |
No | — | Git commit author name |
GIT_USER_EMAIL |
No | — | Git commit author email |
OPENCODE_CONFIG_JSON |
No | — | Full opencode config JSON (overrides default) |
ANTHROPIC_API_KEY |
No | — | Anthropic API key |
OPENAI_API_KEY |
No | — | OpenAI API key |
GOOGLE_API_KEY |
No | — | Google Gemini API key |
OPENROUTER_API_KEY |
No | — | OpenRouter API key |
GROQ_API_KEY |
No | — | Groq API key |
GITHUB_TOKEN |
No | — | GitHub PAT for gh CLI. When set, the entrypoint also runs gh auth setup-git so git push/git fetch over HTTPS work non-interactively from the terminal. |
GITEA_URL |
No | — | Gitea instance URL. When set alongside GITEA_TOKEN, the entrypoint runs tea login add --name default so the tea CLI is authenticated non-interactively. |
GITEA_TOKEN |
No | — | Gitea PAT for the tea CLI |
SSH_PORT |
No | 2222 |
Host port mapped to container SSH port 22 |
Note: Environment variables matching
AWS_*andAZURE_*patterns are also automatically forwarded into the container.
By default, the container uses the OpenCode version that was installed when the image was built. To check for and install the latest version on every container start, set:
OPENCODE_AUTO_UPDATE=trueThe update runs before services start. If the update fails (e.g., due to network issues), the container continues with the existing version.
On startup, the entrypoint validates all configuration before proceeding. Invalid configuration causes the container to exit immediately with clear error messages instead of failing silently later. The following checks are performed:
OPENCODE_MODEmust be one ofssh,web,serve, oropenchamberOPENCODE_PORTmust be a number between 1 and 65535SSH_ENABLEDmust betrueorfalseif setTZmust be a valid timezone from the tz databaseGITEA_URLandGITEA_TOKENmust both be set or both emptyOPENCODE_CONFIG_JSONmust be valid JSON if setGIT_REPO_URLformat is checked (warning only)- A warning is shown if SSH is enabled but no authentication method is configured
- shellcheck — static analysis for shell scripts
- bats-core — installed automatically by
make
# Run lint + unit tests
make test
# Run shellcheck only
make lint
# Run bats unit tests only
make test-unit
# Build Docker image (smoke test)
make buildtests/
test_helper.bash # Shared setup: sources entrypoint functions
test_logging.bats # Tests for log_info, log_warn, log_error
test_validate_env.bats # Tests for all env validation paths (~33 tests)
GitHub Actions runs on every push to main and on all pull requests:
- lint — shellcheck on
entrypoint.sh - unit-tests — all bats test suites
- docker-build — Docker image build smoke test
- Verify the container is running:
docker compose ps - Check container logs:
docker compose logs opencode - On Dokploy, confirm a domain is configured pointing to the container's port 4096
- If auth is enabled, ensure
OPENCODE_SERVER_PASSWORDis set correctly
- The default mode is
serve— verify the container is running - Ensure the port/domain is reachable from your local machine
- If password auth is enabled, pass credentials using the
-pflag or theOPENCODE_SERVER_PASSWORDenvironment variable on the client side:opencode attach -p your-secure-password https://your-domain.example.com # or OPENCODE_SERVER_PASSWORD=your-secure-password opencode attach https://your-domain.example.comClient-side auth support was added in opencode#9095. Ensure your local opencode is up to date.
- Confirm
OPENCODE_MODE=openchamberis set and the container is running - Check logs:
docker compose logs opencode— you should see bothStarting OpenChamber web UI on port …and the spawned opencode subprocess starting - If you set
OPENCODE_SERVER_PASSWORD, note that it's ignored in this mode — useOPENCHAMBER_UI_PASSWORDinstead - The UI uses a single shared password (no username) — enter just the password at the browser prompt
- Remember:
opencode attachdoes not work in openchamber mode because the opencode REST server is bound to internal loopback only. Switch toservemode if you need remote TUI access - The internal opencode subprocess binds to port
4097(or4098if yourOPENCODE_PORT=4097). These ports are not exposed but must be free inside the container
- SSH is disabled by default. Ensure
OPENCODE_MODE=sshorSSH_ENABLED=trueis set - On Dokploy, confirm a TCP port mapping (e.g., 2222 → 22) is configured in the Ports settings
- Check container logs:
docker compose logs opencode - Ensure your firewall allows the SSH port
If you see WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED, the SSH host keys were regenerated. This should not happen during normal restarts (keys are persisted in the ssh-host-keys volume), but can occur if:
- The
ssh-host-keysvolume was deleted - You rebuilt with
docker compose down -v
Fix: Remove the old host key from your local ~/.ssh/known_hosts:
ssh-keygen -R "[localhost]:2222"- Verify the
coder-homevolume exists:docker volume ls | grep coder-home - Ensure you're not using
docker compose down -v(the-vflag deletes volumes)
- The binary should be at
~/.opencode/bin/opencode. Check with:ls -la ~/.opencode/bin/ - If missing, reinstall:
curl -fsSL https://opencode.ai/install | bash
- Check logs:
docker compose logs opencode - Common cause: opencode binary missing or corrupt. Try setting
OPENCODE_AUTO_UPDATE=true
- Verify
GIT_REPO_URLis accessible from the container - For private repos, ensure SSH keys or tokens are configured
- Check logs for specific git error messages
If git push https://github.com/... inside the terminal dies with fatal: could not read Username for 'https://github.com': No such device or address, the container wasn't started with GITHUB_TOKEN. Set GITHUB_TOKEN to a PAT with repo scope — the entrypoint runs gh auth setup-git on startup so HTTPS git ops use the token non-interactively. You can verify with:
docker exec -u coder <container> git config --global --get-regexp credentialIt should list a helper entry for https://github.com. This also silences OpenChamber's Failed to filter active remote branches, returning all warning in the branches panel.
- Verify both
GITEA_URLandGITEA_TOKENare set - Check that the token has appropriate scopes in your Gitea instance
- List configured logins:
docker exec -u coder <container> tea login list - Inspect the generated config:
docker exec -u coder <container> cat ~/.config/tea/config.yml - Check container logs for the
Configured tea CLI login 'default' for …line
See Network Note under Dokploy Deployment.
This container bundles and builds on the work of several upstream open-source projects:
- opencode — the terminal-native AI coding agent that powers every mode of this container.
- OpenChamber by @btriapitsyn — the "control room" web UI enabled via
OPENCODE_MODE=openchamber. Distributed as the@openchamber/webnpm package. - Gitea tea CLI — the official Gitea command-line client, authenticated via
GITEA_URL+GITEA_TOKENon startup. - Dokploy — the self-hosted PaaS this container is primarily designed to deploy on.
All upstream projects retain their own licenses. This repository's glue code and Docker configuration are MIT-licensed — see LICENSE.