Introduction / Why This Is Needed
Docker Compose is a tool that allows you to describe and manage multi-container applications using a single YAML file. Instead of manually launching dozens of containers, configuring networks and volumes, you describe the entire infrastructure as code. This is especially useful for development, testing, and local deployment.
After completing this guide, you will:
- Understand the basic syntax of
docker-compose.yml - Learn how to run a stack consisting of a web service and a database
- Find out how to manage the container lifecycle with a single command
Prerequisites / Preparation
Before starting, make sure you have installed:
- Docker Engine (version 20.10 or newer)
- Docker Compose v2 (included with Docker Desktop for Linux or as the
docker-compose-plugin) - sudo privileges (or a user in the
dockergroup)
Check your installation:
docker --version
docker compose version
If Docker Compose is not installed, on Ubuntu/Debian:
sudo apt update
sudo apt install docker-compose-plugin
On CentOS/Rocky/AlmaLinux:
sudo yum install docker-compose-plugin
Step 1: Create the Project Structure
Create a directory for your application and navigate to it:
mkdir myapp && cd myapp
In this directory, create a docker-compose.yml file. We will deploy a simple stack:
webservice: Python Flask applicationdbservice: PostgreSQL
Step 2: Write the docker-compose.yml File
Create a docker-compose.yml file with the following content:
version: '3.8'
services:
web:
image: python:3.11-slim
command: >
sh -c "pip install flask psycopg2-binary &&
echo 'from flask import Flask\nfrom psycopg2 import connect\napp = Flask(__name__)\n@app.route(\"/\")\ndef hello():\n try:\n conn = connect(dbname=\"testdb\", user=\"user\", password=\"password\", host=\"db\")\n return \"Hello from Docker Compose! DB connection: OK\"\n except Exception as e:\n return f\"DB error: {e}\"\nif __name__ == \"__main__\":\n app.run(host=\"0.0.0.0\", port=5000)' > app.py &&
python app.py"
ports:
- "5000:5000"
depends_on:
- db
environment:
- DATABASE_URL=postgresql://user:password@db/testdb
networks:
- app-network
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: testdb
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- app-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
networks:
app-network:
driver: bridge
File Explanations:
- version: specifies the Compose schema version (use 3.8 for compatibility)
- services: list of containers (services)
- web:
image: base Python imagecommand: runs a script that installs Flask, creates a simple app, and starts itports: maps container port 5000 to host port 5000depends_on: ensuresdbstarts beforewebenvironment: environment variables (used by the app)networks: attaches the service to the customapp-network
- db:
image: PostgreSQL 15 imageenvironment: database credentialsvolumes: thepostgres_datavolume persists database data on the host (even after container removal)healthcheck: checks PostgreSQL readiness (important fordepends_on)
- web:
- volumes: declares the named volume
postgres_data - networks: creates the
app-network(defaultbridgedriver)
Step 3: Start the Stack
In the directory with docker-compose.yml, run:
docker compose up -d
What will happen:
- Docker Compose reads the configuration
- Creates the network
myapp_app-network(prefix — directory name) - Pulls the
python:3.11-slimandpostgres:15-alpineimages (if not present locally) - Starts the containers in detached mode (
-d)
💡 Tip: The first run may take a minute — Docker downloads the images (~500 MB).
Step 4: Verify Operation
- Check container status:
docker compose ps
The output should show both services inUpstate. - Check logs (if something isn't working):
docker compose logs web docker compose logs db - Test the application:
Open your browser and go to
http://localhost:5000. You should see:Hello from Docker Compose! DB connection: OK
This means the web container successfully connected to PostgreSQL. - Check the network:
docker network ls | grep app-network docker network inspect myapp_app-network
Step 5: Managing the Stack
Essential commands:
| Command | Action |
|---|---|
docker compose down | Stops and removes containers, networks (volumes remain) |
docker compose down -v | Also removes volumes (database data will be lost!) |
docker compose logs -f | Follows logs in real time |
docker compose exec web bash | Enters the web container |
docker compose stop | Stops containers (without removing) |
docker compose start | Starts stopped containers |
docker compose rm | Removes stopped containers |
Example of restarting only one service:
docker compose restart web
Step 6: Modification and Rebuilding
If you changed docker-compose.yml or need to rebuild an image (e.g., added a Dockerfile), use:
docker compose up -d --build
To force recreate containers without building:
docker compose up -d --force-recreate
Result Verification
✅ Success Criteria:
- Containers are running (
docker compose psshowsUp) - The web app responds at
http://localhost:5000and reports successful DB connection - PostgreSQL data is persisted in the
postgres_datavolume (checkdocker volume ls)
If something isn't working:
- Check logs:
docker compose logs - Ensure port 5000 isn't used by another application
- Verify both services are on the same network:
docker network inspect myapp_app-network
Possible Issues
Error: Error response from daemon: port is already allocated
Cause: Port 5000 is already in use by another process. Solution:
- Stop the conflicting process (
sudo lsof -i:5000andkill) - Or change the port mapping in
docker-compose.yml(e.g.,"8080:5000")
Error: postgres: connection to server at "db" (172.20.0.2), port 5432 failed: Connection refused
Cause: The web container tries to connect to the DB before it's fully ready. Solution:
- Ensure
dbhas ahealthcheck(as in the example) - Add waiting to the
webcommand (e.g., usingwait-for-it.shordockerize) - Or extend
depends_onwith acondition: service_healthycondition
Error: permission denied while trying to connect to the Docker daemon socket
Cause: The current user is not in the docker group.
Solution:
sudo usermod -aG docker $USER
newgrp docker
Or use sudo for Docker commands.
Volume postgres_data is not created
Cause: Syntax error in volumes or insufficient permissions.
Solution:
- Check YAML indentation (spaces, not tabs)
- Ensure the volume path
/var/lib/postgresql/dataexists in the PostgreSQL image - Check
docker volume ls— the volume should be named[directory]_postgres_data
Images fail to download (network errors)
Cause: Problems accessing Docker Hub. Solution:
- Configure DNS in Docker (
/etc/docker/daemon.json) - Use a mirror (e.g.,
registry-mirrorsfor Kubernetes/OpenShift) - Or manually pull images:
docker pull python:3.11-slim