How to Choose a Database Container vs a Service in GitHub Actions
A services: container gives you automatic startup, health checks, and teardown; a manual docker run gives full control over flags, volumes, and timing.
Use a service container for the common case: the runner starts it, waits on the health check, and tears it down. Run a container manually with docker run when you need custom volumes, an entrypoint, or runtime configuration the service block cannot express.
When to use which
| Need | Service container | Manual docker run |
|---|---|---|
| Automatic health-check gating | Yes, via options | You write the wait loop |
| Custom volumes / entrypoint | Limited | Full control |
| Teardown handled for you | Yes | No, you stop it |
| Reachable on localhost | Yes (ports mapped) | With --network host |
Manual container example
.github/workflows/ci.yml
steps:
- name: Start Postgres manually
run: |
docker run -d --name pg --network host \
-e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=app_test \
postgres:16
for i in $(seq 1 30); do
docker exec pg pg_isready -U postgres && break; sleep 2
done
- run: npm test
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/app_testGotchas
- With a manual container you must add your own readiness loop; the runner does not wait for it.
- Service containers cannot mount arbitrary host volumes, so seed data must be loaded by a step, not a volume.
Related guides
How to Wait for a Database to Be Ready in GitHub ActionsMake GitHub Actions wait until a database accepts connections using service health checks or a pg_isready ret…
How to Spin Up a Postgres Service for Tests in GitHub ActionsRun a real Postgres alongside your GitHub Actions tests using a service container with a pg_isready health ch…
How to Use Service Containers in GitHub ActionsSpin up Postgres or Redis alongside a GitHub Actions job with services, including health checks and port mapp…