Docker Compose Made Simple: A Practical Guide with Real Examples

Pragya SaraswatPragya Saraswat
4 min read

🐳 Understanding Docker Compose With Practical Examples

Your simplified guide to managing multi-container apps

👋 Intro

In the world of Docker, we often run applications in multiple containers—like a web app, a database, a cache, etc. Managing all these manually using docker run commands becomes hectic. That’s where Docker Compose comes in.

Let’s say we have a simple voting app. It’s made up of several components:

  • A frontend app (voting-app) where users vote between two options

  • A backend worker that processes votes

  • A Redis service to temporarily store votes

  • A PostgreSQL database to persist final results

  • A result app (resulting-app) to display the results

Each of these runs in a different container. Instead of starting them one-by-one with docker run, we use Docker Compose to define them all in a single docker-compose.yml file and bring them up with one command.Docker Compose allows us to define and manage multi-container Docker applications using a simple YAML file.

In this article, I’ll show you practical examples of how to write docker-compose.yml files. If you're a beginner, don't worry—I’ve kept things simple and straight to the point.


🧩 Starting Simple

Let’s start with the most basic way of running containers without Compose:

docker run pragya/simple-webapp
docker run mongodb
docker run redis:alpine
docker run ansible

But this is too manual, especially if you want to start everything at once.

Now let’s rewrite that with Docker Compose:

services: 
  web:
    image: "pragya/simple-webapp"
  database:
    image: "mongodb"
  messaging:
    image: "redis:alpine"
  orchestration:
    image: "ansible"

To bring up the whole setup, just run:

docker-compose up

Simple, right?


🧱 Building More Realistic Setups

Here’s another setup using individual docker run commands:

docker run -d --name=redis redis
docker run -d --name=db postgres
docker run -d --name=vote -p 8001:80 voting-app
docker run -d --name=results -p 8002:80 resulting-app
docker run -d --name=worker worker

Now the same thing using Docker Compose:

services: 
  redis:
    image: "redis"
  database:
    image: "postgres"
  vote:
    image: "voting-app"
    ports:
      - "8001:80"
  result:
    image: "resulting-app"
    ports:
      - "8002:80"
  worker:
    image: "worker"

Much cleaner and easier to scale!


🛠️ Building from Source Code

If you don’t have an image pushed to the Docker registry, you can build it locally:

vote:
  image: voting-app
  build: ./voting-app

Here, Docker will look for a Dockerfile inside ./voting-app and build the image automatically.


🔢 Compose File Versions

Let’s look at how Docker Compose evolved across versions.

📄 Without Version

services: 
  redis:
    image: "redis"
  database:
    image: "postgres"
  vote:
    image: "voting-app"
    ports:
      - "8001:80"
  links:
    - redis

Here, links was used to connect containers, but it's now outdated.


📄 Version 2

version: "2"
services:
  redis:
    image: "redis"
  database:
    image: "postgres"
  vote:
    image: "voting-app"
    ports:
      - "8001:80"
    depends_on:
      - redis

In version 2, we use depends_on instead of links. This ensures that Redis starts before the vote container.


📄 Version 3

version: "3"
services:
  redis:
    image: "redis"
  database:
    image: "postgres"
  vote:
    image: "voting-app"
    ports:
      - "8001:80"

depends_on is still supported, but Compose version 3 focuses more on Docker Swarm compatibility.


🌐 Docker Compose with Networks

You can define custom networks too:

services: 
  redis:
    image: "redis"
    networks:
      - back-end

  database:
    image: "postgres"
    networks:
      - back-end

  vote:
    image: "voting-app"
    networks:
      - front-end
      - back-end

  result:
    image: "resulting-app"
    networks:
      - front-end
      - back-end

networks:
  front-end:
  back-end:

This setup separates internal services (back-end) from external-facing services (front-end) for better architecture and security.


🧪 One More Example for Better Understanding

services: 
  redis:
    image: "redis"

  database:
    image: "postgres"

  vote:
    image: "voting-app"
    ports:
      - "5000:80"
    links:
      - redis

  worker:
    image: "worker"
    links:
      - db
      - redis        

  result:
    image: "resulting-app"
    ports:
      - "5001:80"
    links:
      - db

You can start everything using:

docker-compose up

🧪 Environment Variables Example

version: "3"
services: 
  redis:
    image: "redis"

  database:
    image: "postgres:9.4"
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres

  vote:
    image: "voting-app"
    ports:
      - "5000:80"

  worker:
    image: "worker"

  result:
    image: "resulting-app"
    ports:
      - "5001:80"

This is useful when you need to pass credentials or configs securely inside containers.


🔚 Conclusion

Docker Compose helps to define, run, and manage multi-container applications easily with a single docker-compose.yml file. You’ve seen different versions, examples, and ways to connect services.

If you're just getting started, try converting your manual docker run commands into a Compose file like above. You’ll feel the power of simplicity.


0
Subscribe to my newsletter

Read articles from Pragya Saraswat directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Pragya Saraswat
Pragya Saraswat