Back to Blog

Docker Compose: Complete Beginner Guide

Docker Compose: Complete Beginner Guide

Docker Compose is the standard way to run multi-container applications locally using one declarative file. Instead of managing many long docker run commands, you define services once in compose.yaml and control them as one project.

This guide is written for beginners and follows the internal flow of how Compose works, not just commands.

1) What is Docker Compose?

Docker Compose defines and runs multiple services together. A service is usually one container role, for example:

  • frontend
  • api
  • db
  • cache

Compose lets you start all services with one command:

docker compose up -d

And stop/remove them together:

docker compose down

2) How Compose works internally (mental model)

When you run a Compose command, Docker Compose does not invent a new runtime. It translates your file into normal Docker objects.

Internally, Compose manages:

  • Containers (from services)
  • Networks (default or custom)
  • Volumes (named volumes, bind mounts)
  • Environment interpolation (variables from shell or .env)
  • Project naming and labels (used to group resources)

By default, Compose uses your folder name as the project name, then prefixes resources. For example, a db service may become a container named like myapp-db-1.

3) Common use cases

Compose is ideal when:

  • You are developing full stack apps locally.
  • You need one-command onboarding for teammates.
  • You want realistic integration tests with DB/cache/broker dependencies.
  • You need separate overrides for dev, test, or CI workflows.

For very large production orchestration, teams typically use Kubernetes or managed container platforms.

4) How to create a Compose file

Create compose.yaml at project root.

Start with this practical template:

services:
  api:
    build: ./api
    ports:
      - "8080:8080"
    environment:
      DB_HOST: db
      DB_PORT: 5432
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: app_db
      POSTGRES_USER: app_user
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app_user -d app_db"]
      interval: 5s
      timeout: 3s
      retries: 10
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Then validate and run:

docker compose config
docker compose up -d

5) How commands map to the Compose file

docker compose up

  • Reads services, networks, volumes, and config values.
  • Builds images from build or pulls from image.
  • Creates missing networks and volumes.
  • Creates and starts containers.

docker compose down

  • Stops and removes project containers.
  • Removes project networks.
  • Keeps named volumes unless you pass -v.

docker compose ps

  • Lists containers belonging to this Compose project.

docker compose logs -f api

  • Streams logs for one service.

docker compose config

  • Prints the fully resolved config after variable interpolation and merges.
  • Great for debugging and reviewing what Docker will actually use.

6) Why networking is usually not explicitly mapped

Compose auto-creates a default bridge network for each project. That is why beginners often do not see a networks block in simple files.

Because of this default network:

  • Services can reach each other by service name (for example, db:5432).
  • You do not need manual links or host IPs for basic setups.

Add explicit networks only when you need segmentation, custom drivers, or integration with an external shared network.

7) Control startup order correctly

depends_on controls start order, but does not guarantee app readiness by itself.

Important distinction:

  • "Container is running" is not the same as "Service is ready."

Use both:

  • healthcheck in dependency services.
  • depends_on with condition: service_healthy in dependent services.

This avoids race conditions, especially for databases and brokers.

8) Containers are ephemeral (persistence is opt-in)

Container writable layers are temporary. If container is removed, internal data is gone.

Without volumes:

  • Database state is lost when container is recreated.

With named volumes:

  • Data survives container replacement.

Example:

services:
  db:
    image: postgres:16
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Use bind mounts for live source code during development. Use named volumes for persistent service data.

9) Remove hardcoded values and manage secrets

Never hardcode credentials in compose.yaml.

Use variable substitution:

environment:
  POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

Define variables in .env (for local use) or shell environment:

POSTGRES_PASSWORD=replace-with-strong-password

For better production hygiene:

  • Keep secret files out of version control.
  • Use Docker secrets where supported.
  • Prefer external secret managers for serious production systems.

10) Typical lifecycle flow for beginners

Use this order in day-to-day work:

docker compose config
docker compose up -d --build
docker compose ps
docker compose logs -f api
docker compose down

Use docker compose down -v only when you intentionally want to delete named volume data.

11) Common beginner mistakes

  • Expecting depends_on to guarantee readiness without health checks.
  • Storing DB data without volumes.
  • Hardcoding secrets directly in Compose.
  • Using localhost between services instead of service names.
  • Forgetting to inspect resolved config with docker compose config.

Final takeaway

Docker Compose is not just a command shortcut. It is a declarative model that turns one file into containers, networks, and volumes with consistent naming and lifecycle.

If you remember these three rules, you will avoid most beginner pain:

  • Treat services as one project, not isolated commands.
  • Readiness and persistence must be configured intentionally.
  • Keep configuration external and secrets out of source code.

Quick practice repository

If you want to see all concepts from this post in one working demo, check this repository:

docker-compose-connect-demo

It includes a basic frontend, a Node.js/Express backend, MongoDB, Dockerfiles, Compose setup, and .env wiring, so you can quickly understand how all services connect in a real project.