diff --git a/.gitea/workflows/build-next.yml b/.gitea/workflows/build-next.yml
index e2cc93b..ca34d4a 100644
--- a/.gitea/workflows/build-next.yml
+++ b/.gitea/workflows/build-next.yml
@@ -1,4 +1,4 @@
-name: Build and Push Next App
+name: Build and Push Spoon Images
on:
push:
@@ -33,7 +33,7 @@ jobs:
printf '%s\n' "$DOTENV_PROD" > "$env_file"
bunx dotenv -e "$env_file" -- env NODE_ENV=test SKIP_E2E=1 bun run ci:check
- build-next:
+ build-images:
needs: [quality]
runs-on: ubuntu-latest
steps:
@@ -44,7 +44,7 @@ jobs:
with:
bun-version: 1.3.10
- run: bun install --frozen-lockfile
- - name: Build image
+ - name: Build Next image
env:
DOTENV_PROD: ${{ secrets.DOTENV_PROD }}
run: |
@@ -52,9 +52,21 @@ jobs:
trap 'rm -f "$env_file"' EXIT
printf '%s\n' "$DOTENV_PROD" > "$env_file"
CI_ENV_FILE="$env_file" ./scripts/build-next-app production
- - name: Tag and push image
+ - name: Build agent images
+ run: ./scripts/build-agent-images
+ - name: Tag and push images
run: |
docker tag spoon-next:latest git.gbrown.org/gib/spoon-next:${{ gitea.sha }}
docker tag spoon-next:latest git.gbrown.org/gib/spoon-next:latest
docker push git.gbrown.org/gib/spoon-next:${{ gitea.sha }}
docker push git.gbrown.org/gib/spoon-next:latest
+
+ docker tag spoon-agent-worker:latest git.gbrown.org/gib/spoon-agent-worker:${{ gitea.sha }}
+ docker tag spoon-agent-worker:latest git.gbrown.org/gib/spoon-agent-worker:latest
+ docker push git.gbrown.org/gib/spoon-agent-worker:${{ gitea.sha }}
+ docker push git.gbrown.org/gib/spoon-agent-worker:latest
+
+ docker tag spoon-agent-job:latest git.gbrown.org/gib/spoon-agent-job:${{ gitea.sha }}
+ docker tag spoon-agent-job:latest git.gbrown.org/gib/spoon-agent-job:latest
+ docker push git.gbrown.org/gib/spoon-agent-job:${{ gitea.sha }}
+ docker push git.gbrown.org/gib/spoon-agent-job:latest
diff --git a/AGENTS.md b/AGENTS.md
index 6fc7b34..f8b3559 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -16,6 +16,11 @@
Postgres on port 5432 for Convex storage, and the Convex dashboard on port 6791.
Agent jobs are opt-in; build `docker/agent-job.Dockerfile` as
`spoon-agent-job:latest` before running Docker-backed jobs.
+- Gitea CI builds and pushes `spoon-next`, `spoon-agent-worker`, and
+ `spoon-agent-job` images to `git.gbrown.org/gib`. In production,
+ `SPOON_AGENT_JOB_IMAGE` should point to
+ `git.gbrown.org/gib/spoon-agent-job:latest`, and the worker service requires
+ access to the host Docker socket.
## Protected and generated files
diff --git a/README.md b/README.md
index 4559e8b..611dcc2 100644
--- a/README.md
+++ b/README.md
@@ -156,6 +156,56 @@ or job container.
+
+Production agent runtime images
+
+Gitea CI builds and pushes three production images:
+
+```txt
+git.gbrown.org/gib/spoon-next:latest
+git.gbrown.org/gib/spoon-agent-worker:latest
+git.gbrown.org/gib/spoon-agent-job:latest
+```
+
+The worker image is the long-running service that polls Convex. The job image is
+the isolated workbench that the worker launches for each agent job. For the MVP,
+production should use the repo-provided JS/TS workbench image:
+
+```env
+SPOON_AGENT_JOB_IMAGE="git.gbrown.org/gib/spoon-agent-job:latest"
+```
+
+The job image includes Node 22, Bun, package managers through Corepack, git,
+ripgrep, Python, build tools, and the OpenCode CLI. It is not the forked
+project's production runtime; it is the agent execution environment.
+
+Production worker runtime requirements:
+
+- `spoon-agent-worker` must run as a separate service.
+- The worker needs `/var/run/docker.sock` mounted so it can launch job
+ containers.
+- The production Docker host must be logged into `git.gbrown.org` so worker jobs
+ can pull the private `spoon-agent-job` image.
+- `SPOON_WORKER_TOKEN` must match the value stored in Convex production env.
+- `spoon-next` needs `SPOON_AGENT_WORKER_URL=http://spoon-agent-worker:3921` and
+ `SPOON_AGENT_WORKER_INTERNAL_TOKEN` so Next API routes can proxy workspace
+ file, diff, message, command, and draft PR actions.
+- `spoon-agent-worker` also needs `GITHUB_APP_ID` and `GITHUB_APP_PRIVATE_KEY`.
+
+Useful production checks:
+
+```sh
+docker logs --tail=200 spoon-agent-worker
+curl -H "Authorization: Bearer $SPOON_AGENT_WORKER_INTERNAL_TOKEN" \
+ http://spoon-agent-worker:3921/health
+```
+
+For the first production run, use an API-key based AI provider profile. Stored
+OpenCode/Codex `auth.json` profiles are supported in settings, but worker-side
+auth-file injection is still a follow-up before they can execute jobs.
+
+
+
## Architecture
diff --git a/docker/agent-job.Dockerfile b/docker/agent-job.Dockerfile
index d2e5004..3d1697b 100644
--- a/docker/agent-job.Dockerfile
+++ b/docker/agent-job.Dockerfile
@@ -1,4 +1,4 @@
-FROM node:22-bookworm
+FROM docker.io/library/node:22-bookworm
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
diff --git a/docker/agent-worker.Dockerfile b/docker/agent-worker.Dockerfile
index dbb2941..e7c51e9 100644
--- a/docker/agent-worker.Dockerfile
+++ b/docker/agent-worker.Dockerfile
@@ -1,4 +1,4 @@
-FROM oven/bun:1.3.10
+FROM docker.io/oven/bun:1.3.10
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
diff --git a/scripts/build-agent-images b/scripts/build-agent-images
new file mode 100755
index 0000000..52bebd9
--- /dev/null
+++ b/scripts/build-agent-images
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
+
+docker build -f "$ROOT_DIR/docker/agent-worker.Dockerfile" -t spoon-agent-worker:latest "$ROOT_DIR"
+docker build -f "$ROOT_DIR/docker/agent-job.Dockerfile" -t spoon-agent-job:latest "$ROOT_DIR"