Yesterday, I had two containers: one for my backend, one for my frontend.
But running them meant two terminals, two docker run commands, and remembering which port mapped to what.
Today, I fixed that forever.
I learned Docker Compose a tool that lets me define my entire application stack in one file and start everything with a SINGLE command.
First: What Problem Does Docker Compose Solve?
Without Docker Compose (Days 5 & 6)
# Terminal 1
cd backend
sudo docker build -t backend-app .
sudo docker run -p 3001:5000 backend-app
# Terminal 2 (new terminal)
cd frontend
npm run build
sudo docker build -t frontend-app .
sudo docker run -p 8080:80 frontend-app
The pain:
- Multiple terminals
- Many commands to remember
- Containers don't automatically know about each other
- Hard to share setup with others
With Docker Compose (Today)
# One terminal. One command.
sudo docker-compose up --build
That's it. Everything runs.
What is Docker Compose?
In one sentence: Docker Compose is a tool that lets you define and run multiple Docker containers together using a single YAML file.
The Analogy
| Concept | Docker Alone | Docker Compose |
|---|---|---|
| Individual containers | Shipping containers | Shipping containers |
| Managing them | Moving each by hand | A crane operator with a manifest |
| Documentation | Remember what goes where | Everything in one file |
Docker Compose is the crane operator + manifest that organizes all your containers at once.
Step 1: Creating docker-compose.yml
First, I went to my project root:
cd ~/Desktop/Production-Ready-Microservices-Platform
Then I created the Compose file:
touch docker-compose.yml
nano docker-compose.yml
Step 2: Writing the Compose File (Line by Line)
Line 1: Version
version: '3.8'
What this means: Which version of Docker Compose syntax to use (3.8 is current).
Line 2: Services Section
services:
What this means: Everything below defines containers (called "services").
Lines 3-6: Backend Service
backend:
build: ./backend
ports:
- "3001:5000"
| Line | Meaning |
|---|---|
backend: |
Name of the service |
build: ./backend |
Build image using Dockerfile in ./backend
|
ports: |
Define port mapping |
- "3001:5000" |
Map host 3001 → container 5000 |
Lines 7-10: Frontend Service
frontend:
build: ./frontend
ports:
- "8080:80"
Same pattern, but for the frontend (port 8080 → 80).
The Complete File
version: '3.8'
services:
backend:
build: ./backend
ports:
- "3001:5000"
frontend:
build: ./frontend
ports:
- "8080:80"
That's it! 12 lines to define my entire application.
Step 3: What Docker Compose Does Behind the Scenes
When I ran docker-compose up --build, Compose did ALL of this automatically:
# 1. Created a shared network
docker network create microservices-platform_default
# 2. Built backend image
docker build -t microservices-platform-backend ./backend
# 3. Built frontend image
docker build -t microservices-platform-frontend ./frontend
# 4. Started backend container
docker run -d \
--name microservices-platform-backend-1 \
--network microservices-platform_default \
-p 3001:5000 \
microservices-platform-backend
# 5. Started frontend container
docker run -d \
--name microservices-platform-frontend-1 \
--network microservices-platform_default \
-p 8080:80 \
microservices-platform-frontend
All of that from one command. That's the magic of Docker Compose.
Step 4: Running Everything
sudo docker-compose up --build
What I saw:
[+] Building 2.5s (15/15) FINISHED
[+] Running 3/3
✔ backend Built
✔ frontend Built
✔ Network microservices-platform_default Created
Then the containers started:
frontend-1 | /docker-entrypoint.sh: Configuration complete; ready for start up
backend-1 | Server is running on http://localhost:5000
backend-1 | Try these endpoints:
backend-1 | - http://localhost:5000/
backend-1 | - http://localhost:5000/health
backend-1 | - http://localhost:5000/users
Both containers running simultaneously in ONE terminal!
Step 5: Testing Everything
Testing the Backend
In a new terminal:
curl http://localhost:3001/health
Response:
{"status":"healthy","service":"backend-api","timestamp":"2026-04-12T13:10:49.057Z"}
Testing the Frontend
In my browser: http://localhost:8080
My React app appeared! Same as Day 3, but now running inside a container managed by Compose.
Step 6: Stopping Everything
To stop all containers:
# In the Compose terminal, press Ctrl+C
Ctrl+C
# Then clean up
sudo docker-compose down
One command stops EVERYTHING. No more hunting for container IDs.
Docker vs Docker Compose: The Comparison
| Task | Docker Alone | Docker Compose |
|---|---|---|
| Run one container | docker run ... |
Overkill |
| Run multiple containers | Multiple commands | docker-compose up |
| Connect containers | Manual network setup | Automatic network |
| Start order | Manual (wait, then start) | depends_on |
| Environment variables |
-e flag each time |
In file |
| Port mapping |
-p flag each time |
In file |
| Share setup | Send commands to friend | Send one file |
| Rebuild after changes | Manual rebuild |
--build flag |
| View logs | docker logs ID |
docker-compose logs |
Why Your Project NEEDS Docker Compose
Your Architecture
User → Frontend (React) → Backend (Node.js) → (Soon: PostgreSQL)
Each component has different needs:
| Need | Frontend | Backend | Database (coming) |
|---|---|---|---|
| Base image | nginx:alpine | node:18-alpine | postgres:15 |
| Port | 80 | 5000 | 5432 |
| Storage | None | None | Persistent volume |
| Start order | Last | Middle | First |
| Scale for | User traffic | API calls | Data growth |
Without Compose (Manual Nightmare)
# Start database
docker run -d --name postgres -v postgres_data:/var/lib/postgresql/data postgres:15
# Wait for database
sleep 5
# Start backend
docker run -d --name backend --link postgres -e DB_HOST=postgres backend-app
# Start frontend
docker run -d --name frontend -p 8080:80 frontend-app
# Create network
docker network create myapp
docker network connect myapp postgres backend frontend
# To stop: 4 separate commands
15+ commands! One mistake and nothing works.
With Compose (One File)
version: '3.8'
services:
postgres:
image: postgres:15
volumes:
- postgres_data:/var/lib/postgresql/data
backend:
build: ./backend
depends_on:
- postgres
environment:
- DB_HOST=postgres
frontend:
build: ./frontend
# ONE command
docker-compose up --build
3 commands total. Perfect every time.
Issues I Ran Into
Issue 1: "no configuration file provided"
Error:
no configuration file provided: not found
Cause: I was in the wrong directory.
Fix:
cd ~/Desktop/Production-Ready-Microservices-Platform
ls -la docker-compose.yml # Verify file exists
Issue 2: Port already allocated
Error:
Bind for 0.0.0.0:8080 failed: port is already allocated
Cause: I still had my old frontend container running from Day 6.
Fix:
sudo docker ps # Find running containers
sudo docker stop <container_id> # Stop the old one
sudo docker-compose up # Now it works
Issue 3: The "version is obsolete" warning
Warning:
the attribute `version` is obsolete, it will be ignored
Fix: I can remove the version: '3.8' line — newer Docker versions don't need it.
Docker Compose Commands Cheat Sheet
| Command | What it does |
|---|---|
docker-compose up |
Start all services |
docker-compose up -d |
Start in background (detached) |
docker-compose up --build |
Rebuild images then start |
docker-compose down |
Stop and remove containers |
docker-compose down -v |
Also remove volumes (databases!) |
docker-compose ps |
Show status of services |
docker-compose logs |
View logs from all services |
docker-compose logs backend |
View logs from backend only |
docker-compose build |
Rebuild images without starting |
docker-compose restart |
Restart all services |
Key Takeaways
- Docker Compose = Multiple containers, one command — No more terminal juggling
- Everything in one file — Share your setup with anyone
- Automatic networking — Containers can find each other by name
- Declarative configuration — Say WHAT you want, not HOW
- Local = Production — Same Compose file works everywhere
Top comments (0)