All posts
dockerdocker-compose

Docker Compose: A Practical Guide for Full-Stack Developers

A practical guide to Docker Compose — defining and running multi-container local development environments with one command.

SR

Suhail Roushan

August 6, 2026

·
4 min read
·
0 views

The setup instructions for a lot of full-stack projects used to be a wall of "install Postgres, install Redis, make sure they're the right versions, start them, then run the app" — Docker Compose reduced that entire process to one command for most teams.

Docker Compose is a tool for defining and running multi-container applications using a single declarative YAML file. Instead of running separate docker run commands for your app, database, and cache, you describe all of them (and how they connect) in one docker-compose.yml, then bring the entire stack up or down with a single command — a genuinely large improvement to local development onboarding.

Why Docker Compose Matters (and When to Skip It)

New developer onboarding traditionally involves installing and configuring several services locally, each with its own version quirks and setup steps. Compose replaces that with docker compose up, giving every developer an identical, isolated environment regardless of what's already installed on their machine — eliminating an entire category of "works on my machine" issues before they even start.

Skip Compose for genuinely single-service applications with no local dependencies to orchestrate — if there's nothing to coordinate beyond your app itself, the added file and concepts aren't buying you anything.

Getting Started with Docker Compose

# docker-compose.yml
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://user:pass@db:5432/myapp
      REDIS_URL: redis://cache:6379
    depends_on:
      - db
      - cache

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data

  cache:
    image: redis:7

volumes:
  pgdata:
docker compose up
docker compose down

Core Docker Compose Concepts Every Developer Should Know

Services communicate by service name, not localhost. Inside the Compose network, db and cache resolve to their respective containers automatically — this is why the app's DATABASE_URL above points to db, not localhost, a common source of confusion for developers new to Compose.

Volumes persist data across container restarts. Without the pgdata volume above, every docker compose down would wipe the database entirely — volumes are what let you stop and restart the stack without losing local development data.

depends_on controls startup order, not readiness. It ensures db starts before app, but doesn't guarantee Postgres is actually ready to accept connections by the time app starts. Fix this with a proper health check and condition: service_healthy:

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

  app:
    depends_on:
      db:
        condition: service_healthy

Override files let you customize behavior per environment without duplicating the whole configuration — a docker-compose.override.yml for local development on top of a base file used across environments.

Common Docker Compose Mistakes and How to Fix Them

Mistake 1: not using health checks, assuming depends_on guarantees the dependency is ready. This causes intermittent startup failures when the app tries to connect before the database has finished initializing. Fix: add proper health checks and condition: service_healthy for any dependency the app needs to be genuinely ready, not just started.

Mistake 2: no named volumes for stateful services, losing local data on every down. Fix: always define volumes for databases and other services with data you want to persist across restarts.

Mistake 3: using Docker Compose as a production deployment tool without adaptation. Compose is excellent for local development, but production deployments typically need orchestration features (scaling, rolling updates, secrets management) that plain Compose doesn't provide — teams sometimes outgrow it without realizing. Fix: use Compose for local dev, and a proper orchestrator (Kubernetes) or PaaS for production once you need those capabilities.

When Should You Use Docker Compose Instead of Running Services Natively?

Use Docker Compose whenever your local development setup involves more than just your application itself — any database, cache, or supporting service is worth containerizing for onboarding consistency and environment parity. Run a service natively only when you have a specific performance or debugging reason to bypass containerization for that particular piece.

Docker Compose in Production

Keep docker-compose.yml as your canonical local development setup, updated alongside any change to your app's runtime dependencies — a stale Compose file that doesn't match production configuration defeats its own purpose. Also document any environment-specific overrides clearly, since a new developer's first experience with the project is often exactly this file.

If your project's setup instructions currently involve manually installing and starting a database, that's the concrete signal to add a docker-compose.yml before the next new team member's onboarding.

Related posts

Written by Suhail Roushan — Full-stack developer. More posts on AI, Next.js, and building products at suhailroushan.com/blog.

Get in touch