# AGENTS.md ## Project overview This repository builds and publishes a Docker image for [OpenCode](https://opencode.ai), the open source AI coding agent. The image ships OpenCode 2, runs it in headless server mode (`opencode serve`), and is automatically rebuilt and pushed to Docker Hub (`jcabillot/opencode`) every night by a Gitea Actions pipeline. ## Repository structure ``` . ├── Dockerfile # Image definition ├── .gitea/workflows/docker-build.yaml # CI/CD pipeline (Gitea Actions) ├── renovate.json # Dependency update automation (Renovatebot) ├── opencode-attach # Helper script for attaching to a running server └── README.md # Usage documentation ``` ## Dependency management - **Always pin versions** in the Dockerfile `npm install` command (e.g. `@opencode/cli@2.0.32 n2-soul@9.0.9`). Never leave packages unpinned. - **Update renovate customManagers** when adding, removing, or renaming a dependency tracked in the Dockerfile. Each pinned package must have a corresponding `customManager` entry in `renovate.json` with a regex `matchStrings` pattern that captures the version. If a dependency is added without a renovate entry, Renovatebot will not open automated PRs for it. - **apt packages** (apt-get install lines in Dockerfile) and **COPY --from** image references are not currently tracked by Renovate. Pinning these manually is acceptable for now but adding renovate managers for them is encouraged. ## Dockerfile conventions - **Base image**: `node:26-trixie` — Debian-based Node.js image (not Alpine) so that downstream images can still use `apt-get` and Node-based MCP servers keep working. - **Install**: `npm i -g @opencode/cli@ n2-soul@` — installs OpenCode 2 and Soul globally, both pinned. `@opencode/cli` replaced `opencode-ai` as the published package in OpenCode 2; `--allow-scripts=@opencode/cli` is required for its `postinstall` to select the native binary. - **Version check**: `RUN opencode --version` after install to validate the build and record the installed version in build logs. - **Dedicated user**: a non-root `opencode` user and group are created with `groupadd`/`useradd` (UID/GID 1000). All runtime steps run as this user. - **Cluster tooling**: `kubectl` is copied from the official `registry.k8s.io/kubectl` image (multi-stage COPY). - **Entrypoint**: `["opencode"]` — arguments are passed at runtime; `serve` starts the headless server. Bare `opencode` opens the TUI, so the server is always invoked explicitly. ## OpenCode 2 notes - The npm package is `@opencode/cli` (bins `opencode` and `opencode2`); `opencode-ai` stops at 1.19.x and must not come back. - `opencode attach` no longer exists: point a client at a server with `opencode --server ` (after the subcommand when one is given). `opencode-attach` wraps this. - The server always authenticates: username is fixed to `opencode`, password comes from `OPENCODE_SERVER_PASSWORD` or `OPENCODE_PASSWORD`, and a random one is generated and logged when neither is set. - The v1 REST API was replaced (`/global/health` is gone, every route now lives under `/api/*` and requires auth). Any client calling the v1 API — e.g. `perso/opencode-dispatch` — must be migrated separately. ## CI/CD conventions (Gitea Actions) - Pipeline triggers on push to `main`, PRs targeting `main`, and a nightly cron (`cron: '0 0 * * *'`). - Image is pushed to Docker Hub only on non-PR events (main branch pushes and cron runs). - Docker Hub credentials are stored in Gitea Actions secrets (`DOCKERHUB_USERNAME`, `DOCKERHUB_TOKEN`). - The image is published as `jcabillot/opencode:latest` with digest and branch tags. ## Useful commands ```bash # Build the image locally docker build -t opencode . # Run the server on all interfaces docker run -it -p 4096:4096 opencode serve --hostname 0.0.0.0 # Run with a project mounted docker run -it -p 4096:4096 \ -v $(pwd):/home/opencode/project \ opencode serve --hostname 0.0.0.0 # Protect with a password docker run -it -p 4096:4096 \ -e OPENCODE_SERVER_PASSWORD=secret \ opencode serve --hostname 0.0.0.0 ``` ## opencode serve options | Flag | Default | Description | |------|---------|-------------| | `--port` | `4096` | Port to listen on | | `--hostname` | `127.0.0.1` | Hostname to bind | | `--cors` | `[]` | Additional allowed browser origins (repeatable) | | `--service` | `false` | Run as the shared background service | | `--stdio` | `false` | Serve over stdio for editor integrations | ## Environment variables | Variable | Description | |----------|-------------| | `OPENCODE_SERVER_PASSWORD` | Server password (HTTP basic auth, username `opencode`) | | `OPENCODE_PASSWORD` | Same, takes precedence over `OPENCODE_SERVER_PASSWORD` | `OPENCODE_SERVER_USERNAME` existed in OpenCode 1 and is no longer read: the username is always `opencode`.