GitHub Actions Tutorial — Container Jobs

2 minute read

Container Jobs

Instead of running steps directly on the GitHub-hosted runner VM, you can run them inside a Docker container. This gives you exact control over the runtime environment — specific OS versions, language versions, and pre-installed tools.


Why Use Container Jobs?

Direct on Runner Container Job
Uses runner’s pre-installed tools Uses exact image you specify
Language version may change Version locked to container image
Quick to start Slight overhead for image pull
Good for most CI Best for reproducible, controlled environments

Basic Container Job


jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: node:20-alpine

    steps:
      - uses: actions/checkout@v4
      - run: node --version         # 20.x (from container)
      - run: npm ci
      - run: npm test

The container: field tells GitHub Actions to run all steps inside the specified Docker image.


Container with Options


jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: node:20
      env:
        NODE_ENV: test
      ports:
        - 3000:3000
      volumes:
        - /tmp/data:/data
      options: --user root

Option Purpose
image: Docker image to use
env: Environment variables inside the container
ports: Port mapping
volumes: Volume mounts
options: Additional docker run options
credentials: Auth for private registries

Service Containers

Service containers run alongside your job container. Common use: databases for integration tests.


jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: node:20

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

      redis:
        image: redis:7
        ports:
          - 6379:6379

    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - name: Run tests with database
        env:
          DATABASE_URL: postgresql://testuser:testpass@postgres:5432/testdb
          REDIS_URL: redis://redis:6379
        run: npm test

Service Container Networking

Scenario How to Connect
Job in container Use service name as hostname: postgres:5432
Job directly on runner Use localhost:5432 (mapped port)

Private Registry Images

To pull from a private Docker registry:


jobs:
  build:
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/my-org/my-image:latest
      credentials:
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}


When to Use Container Jobs

Use Case Container?
Need specific language version ✅ Yes
Integration tests with databases ✅ Yes (service containers)
Simple npm/pip install + test ❌ Use runner directly
Need Docker-in-Docker ❌ Use runner directly
Reproducible builds ✅ Yes
macOS or Windows builds ❌ Container jobs only work on Linux runners

Important: Container jobs only work with ubuntu-* runners. You cannot use container: with windows-latest or macos-latest.


Common Mistakes

Mistake Fix
Container job on macOS/Windows Container jobs only work on Linux runners
Can’t connect to service container Use service name (not localhost) when job runs in a container
Image pull fails Check image name spelling; for private images, add credentials:
Permission denied in container Add options: --user root or adjust image permissions

Quick Reference

Concept Syntax
Run in container container: image: node:20
Service container services: db: image: postgres:16
Container env container: env: KEY: value
Private registry container: credentials: username: / password:
Works on Linux runners only (ubuntu-*)

← Previous: Job Outputs

Next: Practice Exercises →

Updated: