๐งฉ Docker Compose โ Full DevOps Power
๐ What is Docker Compose?
Docker Compose is a tool for defining and managing multi-container Docker applications using a simple YAML file.
Instead of running docker run
commands multiple times, Compose lets you:
โ
Define containers, networks, volumes, and environment variables
โ
Start everything with docker compose up
โ
Manage dependencies, ports, and data easily
โ๏ธ Basic Syntax of docker-compose.yml
version: "3.9" # Compose file version services: # Define all containers here service1: # A named service (container) image: nginx # Use this image ports: - "8080:80" # host:container
๐ง Why Use Docker Compose?
Feature ๐ก | Why It Helps ๐ |
---|---|
๐งฑ Declarative Setup | All infra defined in one YAML file |
๐ Built-in Networking | Services can talk by name (like DNS) |
๐งณ Volume Integration | Persistent data made easy |
๐ Auto-Dependency Mgmt | Start db before app, cache, etc. |
๐ฅ Dev & Prod Configs | Easy environment switching |
๐ Networking in Docker Compose
โ Auto Network Creation:
All services in a Compose file automatically share a custom bridge network with internal DNS!
Service Name | Container DNS Name |
---|---|
db | db |
redis | redis |
backend | backend |
๐ง You can ping other containers by service name.
๐ Example:
services: web: build: . ports: - "3000:3000" api: image: node:alpine depends_on: - web
In api
, you can make requests like:
fetch("http://web:3000")
๐๏ธ Volumes in Docker Compose
โ Define persistent data storage:
volumes: mydata:
Then attach to a service:
services: db: image: postgres volumes: - mydata:/var/lib/postgresql/data
๐ฅ Compose creates and manages these volumes for you!
๐ง Understanding Docker Compose Networking + Port Mapping ๐
๐๏ธ Sample docker-compose.yml
Setup:
Your project folder is:
๐ myapp/ โโโ docker-compose.yml
๐ง Compose File:
services: web: build: . ports: - "8000:8000" db: image: postgres ports: - "8001:5432"
๐ What Happens When You Run:
docker compose up
โ Docker Compose Automatically Does:
๐ง Action | ๐ฌ What Happens |
---|---|
๐งฑ Creates a network | Named myapp_default (based on folder name) |
๐ฆ Launches web container | Joins myapp_default as web |
๐ฆ Launches db container | Joins myapp_default as db |
๐ง Enables DNS lookup | web can reach db by hostname db |
๐ Internal Networking (Container โ Container)
โ
Inside the web
container, your app can connect to the database like this:
postgres://db:5432
๐ง Why? Because:
- Docker provides internal DNS to resolve service names
- The port
5432
is the internal (container) port, exposed by the Postgres container
๐ External Networking (Host โ Container)
Youโve mapped container ports to host ports like this:
web: ports: - "8000:8000" # Host: 8000 โ Container: 8000 db: ports: - "8001:5432" # Host: 8001 โ Container: 5432
So from your host machine, you can:
- Access web on ๐
http://localhost:8000
- Connect to Postgres on ๐
postgres://localhost:8001
๐ง Important Concept: HOST_PORT:CONTAINER_PORT
Concept | Example | Meaning |
---|---|---|
HOST_PORT | 8001 | Port on your machine |
CONTAINER_PORT | 5432 | Port inside the container |
Mapping | 8001:5432 | Requests to localhost:8001 go to Postgres in container on port 5432 |
๐งฉ Real World Analogy
๐งณ Think of containers as hotel rooms.
- Each has its own room number (container port)
- The front desk (your host machine) assigns a guest-access number (host port)
So:
-
5432
= actual DB server inside room -
8001
= external phone number to reach that room from outside
๐ Internal vs External Communication Recap
Context | URL Format | Who uses it? |
---|---|---|
๐ Container-to-container | postgres://db:5432 | Inside Docker network |
๐ Host-to-container | postgres://localhost:8001 | From your laptop / browser |
โ
Internal comms use service names + container port
โ
External comms use localhost + host port
๐ Final Notes
- Docker Compose auto-creates a network (unless you override it)
- You donโt need to expose ports unless you want outside access
- Use internal ports (
CONTAINER_PORT
) when services talk to each other - Expose only the ports you need to keep things secure ๐
๐งช Bonus Tip: Inspect the Compose Network
docker network inspect myapp_default
This will show:
- Containers in the network
- Their IPs
- Connection metadata
๐ Docker Compose Advanced Networking โ A Complete Guide
๐ 1. Multi-Host Networking via Overlay (Swarm Mode)
๐ก Overlay networking allows containers on different Docker hosts to communicate โ as if they were on the same network!
๐ Use Case:
โ
Deploying a multi-host microservice system
โ
Need backend containers on host A to talk to database on host B
๐ ๏ธ How It Works:
- Requires Swarm mode (
docker swarm init
) - Docker uses overlay driver to create a virtual network across machines
- Compose uses this with no special setup, if Swarm mode is active
๐งช Example:
networks: my_overlay: driver: overlay
Then attach to services:
services: web: networks: - my_overlay db: networks: - my_overlay
โ Compose will automatically connect services to the multi-host overlay network
๐ง Overlay vs Bridge (Single Host Only)
Feature | bridge (default) | overlay (Swarm) |
---|---|---|
Host Limit | 1 machine | Multiple machines |
Use case | Local dev | Distributed apps |
DNS-based discovery | โ Yes | โ Yes |
Needs Swarm mode? | โ No | โ Yes |
๐งฑ 2. Custom Networks in Compose
Youโre not limited to just the default network. You can define and connect containers to specific networks using the networks:
key.
๐งช Example Topology
services: proxy: build: ./proxy networks: - frontend app: build: ./app networks: - frontend - backend db: image: postgres networks: - backend
๐งญ Network Layout
frontend network: - proxy - app backend network: - app - db proxy ๐ app ๐ db
โ
proxy
can't see db
, but app
can talk to both
โ
Great for enforcing security boundaries ๐
๐ 3. Custom Network Drivers & Options
networks: frontend: driver: bridge driver_opts: com.docker.network.bridge.host_binding_ipv4: "127.0.0.1" backend: driver: custom-driver
-
bridge
: Standard Docker single-host network -
custom-driver
: Plug in advanced/third-party networking solutions (e.g., macvlan, overlay, weave)
๐ 4. Rename Docker Network (Custom Name)
networks: frontend: name: custom_frontend driver: bridge
๐ Instead of projectname_frontend
, Docker will create custom_frontend
.
โ Useful in CI/CD or pre-defined environments.
๐งฎ 5. Assigning Static IPs in Compose
Use ipv4_address
under the service's network attachment.
services: web: networks: app_net: ipv4_address: 172.28.0.4 networks: app_net: driver: bridge ipam: config: - subnet: 172.28.0.0/16
โ Be careful โ misconfiguring subnets or overlapping IPs can break your network.
โ๏ธ 6. Customize the default
Network
You can override the default Compose-generated network like this:
services: web: build: . ports: - "8000:8000" networks: default: driver: custom-driver-1
โ Still lets you use default behavior, but with custom driver/settings.
๐ 7. Use a Pre-Existing Docker Network
Want to connect to a network created outside Compose? Use external: true
networks: network1: name: my-pre-existing-network external: true
๐ฏ Compose will connect to the existing network, not recreate it.
๐ Helpful when:
- Using shared infra (like reverse proxies)
- Reusing CI/CD networking
- Linking across Compose projects
๐งพ Final Cheatsheet
Feature | Keyword | Description |
---|---|---|
Multi-host networking | overlay driver | Works with Swarm |
Isolated networks | networks: per service | Scoped communication |
Custom drivers | driver: bridge | Control behavior |
Rename network | name: | Use meaningful names |
Static IPs | ipv4_address | Predictable networking |
Pre-existing networks | external: true | Connect to outside network |
Customize default | networks: default | Override auto network |
๐ง Final Takeaways
โ
Compose makes networking declarative, secure, and powerful
โ
Use custom networks to enforce boundaries and structure
โ
Use overlay for multi-host magic
โ
Use external to plug into existing infra
โ
DNS-based discovery simplifies microservice URLs
๐๏ธ Custom Docker Builds in Compose
Use your own Dockerfile
with build context:
backend: build: context: . # current folder dockerfile: Dockerfile # name of your Dockerfile ports: - "8000:8000"
๐ง Docker Compose will build the image before running the service.
๐งช Compose vs Manual Docker Commands
Task | Docker CLI | Docker Compose |
---|---|---|
Start container | docker run | docker compose up |
Stop container | docker stop | docker compose down |
Rebuild image | docker build | docker compose build |
View logs | docker logs | docker compose logs |
๐งฑ Full Project Example: E-Commerce Stack
# ๐ฆ Project Name name: e-commerce services: # ๐ง Backend Service backend: build: context: . # Use current directory as build context dockerfile: Dockerfile # Dockerfile to build the backend image container_name: backend # Name of the backend container ports: - "8000:8000" # Map host:container port depends_on: - db # Start db first - redis # Start redis first # ๐๏ธ PostgreSQL Database Service db: image: postgres:16 # Latest stable PostgreSQL container_name: postgres environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: postgres volumes: - postgres_data:/var/lib/postgresql/data # โก Redis In-Memory Data Store redis: image: redis:7-alpine # Lightweight Redis image container_name: redis volumes: - redis_data:/data # ๐๏ธ Named Volumes for Persistent Storage volumes: postgres_data: redis_data:
๐ฅ How It Works:
-
backend
is built from the localDockerfile
-
db
&redis
use official images - Volumes persist data between restarts
- All services can talk to each other via names (
backend
,db
,redis
)
โ Start Everything:
docker compose up --build
โ Stop & Clean Everything:
docker compose down -v
๐งฐ Pro Tips
Tip ๐ก | Description |
---|---|
depends_on | Control boot order of containers (not readiness!) |
volumes: | Use named volumes for clean reuse & backups |
env_file: | Load .env for cleaner configuration |
profiles: | Enable or disable services dynamically |
networks: | Customize default networks if needed |
๐ Final Summary
Feature | Compose Benefit ๐ |
---|---|
Networking | ๐ Name-based access between containers |
Volumes | ๐ฆ Persistent storage, managed cleanly |
Builds | ๐ ๏ธ Custom image building from source |
Automation | ๐ง Multi-container orchestration made easy |
Reusability | โป๏ธ YAML can be reused across environments |
๐งฉ Example: Internal-only Docker Compose Network (No Exposed Ports) ๐ณ
โ Scenario:
Youโre building a simple backend + database + Redis stack that:
- Does not need to be accessed from the host/browser
- Only needs container-to-container communication
- Should remain internal and secure (no
ports:
exposed)
๐ฆ docker-compose.yml
(No Ports Exposed)
version: "3.9" services: # ๐ง Backend API backend: build: context: . container_name: backend depends_on: - db - redis environment: DB_HOST: db REDIS_HOST: redis # ๐๏ธ PostgreSQL Database db: image: postgres:16 container_name: postgres environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: postgres volumes: - postgres_data:/var/lib/postgresql/data # โก Redis Cache redis: image: redis:7-alpine container_name: redis volumes: - redis_data:/data volumes: postgres_data: redis_data:
๐ง Key Points
๐ก Concept | โ Benefit |
---|---|
โ No ports: field | Nothing is exposed to host machine |
โ Internal network | Docker Compose auto-creates a shared bridge |
๐ DNS by service name | backend can talk to db , redis via name |
๐ Security | Fully isolated, no public entry points |
๐ฌ How Services Communicate
Inside backend
container, you can do:
// Node.js example (env used above) const pg = require('pg'); const redis = require('redis'); const db = new pg.Client({ host: process.env.DB_HOST, // "db" user: 'postgres', password: 'postgres', database: 'postgres' }); const redisClient = redis.createClient({ url: 'redis://redis:6379' });
โ Works seamlessly, because Docker Compose provides built-in DNS resolution for service names.
๐ Run the Stack:
docker compose up --build
๐ Nothing exposed outside, but all services talk inside.
Want to debug? Use:
docker exec -it backend sh
๐งช When to Use This Pattern
Use Case | Why it Fits |
---|---|
๐งฑ Internal APIs/microservices | No external access needed |
๐ท Workers/CRON jobs | Runs in background only |
๐ Security-sensitive apps | Reduce attack surface |
๐งช Local-only testing | Don't expose unnecessary ports |
๐ Final Tip
If you later need external access (e.g., for testing):
Just add a single port to the backend:
ports: - "8000:8000"
But for private, secure, container-to-container apps โ no ports is cleanest. โ
๐ ๏ธ Docker Compose โ Custom Docker Builds (Full Guide) ๐ณ
๐งฉ Overview
Docker Compose allows two primary ways to set up containers:
Method | Keyword | Use Case |
---|---|---|
๐ ๏ธ Build locally from Dockerfile | build: | During development |
โ๏ธ Pull prebuilt image | image: | CI/CD & production |
โ
1. Local Dockerfile Build โ Using build:
When you want to build your Docker image directly from your source code:
services: backend: build: context: . # ๐ Location of source code (root of app) dockerfile: Dockerfile # ๐ File to use (default is 'Dockerfile') ports: - "8000:8000" container_name: backend
๐ Folder Structure:
myapp/ โโโ backend/ โ โโโ Dockerfile โ โโโ server.js โ โโโ package.json โโโ docker-compose.yml
๐ง Explanation:
Field | Meaning |
---|---|
context: | Folder Docker will send to the daemon for the build |
dockerfile: | Custom name or path to Dockerfile |
build: | Triggers a local build when you run docker compose up --build |
๐ You donโt push/pull โ just edit โ build โ run locally:
docker compose up --build
โ๏ธ 2. Pull Image from Registry โ Using image:
In production or CI/CD, itโs better to pull prebuilt, versioned images from Docker Hub or GitHub Container Registry.
services: backend: image: dpvasani56/myapp-backend:v1.0.3 ports: - "8000:8000"
โ Works only if you've pushed this image earlier:
docker build -t dpvasani56/myapp-backend:v1.0.3 . docker push dpvasani56/myapp-backend:v1.0.3
๐ง This is great when:
- You want reproducible builds โ
- You deploy to servers that donโt have your source code โ
- You want fast CI/CD โ
๐ Both Build & Image (Hybrid)
services: backend: image: dpvasani56/myapp-backend:latest build: context: ./backend dockerfile: Dockerfile
๐ก In this case:
- Local build happens first
- Image is tagged and stored locally as
dpvasani56/myapp-backend:latest
- Useful for building locally but keeping consistent image tags
๐งช Real Example: Backend + Frontend + DB
services: backend: build: context: ./backend dockerfile: Dockerfile image: dpvasani56/app-backend:dev ports: - "8000:8000" frontend: image: dpvasani56/app-frontend:latest # Pulled from Docker Hub db: image: postgres:16 volumes: - pgdata:/var/lib/postgresql/data volumes: pgdata:
๐ค Push/Deploy Workflow
Step | Action |
---|---|
1๏ธโฃ | Build locally: docker build -t dpvasani56/app-backend:dev . |
2๏ธโฃ | Push to registry: docker push dpvasani56/app-backend:dev |
3๏ธโฃ | On server: docker compose pull && docker compose up -d |
โ
Easy deployment
โ
No source code leak
โ
Fast start time
๐งฑ Custom Dockerfile Path or Name
build: context: ./src dockerfile: Dockerfile.prod
๐ You can place your Dockerfile anywhere and name it however you want.
๐ง Best Practices
Stage | Use build: | Use image: |
---|---|---|
Development ๐จโ๐ป | โ Yes | โ Optional |
Production ๐ | โ Avoid | โ Required |
CI/CD ๐งช | โ Build & Push | โ Pull |
Collaboration ๐งโ๐คโ๐ง | โ Share Compose file | โ Share tagged image |
๐๏ธ Full Compose File with Both Options
version: "3.9" services: backend: build: context: ./backend dockerfile: Dockerfile image: dpvasani56/my-backend:latest ports: - "8000:8000" frontend: image: dpvasani56/my-frontend:latest ports: - "3000:3000" postgres: image: postgres:16 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password volumes: - pgdata:/var/lib/postgresql/data volumes: pgdata:
๐งพ Summary Cheatsheet
Field | Use When? | Notes |
---|---|---|
build: | You have Dockerfile locally | Great for dev |
image: | You push/pull from registry | Ideal for prod |
build + image | Build locally with version tag | Best of both worlds |
context | Define source code directory | Defaults to . |
dockerfile | Use custom name/location | Optional |
Top comments (0)