Networking and Volumes in Docker Compose

Understanding how containers communicate and persist data in multi-container applications

The Foundation of Multi-container Applications

Networking and volumes are the two fundamental pillars that make Docker containers truly useful in real-world applications. Without networking, containers would be isolated silos unable to communicate. Without volumes, containers would lose all their data when restarted. Understanding these concepts is crucial for building robust, production-ready applications with Docker Compose.

The City Utility Analogy

Think of Docker networking and volumes like a city's utility systems:

  • Networks are like the road system connecting buildings (containers) together. Some roads are private (internal networks), while others connect to highways (external networks).
  • Volumes are like the water and power utilities. The infrastructure (pipes, wires) persists even when buildings are renovated or rebuilt. Your data flows through these permanent systems, ensuring continuity despite changes to the containers.

Just as a city would fail without its utility infrastructure, a containerized application ecosystem needs properly designed networking and storage to function effectively.

Container Ecosystem Components

flowchart TD A[Application Ecosystem] --- B[Containers] A --- C[Networks] A --- D[Volumes] B --- B1[Isolated Processes] B --- B2[Filesystem] B --- B3[Application Code] C --- C1[Service Discovery] C --- C2[Container Communication] C --- C3[Network Isolation] D --- D1[Data Persistence] D --- D2[Configuration Files] D --- D3[Shared Resources]

Docker Networking Fundamentals

Docker provides a powerful networking system that enables containers to communicate with each other and with the outside world. Docker Compose builds on this to simplify networking in multi-container applications.

Docker Network Drivers

Docker supports several network drivers, each with different capabilities:

Bridge Network Default network driver Containers on same host Internal private network Usage: Most applications Host Network No network isolation Uses host's network stack Better performance Usage: High performance Overlay Network Multi-host communication Swarm mode services Encrypted communication Usage: Cluster deployments None Network No external connectivity Complete network isolation Container has only loopback Usage: Maximum security Macvlan Network Assigns MAC address Container appears as device Direct on physical network Usage: Network appliances Custom Network Third-party plugins Specialized networking Cloud integrations Usage: Advanced scenarios

Docker Compose Networking

Docker Compose simplifies networking by automatically creating a default network for your application and connecting all services to it.

graph TD subgraph "Default Network" WebApp[Web App Container] API[API Container] Database[Database Container] end WebApp <--> API API <--> Database Client[External Client] --> WebApp Client --> API

By default, every container can reach any other container within the same Docker Compose application by its service name.

Networking in Docker Compose

Default Networking Behavior

When you run docker-compose up, the following happens:

version: '3.8'

services:
  web:
    image: nginx
    ports:
      - "8080:80"  # Accessible from host at port 8080
      
  api:
    build: ./api
    ports:
      - "3000:3000"  # Accessible from host at port 3000
      
  database:
    image: postgres
    # No ports exposed to host, but accessible to other containers

In this example:

Custom Networks

You can define custom networks for more complex scenarios:

version: '3.8'

services:
  web:
    image: nginx
    networks:
      - frontend
      
  api:
    build: ./api
    networks:
      - frontend
      - backend
      
  database:
    image: postgres
    networks:
      - backend

networks:
  frontend:
    # Configuration for frontend network
  backend:
    # Configuration for backend network

In this example:

graph TD subgraph "Frontend Network" WebApp[Web App Container] API1[API Container] end subgraph "Backend Network" API2[API Container] Database[Database Container] end WebApp <--> API1 API2 <--> Database API1 -.-> API2 class API1,API2 common

Network Configuration Options

Docker Compose networks can be customized with various options:

networks:
  frontend:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: frontend_bridge
    ipam:
      driver: default
      config:
        - subnet: 172.28.0.0/16
    
  backend:
    driver: bridge
    internal: true  # No external access
    
  external_net:
    external: true  # Use pre-existing network

Network Aliases

You can give a service multiple names on a network using aliases:

services:
  database:
    image: postgres
    networks:
      backend:
        aliases:
          - db
          - postgres
          - sql-server

Now the database can be reached as database, db, postgres, or sql-server by other services on the backend network.

Understanding Docker Volumes

Volumes are the preferred mechanism for persisting data generated and used by Docker containers. While containers themselves are ephemeral (temporary), volumes persist beyond the lifecycle of containers.

The Moving House Analogy

Think of Docker volumes like boxes during a move:

  • Container filesystem is like the rental property—temporary and returns to its original state when you leave
  • Volumes are like your moving boxes—they contain your belongings and move with you from house to house
  • Bind mounts are like built-in furniture that stays with the property but that you can use while you're there

When you "move" (recreate your container), all your important data in volumes comes with you, while the temporary environment is refreshed.

Types of Data Persistence in Docker

Docker Data Persistence Methods Named Volumes Managed by Docker Portable across hosts Best for application data Easy backup & migration Volume drivers for cloud Example: my_db_data:/var/lib/mysql Bind Mounts Host file system path Host dependent Great for development Direct access to host files Configuration/code sharing Example: ./app:/app tmpfs Mounts In-memory storage Non-persistent High performance Sensitive data use cases Temporary content Example: tmpfs:/tmp

Volumes in Docker Compose

Declaring and Using Volumes

Docker Compose provides a simple way to define and use volumes:

version: '3.8'

services:
  database:
    image: postgres:14
    volumes:
      - db_data:/var/lib/postgresql/data
      - ./init-scripts:/docker-entrypoint-initdb.d
      
  redis:
    image: redis:alpine
    volumes:
      - redis_data:/data
    
volumes:
  db_data:    # Named volume, managed by Docker
  redis_data:  # Another named volume

This configuration creates two named volumes (db_data and redis_data) and one bind mount (./init-scripts).

Volume Types in Docker Compose

Docker Compose supports three types of volumes:

Common Volume Use Cases

Database Data Persistence

services:
  postgres:
    image: postgres:14
    volumes:
      - postgres_data:/var/lib/postgresql/data
      
  mongodb:
    image: mongo:6
    volumes:
      - mongo_data:/data/db
      
  mysql:
    image: mysql:8
    volumes:
      - mysql_data:/var/lib/mysql
      
volumes:
  postgres_data:
  mongo_data:
  mysql_data:

Development Hot Reloading

services:
  frontend:
    build: ./frontend
    volumes:
      - ./frontend:/app  # Bind mount for live code changes
      - /app/node_modules  # Anonymous volume for node_modules
    
  backend:
    build: ./backend
    volumes:
      - ./backend:/app  # Bind mount for live code changes
      - /app/node_modules  # Anonymous volume for node_modules

Configuration Files

services:
  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./ssl:/etc/nginx/ssl
      
  app:
    build: ./app
    volumes:
      - ./app/config:/app/config

Advanced Volume Configuration

Volume Drivers

Docker volumes can use different drivers for various storage backends:

volumes:
  db_data:
    driver: local
    
  s3_data:
    driver: rexray/s3
    driver_opts:
      size: "100"
      volumetype: "gp2"
      
  nfs_data:
    driver: local
    driver_opts:
      type: "nfs"
      o: "addr=192.168.1.1,rw"
      device: ":/path/to/nfs/share"

External Volumes

You can use pre-existing volumes that are managed outside of your Compose file:

volumes:
  db_data:
    external: true  # Use a pre-existing volume
    
  db_backup:
    external:
      name: production_db_backup  # Use volume with different name

Read-Only Volumes

For security, you can mount volumes as read-only:

services:
  app:
    image: myapp
    volumes:
      - ./config:/app/config:ro  # Read-only bind mount
      - data_volume:/app/data:ro  # Read-only named volume

Named Volumes with Options

volumes:
  db_data:
    driver: local
    driver_opts:
      type: "btrfs"  # Filesystem type
      device: "/dev/sda2"
    labels:
      com.example.environment: "production"
      com.example.backup: "weekly"

Practical Examples: Networking and Volumes

Example 1: Multi-tier Web Application

A typical web application with frontend, backend, and database:

version: '3.8'

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./frontend/build:/usr/share/nginx/html
      - ./ssl:/etc/nginx/ssl
    networks:
      - frontend
    depends_on:
      - api

  api:
    build: ./backend
    expose:
      - "3000"
    environment:
      - DB_HOST=postgres
      - REDIS_HOST=redis
    volumes:
      - ./backend:/app
      - /app/node_modules
      - ./uploads:/app/uploads
    networks:
      - frontend
      - backend
    depends_on:
      - postgres
      - redis

  postgres:
    image: postgres:14
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
    environment:
      - POSTGRES_PASSWORD=mypassword
      - POSTGRES_USER=myuser
      - POSTGRES_DB=myapp
    networks:
      - backend

  redis:
    image: redis:alpine
    volumes:
      - redis_data:/data
    networks:
      - backend

networks:
  frontend:
  backend:
    internal: true  # Not accessible from the outside

volumes:
  postgres_data:
  redis_data:
graph TD subgraph "Frontend Network" NGINX[Nginx Container] API1[API Container] end subgraph "Backend Network (internal)" API2[API Container] PG[Postgres Container] REDIS[Redis Container] end NGINX <--> API1 API2 <--> PG API2 <--> REDIS API1 -.-> API2 USER[External User] ---> NGINX PG -.volume.-> PG_VOL[Postgres Data Volume] REDIS -.volume.-> REDIS_VOL[Redis Data Volume] NGINX -.mount.-> NGINX_CONF[Configuration Bind Mount] API2 -.mount.-> UPLOADS[Uploads Bind Mount] class API1,API2 common

Example 2: Development Environment

A development setup with hot reloading:

version: '3.8'

services:
  frontend:
    build: 
      context: ./frontend
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    environment:
      - REACT_APP_API_URL=http://localhost:4000
    networks:
      - dev-network

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.dev
    ports:
      - "4000:4000"
    volumes:
      - ./backend:/app
      - /app/node_modules
    environment:
      - DB_HOST=db
      - DB_USER=dev
      - DB_PASSWORD=devpass
      - DB_NAME=devdb
    networks:
      - dev-network
    depends_on:
      - db

  db:
    image: postgres:14
    environment:
      - POSTGRES_USER=dev
      - POSTGRES_PASSWORD=devpass
      - POSTGRES_DB=devdb
    ports:
      - "5432:5432"
    volumes:
      - dev-db-data:/var/lib/postgresql/data
      - ./backend/db/seeds:/docker-entrypoint-initdb.d
    networks:
      - dev-network

networks:
  dev-network:

volumes:
  dev-db-data:

Example 3: Data Processing Pipeline

A complex data processing pipeline with multiple services:

version: '3.8'

services:
  collector:
    build: ./collector
    volumes:
      - raw_data:/data/raw
    networks:
      - ingestion_net
    depends_on:
      - kafka

  processor:
    build: ./processor
    volumes:
      - raw_data:/data/raw:ro
      - processed_data:/data/processed
    networks:
      - ingestion_net
      - processing_net
    depends_on:
      - collector
      - mongo

  analyzer:
    build: ./analyzer
    volumes:
      - processed_data:/data/processed:ro
      - analysis_results:/data/results
    networks:
      - processing_net
      - presentation_net
    depends_on:
      - processor

  api:
    build: ./api
    ports:
      - "4000:4000"
    volumes:
      - analysis_results:/data/results:ro
    networks:
      - presentation_net
    depends_on:
      - analyzer

  # Message broker
  kafka:
    image: confluentinc/cp-kafka:latest
    volumes:
      - kafka_data:/var/lib/kafka/data
    networks:
      - ingestion_net

  # Database
  mongo:
    image: mongo:6
    volumes:
      - mongo_data:/data/db
    networks:
      - processing_net

networks:
  ingestion_net:
  processing_net:
    internal: true
  presentation_net:

volumes:
  raw_data:
  processed_data:
  analysis_results:
  kafka_data:
  mongo_data:

Best Practices for Networking and Volumes

Networking Best Practices

Volume Best Practices

Security Considerations

Common Issues and Troubleshooting

Networking Issues

Problem Possible Causes Solutions
Container can't connect to another container
  • Not on same network
  • Using wrong service name
  • Service not running
  • Verify networks in compose file
  • Check service name spelling
  • Ensure dependent service is healthy
Host can't connect to container
  • Port not published
  • Firewall blocking
  • Service not listening on all interfaces
  • Add ports: directive
  • Check firewall settings
  • Configure service to listen on 0.0.0.0
Network conflicts
  • Overlapping subnet ranges
  • Port conflicts
  • Define explicit subnets for networks
  • Change port mappings

Volume Issues

Problem Possible Causes Solutions
Data lost between container restarts
  • Not using volumes
  • Volume path incorrect
  • Running docker-compose down -v
  • Add volume configuration
  • Verify container paths
  • Avoid -v flag when stopping
Permission issues with volumes
  • User IDs don't match between host and container
  • Incorrect permissions on host files
  • Fix permissions with chown/chmod
  • Use user namespaces
  • Configure container user
Bind mount not updating
  • Path incorrect
  • Caching issues
  • Inner path overridden
  • Verify paths are correct
  • Check for nested mounts
  • Use tools like docker-sync for better performance

Debugging Tools and Commands

# Inspect networks
docker network ls
docker network inspect my_network

# Container networking
docker exec -it container_name ping service_name
docker exec -it container_name curl service_name:port
docker exec -it container_name nslookup service_name

# List volumes
docker volume ls
docker volume inspect volume_name

# Check bind mounts
docker inspect container_name -f '{{ .Mounts }}'

# View container logs
docker-compose logs service_name

Real-world Network and Volume Patterns

Reverse Proxy with Service Discovery

A common pattern is using Nginx as a reverse proxy with automatic service discovery:

version: '3.8'

services:
  nginx:
    image: nginx
    volumes:
      - ./nginx/templates:/etc/nginx/templates
    environment:
      - SERVICE1_HOST=service1
      - SERVICE1_PORT=8000
      - SERVICE2_HOST=service2
      - SERVICE2_PORT=8000
    ports:
      - "80:80"
    networks:
      - frontend
    command: /bin/bash -c "envsubst < /etc/nginx/templates/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
    
  service1:
    image: my-service:v1
    networks:
      - frontend
    
  service2:
    image: my-service:v2
    networks:
      - frontend
    
networks:
  frontend:

Database with Regular Backups

Automating database backups using volumes:

version: '3.8'

services:
  postgres:
    image: postgres:14
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=password
    networks:
      - db_net
      
  backup:
    image: postgres:14
    volumes:
      - postgres_data:/var/lib/postgresql/data:ro
      - ./backups:/backups
    networks:
      - db_net
    environment:
      - PGPASSWORD=password
    command: |
      /bin/bash -c '
        sleep 30s
        pg_dump -h postgres -U postgres mydatabase > /backups/backup_$$(date +%Y%m%d_%H%M%S).sql
        echo "Backup completed"
        tail -f /dev/null
      '
    depends_on:
      - postgres
    
networks:
  db_net:
    
volumes:
  postgres_data:

Development/Production Config Difference

Using different volume configurations for development and production:

# docker-compose.yml (base config)
version: '3.8'

services:
  api:
    build: ./api
    networks:
      - app_net
    volumes:
      - api_data:/app/data
      
  web:
    build: ./web
    networks:
      - app_net
    volumes:
      - web_data:/app/data
      
networks:
  app_net:
  
volumes:
  api_data:
  web_data:
# docker-compose.override.yml (development)
version: '3.8'

services:
  api:
    volumes:
      - ./api:/app
      - /app/node_modules
      - api_data:/app/data
    
  web:
    volumes:
      - ./web:/app
      - /app/node_modules
      - web_data:/app/data
    ports:
      - "3000:3000"
# docker-compose.prod.yml (production)
version: '3.8'

services:
  api:
    restart: always
    # Only named volumes, no bind mounts
    
  web:
    restart: always
    # Only named volumes, no bind mounts
    
volumes:
  api_data:
    driver: rexray/s3
    driver_opts:
      size: "10"
  web_data:
    driver: rexray/s3
    driver_opts:
      size: "5"

Hands-on Exercises

Exercise 1: Network Isolation

Create a Docker Compose application with three services and proper network isolation:

  1. Frontend service (Nginx) accessible from the outside
  2. API service accessible from the frontend but not directly from outside
  3. Database service accessible only from the API
  4. Create two separate networks to implement this isolation
  5. Test connectivity between services

Exercise 2: Data Persistence

Implement different types of data persistence in a web application:

  1. Set up a database container with a named volume for data persistence
  2. Configure a bind mount for application code to enable live code reloading
  3. Use an anonymous volume for the node_modules folder
  4. Set up a read-only configuration volume
  5. Test persistence by restarting containers

Exercise 3: Backup and Restore

Create a backup solution for a PostgreSQL database:

  1. Set up a PostgreSQL container with a named volume
  2. Create a backup service that exports database dumps to a bind-mounted directory
  3. Schedule the backup service to run periodically
  4. Create a restore service that can import dumps back into the database
  5. Test the backup and restore process

Additional Resources

Summary

Docker networking and volumes are essential components for building robust, scalable containerized applications:

By properly implementing these concepts, you can create applications that are:

When designing your Docker Compose applications, always consider how services need to communicate and what data needs to persist. With these fundamentals in place, you'll be well-equipped to tackle complex containerized architectures.