DEV Community

Cover image for Docker Compose: Run Your Whole App with One Command
Md Mohiuddin
Md Mohiuddin

Posted on

Docker Compose: Run Your Whole App with One Command

From multiple docker run commands to one declarative YAML file.

A real application rarely consists of just one container.

You might have:

Web App
   ↓
Database
   ↓
Redis
   ↓
Message Queue
Enter fullscreen mode Exit fullscreen mode

Starting and configuring all of these containers manually can quickly become painful.

That's where Docker Compose comes in.

In this article, we'll learn how Docker Compose simplifies multi-container applications and how to define an entire application stack in a single YAML file.


Why Docker Compose Matters

Think back to a typical multi-container setup.

You might need commands like:

docker network create mongo-network
docker volume create mongo-data

docker run -d \
  --name mongo \
  --network mongo-network \
  -v mongo-data:/data/db \
  -e MONGO_INITDB_ROOT_USERNAME=admin \
  -e MONGO_INITDB_ROOT_PASSWORD=password123 \
  mongo

docker run -d \
  --name mongo-express \
  --network mongo-network \
  -e ME_CONFIG_MONGODB_SERVER=mongo \
  -e ME_CONFIG_MONGODB_ADMINUSERNAME=admin \
  -e ME_CONFIG_MONGODB_ADMINPASSWORD=password123 \
  -p 8081:8081 \
  mongo-express
Enter fullscreen mode Exit fullscreen mode

It works.

But it's:

  • Long
  • Difficult to remember
  • Easy to mistype
  • Hard to share
  • Difficult to maintain

Now imagine having six or ten services.

You don't want to manage your application this way.

Docker Compose lets you describe the entire application in a single file.

Then:

docker compose up
Enter fullscreen mode Exit fullscreen mode

And your application stack starts.

That's the real power of Compose.


What Is Docker Compose?

Docker Compose is a tool for defining and running multi-container Docker applications.

Instead of manually running individual containers, you describe your desired application architecture in a YAML file.

For example:

services:
  web:
    build: .

  database:
    image: mongo

  cache:
    image: redis
Enter fullscreen mode Exit fullscreen mode

Compose reads this configuration and creates the required containers, networks, and volumes.

The important idea is:

Define your application once, then let Docker Compose create and manage it.


Imperative vs Declarative

Docker Compose introduces an important DevOps concept.

Imperative

With individual Docker commands, you tell Docker what to do.

docker network create app-network
docker volume create database-data
docker run ...
docker run ...
Enter fullscreen mode Exit fullscreen mode

You're specifying the steps.

Declarative

With Compose, you describe what you want.

services:
  web:
    build: .

  database:
    image: mongo
Enter fullscreen mode Exit fullscreen mode

You're saying:

"I want a web service and a database service."

Compose determines how to create them.

This declarative approach becomes extremely important later when you learn:

  • Kubernetes
  • Terraform
  • Infrastructure as Code
  • CI/CD configuration

The docker-compose.yml File

Docker Compose uses YAML configuration.

A simple file looks like this:

services:
  web:
    image: nginx

  database:
    image: mongo
Enter fullscreen mode Exit fullscreen mode

The main section is:

services:
Enter fullscreen mode Exit fullscreen mode

Each service represents a container.

For example:

services:
  web:
    image: nginx

  database:
    image: mongo

  cache:
    image: redis
Enter fullscreen mode Exit fullscreen mode

This describes three services:

        Docker Compose
              |
      +-------+-------+
      |       |       |
     Web   Database  Cache
    nginx    mongo   redis
Enter fullscreen mode Exit fullscreen mode

YAML Indentation Matters

YAML uses indentation to represent structure.

Use spaces, not tabs.

For example:

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

Incorrect indentation can cause YAML parsing errors.


Defining a Service

Let's look at a realistic service:

services:
  mongo:
    image: mongo
    container_name: mongo
    restart: unless-stopped

    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: password123

    ports:
      - "27017:27017"

    volumes:
      - mongo-data:/data/db
Enter fullscreen mode Exit fullscreen mode

Most of these options map directly to docker run flags you've already learned.


image

image: mongo
Enter fullscreen mode Exit fullscreen mode

This tells Compose which Docker image to use.

It's similar to:

docker run mongo
Enter fullscreen mode Exit fullscreen mode

You can also specify a version:

image: mongo:8
Enter fullscreen mode Exit fullscreen mode

Pinning versions is generally better than relying on a moving latest tag for production workloads.


container_name

container_name: mongo
Enter fullscreen mode Exit fullscreen mode

This gives the container a predictable name.

It's similar to:

docker run --name mongo
Enter fullscreen mode Exit fullscreen mode

In Compose projects, you often don't need container_name because Compose already provides predictable service-based naming and DNS.


environment

Environment variables can be defined like this:

environment:
  MONGO_INITDB_ROOT_USERNAME: admin
  MONGO_INITDB_ROOT_PASSWORD: password123
Enter fullscreen mode Exit fullscreen mode

This is equivalent to:

-e MONGO_INITDB_ROOT_USERNAME=admin
-e MONGO_INITDB_ROOT_PASSWORD=password123
Enter fullscreen mode Exit fullscreen mode

For real applications, avoid committing production secrets directly into your Compose file. Environment files or secret-management solutions are better choices.


ports

Port mappings use the same familiar syntax:

ports:
  - "8080:80"
Enter fullscreen mode Exit fullscreen mode

This means:

Host              Container
8080      --->       80
Enter fullscreen mode Exit fullscreen mode

It's the Compose equivalent of:

-p 8080:80
Enter fullscreen mode Exit fullscreen mode

volumes

Volumes provide persistent storage:

volumes:
  - mongo-data:/data/db
Enter fullscreen mode Exit fullscreen mode

This connects the named volume mongo-data to /data/db inside the MongoDB container.

At the bottom of the Compose file, declare the volume:

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

build — Building Your Own Application

For your own application, use build: instead of image:.

Suppose your project looks like this:

my-app/
├── Dockerfile
├── app.py
├── requirements.txt
└── docker-compose.yml
Enter fullscreen mode Exit fullscreen mode

Your Compose file can contain:

services:
  web:
    build: .
    ports:
      - "5000:5000"
Enter fullscreen mode Exit fullscreen mode

The:

build: .
Enter fullscreen mode Exit fullscreen mode

means:

Build the image using the Dockerfile in the current directory.


depends_on

Suppose your web application needs MongoDB.

You can write:

services:
  web:
    build: .
    depends_on:
      - mongo

  mongo:
    image: mongo
Enter fullscreen mode Exit fullscreen mode

This tells Compose to start MongoDB before starting the web container.

But remember:

depends_on controls startup order, not application readiness.

A database can still be initializing after its container has started.

For more reliable startup behavior, healthcheck can be used.


Docker Compose Networking

One of Docker Compose's biggest advantages is automatic networking.

When you start a Compose project, Compose creates a network for the application by default.

Services can communicate with each other using their service names.

For example:

services:
  web:
    build: .

  mongo:
    image: mongo
Enter fullscreen mode Exit fullscreen mode

The web container can connect to MongoDB using:

mongo
Enter fullscreen mode Exit fullscreen mode

as the hostname.

You don't need to know MongoDB's container IP address.

Conceptually:

+-----------------------------+
|      Compose Network        |
|                             |
|   +--------+   +---------+  |
|   |  web   |-->|  mongo  |  |
|   +--------+   +---------+  |
|                             |
+-----------------------------+
Enter fullscreen mode Exit fullscreen mode

Defining a Custom Network

You can also explicitly define your network:

services:
  web:
    build: .
    networks:
      - app-network

  mongo:
    image: mongo
    networks:
      - app-network

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

Now both services belong to app-network and can communicate with each other.


Restart Policies

Compose makes it easy to define what should happen when a container stops.

restart: unless-stopped
Enter fullscreen mode Exit fullscreen mode

Common policies include:

Policy Behavior
no Never automatically restart
always Always restart
unless-stopped Restart unless manually stopped
on-failure Restart when the process exits with an error

For many long-running services:

restart: unless-stopped
Enter fullscreen mode Exit fullscreen mode

is a useful default.


The Most Important Compose Commands

Start the Application

docker compose up
Enter fullscreen mode Exit fullscreen mode

Run in the Background

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

Build Before Starting

docker compose up --build
Enter fullscreen mode Exit fullscreen mode

Check Service Status

docker compose ps
Enter fullscreen mode Exit fullscreen mode

View Logs

docker compose logs
Enter fullscreen mode Exit fullscreen mode

View Logs for One Service

docker compose logs mongo
Enter fullscreen mode Exit fullscreen mode

Follow Logs

docker compose logs -f mongo
Enter fullscreen mode Exit fullscreen mode

docker compose stop vs docker compose down

This distinction is extremely important.

docker compose stop

docker compose stop
Enter fullscreen mode Exit fullscreen mode

Stops the containers but doesn't remove them.

You can start them again with:

docker compose start
Enter fullscreen mode Exit fullscreen mode

docker compose down

docker compose down
Enter fullscreen mode Exit fullscreen mode

Stops and removes the containers and the Compose-created network.

By default, named volumes are preserved.

That means your database data can remain intact.


Be Careful With down -v

You can also run:

docker compose down -v
Enter fullscreen mode Exit fullscreen mode

The -v option removes the Compose-managed volumes.

If your database uses those volumes, your database data can be deleted.

Use this command carefully, especially on production systems.


A Complete Real-World Example

Imagine we have:

  • Flask application
  • MongoDB
  • Mongo Express
  • Persistent MongoDB storage
  • Custom application network
  • Restart policies

Our Compose file can look like this:

services:
  web:
    build: .
    container_name: flask-app
    restart: unless-stopped

    ports:
      - "5000:5000"

    depends_on:
      - mongo

    networks:
      - app-network

  mongo:
    image: mongo
    container_name: mongo
    restart: unless-stopped

    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: password123

    ports:
      - "27017:27017"

    volumes:
      - mongo-data:/data/db

    networks:
      - app-network

  mongo-express:
    image: mongo-express
    container_name: mongo-express
    restart: unless-stopped

    environment:
      ME_CONFIG_MONGODB_SERVER: mongo
      ME_CONFIG_MONGODB_ADMINUSERNAME: admin
      ME_CONFIG_MONGODB_ADMINPASSWORD: password123

    ports:
      - "8081:8081"

    depends_on:
      - mongo

    networks:
      - app-network

networks:
  app-network:

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

Now we have an entire application architecture described in one file:

                 Docker Compose
                       |
          +------------+------------+
          |            |            |
       Flask App     MongoDB    Mongo Express
          |            |            |
          +------------+------------+
                       |
                 app-network
                       |
                  mongo-data
                    volume
Enter fullscreen mode Exit fullscreen mode

Start everything with:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

Check the services:

docker compose ps
Enter fullscreen mode Exit fullscreen mode

View all logs:

docker compose logs
Enter fullscreen mode Exit fullscreen mode

Stop everything:

docker compose down
Enter fullscreen mode Exit fullscreen mode

And bring it back:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

Your MongoDB data remains because the named volume wasn't removed.


Why Compose Is a Big Step Forward

Before Compose, your workflow looked like this:

Remember commands
      ↓
Create network
      ↓
Create volume
      ↓
Start database
      ↓
Start application
      ↓
Start admin UI
      ↓
Check configuration
Enter fullscreen mode Exit fullscreen mode

With Compose:

docker compose up -d
        ↓
     Everything
Enter fullscreen mode Exit fullscreen mode

More importantly, the architecture is now stored as code.

You can:

  • Commit it to Git
  • Review changes
  • Share it with teammates
  • Reproduce the environment
  • Use it in development
  • Use it in CI/CD workflows

That's a major shift in how you manage applications.


Docker Compose Is Infrastructure as Code Practice

You may not think of a Compose file as infrastructure code yet.

But it is teaching you the right mindset.

Instead of:

"I remember how I configured this server."

You say:

"The configuration is defined in Git."

Instead of:

"Run these 12 commands in this exact order."

You say:

"Here is the desired state."

This mindset becomes extremely important when you move into:

  • CI/CD
  • Kubernetes
  • Terraform
  • Ansible
  • Cloud infrastructure

Docker Compose is one of the first practical places where you'll experience this shift.


Final Thoughts

Docker made it easy to package applications into containers.

Docker networking made it possible for containers to communicate.

Docker volumes made persistent data possible.

And Docker Compose brings all of these concepts together into one manageable application definition.

The biggest lesson today isn't just learning:

docker compose up
Enter fullscreen mode Exit fullscreen mode

It's learning to describe infrastructure declaratively.

Instead of manually managing containers one by one, you define the entire application stack as code and let Docker Compose handle the implementation.

That's the real bridge from simply using Docker to thinking like a DevOps engineer.

Define it once. Version it. Share it. Reproduce it.

Top comments (0)