Skip to content
Latchkey

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

NeedService containerManual docker run
Automatic health-check gatingYes, via optionsYou write the wait loop
Custom volumes / entrypointLimitedFull control
Teardown handled for youYesNo, you stop it
Reachable on localhostYes (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_test

Gotchas

  • 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

Run this faster and cheaper on Latchkey managed runners. Start free →