6.3 Service Containers & Network Topology

Key Takeaways

  • Service containers (`services:`) provide ephemeral, isolated Docker containers (e.g., PostgreSQL, Redis, MySQL) alongside workflow jobs to facilitate integration and end-to-end testing.
  • When a job runs directly on a virtual machine host runner, service container ports must be mapped (`ports: - 5432:5432`), and steps communicate with the service via `localhost:<port>` or `127.0.0.1:<port>`.
  • When a job runs inside a container (`container: { image: ... }`), all service containers and the job container share the same user-defined Docker bridge network, allowing direct communication via the service container name (`postgres:5432`, `redis:6379`) without port mapping.
  • Service health checks (`options: --health-cmd ... --health-interval ... --health-timeout ... --health-retries ...`) ensure the service daemon is fully initialized before workflow steps begin execution.
  • Service containers are isolated per job and automatically torn down when the job finishes, preventing cross-test data pollution and resource leaks.
Last updated: August 2026

Service Containers & Network Topology

Modern continuous integration pipelines require realistic testing environments that mirror production infrastructure. Rather than relying on fragile mock libraries, integration tests frequently require real database engines (PostgreSQL, MySQL, MongoDB), in-memory caches (Redis, Memcached), or messaging brokers (RabbitMQ, Kafka).

GitHub Actions provides Service Containers (services:) to host these auxiliary services in ephemeral, isolated Docker containers for the duration of a job. Mastering the network topology differences between host-level VM jobs and containerized jobs is a fundamental concept on the GH-200 exam.


1. Service Containers Architecture & Lifecycle

A service container is declared under the jobs.<job_id>.services key. GitHub Actions manages the complete lifecycle of service containers:

  1. Pre-Job Initialization: Before any workflow steps execute, the runner pulls the container images and starts all defined service containers concurrently.
  2. Health Check Validation: If health checks are configured, the runner waits until all services report a healthy status before invoking the first step.
  3. Step Execution: Job steps interact with the services across the network.
  4. Post-Job Teardown: Once all job steps complete (whether successful, failed, or cancelled), the runner automatically stops and deletes all service containers and their associated networks.
+-----------------------------------------------------------------------------+
|                     SERVICE CONTAINER LIFECYCLE                             |
|                                                                             |
|   [Job Queued] ──> [Pull Images & Start Containers in Parallel]             |
|                                  │                                          |
|                                  v                                          |
|                    [Evaluate Health Checks (--health-cmd)]                  |
|                                  │                                          |
|                                  v                                          |
|                    [Execute Job Steps (Checkout, Test)]                     |
|                                  │                                          |
|                                  v                                          |
|                    [Stop & Destroy Service Containers] ──> [Job Completed]  |
+-----------------------------------------------------------------------------+

2. Network Topology: Host VM Runner vs. Container Job

The way workflow steps connect to service containers depends entirely on whether the job itself executes directly on the host virtual machine or inside a job container.

+-----------------------------------------------------------------------------+
|                   SCENARIO A: JOB ON HOST VM RUNNER                         |
|                                                                             |
|   +---------------------------------------------------------------------+   |
|   | RUNNER HOST VM (runs-on: ubuntu-latest)                             |   |
|   |                                                                     |   |
|   |   Steps execute on Host OS:                                         |   |
|   |   `npm test` connects to: `localhost:5432` or `127.0.0.1:6379`      |   |
|   |                     │                            │                  |   |
|   |                     │ Port 5432:5432             │ Port 6379:6379   |   |
|   |                     v                            v                  |   |
|   |   +───────────────────────────+    +───────────────────────────+    |   |
|   |   | Service Container: pg     |    | Service Container: redis  |    |   |
|   |   | image: postgres:16        |    | image: redis:7-alpine     |    |   |
|   |   +───────────────────────────+    +───────────────────────────+    |   |
|   +---------------------------------------------------------------------+   |
+-----------------------------------------------------------------------------+

+-----------------------------------------------------------------------------+
|                   SCENARIO B: JOB INSIDE CONTAINER                          |
|                                                                             |
|   +---------------------------------------------------------------------+   |
|   | RUNNER HOST VM (runs-on: ubuntu-latest)                             |   |
|   |                                                                     |   |
|   |   +----------------------- DOCKER BRIDGE NETWORK -----------------+ |   |
|   |   |                                                               | |   |
|   |   |  +──────────────────────+       +──────────────────────────+  | |   |
|   |   |  | Job Container        |       | Service Container        |  | |   |
|   |   |  | (container: node:20) |       | (services.postgres)      |  | |   |
|   |   |  |                      |       |                          |  | |   |
|   |   |  | Connects directly    |──────>| Hostname: 'postgres'     |  | |   |
|   |   |  | to: 'postgres:5432'  |       | Port: 5432 (Internal)    |  | |   |
|   |   |  +──────────────────────+       +──────────────────────────+  | |   |
|   |   |                                                               | |   |
|   |   +───────────────────────────────────────────────────────────────+ |   |
|   +---------------------------------------------------------------------+   |
+-----------------------------------------------------------------------------+

Detailed Comparison Matrix

Configuration DimensionJob on Host VM Runner (runs-on: ubuntu-latest)Job in Container (container: node:20)
Where Steps RunDirectly in the host virtual machine operating system.Inside the specified container execution context.
Service Hostnamelocalhost or 127.0.0.1Service container label name (e.g., postgres, redis).
Port Mapping (ports:)Mandatory: Must map host port to container port (e.g., 5432:5432).Not Required: Containers share the Docker bridge network; all ports are accessible directly.
Docker NetworkRunner creates bridge network for services; maps ports to host.Runner attaches job container and service containers to the same bridge network.
DNS ResolutionHost OS cannot resolve container names via Docker DNS.Docker internal DNS resolves container label names automatically.

[!WARNING] If your job runs on a host VM and you omit the ports: declaration in your service definition, your steps will not be able to connect to the service container via localhost, resulting in connection refused errors (ECONNREFUSED).


3. Configuring Health Checks for Service Containers

Database engines and distributed systems take several seconds to initialize filesystems, run migrations, and open listening sockets. If steps execute before the service is ready, tests will fail with race conditions.

Health checks are configured using Docker daemon options passed via the options: key:

services:
  postgres:
    image: postgres:16
    env:
      POSTGRES_DB: testdb
      POSTGRES_USER: testuser
      POSTGRES_PASSWORD: testpassword
    ports:
      - 5432:5432
    # Docker health check parameters
    options: >-
      --health-cmd pg_isready
      --health-interval 10s
      --health-timeout 5s
      --health-retries 5

Health Check Parameters Explained

  • --health-cmd: The command executed inside the container to test service readiness (e.g., pg_isready, mysqladmin ping, redis-cli ping, or curl -f http://localhost:8080/health).
  • --health-interval: The duration between consecutive health check probes (e.g., 10s).
  • --health-timeout: The maximum time permitted for the health command to complete before marking the check as a failure (e.g., 5s).
  • --health-retries: The number of consecutive failed checks required before the container is marked unhealthy (e.g., 5).
  • --health-start-period: Grace period providing the container time to bootstrap before health checks begin counting against retries.

4. Complete Multi-Service Integration Workflow

The following complete workflow demonstrates running both PostgreSQL and Redis service containers to support an integration test suite running on an Ubuntu VM host:

name: Backend Integration Tests
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  integration-suite:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_DB: test_enterprise
          POSTGRES_USER: runner_admin
          POSTGRES_PASSWORD: secret_password123
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 5s
          --health-timeout 3s
          --health-retries 3

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js runtime
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install test dependencies
        run: npm ci

      - name: Execute Integration Tests against Services
        run: npm run test:integration
        env:
          DATABASE_URL: postgres://runner_admin:secret_password123@localhost:5432/test_enterprise
          REDIS_URL: redis://localhost:6379
Loading diagram...
Service Container Startup, Health Verification, and Teardown Sequence
Test Your Knowledge

A developer configures a workflow job with runs-on: ubuntu-latest and declares a PostgreSQL service container under services.postgres with image: postgres:16. In the job steps, the test application fails with ECONNREFUSED when trying to connect to localhost:5432. What is the most likely configuration omission in the workflow file?

A
B
C
D
Test Your Knowledge

A workflow job is configured to execute inside a container using container: node:20-alpine, and defines a Redis cache service under services.redis with image: redis:7-alpine. How should the Node.js test script running in this job connect to the Redis service?

A
B
C
D
Test Your Knowledge

An integration test suite periodically fails because database migration scripts execute immediately before the PostgreSQL database engine in the service container has finished bootstrapping. What is the recommended GitHub Actions configuration to eliminate this race condition?

A
B
C
D