Documentation/Buki/Docker Compose/ skills /docker-compose-networking

📖 docker-compose-networking

Use when configuring networks and service communication in Docker Compose including bridge networks, overlay networks, service discovery, and inter-service communication.



Overview

Master network configuration and service communication patterns in Docker Compose for building secure, scalable multi-container applications.

Default Bridge Network

Docker Compose automatically creates a default bridge network for all services in a compose file:

version: '3.8'

services:
  frontend:
    image: nginx:alpine
    ports:
      - "80:80"
    # Can reach backend using service name as hostname

  backend:
    image: node:18-alpine
    command: node server.js
    # Accessible at hostname 'backend' from frontend

  database:
    image: postgres:15-alpine
    environment:
      POSTGRES_PASSWORD: secret
    # Accessible at hostname 'database' from backend

In this setup:

  • All services can communicate using service names as hostnames
  • Frontend can reach backend at http://backend:3000
  • Backend can reach database at postgres://database:5432
  • Only frontend's port 80 is exposed to host

Custom Bridge Networks

Define custom networks for service isolation and segmentation:

version: '3.8'

services:
  frontend:
    image: nginx:alpine
    networks:
      - frontend-network
    ports:
      - "80:80"

  api:
    image: node:18-alpine
    networks:
      - frontend-network
      - backend-network
    environment:
      DATABASE_URL: postgresql://db:5432/app

  database:
    image: postgres:15-alpine
    networks:
      - backend-network
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: app
    volumes:
      - db-data:/var/lib/postgresql/data

  cache:
    image: redis:7-alpine
    networks:
      - backend-network
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data

networks:
  frontend-network:
    driver: bridge
  backend-network:
    driver: bridge
    internal: true

volumes:
  db-data:
  redis-data:

Network isolation:

  • Frontend can only reach API
  • Frontend cannot reach database or cache directly
  • API can reach all services
  • Backend network is internal (no external access)

Network Aliases

Configure multiple hostnames for service discovery:

version: '3.8'

services:
  web:
    image: nginx:alpine
    networks:
      public:
        aliases:
          - website
          - www
          - web-server
      internal:
        aliases:
          - web-internal

  api:
    image: node:18-alpine
    networks:
      public:
        aliases:
          - api-server
          - backend
      internal:
        aliases:
          - api-internal
    depends_on:
      - database

  database:
    image: postgres:15-alpine
    networks:
      internal:
        aliases:
          - db
          - postgres
          - primary-db
    environment:
      POSTGRES_PASSWORD: secret

networks:
  public:
    driver: bridge
  internal:
    driver: bridge
    internal: true

Services can be reached by any of their aliases:

  • http://website, http://www, http://web-server all reach web service
  • postgresql://db:5432, postgresql://postgres:5432 both reach database

Static IP Addresses

Assign fixed IP addresses for services requiring stable networking:

version: '3.8'

services:
  loadbalancer:
    image: nginx:alpine
    networks:
      app-network:
        ipv4_address: 172.28.1.10
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro

  app-1:
    image: myapp:latest
    networks:
      app-network:
        ipv4_address: 172.28.1.11
    environment:
      APP_ID: "1"

  app-2:
    image: myapp:latest
    networks:
      app-network:
        ipv4_address: 172.28.1.12
    environment:
      APP_ID: "2"

  app-3:
    image: myapp:latest
    networks:
      app-network:
        ipv4_address: 172.28.1.13
    environment:
      APP_ID: "3"

  database:
    image: postgres:15-alpine
    networks:
      app-network:
        ipv4_address: 172.28.1.20
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

networks:
  app-network:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.28.0.0/16
          gateway: 172.28.1.1

volumes:
  pgdata:

External Networks

Connect to existing Docker networks created outside Compose:

version: '3.8'

services:
  api:
    image: node:18-alpine
    networks:
      - app-network
      - shared-network
    environment:
      DATABASE_URL: postgresql://db:5432/app

  database:
    image: postgres:15-alpine
    networks:
      - app-network
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

networks:
  app-network:
    driver: bridge

  shared-network:
    external: true
    name: company-shared-network

volumes:
  pgdata:

Create external network first:

docker network create company-shared-network
docker compose up -d

Host Network Mode

Use host networking for maximum performance (Linux only):

version: '3.8'

services:
  high-performance-app:
    image: myapp:latest
    network_mode: "host"
    environment:
      BIND_ADDRESS: "0.0.0.0"
      PORT: "8080"
    # No port mapping needed - directly uses host's network stack

  monitoring:
    image: prometheus:latest
    network_mode: "host"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.listen-address=0.0.0.0:9090'

volumes:
  prometheus-data:

Note: Host networking bypasses Docker network isolation and is typically used for monitoring tools or high-throughput applications.

Service Discovery and DNS

Configure DNS resolution and service discovery:

version: '3.8'

services:
  api:
    image: node:18-alpine
    networks:
      - app-network
    dns:
      - 8.8.8.8
      - 8.8.4.4
    dns_search:
      - company.local
    extra_hosts:
      - "legacy-api.company.local:192.168.1.100"
      - "auth-service.company.local:192.168.1.101"
    environment:
      DATABASE_HOST: database.company.local

  database:
    image: postgres:15-alpine
    networks:
      app-network:
        aliases:
          - database.company.local
          - db.company.local
    hostname: primary-database
    domainname: company.local
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

networks:
  app-network:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: br-company-app

volumes:
  pgdata:

Link Containers (Legacy)

While links is deprecated, understanding it helps migrate legacy configurations:

version: '3.8'

services:
  # Modern approach - use networks instead
  web:
    image: nginx:alpine
    networks:
      - app-network
    depends_on:
      - api

  api:
    image: node:18-alpine
    networks:
      - app-network
    depends_on:
      - database
    environment:
      # Use service name as hostname
      DATABASE_URL: postgresql://database:5432/app

  database:
    image: postgres:15-alpine
    networks:
      - app-network
    environment:
      POSTGRES_PASSWORD: secret

networks:
  app-network:
    driver: bridge

Multi-Network Architecture

Complex applications with multiple isolated networks:

version: '3.8'

services:
  nginx:
    image: nginx:alpine
    networks:
      - public
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - frontend
      - api

  frontend:
    image: react-app:latest
    networks:
      - public
      - frontend-tier
    environment:
      API_URL: http://api:3000

  api:
    image: node-api:latest
    networks:
      - frontend-tier
      - backend-tier
    environment:
      DATABASE_URL: postgresql://postgres:5432/app
      REDIS_URL: redis://cache:6379
      QUEUE_URL: amqp://rabbitmq:5672
    depends_on:
      - database
      - cache
      - queue

  worker:
    image: node-worker:latest
    networks:
      - backend-tier
    environment:
      DATABASE_URL: postgresql://postgres:5432/app
      QUEUE_URL: amqp://rabbitmq:5672
    depends_on:
      - database
      - queue
    deploy:
      replicas: 3

  database:
    image: postgres:15-alpine
    networks:
      - backend-tier
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: app
    volumes:
      - pgdata:/var/lib/postgresql/data

  cache:
    image: redis:7-alpine
    networks:
      - backend-tier
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data

  queue:
    image: rabbitmq:3-management-alpine
    networks:
      - backend-tier
      - management
    ports:
      - "15672:15672"  # Management UI
    environment:
      RABBITMQ_DEFAULT_USER: admin
      RABBITMQ_DEFAULT_PASS: secret
    volumes:
      - rabbitmq-data:/var/lib/rabbitmq

  monitoring:
    image: prometheus:latest
    networks:
      - management
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus

networks:
  public:
    driver: bridge
  frontend-tier:
    driver: bridge
    internal: true
  backend-tier:
    driver: bridge
    internal: true
  management:
    driver: bridge

volumes:
  pgdata:
  redis-data:
  rabbitmq-data:
  prometheus-data:

Network segmentation:

  • Public: Internet-facing services (nginx, frontend)
  • Frontend-tier: Frontend and API communication
  • Backend-tier: API, workers, databases, cache, queue
  • Management: Monitoring and administration tools

Port Publishing Strategies

Control how services expose ports:

version: '3.8'

services:
  # Short syntax - host:container
  web:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    networks:
      - public

  # Long syntax with protocol specification
  api:
    image: node:18-alpine
    ports:
      - target: 3000
        published: 3000
        protocol: tcp
        mode: host
    networks:
      - app-network

  # Random host port
  app:
    image: myapp:latest
    ports:
      - "3000"  # Docker assigns random host port
    networks:
      - app-network

  # Bind to specific host interface
  admin:
    image: admin-panel:latest
    ports:
      - "127.0.0.1:8080:80"  # Only accessible from localhost
    networks:
      - admin-network

  # UDP protocol
  dns:
    image: bind9:latest
    ports:
      - "53:53/udp"
      - "53:53/tcp"
    networks:
      - dns-network

  # Range of ports
  streaming:
    image: rtmp-server:latest
    ports:
      - "1935:1935"
      - "8080-8089:8080-8089"
    networks:
      - streaming-network

networks:
  public:
  app-network:
  admin-network:
    internal: true
  dns-network:
  streaming-network:

Container Communication Patterns

Request-Response Pattern

version: '3.8'

services:
  gateway:
    image: nginx:alpine
    networks:
      - frontend
    ports:
      - "80:80"
    volumes:
      - ./nginx-gateway.conf:/etc/nginx/nginx.conf:ro

  service-a:
    image: service-a:latest
    networks:
      - frontend
      - backend
    environment:
      SERVICE_B_URL: http://service-b:8080
      DATABASE_URL: postgresql://db:5432/service_a

  service-b:
    image: service-b:latest
    networks:
      - frontend
      - backend
    environment:
      DATABASE_URL: postgresql://db:5432/service_b

  database:
    image: postgres:15-alpine
    networks:
      - backend
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true

volumes:
  pgdata:

Pub-Sub Pattern

version: '3.8'

services:
  publisher:
    image: publisher:latest
    networks:
      - messaging
    environment:
      REDIS_URL: redis://redis:6379
    depends_on:
      - redis

  subscriber-1:
    image: subscriber:latest
    networks:
      - messaging
    environment:
      REDIS_URL: redis://redis:6379
      SUBSCRIBER_ID: "1"
    depends_on:
      - redis

  subscriber-2:
    image: subscriber:latest
    networks:
      - messaging
    environment:
      REDIS_URL: redis://redis:6379
      SUBSCRIBER_ID: "2"
    depends_on:
      - redis

  redis:
    image: redis:7-alpine
    networks:
      - messaging
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data

networks:
  messaging:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.enable_icc: "true"

volumes:
  redis-data:

Network Troubleshooting Configuration

Enable debugging and monitoring:

version: '3.8'

services:
  app:
    image: myapp:latest
    networks:
      app-network:
        aliases:
          - primary-app
    cap_add:
      - NET_ADMIN
      - NET_RAW

  debug:
    image: nicolaka/netshoot:latest
    networks:
      - app-network
    command: sleep infinity
    cap_add:
      - NET_ADMIN
      - NET_RAW
    stdin_open: true
    tty: true

  database:
    image: postgres:15-alpine
    networks:
      app-network:
        aliases:
          - db
    environment:
      POSTGRES_PASSWORD: secret

networks:
  app-network:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.enable_ip_masquerade: "true"
      com.docker.network.driver.mtu: "1500"
    ipam:
      driver: default
      config:
        - subnet: 172.28.0.0/16

Debug commands:

# Enter debug container
docker compose exec debug bash

# Test connectivity
ping app
curl http://app:8080/health

# Check DNS resolution
nslookup app
dig app

# Network scanning
nmap -p- app

# Trace route
traceroute app

# Monitor traffic
tcpdump -i eth0 -n

IPv6 Networking

Enable IPv6 support:

version: '3.8'

services:
  web:
    image: nginx:alpine
    networks:
      - ipv6-network
    ports:
      - "80:80"

  api:
    image: node:18-alpine
    networks:
      ipv6-network:
        ipv6_address: 2001:db8:1::10

  database:
    image: postgres:15-alpine
    networks:
      ipv6-network:
        ipv6_address: 2001:db8:1::20
    environment:
      POSTGRES_PASSWORD: secret

networks:
  ipv6-network:
    driver: bridge
    enable_ipv6: true
    ipam:
      driver: default
      config:
        - subnet: 172.28.0.0/16
        - subnet: 2001:db8:1::/64

When to Use This Skill

Use docker-compose-networking when you need to:

  • Configure custom network topologies for multi-container applications
  • Implement network segmentation and service isolation
  • Set up service discovery and inter-service communication
  • Design secure network architectures with frontend/backend separation
  • Configure static IP addresses for services
  • Connect to external Docker networks
  • Implement complex microservices communication patterns
  • Troubleshoot network connectivity issues
  • Configure DNS resolution and hostname aliases
  • Set up pub-sub or message queue architectures
  • Enable IPv6 networking
  • Optimize network performance with host networking
  • Configure port publishing and exposure strategies

Best Practices

  1. Use Custom Networks for Isolation: Always create custom networks instead of relying solely on the default network for better security and organization.

  2. Implement Network Segmentation: Separate frontend, backend, and data tiers into different networks to limit attack surface.

  3. Use Internal Networks: Mark backend networks as internal: true to prevent external access to sensitive services like databases.

  4. Prefer Service Names Over IPs: Use Docker's built-in DNS and service names instead of hardcoding IP addresses for maintainability.

  5. Configure Health Checks: Implement health checks to ensure services are ready before routing traffic to them.

  6. Use Network Aliases: Define meaningful aliases for services to support multiple naming conventions and easier migration.

  7. Avoid Host Networking Unless Necessary: Use bridge networks by default; host networking should only be used for specific performance requirements.

  8. Document Network Architecture: Clearly comment your network design and document which services can communicate with each other.

  9. Use Depends_on Wisely: Combine depends_on with health checks to ensure services start in the correct order.

  10. Implement Least Privilege: Only expose ports that absolutely need to be accessible from outside the Docker network.

  11. Use Environment Variables for URLs: Configure service endpoints through environment variables for flexibility across environments.

  12. Test Network Isolation: Regularly verify that services can only communicate through intended network paths.

  13. Configure Appropriate MTU: Set MTU values based on your network infrastructure to avoid fragmentation issues.

  14. Use External Networks for Shared Resources: When multiple Compose projects need to communicate, use external networks rather than duplicating services.

  15. Monitor Network Performance: Use tools like docker stats and dedicated monitoring containers to track network usage and identify bottlenecks.

Common Pitfalls

  1. Exposing All Services Publicly: Don't publish ports for services that should only be accessed internally; use networks instead of port publishing.

  2. Hardcoding IP Addresses: Avoid static IP addresses unless absolutely necessary; rely on service discovery instead.

  3. Using Default Network Only: Not creating custom networks misses opportunities for proper segmentation and isolation.

  4. Ignoring Network Modes: Using the wrong network mode (bridge vs host vs overlay) for your use case can cause connectivity or performance issues.

  5. Missing Network Dependencies: Not properly configuring depends_on can cause services to fail when trying to connect to unavailable services.

  6. Overusing Host Networking: Using network_mode: host unnecessarily breaks container isolation and portability.

  7. Not Using Internal Networks: Failing to mark backend networks as internal leaves databases and sensitive services exposed.

  8. Mixing Network Modes: Trying to publish ports or connect to networks when using network_mode: host causes configuration errors.

  9. Circular Network Dependencies: Creating network dependencies that form a circle prevents containers from starting properly.

  10. Ignoring DNS Configuration: Not configuring DNS properly can cause name resolution failures in containerized applications.

  11. Subnet Conflicts: Using IP ranges that conflict with host or other Docker networks causes routing issues.

  12. Not Testing Network Policies: Assuming network isolation works without testing can leave security vulnerabilities.

  13. Exposing Management Interfaces: Publishing management ports (like RabbitMQ, Redis, PostgreSQL) without authentication or IP restrictions.

  14. Using Links Instead of Networks: The deprecated links feature should be replaced with modern network configuration.

  15. Ignoring Network Driver Options: Not configuring driver options like MTU or IP masquerade can cause subtle connectivity problems in production.

Resources

Official Documentation

Network Troubleshooting

Architecture Patterns

Tools