๐Ÿงฉ Docker Compose โ€“ Full DevOps Power



This content originally appeared on DEV Community and was authored by Darshan Vasani

๐Ÿงฉ 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 local Dockerfile
  • 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


This content originally appeared on DEV Community and was authored by Darshan Vasani