You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(infra): build claude-runner image out-of-band instead of via compose profile
The `claude-runner` image is a runtime dependency (the API spawns containers
from it per session), not a Compose service. A previous attempt shipped it as
a `profiles: [build-only]` service, but Compose excludes profiled services
from both build and run unless the profile is explicitly activated — which
Coolify does not do, causing `404 No such image: claude-runner:latest` when
the API tries to start a session.
Changes:
- Remove `claude-runner` service from infra/coolify/docker-compose.prod.yml
- Fix the Makefile `build-runner` tag (`claude-runner:latest`, no namespace)
- Make `make build` also build the runner
- Document the per-host one-time build in Quick Start, self-hosting guide,
and Coolify guide (with pre-deployment command)
- Update CLAUDE.md decisions and troubleshooting
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
-**Open source target**: designed for self-hosting with own Claude licenses
138
139
-**Post-results to PR**: after session completion, the API can post score card as a PR comment — opt-in per installation via `post_results_to_pr` boolean; extraction and formatting in `container/pr_comment.py`, triggered in `_event_stream()` after `mark_completed()`
139
140
-**Coolify deployment**: Two-domain setup via Traefik: `helprs.tech` (web) and `api.helprs.tech` (API). TLS via Let's Encrypt, managed by Coolify. Domains are set in Coolify UI (General > Domains for api / Domains for web) — they may get cleared on redeploy, re-check after each deploy. "Preserve Repository During Deployment" must be enabled so skills are available on the host. The prod compose uses `./` paths (repo-root-relative) because Coolify sets `--project-directory` to the repo root.
140
-
-**claude-runner image**: built via a `profiles: [build-only]` service in the prod compose — Coolify builds the image but never starts the container. The API spawns it dynamically. Image name is `claude-runner:latest` (no namespace prefix).
141
+
-**claude-runner image**: NOT a Compose service — it is a runtime dependency built once per Docker host with `make build-runner` (or a pre-deploy command on managed platforms). The API spawns containers from it dynamically via the mounted Docker socket. Image name is `claude-runner:latest` (no namespace prefix — hard-coded in `container/service.py:CLAUDE_RUNNER_IMAGE`). Previous attempt to include it as a `profiles: [build-only]` compose service was removed because `profiles:` excludes the service from both build and run unless the profile is explicitly activated — which Coolify does not do, causing `404 No such image` failures on spawn.
141
142
-**Non-root API container**: production Dockerfile uses `appuser` with `chown -R appuser:appuser /app` for uv cache writes. Docker socket access requires `group_add: ["${DOCKER_GID:-994}"]` in the compose to match the host's docker group GID.
142
143
-**BYOK supports OAuth tokens**: `validate_claude_key()` accepts both API keys (`sk-ant-api03-...`) and OAuth tokens (`sk-ant-oat...`). OAuth tokens skip server-side validation (validated at runtime by Claude Code CLI). Frontend setup page guides users to `claude setup-token`.
143
144
-**VITE_* build args**: `VITE_API_URL` and `VITE_GITHUB_APP_SLUG` are build-time variables — must be passed as `args` in the compose and declared as `ARG`/`ENV` in `Dockerfile.web`. They cannot be set at runtime.
-**Preserve Repository During Deployment**: **Enable this**. Without it, Coolify removes the cloned repo after building images. The `skills/` directory must remain on the host because the API mounts it into claude-runner containers at runtime.
135
135
136
+
### Pre-deployment Command (required)
137
+
138
+
The API spawns ephemeral containers from the `claude-runner` image. This image is **not** a Compose service — it is a runtime dependency that must exist on the Docker host before the API can start a session.
Replace `<uuid>` with your Coolify application directory (same one you use for `SKILLS_HOST_PATH` in step 7). The image is tagged `claude-runner:latest` with no namespace prefix — this is the exact tag the API references (`CLAUDE_RUNNER_IMAGE` in `apps/api/src/helprs/modules/container/service.py`).
147
+
148
+
!!! tip "Why a pre-deploy command?"
149
+
Coolify runs `docker compose up --build`, which only builds services listed in the compose file. Keeping `claude-runner` out of the compose avoids two footguns: Compose trying to start a container that has no long-lived process, and `profiles:` silently excluding the service from the build. A one-line pre-deploy command is explicit and portable to any host. Re-runs are cheap (Docker caches the layers).
150
+
136
151
### Advanced > General
137
152
138
153
-**Auto Deploy**: Enable for automatic redeployment on push to `main`.
Then re-check the pre-deployment command in Coolify (step 5) so future deploys rebuild it automatically. An image pruning operation (`docker image prune -a`) will also remove it — the pre-deploy command will rebuild it on the next deploy.
249
+
237
250
---
238
251
239
252
## 10. Install the GitHub App
@@ -295,7 +308,13 @@ The `DOCKER_GID` does not match your server's Docker group. See [step 8](#8-dock
295
308
No such image: claude-runner:latest
296
309
```
297
310
298
-
Verify the compose includes the claude-runner service with `profiles: [build-only]`. Redeploy to rebuild the image.
311
+
This happens when the pre-deployment command (step 5) did not run or the image was pruned. Build it manually on the host:
Then verify the pre-deployment command is configured in Coolify so it rebuilds on every deploy. The image name must be exactly `claude-runner:latest` (no namespace prefix) — the API references this tag in `apps/api/src/helprs/modules/container/service.py`.
- Verify the claude-runner image exists: `docker images | grep claude-runner` — if missing, run `make build-runner` (the image is a per-host one-time build, not a Compose service)
322
334
- Check `SKILLS_HOST_PATH` is an absolute path and the directory exists on the Docker host
323
335
- Verify the `DOCKER_GID` matches the host's Docker group: `getent group docker | cut -d: -f3`
324
336
- Check API logs for container creation errors: `docker compose logs api | grep container`
0 commit comments