[Docker Series Part 6] Docker Compose Fundamentals and a Quick Lab

한국어 버전

If Part 5 was about building images, Part 6 asks a new question: how do you define and run related containers together? Compose makes that easier with one Compose file, usually named compose.yaml, where you describe services, networks, and volumes. This article introduces the basics with an nginx static site plus MySQL example.

How this post flows

  1. Build a mental model of the problem Compose solves
  2. Define the smallest web + DB Compose example
  3. Practice the must-know commands (up, down, logs, ps)
  4. Read volume, environment, and network declarations with confidence
  5. Capture the instincts beginners need first

Reading card

  • Estimated time: 16 minutes
  • Prereqs: you have already built an image with a Dockerfile
  • After reading: you can write a Compose file and launch two containers together.

Mental model: one file for one small app

Before reading the YAML, lock in three words first.

services: the app components you want to run (web, db, ...)
networks: the communication paths between those components
volumes: the persistent storage that stays even if containers are recreated

Once those three pieces are in a Compose file, docker compose up can create and start the whole app, and docker compose down can stop and remove it. That is the main friction Compose removes for beginners.

Web + DB Compose example

This example is intentionally small. A static nginx site does not usually need MySQL, but putting a web service and a database service in one file makes the Compose structure easy to read for the first time.

Here is a minimal compose.yaml.

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./public:/usr/share/nginx/html:ro
    networks:
      - app-net

  db:
    image: mysql:8.4
    environment:
      - MYSQL_ROOT_PASSWORD=your-root-password
      - MYSQL_DATABASE=school
    volumes:
      - db-data:/var/lib/mysql
    networks:
      - app-net

volumes:
  db-data:

networks:
  app-net:
    driver: bridge

In a simple project, the explicit app-net block is optional because Compose can create a default network for you. It is shown here so you can see where network settings live.

This one file already answers the core questions:

  • The web service mounts static files read-only and maps host port 8080 to container port 80.
  • The db service uses environment variables for first-run initialization and stores its data in the db-data volume.
  • Both services share the app-net network, so containers on that network can reach MySQL at db:3306.

The two volume styles are worth separating clearly:

  • ./public:/usr/share/nginx/html:ro is a bind mount, so the container reads files from your local folder.
  • db-data:/var/lib/mysql is a named volume, so Docker manages that storage and keeps it across container replacement.

Compose commands to try in the terminal

  1. Save the file and run docker compose up -d to start both services in the background. On current Docker Desktop releases, docker compose replaces the legacy docker-compose binary.
  2. Use docker compose ps to view container names, ports, and status.
  3. Run docker compose logs -f web to stream only the web server logs.
  4. When you need to stop everything, run docker compose down to remove containers and networks.
  5. Named volumes declared under the volumes block survive down, so db-data reattaches on the next up. Pass docker compose down -v only when you truly want to remove this project's volumes and wipe its data.

Mini Lab: Change the web service ports entry to 9090:80, then run docker compose up -d again. You now have to visit http://localhost:9090. Switching the port in your browser makes port mapping feel concrete. If a project uses build:, that is when --build becomes useful.

Checklist for reading volumes, environment variables, and networks

  • volumes: Named volumes such as db-data keep data across restarts. Bind mounts like ./public mirror local folder changes immediately.
  • environment: Define configuration such as database names or application modes. For local development, .env or env_file can keep values out of the main YAML, but they are not secure secret storage for production.
  • networks: In simple cases Compose creates a default network automatically. You can also declare multiple networks to let web talk to an external reverse proxy while keeping db on an internal-only network.

One critical reminder: service names like db only resolve inside the same Compose network. From your Mac terminal, you can only connect through published host ports. In this example, web is reachable at http://localhost:8080, but MySQL is not reachable from the host unless you also publish 3306:3306.

When you read a Compose file for the first time, look for these three blocks. Most other options are details layered on top of this structure.

Instincts beginners should build first

Compose has one essential separation of concerns:

  • Services: what to run
  • Networks: who talks to whom
  • Volumes: what data should survive container replacement

Once that framework is solid, you can scan any project’s docker-compose.yml quickly. Part 7 explains why dev and prod containers differ inside the same file, using a Node app as the practical comparison.

💬 댓글

이 글에 대한 의견을 남겨주세요