Docker Compose for Multi-Container Applications: A Practical Guide
Docker
In the world of modern application development, it’s rare to find an application that consists of a single, monolithic service. Most applications are composed of multiple interconnected services—a web server, a database, a caching layer, a message queue, and so on. Managing these services individually with Docker can quickly become cumbersome. This is where Docker Compose comes to the rescue.
Docker Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.
Why Docker Compose?
- Simplified Configuration: Define your entire application stack in a single
docker-compose.ymlfile. - Easy Environment Setup: Spin up complex development, testing, or staging environments with one command.
- Service Orchestration: Manage the lifecycle of all services in your application together.
- Portability: Your
docker-compose.ymlfile can be shared and used across different environments and teams.
Understanding docker-compose.yml
The docker-compose.yml file is the heart of your Docker Compose setup. It’s a YAML file that defines your services, networks, and volumes.
Here’s a basic structure:
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
volumes:
- .:/app
depends_on:
- db
db:
image: postgres:13
environment:
POSTGRES_DB: mydatabase
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
networks:
default:
# You can define custom networks here if needed
# driver: bridge
Let’s break down the key sections:
version
Specifies the Compose file format version. It’s important for compatibility and features. 3.8 is a commonly used recent version.
services
This is where you define the individual components (containers) of your application. Each service typically corresponds to a single container.
build: Specifies the path to the directory containing theDockerfilefor this service. If omitted,imagemust be specified.image: Specifies the Docker image to use for the service (e.g.,postgres:13,nginx:latest).ports: Maps host ports to container ports. Format:"HOST_PORT:CONTAINER_PORT".volumes: Mounts host paths or named volumes into the container for data persistence or code synchronization. Format:"HOST_PATH:CONTAINER_PATH"or"VOLUME_NAME:CONTAINER_PATH".environment: Sets environment variables inside the container.depends_on: Expresses dependency between services. Services listed here will be started before the current service. Note thatdepends_ononly ensures the order of startup, not that the dependent service is ready.
volumes
Defines named volumes that can be used by services for persistent data storage. This is crucial for databases where you don’t want data to be lost when containers are removed.
networks
Defines custom networks. By default, Compose creates a default network for your application, allowing all services to communicate with each other using their service names as hostnames.
Essential Docker Compose Commands
docker compose up
Builds, (re)creates, starts, and attaches to containers for all services defined in docker-compose.yml.
Usage: docker compose up [OPTIONS] [SERVICE...]
Common Options:
-d: Run containers in detached mode (in the background).--build: Build images before starting containers.
Example: Start all services in detached mode.
docker compose up -d
docker compose down
Stops and removes containers, networks, and volumes created by up.
Usage: docker compose down [OPTIONS]
Common Options:
--volumes(or-v): Remove named volumes declared in thevolumessection of the Compose file.
Example: Stop and remove all services and their associated networks.
docker compose down
Example: Stop and remove services, networks, and volumes.
docker compose down -v
docker compose build
Builds or rebuilds services.
Usage: docker compose build [OPTIONS] [SERVICE...]
Example: Build the web service image.
docker compose build web
Other Useful Commands
docker compose ps: Lists containers for the current project.docker compose logs [SERVICE]: Displays log output from services.docker compose exec [SERVICE] COMMAND: Executes a command in a running container.
Practical Example: An Express.js Web App with PostgreSQL
Let’s create a simple Python Flask application that connects to a PostgreSQL database, all managed by Docker Compose.
1. Project Structure
my-flask-app/
├── app.py
├── requirements.txt
├── Dockerfile
└── docker-compose.yml
2. app.js (Express.js Application)
const express = require('express');
const { Pool } = require('pg');
const app = express();
const port = 8000;
const pool = new Pool({
host: process.env.DB_HOST || 'db',
user: process.env.DB_USER || 'user',
password: process.env.DB_PASSWORD || 'password',
database: process.env.DB_NAME || 'mydatabase',
port: 5432,
});
app.get('/', async (req, res) => {
try {
await pool.query('SELECT 1');
res.send('Hello from Express.js! Connected to PostgreSQL successfully!');
} catch (err) {
console.error(err);
res.status(500).send(`Hello from Express.js! Could not connect to PostgreSQL: ${err.message}`);
}
});
app.listen(port, () => {
console.log(`App listening at http://localhost:${port}`);
});
3. package.json
{
"name": "my-express-app",
"version": "1.0.0",
"description": "A simple Express.js app with PostgreSQL",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "^4.18.2",
"pg": "^8.11.3"
}
}
4. Dockerfile (for the Express.js app)
FROM node:18-alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
EXPOSE 8000
CMD ["npm", "start"]
5. docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
volumes:
- .:/app
environment:
DB_HOST: db
DB_NAME: mydatabase
DB_USER: user
DB_PASSWORD: password
depends_on:
- db
db:
image: postgres:13
environment:
POSTGRES_DB: mydatabase
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
How to Run This Example
- Save the files above in a directory named
my-express-app. - Navigate to the
my-express-appdirectory in your terminal. - Run
docker compose up -d. - Open your web browser and go to
http://localhost:8000. You should see the message “Hello from Express.js! Connected to PostgreSQL successfully!”. - To stop and remove the services, run
docker compose down -v.
Conclusion
Docker Compose is an incredibly powerful tool for managing multi-container applications. It simplifies the development workflow, making it easier to define, run, and scale complex applications. By mastering docker-compose.yml and its associated commands, you can significantly boost your productivity and streamline your containerization efforts. Happy composing!
Latest Posts
How Does React's useContext Really Work?
Explore the mechanics behind the useContext hook and the Context API. Learn how it solves prop drilling through a provider model and a subscription-based system.
Optimizing Docker Images for Production: Best Practices
Learn best practices for creating efficient, secure, and small Docker images for production environments, covering multi-stage builds, minimal base images, and more.
A Developer's Guide to Setting Up Docker on Linux
Learn how to install and configure Docker on your Linux machine to streamline your development workflow. A step-by-step guide for developers.
Enjoyed this article? Follow me on X for more content and updates!
Follow @Ctrixdev