feat(opencode): ship tooling image with git, node, and build deps
Some checks are pending
CI / Lint, type-check, test (push) Waiting to run
Deploy homelab (CT 116) / Deploy to CT 116 (push) Waiting to run

Extend the Alpine OpenCode base so agent sessions can clone, npm/pnpm
install, and compile native modules; seed Forgejo HTTPS creds at start.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Randall Stillwell 2026-09-14 12:16:26 -05:00
parent 74f1e9eb0b
commit d90ff11f5b
4 changed files with 87 additions and 8 deletions

View file

@ -214,7 +214,10 @@ services:
opencode:
profiles: ["opencode"]
image: ghcr.io/anomalyco/opencode:latest
build:
context: .
dockerfile: docker/opencode/Dockerfile
image: submatter-opencode:tooling
container_name: submatter-opencode
restart: unless-stopped
working_dir: /workspace/repo
@ -235,6 +238,10 @@ services:
- OPENCODE_SERVER_PASSWORD=${OPENCODE_SERVER_PASSWORD}
- OPENCODE_SERVER_USERNAME=${OPENCODE_SERVER_USERNAME:-opencode}
- OMNIROUTE_API_KEY=${OMNIROUTE_API_KEY}
- FORGEJO_TOKEN=${FORGEJO_TOKEN:-}
- GITHUB_TOKEN=${GITHUB_TOKEN:-}
- GIT_USER_NAME=${GIT_USER_NAME:-opencode}
- GIT_USER_EMAIL=${GIT_USER_EMAIL:-opencode@stillwell.cloud}
volumes:
- echodo-workspace:/workspace
- opencode-data:/data
@ -255,7 +262,8 @@ services:
deploy:
resources:
limits:
memory: 2G
# Next.js builds need headroom beyond the bare serve binary.
memory: 3G
cpus: "2"
networks:
- echodo

View file

@ -0,0 +1,31 @@
# OpenCode serve image + host tooling for interactive / worker agent sessions.
# Official image is Alpine and ships only the `opencode` binary.
FROM ghcr.io/anomalyco/opencode:latest
USER root
RUN apk add --no-cache \
bash \
ca-certificates \
curl \
git \
github-cli \
openssh-client \
ripgrep \
python3 \
make \
g++ \
libc6-compat \
nodejs \
npm \
&& curl -fsSL -o /usr/local/bin/pnpm \
https://github.com/pnpm/pnpm/releases/download/v9.15.9/pnpm-linuxstatic-x64 \
&& chmod +x /usr/local/bin/pnpm \
&& npm install -g tsx \
&& rm -rf /root/.npm/_cacache /tmp/*
ENV GIT_CONFIG_COUNT=1 \
GIT_CONFIG_KEY_0=safe.directory \
GIT_CONFIG_VALUE_0=*
WORKDIR /workspace

View file

@ -7,17 +7,37 @@ Runs `opencode serve` on `:4096` (HTTP API for the worker). Attach a TUI with
is happy. The image has no desktop browser — do not use `opencode web` in
compose without an `xdg-open` stub (entrypoint provides one if you switch back).
## Tooling image
`docker/opencode/Dockerfile` extends `ghcr.io/anomalyco/opencode:latest` with:
- `git`, `openssh-client`, `gh`
- Node.js + `npm` + `pnpm` + `tsx`
- `bash`, `curl`, `ripgrep`, `python3`, `make`, `g++` (native module builds)
Forgejo HTTPS auth is seeded from `FORGEJO_TOKEN` into `~/.git-credentials` at
container start. Projects live under `/workspace/<repo>` on the shared volume.
```bash
export COMPOSE_PROFILES=opencode
docker compose --project-directory /opt/submatter \
-f docker/docker-compose.prod.yml --env-file .env \
build opencode
bash scripts/deploy-prod.sh up
```
## Enable
On CT 116 `/opt/submatter/.env`:
```bash
COMPOSE_PROFILES=opencode
WORKER_SESSION_TYPE=opencode # after worker image rebuild with opencode session
WORKER_SESSION_TYPE=opencode
OPENCODE_SERVER_PASSWORD=<openssl rand -hex 24>
OPENCODE_SERVER_URL=http://opencode:4096
OMNIROUTE_BASE_URL=https://omniroute.stillwell.cloud/v1
OMNIROUTE_API_KEY=<gateway inference key>
FORGEJO_TOKEN=<forgejo pat with repo scope>
```
Then:
@ -29,9 +49,6 @@ bash scripts/deploy-prod.sh up
# bash scripts/deploy-prod.sh build
```
Keep `WORKER_SESSION_TYPE=minimal` until the rebuilt worker image includes the
`opencode` session type (and the `yaml` dependency fix).
## URLs
| Surface | URL |
@ -45,7 +62,8 @@ Keep `WORKER_SESSION_TYPE=minimal` until the rebuilt worker image includes the
- Browser: Authentik via Traefik (`authentik-auth`) **and** OpenCode basic auth.
- Worker: same basic auth via `OPENCODE_SERVER_PASSWORD`.
- Models: `@omniroute/opencode-plugin` + `OMNIROUTE_API_KEY` (seeded into `auth.json` by `entrypoint.sh`).
- Git: `FORGEJO_TOKEN` → HTTPS credential helper for `forgejo.stillwell.cloud`.
## RAM
Container capped at 2 GB. Keep one concurrent agent until host RAM → 64 GB.
Container capped at 3 GB (Next.js builds). Keep one concurrent agent until host RAM → 64 GB.

View file

@ -4,7 +4,7 @@ set -eu
CONFIG_HOME="${XDG_CONFIG_HOME:-${HOME:-/data}/.config}"
DATA_HOME="${XDG_DATA_HOME:-${HOME:-/data}/.local/share}"
mkdir -p "$CONFIG_HOME/opencode" "$DATA_HOME/opencode" /data/bin
mkdir -p "$CONFIG_HOME/opencode" "$DATA_HOME/opencode" /data/bin "$HOME"
# Container has no desktop — stub xdg-open so `opencode web` does not exit.
if [ ! -x /data/bin/xdg-open ]; then
@ -24,4 +24,26 @@ if [ -n "${OMNIROUTE_API_KEY:-}" ]; then
chmod 600 "$DATA_HOME/opencode/auth.json" || true
fi
# Git identity + Forgejo HTTPS auth for clone/push from agent sessions.
if command -v git >/dev/null 2>&1; then
git config --global --add safe.directory '*'
if [ -n "${GIT_AUTHOR_NAME:-}" ]; then
git config --global user.name "$GIT_AUTHOR_NAME"
else
git config --global user.name "${GIT_USER_NAME:-opencode}"
fi
if [ -n "${GIT_AUTHOR_EMAIL:-}" ]; then
git config --global user.email "$GIT_AUTHOR_EMAIL"
else
git config --global user.email "${GIT_USER_EMAIL:-opencode@stillwell.cloud}"
fi
if [ -n "${FORGEJO_TOKEN:-}" ]; then
git config --global credential.helper store
# oauth2:<token> is Forgejo's HTTPS token username convention.
printf 'https://oauth2:%s@forgejo.stillwell.cloud\n' "$FORGEJO_TOKEN" \
> "$HOME/.git-credentials"
chmod 600 "$HOME/.git-credentials"
fi
fi
exec "$@"