Docker images for Multica agent daemons — one tagged image per AI CLI.
| Image | Dockerfile.agent target |
Agent CLI |
|---|---|---|
ghcr.io/sapk/multica-agent-claude |
claude |
Claude Code |
ghcr.io/sapk/multica-agent-cursor |
cursor |
Cursor CLI |
ghcr.io/sapk/multica-agent-opencode |
opencode |
OpenCode 1.x |
ghcr.io/sapk/multica-agent-opencode-v2 |
opencode-v2 |
OpenCode 2.x — see the caveat below |
ghcr.io/sapk/multica-agent-codex |
codex |
OpenAI Codex CLI |
ghcr.io/sapk/multica-agent-kimi |
kimi |
Kimi Code CLI |
ghcr.io/sapk/multica-agent-agy |
agy |
Antigravity CLI |
Shared layers live in the base stage (multica CLI from the official backend image, Podman, nvm, pnpm, the Go toolchain, glab, entrypoint). Each variant adds its CLI.
The base also ships a C toolchain (gcc + libc6-dev) so cgo-dependent Go builds work out of the box — in particular go test -race, which requires CGO_ENABLED=1 and a linker against the C runtime.
It also ships git-flow (the Debian git-flow package, AVH edition) so agents can run git-flow release/hotfix workflows without an extra install step.
The base image includes DuckDB CLI for querying CSV, Parquet, JSON, SQLite, and other data sources directly from the command line.
The base image includes age and sops for encrypting/decrypting secrets files (e.g. .sops.yaml-managed YAML/JSON/ENV secrets), so agents can work with encrypted config without an extra install step.
The base image pre-installs Playwright browser binaries (Chromium, Firefox, WebKit) with a 5-minute timeout. System libraries for headless browsing (X11, NSS, ATK, Pango, GBM, fonts) are also included. If the installation times out during build, the build fails fast with diagnostics (memory, disk space). To adjust the timeout, pass --build-arg PLAYWRIGHT_TIMEOUT=600 (seconds). Browsers are installed to $PLAYWRIGHT_BROWSERS_PATH (~/.cache/ms-playwright by default).
Published by CI to GitHub Container Registry (ghcr.io/sapk/…).
docker pull ghcr.io/sapk/multica-agent-claude:latest
docker pull ghcr.io/sapk/multica-agent-cursor:latest
docker pull ghcr.io/sapk/multica-agent-opencode:latest
docker pull ghcr.io/sapk/multica-agent-opencode-v2:latest
docker pull ghcr.io/sapk/multica-agent-codex:latest
docker pull ghcr.io/sapk/multica-agent-kimi:latest
docker pull ghcr.io/sapk/multica-agent-agy:latestPin a daily snapshot or release:
docker pull ghcr.io/sapk/multica-agent-claude:2026-06-01
docker pull ghcr.io/sapk/multica-agent-claude:1.0.0 # after git tag v1.0.0
docker pull ghcr.io/sapk/multica-agent-claude:sha-a248603Browse tags: open the package page for each variant (e.g. multica-agent-claude) or run:
gh api user/packages/container/multica-agent-claude/versions \
--jq '.[].metadata.container.tags[]' | sort -uIf docker pull returns 404, the package may be private — make it public under Package settings → Change visibility, or docker login ghcr.io.
All variants:
make build-all
# → ghcr.io/sapk/multica-agent-claude:latest (local tag; not pushed)Single variant:
make build-cursor IMAGE=ghcr.io/sapk/multica-agent TAG=localPlain docker build:
docker build -f Dockerfile.agent --target claude \
--build-arg MULTICA_TAG=v0.1.12 \
-t ghcr.io/sapk/multica-agent-claude:local \
.Pin MULTICA_TAG to the same release as your Multica backend so the daemon CLI matches the server.
Base only (no AI CLI — debugging or custom downstream images):
docker build -f Dockerfile.agent --target base -t multica-agent-base:local .multica-agent-opencode is OpenCode 1.x and stays the default. multica-agent-opencode-v2 is
OpenCode 2.x. They are separate images because the two lines are genuinely independent:
| Install channel | Binary lands in | |
|---|---|---|
opencode (1.x) |
curl -fsSL https://opencode.ai/install |
~/.opencode/bin/opencode |
opencode-v2 (2.x) |
npm @opencode/cli |
~/.local/node-active/opencode |
The installer script's releases/latest is the 1.x channel, and 2.x publishes no release assets at
all — so 2.x is only reachable from npm. Both versions are pinned via OPENCODE_VERSION and
OPENCODE_V2_VERSION rather than floating. That matters more than usual here: the daemon chooses
the CLI's argv from the binary's --version string, not from the image name, so a silent upstream
major bump would change the contract every task runs under with no commit and no review. The
opencode-v2 build additionally asserts the opencode v2.x version shape and fails the build
otherwise.
Verify either image before rolling it out. Use the absolute path: on the v1 image opencode is
not on PATH (the installer only appends it to ~/.bashrc), so a bare --entrypoint opencode
fails there while succeeding on v2.
docker run --rm --entrypoint /home/agent/.opencode/bin/opencode ghcr.io/sapk/multica-agent-opencode:latest --version # 1.18.32
docker run --rm --entrypoint /home/agent/.local/node-active/opencode ghcr.io/sapk/multica-agent-opencode-v2:latest --version # opencode v2.0.18Use the v1 image for any runtime that relies on Multica-managed MCP servers. OpenCode 2.x honours
no environment-based config channel — it only reads <workdir>/opencode.json, and its MCP entries
carry bearer headers and OAuth secrets that the agent's own commits would capture. Rather than write
those secrets to disk, the daemon refuses runs that carry MCP config on a 2.x runtime
(ErrOpenCodeV2MCPUnsupported, server/pkg/agent/opencode_v2.go), naming every MCP source in the
error.
Pick opencode-v2 only for single-agent setups that do not use Multica-managed MCP, until MCP
delivery is restored.
rtk init -g --opencode (run from docker/entrypoint-podman.sh when ENABLE_RTK=true) keys on
MULTICA_OPENCODE_PATH, so it fires on both images and writes the plugin to
~/.config/opencode/plugins/rtk.ts, which 2.x documents as an auto-loaded global plugin directory.
ENABLE_RTK has three states, and only an explicit off disables:
ENABLE_RTK |
effect |
|---|---|
true |
initialise the variant the image ships (detected from MULTICA_*_PATH) |
false, 0, no, off |
remove rtk from every variant (see the table below) |
| unset, or any other value | no-op, reported in the boot log |
ENABLE_RTK is not set by Dockerfile.agent, so an image that never configures it is a no-op —
hand-running rtk init inside a live container is not undone by a later restart. A typo such as
True or 1 is logged, not silently treated as "on".
The disable path deliberately does not key on MULTICA_*_PATH: removal keyed on the same
detection as the install is conditional on a variable a runtime may not carry (the Multica daemon
strips MULTICA_*_PATH from the agent shell, though it is present in the entrypoint's own
environment), and a stale or absent value would silently restore the old one-way behaviour. Each
call is idempotent and exits 0 on a clean image, so a clean boot costs ~2 ms per variant.
Coverage of the disable path, per variant — rtk's own uninstall does not reach all of them:
| variant | enable | disable | note |
|---|---|---|---|
| claude | rtk init -g |
full | |
| opencode | rtk init -g --opencode |
full | removes ~/.config/opencode/plugins/rtk.ts |
| codex | rtk init -g --codex |
full | |
| cursor | rtk init -g --agent cursor |
partial | empties the preToolUse hook array, but leaves the .claude/RTK.md / .claude/CLAUDE.md awareness text and drops a hooks.json.bak |
| antigravity | rtk init --agent antigravity |
by hand | rtk refuses (--uninstall without -g exits 1; -g only sweeps ~/.claude), so the entrypoint rms the .agents/rules/antigravity-rtk-rules.md it wrote |
--uninstall is present in v0.46.0 (the pinned version) and v0.50.0.
The generated plugin type-imports @opencode-ai/plugin; that is a type-only import, so it is erased
at compile time and is not itself a risk. What is unverified is whether the rewrite hook actually
fires on 2.x — a 2.x server here reported zero loaded plugins under every configuration tried.
| Variable | Default | Purpose |
|---|---|---|
IMAGE |
ghcr.io/sapk/multica-agent |
Image name prefix (variant appended: -claude, etc.) |
TAG |
latest |
Local image tag |
MULTICA_IMAGE |
ghcr.io/multica-ai/multica-backend |
Source of /app/multica |
MULTICA_TAG |
latest |
Backend image tag |
| Build arg | Default | Purpose |
|---|---|---|
USER_UID / USER_GID |
1000 |
Container user (agent) |
GIT_USER_NAME / GIT_USER_EMAIL |
placeholder | Baked .gitconfig |
NVM_VERSION |
master |
nvm ref (master = rolling; pin e.g. 0.40.4) |
NODE_VERSION |
node |
Node via nvm (node = latest; pin e.g. 24.15.0) |
OPENCODE_VERSION |
1.18.32 |
OpenCode 1.x release, for the opencode target |
OPENCODE_V2_VERSION |
2.0.18 |
@opencode/cli version, for the opencode-v2 target |
PLAYWRIGHT_VERSION |
1.63.0 |
@playwright/test version (coupled to the system-library list in Dockerfile.agent) |
GO_VERSION |
1.27.1 |
Go toolchain release |
GOLANGCI_LINT_VERSION |
v2.14.0 |
golangci-lint release tag (Go static analysis) |
RTK_VERSION |
v0.50.0 |
rtk release tag (token-optimised CLI proxy) |
UV_VERSION |
0.12.19 |
uv release tag (used by uv tool install to install mcp-proxy) |
MCP_PROXY_VERSION |
v0.12.0 |
mcp-proxy release tag (stdio↔SSE/Streamable-HTTP bridge) |
GLAB_VERSION |
1.119.0 |
glab release tag (GitLab CLI) |
DUCKDB_VERSION |
v1.5.5 |
DuckDB release tag (in-process SQL OLAP CLI) |
AGE_VERSION |
v1.3.2 |
age release tag (file encryption tool) |
SOPS_VERSION |
v3.13.3 |
sops release tag (secrets file encryption CLI) |
PLAYWRIGHT_TIMEOUT |
300 |
Seconds before Playwright browser install fails (timeout wrapper) |
These are docker build --build-arg arguments. make does not forward them — the Makefile's
VARIANT_ARGS passes only MULTICA_IMAGE, MULTICA_TAG, and AGENT_BASE_IMAGE, so
make build-opencode-v2 OPENCODE_V2_VERSION=… is a silent no-op that builds the default. Bump a pin
with docker build, or add it to VARIANT_ARGS in the Makefile.
GitHub Actions (.github/workflows/docker.yml) builds and pushes to GHCR:
| Trigger | Tags (per variant) |
|---|---|
Push to main |
latest, sha-<commit> |
Git tag v*.*.* |
1.2.3, 1.2, latest, sha-… |
| Daily 06:00 UTC | latest, YYYY-MM-DD |
| Pull request | Build only (no push) |
Workflow runs: https://github.com/sapk/multica-docker-env/actions
.github/workflows/staging-build.yml builds the backend, web frontend, and all agent images from sapk-fork/multica features/staging:
| Trigger | Tags (per variant) |
|---|---|
repository_dispatch (staging-push) |
staging, staging-sha-<commit> |
workflow_dispatch (manual) |
staging, staging-sha-<commit> |
The backend image is also published as ghcr.io/sapk/multica-backend:staging.
The web frontend is also published as ghcr.io/sapk/multica-web:staging.
To trigger from a workflow in sapk-fork/multica:
- uses: peter-evans/repository-dispatch@v3
with:
repository: sapk/multica-docker-env
token: ${{ secrets.DISPATCH_TOKEN }}
event-type: staging-push
client-payload: '{"ref": "features/staging"}'MIT for files in this repo. The multica binary is part of Multica — see its LICENSE.