DevDocs v1.0

Docker Compose Essentials

Learn how to manage multi-container applications using Docker Compose.

Docker Compose Essentials

As applications grow, managing containers individually becomes difficult.

Consider a typical web application:

  • Frontend
  • Backend API
  • Database

Running each service manually with docker run quickly becomes repetitive and error-prone.

Docker Compose solves this problem by allowing you to define and manage multi-container applications using a single configuration file.

What is Docker Compose?

Docker Compose is a tool that allows you to define an entire application stack inside a YAML file.

Instead of running multiple commands:

docker run ...
docker run ...
docker run ...

You can start everything with:

docker compose up

Compose automatically handles:

  • Networks
  • Service discovery
  • Volumes
  • Environment variables
  • Container lifecycle

Creating Your First Compose File

Docker Compose uses a file named:

compose.yaml

A minimal example:

services:
  app:
    image: nginx
    ports:
      - "8080:80"

Start the application:

docker compose up

Docker downloads the image and starts the container.

Visit:

http://localhost:8080

Understanding the Structure

A Compose file typically contains:

services:
  app:
    image: nginx

volumes:

networks:

Services

Services define the containers that make up your application.

Example:

services:
  frontend:
    image: nginx

  backend:
    image: node:20

Each service becomes its own container.

Building Images with Compose

Instead of pulling an image, Compose can build one directly.

services:
  api:
    build: .

Compose automatically executes:

docker build .

before starting the container.

You can also specify a directory:

services:
  api:
    build: ./backend

Port Mapping

Expose container ports to the host machine.

services:
  api:
    build: .
    ports:
      - "3000:3000"

Format:

HOST_PORT:CONTAINER_PORT

Example:

localhost:3000
        │
        ▼
Container Port 3000

Environment Variables

Applications often require configuration.

services:
  api:
    environment:
      NODE_ENV: production
      PORT: 3000

You can access these values inside the container.

Example:

process.env.NODE_ENV

Using Environment Files

Instead of hardcoding values:

environment:
  DATABASE_URL: postgres://user:pass@db:5432/app

Create a .env file:

DATABASE_URL=postgres://user:pass@db:5432/app

Then reference it:

services:
  api:
    env_file:
      - .env

This keeps sensitive values out of source code.

Working with Volumes

Volumes allow data to persist after containers are removed.

Example:

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

volumes:
  postgres-data:

Without the volume:

Container Removed
        │
        ▼
Database Lost

With the volume:

Container Removed
        │
        ▼
Data Persists

Service Communication

One of Compose's biggest advantages is automatic networking.

Example:

services:
  api:
    build: .

  database:
    image: postgres

Docker automatically creates a network.

The API can connect to PostgreSQL using:

database:5432

No manual network configuration is required.

Multi-Container Example

A simple application stack:

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: secret

  api:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - postgres

Architecture:

Browser
   │
   ▼
API Container
   │
   ▼
PostgreSQL Container

Starting Services

Start all services:

docker compose up

Start in the background:

docker compose up -d

The -d flag runs containers in detached mode.

Viewing Running Services

Check status:

docker compose ps

Example output:

NAME       STATUS
api        running
postgres   running

Viewing Logs

Display logs from all services:

docker compose logs

Follow logs in real time:

docker compose logs -f

View logs for a specific service:

docker compose logs api

Stopping Services

Stop the stack:

docker compose down

This removes:

  • Containers
  • Networks

Volumes remain unless explicitly removed.

Remove everything:

docker compose down -v

Use caution when deleting volumes.

Rebuilding Services

If application code changes:

docker compose build

Or rebuild and restart:

docker compose up --build

Common Commands

Start services:

docker compose up

Run in background:

docker compose up -d

View status:

docker compose ps

View logs:

docker compose logs

Stop services:

docker compose down

Rebuild images:

docker compose build

Best Practices

Use Compose for Local Development

Compose makes development environments reproducible.

Every developer can start the entire stack with:

docker compose up

Keep Secrets Out of Source Control

Prefer:

DATABASE_URL=...

over hardcoded credentials.

Use Named Volumes

For databases and persistent storage:

volumes:
  postgres-data:

Separate Services

Avoid combining multiple applications into a single container.

One service should perform one primary responsibility.

Summary

Docker Compose simplifies multi-container application management.

Key benefits include:

  • Single-file application configuration
  • Automatic networking
  • Volume management
  • Environment variable support
  • Simplified container lifecycle management

For most development workflows, Docker Compose becomes the primary way to run containerized applications.