Defining Services

[!NOTE] This module explores the core principles of Defining Services, deriving solutions from first principles and hardware constraints to build world-class, production-ready expertise.

1. What is a Service?

In Docker Compose, a Service is a configuration for a container. It tells Docker:

  • Which image to use (or how to build it).
  • What ports to open.
  • What volumes to mount.
  • How to talk to other services.

Ideally, one service equals one process (e.g., web, worker, db).


2. Core Configuration

1. Build vs Image

You can either pull an image or build one from source.

services:
  # Option A: Pull from Registry
  redis:
    image: redis:alpine

  # Option B: Build from Dockerfile
  api:
    build:
      context: ./api
      dockerfile: Dockerfile.dev
      args:
        GO_VERSION: 1.21

2. Startup Order (depends_on)

By default, Compose starts services in parallel. This causes race conditions (e.g., the API tries to connect to the DB before the DB is ready).

depends_on defines dependency order.

Interactive: Dependency Graph

Toggle the dependencies to see how startup order changes.

API Service
Database
Redis
Startup Order: API, Database, Redis (Parallel)

3. Healthchecks (The “Real” dependency)

depends_on only waits for the container to start, not to be ready. If Postgres takes 10 seconds to boot, your API will crash if it tries to connect immediately.

Solution: Healthchecks.

services:
  db:
    image: postgres
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  api:
    depends_on:
      db:
        condition: service_healthy # Wait for GREEN status

3. Production Patterns

Restart Policies

What happens when your container crashes?

  • no: Do not restart (default).
  • always: Always restart.
  • on-failure: Restart only if exit code ≠ 0.
  • unless-stopped: Always restart unless explicitly stopped by user.
services:
  worker:
    image: my-worker
    restart: on-failure

Resource Limits (Deploy)

Prevent a memory leak from crashing your entire server.

services:
  app:
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 512M

4. Code Example: Robust Go Service

Here is how you structure a Go service in Compose with all best practices.

# docker-compose.yaml
services:
  # The Application
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - DB_HOST=db
      - DB_PORT=5432
    depends_on:
      db:
        condition: service_healthy
    restart: always

  # The Database
  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_PASSWORD=secret
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  pgdata:

This configuration ensures:

  1. Zero Downtime: The app waits for the DB to be ready.
  2. Resilience: Both services restart automatically on failure.
  3. Persistence: Data is saved in a named volume.