DEV Community

Celso Nery
Celso Nery

Posted on

Part 6: Automating with Docker Compose

🇧🇷 Artigo em português aqui

Using Docker — Part 6: Automating with Docker Compose

Throughout this series, we've covered containers, volumes, networks, and image management — always using individual commands like docker run, docker network create, docker volume create, etc. That works, but it becomes tedious (and error-prone) once an application starts depending on multiple services. That's where Docker Compose comes in: it lets you describe all of this configuration in a single declarative file, making the process cleaner, easier to repeat, and easier to version in a Git repository.

Creating your docker-compose.yaml

A minimal docker-compose.yaml

The simplest possible structure, running a single service:

services:
  web:
    image: httpd
    ports:
      - "80:80"
Enter fullscreen mode Exit fullscreen mode

This is already equivalent to running docker run -p 80:80 httpd — but in a declarative, reapplyable, and easily versioned way.

Adding a volume

To persist that service's data (as we saw in Part 3 of this series), just add a volumes section:

services:
  web:
    image: httpd
    ports:
      - "80:80"
    volumes:
      - web-data:/usr/local/apache2/htdocs

volumes:
  web-data:
Enter fullscreen mode Exit fullscreen mode

Notice the volume needs to be declared in two places: referenced inside the service (volumes: under web) and defined in the root-level volumes: section at the end of the file — that's where Compose actually creates the named volume.

Adding a network

Likewise, you can declare custom networks (covered in Part 4):

services:
  web:
    image: httpd
    ports:
      - "80:80"
    volumes:
      - web-data:/usr/local/apache2/htdocs
    networks:
      - app-network

volumes:
  web-data:

networks:
  app-network:
Enter fullscreen mode Exit fullscreen mode

In practice, when you don't explicitly declare a network, Compose already creates a default network for the project automatically, allowing services within the same docker-compose.yaml to see each other by name. Declaring networks manually is worth it when you need more than one isolated network, or want to connect services across different Compose files.

Bringing up the containers

To create and start all the services defined in the file:

$ docker compose up
Enter fullscreen mode Exit fullscreen mode

Just like with docker run, the terminal stays "stuck," showing the logs of all services. To run it in the background:

$ docker compose up -d
Enter fullscreen mode Exit fullscreen mode

Tearing down the containers

To stop and remove the containers and networks, while (by default) preserving the volumes created by Compose:

$ docker compose down
Enter fullscreen mode Exit fullscreen mode

By default, docker compose down does not remove volumes — this prevents accidental data loss. If you want to remove volumes too, use docker compose down -v (with caution).

Bringing multiple containers together: App + Database + Redis

Compose's real power shows up when an application depends on multiple services working together. A common example: an Nginx proxy in front, an application, a MySQL database, and a Redis cache.

services:
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - app
    networks:
      - app-network

  app:
    image: myapp:1.0.0
    environment:
      DB_HOST: db
      DB_USER: appuser
      DB_PASSWORD: apppassword
      REDIS_HOST: redis
    depends_on:
      - db
      - redis
    networks:
      - app-network

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: appdb
      MYSQL_USER: appuser
      MYSQL_PASSWORD: apppassword
    volumes:
      - db-data:/var/lib/mysql
    networks:
      - app-network

  redis:
    image: redis:alpine
    networks:
      - app-network

volumes:
  db-data:

networks:
  app-network:
Enter fullscreen mode Exit fullscreen mode

A few important points from this example:

  • depends_on: defines the startup order of the containers (app only starts after db and redis). Note this only guarantees the starting order of the container, not that the internal service (like MySQL) is already ready to accept connections — for that, the application usually needs to implement some retry logic on the database connection;
  • Environment variables (environment): used to configure the application and MySQL without needing to modify images or hardcode values;
  • db-data: a named volume ensuring MySQL's data survives container restarts and recreations;
  • app-network: all services share the same network, letting them communicate with each other by service name (db, redis) instead of fixed IPs.

Bringing everything up at once:

$ docker compose up -d
Enter fullscreen mode Exit fullscreen mode

With a single command, Compose creates the network, the volumes, and brings up all four containers already connected and ready to talk to each other.

Series wrap-up

Throughout this series, we covered the complete day-to-day Docker lifecycle: installation, essential CLI commands, data persistence with volumes, container-to-container communication via networks, image management and publishing, and finally, orchestrating multiple services with Docker Compose. With this foundation, you now have everything you need to run containerized applications professionally — whether in a local development environment or on a production server.

For anyone looking to go beyond a single host, the natural next step is Kubernetes — which solves the same orchestration problems, but at scale, distributing containers across multiple servers.

Top comments (0)