feat(runner): support `command:` field on services #11
runner-services-command
into main
Why
Services declared in `.anvil.yml` couldn’t override the container’s CMD. Critical for images whose default entrypoint needs args — e.g. `minio/minio:latest` needs `server /data`, otherwise it prints help and exits (code 0).
Caught while trying to add MinIO to Anvil’s own CI to unblock the chiron PR-list perf branch (fangorn/anvil#108). The runner accepted the `command` field in the YAML, parsed it into the service spec JSON, and silently dropped it — the test container then saw `:nxdomain` for `minio` because the service was already dead.
What
- New `command:` key on services in the YAML. String form (`server /data`) is split on whitespace; list form (`[“sh”, “-c”, “…”]`) is passed verbatim.
- Extracted `build_run_args/4` from `start_services` so the arg-building is unit-testable without spinning containers.
- 4 unit tests:
- command lands AFTER the image in the docker run argv (the regression that bit us)
- list form preserves order
- omitted command leaves image as the trailing arg (existing services unaffected)
- `–network-alias` is set so peer containers can resolve service-name DNS
Verification
End-to-end on my Mac:
- minio container started via the new code path stays `running` (not exited)
- alpine peer container on the same docker network resolves `minio` → IP
- TCP connect to `minio:9000` succeeds
Backwards-compatible: services without a `command:` key get the same behavior as before.
Test plan
- CI green
- Build aarch64 binary on `carl` (the Pi runner) and replace `~/.local/bin/anvil`, restart runner, re-run fangorn/anvil#108 — minio service should stay alive and tests should pass
🤖 Generated with Claude Code