aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Tools & DevOps

Docker Compose: Build a Multi-Service App in One File

Docker Compose is the tool that lets you define multiple containers in a single YAML file and bring them all up with one command. A real application rarely consists of a single process: a web server, a database and, more often than not, a cache layer all run together. Starting them one by one with docker run is both tedious and error-prone. In this article we will build a typical stack made of web, PostgreSQL and Redis services from scratch, covering inter-service communication, persistent data and environment variables through practical examples.

What does Docker Compose solve?

Running a single container is easy, but in real projects things get complicated fast. Each service has its own image, port, network and environment variables — and they often need to start in a specific order. Compose moves that complexity into a declarative file:

  • Single source: The whole stack lives in compose.yaml; a teammate clones the repo and just types docker compose up.
  • Automatic networking: Compose creates a shared network for your services, and containers reach one another by service name.
  • Reproducibility: The same file produces the same stack on a developer machine and in CI.

Note: in modern Docker the command is docker compose (with a space); the old docker-compose (hyphenated) binary still works but has been superseded by Compose V2.

Your first compose.yaml file

Create a folder and add a compose.yaml inside it. Let's start with just a web service and a database:

services:
  web:
    image: nginx:1.27-alpine
    ports:
      - "8080:80"
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: appdb
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:

A few important details: the "8080:80" entry under ports maps port 8080 on the host to port 80 inside the container. depends_on makes the web service start after db (though it does not guarantee db is actually ready — more on that shortly). The volumes block creates a persistent volume that keeps the database files even if the container is removed.

Adding a cache layer: Redis

Most applications use Redis for sessions, queues or caching. Adding a third service is just a matter of one more block:

services:
  web:
    image: nginx:1.27-alpine
    ports:
      - "8080:80"
    depends_on:
      - db
      - cache

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: appdb
    volumes:
      - db-data:/var/lib/postgresql/data

  cache:
    image: redis:7-alpine
    command: ["redis-server", "--save", "60", "1"]

volumes:
  db-data:

Here's the nice part: from inside the web container you reach the database via the db hostname and Redis via cache. The internal DNS that Compose sets up resolves the service name to the correct IP automatically. So in your application config the connection strings become postgres://app:secret@db:5432/appdb and redis://cache:6379 — no hard-coded IPs needed.

Environment variables and the .env file

Hard-coding passwords into YAML is a bad idea. Compose automatically reads a neighbouring .env file and substitutes variables with the ${...} syntax:

# .env
POSTGRES_PASSWORD=a-very-secret-value
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: appdb

Add .env to your .gitignore so secrets never land in the repository. Leaving an example .env.example for the rest of the team is a good habit.

Real readiness with health checks

depends_on only controls start order; it does not guarantee that the database is accepting connections. The web service may try to connect while db is still booting and fail. The fix is to define a healthcheck and combine depends_on with condition: service_healthy:

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: appdb
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
      interval: 5s
      timeout: 3s
      retries: 5

  web:
    image: nginx:1.27-alpine
    ports:
      - "8080:80"
    depends_on:
      db:
        condition: service_healthy

Now the web container starts only after the pg_isready command returns successfully. This eliminates most of those "the database isn't ready yet" errors.

Day-to-day use: the core commands

These are the commands you'll reach for most often to run and manage the stack:

  • docker compose up -d — starts all services in the background.
  • docker compose ps — lists the running services and their state.
  • docker compose logs -f web — streams the logs of a specific service live.
  • docker compose exec db psql -U app appdb — runs a command inside the running db container.
  • docker compose down — stops and removes the services. Add -v to remove the volumes too.

If you changed an image (for example, you build your own Dockerfile), rebuild with docker compose up -d --build.

Frequently Asked Questions

What's the difference between docker-compose and docker compose?

docker-compose (hyphenated) is the old V1 tool written in Python. docker compose (with a space) is the Go-based V2 plugin integrated into the Docker CLI. On current installations you should use the spaced version; the YAML syntax is largely the same.

Will my data be lost when the container is removed?

No — named volumes defined under volumes are independent of the container lifecycle. docker compose down removes the containers, but the volume stays; your data is still there on the next up. Only down -v deletes the volumes as well.

Is Compose suitable for production?

It's perfectly fine for small to medium single-server setups. For multi-node, auto-scaling and self-healing systems, an orchestrator like Kubernetes is the better choice. Think of Compose for development and simple production scenarios, and Kubernetes for large scale.

Want to consolidate your stack into one file? To simplify your development and deployment with Docker Compose, get in touch with me — let's plan your project's infrastructure together.

Bu kategorideki tüm yazılar →

Devamı için