Skip to content

feat: make the three-runner compose a Coolify service - #10

Closed
azlekov wants to merge 1 commit into
mainfrom
claude/docker-compose-coolify-runners-ieu088
Closed

feat: make the three-runner compose a Coolify service#10
azlekov wants to merge 1 commit into
mainfrom
claude/docker-compose-coolify-runners-ieu088

Conversation

@azlekov

@azlekov azlekov commented Aug 25, 2026

Copy link
Copy Markdown

Deploys docker-compose.yml as one Coolify resource running all three X64 runners, and fixes two upstream footguns found while verifying the file against myoung34's entrypoint.sh.

Coolify contract

  • APP_ID / APP_PRIVATE_KEY / APP_LOGIN use ${VAR:?}, so a half-filled Environment Variables tab blocks the deploy instead of crash-looping the containers. Optional vars keep ${VAR:-default} so they stay editable in the UI.
  • Coolify's SERVICE_FQDN_* / SERVICE_URL_* / SERVICE_PASSWORD_* magic variables are deliberately unused: the runners publish no port, need no domain, and their only secret is the GitHub App key the operator supplies. Nothing here needs generating — forcing a generated value in would only put 32 random characters into the runner names. Documented in the README so the omission reads as a decision, not an oversight.
  • Per-service healthcheck on Runner.Listener, so Coolify can tell a registered runner from a crash-looping one. Exec form on purpose — under CMD-SHELL the wrapping sh -c carries the pattern in its own command line and pgrep -f matches itself, reporting healthy forever.
  • json-file log rotation, stop_grace_period: 30s for the entrypoint's deregister trap, and pull_policy: always for the floating tag.
  • Named volumes are unchanged; Coolify prefixes them with the resource UUID, so two stacks on one host never collide.

Behaviour fixes

Both come from the upstream entrypoint testing these with [ -n ... ] rather than comparing to "true":

  • EPHEMERAL: "false" was enabling --ephemeral. Any non-empty value turns the flag on, so the three runners were being torn down after every job — the opposite of what the file's own comment and the README claimed, and enough to make the new health check flap. EPHEMERAL is no longer passed at all.
  • DISABLE_AUTO_UPDATE had the same trap. It is now hardcoded to "true" and hidden from the Coolify UI, matching the previous effective behaviour while removing the chance of typing false and getting the opposite. A self-updating runner writes into the container layer and loses the update on the next redeploy anyway.

Stable runner names

Each service pins RUNNER_NAME to ${RUNNER_NAME_PREFIX}-1-3 instead of using the upstream random suffix. Coolify recreates containers on every redeploy and config.sh runs with --replace, so the org runner list keeps exactly three entries instead of collecting an offline runner per redeploy. Running the stack on a second host against the same org means changing RUNNER_NAME_PREFIX there — documented in both the README and .env.example.

Validation

  • docker compose config parses cleanly (merge keys resolve, all 18 named volumes are declared and mounted exactly once).
  • With the App variables unset it fails fast: required variable APP_ID is missing a value.
  • Registration flags checked against the upstream entrypoint.sh on master: --replace is always passed (line 185), RUNNER_NAME wins over the random suffix, and RUN_AS_ROOT defaults to true, so the /root/* cache mounts stay correct.

Not deployed to a live Coolify instance from here — worth one redeploy to confirm the health check goes green before merging.

🤖 Generated with Claude Code

https://claude.ai/code/session_01T3cupBSAf2XKiDu1AYPZ3R


Generated by Claude Code

Deploys docker-compose.yml as one Coolify resource running all three X64
runners, and fixes two upstream footguns found while verifying the file
against myoung34's entrypoint.

Coolify contract:
- APP_ID / APP_PRIVATE_KEY / APP_LOGIN use ${VAR:?} so a half-filled
  Environment Variables tab blocks the deploy instead of crash-looping;
  the optional vars keep ${VAR:-default} so they stay UI-editable.
- SERVICE_FQDN_* / SERVICE_URL_* / SERVICE_PASSWORD_* magic variables are
  deliberately unused: no port, no domain, and the only secret is the
  GitHub App key the operator supplies.
- Per-service healthcheck on Runner.Listener so Coolify can tell a
  registered runner from a crash-looping one. Exec form on purpose: under
  CMD-SHELL the wrapping sh -c carries the pattern in its own cmdline and
  pgrep -f matches itself, reporting healthy forever.
- json-file log rotation, stop_grace_period for the deregister trap, and
  pull_policy: always for the floating tag.

Behaviour fixes:
- EPHEMERAL: "false" was enabling --ephemeral. The entrypoint tests it
  with [ -n ... ], so any non-empty value turns the flag on, which tore
  each runner down after every job and contradicted the file's own
  comment. EPHEMERAL is no longer passed at all.
- DISABLE_AUTO_UPDATE had the same trap; it is hardcoded to "true" and
  hidden from the UI so nobody can type "false" and get the opposite.
- Each service pins RUNNER_NAME to ${RUNNER_NAME_PREFIX}-N. Coolify
  recreates containers on every redeploy and config.sh runs with
  --replace, so the org runner list keeps exactly three entries instead
  of collecting an offline runner per redeploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3cupBSAf2XKiDu1AYPZ3R
@azlekov azlekov closed this Aug 25, 2026
@azlekov
azlekov deleted the claude/docker-compose-coolify-runners-ieu088 branch August 25, 2026 15:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants