π³ Everything About Docker Compose β Simplify Your Multi-Container Development

In modern app development, it's rare to run just a single container. A typical full-stack project might need a backend, a frontend, a database, a cache mechanism, etc β all running together. Managing these containers manually with separate docker run commands quickly becomes complex and error-prone.
Thatβs where Docker Compose shines.
With Docker Compose, you can define and run multi-container applications with a single YAML file. It lets you spin up all services in one go, making development, testing, and deployment smooth and reproducible.
In this blog, you'll learn:
β What Docker Compose is and why it's useful
β
How to write and understand a docker-compose.yml file
β Real-world use cases and best practices
β A hands-on full-stack example using Node.js, React, MongoDB, and Redis
β
Key differences between Dockerfile and docker-compose.yml
Whether you're just getting started with containers or looking to streamline your workflow, this guide will help you master Docker Compose from the ground up.
π What is Docker Compose?
Docker Compose is a tool that allows you to define and run multi-container Docker applications. With Compose, you use a YAML file (docker-compose.yml) to configure your application's services (e.g., backend, frontend, database), and then start all of them with a single command:
docker-compose up
βοΈ In short:
It orchestrates multiple containers as services.
Itβs part of the Docker CLI and installed by default with Docker Desktop.
Great for local development, testing, and CI pipelines.
π§ Why and Where Do We Use Docker Compose?
You should use Docker Compose when:
You have multiple containers (like Node.js backend, React frontend, MongoDB) that need to work together.
You want a repeatable, version-controlled environment setup.
Youβre developing microservices or need isolated dev environments.
Youβre working on CI/CD pipelines or want to automate container startup.
π How to Write docker-compose.yml File β With Syntax & Explanation
Docker Compose uses a YAML file to define, configure, and run multi-container Docker applications. The file is commonly named docker-compose.yml.
Letβs break down the file syntax and all important keywords with explanation.
β Basic Syntax
version: "3.8" # version is optional in Compose v3.9+ as Docker automatically uses the latest schema.
services:
service_name:
image: image_name
build: path_or_options
ports:
- "host_port:container_port"
environment:
- KEY=value
volumes:
- host_path:container_path
depends_on:
- another_service
π Top-Level Keywords
| Keyword | Type | Description |
version | string | Compose file format version (3.8 is recommended for latest compatibility) |
services | map | Defines all the containers (services) you want to run |
volumes | map | Named volumes for data persistence |
networks | map | Custom networks (optional; Compose uses default if not specified) |
π§© services β All Your Containers
Each service under services: defines a separate container.
Common Service Options:
| Option | Type | Description |
build | string/map | Path to Dockerfile or build options |
image | string | Use a prebuilt image from Docker Hub or local |
container_name | string | Optional custom container name |
ports | list | Map ports from host to container (host:container) |
volumes | list | Mount files/dirs from host or named volume |
environment | list/map | Environment variables for the container |
env_file | list/string | Load environment variables from file |
depends_on | list | Wait for listed services to start first |
command | string/list | Override default command (CMD) of the image |
restart | string | Restart policy (no, on-failure, always, unless-stopped) |
π Detailed Example
version: "3.8"
services:
app:
build: ./app
container_name: my-app
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- API_KEY=abc123
volumes:
- .:/app
depends_on:
- db
- redis
restart: unless-stopped
db:
image: mongo:6
container_name: my-db
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
redis:
image: redis
container_name: my-redis
ports:
- "6379:6379"
volumes:
mongo-data:
π¬ Explanation of All Commands
version: "3.8" (Deprecated)
- Specifies the version of the Docker Compose file format. Use
"3.8"for modern compatibility.
services:
- Group of all containers your app needs.
build: ./app
- Tells Docker to build an image using the Dockerfile in the
./appdirectory.
image: mongo:6
- Pulls the
mongoimage with tag6from Docker Hub.
container_name: my-app
- Optional. Gives your container a fixed name instead of a random one.
ports:
ports:
- "3000:3000"
Maps port 3000 on the host to port 3000 in the container.
Syntax:
"HOST_PORT:CONTAINER_PORT"
environment: or env_file:
environment:
- NODE_ENV=production
- API_KEY=xyz123
- Sets environment variables inside the container.
Or use:
env_file: .env
To load from a file.
volumes:
volumes:
- ./data:/app/data
Mounts
./datafrom the host into/app/datain the container.Useful for persistent storage or live-reloading code in dev mode.
Also at the bottom:
volumes:
mongo-data:
- Defines a named volume for MongoDB data.
depends_on:
depends_on:
- db
- redis
- Ensures that
dbandredisstart before theapp.
β οΈ Note: This doesn't wait for services to be ready β only that they start.
restart: unless-stopped
Automatically restarts containers unless explicitly stopped.
Other options:
noβ never restart (default)alwaysβ always restarton-failureβ restart only on non-zero exit codes
command:
command: ["npm", "run", "dev"]
- Overrides the default CMD in Dockerfile.
π§ͺ Useful Docker Compose Commands
- Start Services: Run the services defined in the
docker-compose.ymlfile.
docker-compose up -d
This command starts all the services defined in the docker-compose.yml file in detached mode (-d). If you want to run the services in the foreground, you can omit the -d flag:
docker-compose up
- Stop Services: Stop the running services defined in the
docker-compose.ymlfile.
docker-compose down
This command stops and removes all the containers defined in the docker-compose.yml file, along with their networks and volumes. If you want to remove the volumes as well, you can use the -v flag:
docker-compose down -v
- Build services: Build the images for the services defined in the
docker-compose.ymlfile.
docker-compose build
This command builds the images for the services defined in the docker-compose.yml file. If you have made changes to the Dockerfiles or the context, this command will rebuild the images accordingly.
- List running services: View the status of the services defined in the
docker-compose.ymlfile.
docker-compose ps
This command lists all the services defined in the docker-compose.yml file along with their current status (running, exited, etc.). It provides a quick overview of the state of your multi-container application.
- Execute command in a container: Run a command inside a specific service container.
docker-compose exec <service_name> <command>
For example, to open a shell in the web service container:
docker-compose exec web /bin/bash
This command allows you to run a command inside a specific service container defined in the docker-compose.yml file. The exec command is useful for debugging or performing administrative tasks directly within the container. You can replace <service_name> with the name of the service you want to access, and <command> with the command you want to run inside that service's container. If you want to run an interactive shell, you can use -it flags:
docker-compose exec -it <service_name> /bin/bash
π§― Clean Up
To stop and remove all containers, networks, and volumes created by Compose:
docker-compose down
To remove volumes too:
docker-compose down -v
ποΈ Hands-On Example: Full-Stack App with Docker Compose
Letβs build a complete end-to-end Dockerized application using:
Node.js backend (Express)
React frontend (Vite)
MongoDB database
Redis cache
We'll use Dockerfile for backend/frontend builds and Docker Compose to orchestrate all services. This example will explain every command and configuration step-by-step.
ποΈ Project Structure
my-fullstack-app/
βββ backend/
β βββ Dockerfile
β βββ index.js
β βββ package.json
βββ frontend/
β βββ Dockerfile
β βββ vite.config.js
β βββ src/
β β βββ App.jsx
β βββ package.json
βββ docker-compose.yml
βββ .env
π§ Step-by-Step Setup
1οΈβ£ Backend β Node.js + Express + Redis + MongoDB
backend/index.js
const express = require('express');
const mongoose = require('mongoose');
const redis = require('redis');
const app = express();
app.use(express.json());
const mongoURL = process.env.MONGO_URL || 'mongodb://localhost:27017/mydb';
const redisHost = process.env.REDIS_HOST || 'localhost';
mongoose.connect(mongoURL)
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error(err));
const client = redis.createClient({ url: `redis://${redisHost}:6379` });
client.connect().then(() => console.log('Connected to Redis'));
app.get('/', async (req, res) => {
await client.set('message', 'Hello from Redis!');
const msg = await client.get('message');
res.json({ msg });
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Backend running on port ${PORT}`));
backend/package.json
{
"name": "backend",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.0.0",
"redis": "^4.6.7"
}
}
backend/Dockerfile
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 5000
CMD ["npm", "start"]
2οΈβ£ Frontend β React + Vite
frontend/src/App.jsx
import { useEffect, useState } from 'react';
function App() {
const [message, setMessage] = useState('');
useEffect(() => {
fetch('http://localhost:5000/')
.then(res => res.json())
.then(data => setMessage(data.msg));
}, []);
return (
<div>
<h1>Frontend + Backend + Redis</h1>
<p>Message from backend: {message}</p>
</div>
);
}
export default App;
frontend/package.json
{
"name": "frontend",
"version": "1.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"vite": "^4.5.0",
"@vitejs/plugin-react": "^4.0.0"
}
}
frontend/Dockerfile
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
3οΈβ£ Docker Compose β Tying It All Together
docker-compose.yml
version: "3.8"
services:
backend:
build: ./backend
ports:
- "5000:5000"
environment:
- MONGO_URL=mongodb://mongo:27017/mydb
- REDIS_HOST=redis
depends_on:
- mongo
- redis
frontend:
build: ./frontend
ports:
- "3000:80"
depends_on:
- backend
mongo:
image: mongo
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
redis:
image: redis
ports:
- "6379:6379"
volumes:
mongo-data:
4οΈβ£ .env (Optional)
If you want to externalize environment variables:
.env
MONGO_URL=mongodb://mongo:27017/mydb
REDIS_HOST=redis
Then in docker-compose.yml, use:
env_file:
- .env
5οΈβ£ How to Run
π One-time setup:
docker-compose up --build
π§Ή To stop and clean up:
docker-compose down -v
π Access the App
Frontend: http://localhost:3000
Backend API: http://localhost:5000
MongoDB: runs on port 27017
Redis: runs on port 6379
π Difference Between Dockerfile and docker-compose.yml
Dockerfile is used to build a Docker image. It contains a set of instructions (like
FROM,RUN,COPY,CMD, etc.) to define how an image should be created β e.g., installing dependencies, copying code, and setting up the environment.docker-compose.yml is used to run and manage multiple containers together as services. It defines how to run containers, configure networks, set environment variables, link services like databases, and expose ports β all in a single YAML file.
π In Short:
| Feature | Dockerfile | docker-compose.yml |
| Purpose | Defines how to build an image | Defines how to run containers |
| Scope | Single container image | Multi-container application setup |
| Used By | docker build, docker run | docker-compose up, docker-compose down |
| File Format | Dockerfile instructions | YAML configuration |
| Example Use | Build a Node.js image | Run Node.js app with MongoDB and Redis |
π§ͺ Bonus Tips
Use
depends_onto set container startup order.Use
.envfile to manage secrets and ports.Use
volumesfor persistent data (like databases).Add
restart: alwaysfor production-like resiliency.
π Conclusion
Docker Compose is a game-changer for local development and multi-container apps. Instead of running multiple docker run commands, Compose gives you a single declarative file to define and run everything β clean, consistent, and reproducible.
Whether you're building full-stack apps, microservices, or dev environments, mastering Docker Compose will save you time and boost your productivity.
π Whatβs Next?
You might want to explore:
π Additional Resources
βοΈ Authorβs Note
This blog is a comprehensive guide to Docker Compose, but the best way to learn is by doing! Try building your own multi-container app using the examples provided. If you have any questions or suggestions, feel free to reach out.
π¬ Have Questions or Suggestions?
Drop a comment below or connect with me on LinkedIn or GitHub. Letβs make apps faster together! π






