Docker Compose — Services Exercises

2 minute read

Practice what you’ve learned about services. These exercises cover service definitions, image vs build, ports, commands, and DNS.


Exercise 1 — Service Definition

Task: Write a compose file with these requirements:

  • Service web using nginx:alpine on port 8080
  • Service api built from ./backend directory on port 3000
  • Service db using postgres:16 with password “secret”
  • All services restart unless stopped
Show Answer
services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    restart: unless-stopped

  api:
    build: ./backend
    ports:
      - "3000:3000"
    restart: unless-stopped

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    restart: unless-stopped

Exercise 2 — Ports

Question: For each scenario, write the correct ports: configuration:

  1. Nginx accessible on host port 80
  2. PostgreSQL accessible only from localhost
  3. App on port 3000, same on host
  4. Two web servers on different host ports, both using container port 80
  5. UDP service on port 5353
Show Answer
# 1. Nginx on port 80
ports:
  - "80:80"

# 2. PostgreSQL localhost only
ports:
  - "127.0.0.1:5432:5432"

# 3. Same port
ports:
  - "3000:3000"

# 4. Two web servers
# web1:
ports:
  - "8081:80"
# web2:
ports:
  - "8082:80"

# 5. UDP
ports:
  - "5353:5353/udp"

Exercise 3 — DNS Connection

Question: Given this compose file, write the correct DATABASE_URL and REDIS_URL for the api service:

services:
  api:
    build: .
    environment:
      DATABASE_URL: ???
      REDIS_URL: ???

  database:
    image: postgres:16
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: apppass
      POSTGRES_DB: mydb

  redis:
    image: redis:7
Show Answer
DATABASE_URL: postgres://appuser:apppass@database:5432/mydb
REDIS_URL: redis://redis:6379

Key points:

  • Use the service name as the hostname: database (not db)
  • Use the service name as the hostname: redis
  • PostgreSQL default port: 5432
  • Redis default port: 6379

Exercise 4 — Command Override

Task: Write a compose service that:

  1. Uses the node:20-alpine image
  2. Mounts the current directory to /app
  3. Sets the working directory to /app
  4. Runs npm install && npm run dev as the command
Show Answer
services:
  app:
    image: node:20-alpine
    volumes:
      - .:/app
    working_dir: /app
    command: sh -c "npm install && npm run dev"
    ports:
      - "3000:3000"

Using shell form (sh -c "...") because we need the && operator to chain commands.


Exercise 5 — Image vs Build

Question: For each scenario, should you use image:, build:, or both?

  1. Running a MySQL database
  2. Building your Python Flask API from source
  3. Building your app and pushing to a private registry
  4. Running Redis as a cache
  5. Using Nginx with a custom configuration
Show Answer
  1. image: — image: mysql:8 — official image, no customization needed
  2. build: — build: ./api — your custom code needs a Dockerfile
  3. Both — build: . + image: registry.example.com/myapp:v1 — build and tag for push
  4. image: — image: redis:7-alpine — official image
  5. image: + volumes — image: nginx:alpine with volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro — mount config without custom build