01Why Network Isolation Matters
By default, all containers in a Docker Compose project can communicate with each other. This is convenient but insecure—if one container is compromised, it can potentially attack others.
**Isolation benefits:**
• Limit blast radius of compromises
• Databases only accessible by their apps
• Public-facing services separated from internal
• Follows principle of least privilege
**Common patterns:**
• Frontend/backend separation
• Database isolation
• Reverse proxy networks
• Management network separation
Think of Docker networks like VLANs. Services on different networks can't communicate unless you explicitly connect them to shared networks.
02Basic Two-Network Pattern
The simplest isolation pattern uses two networks: 'frontend' for public-facing services and 'backend' for internal services. The reverse proxy bridges both.
**Traffic flow:**
Internet → Reverse Proxy (frontend) → App (frontend+backend) → Database (backend only)
The database is never directly reachable from the internet because it's only on the backend network.
1services: 2 traefik: 3 image: traefik:v3.04 ports: 5 - "80:80"6 - "443:443"7 networks: 8 - frontend # Connects to public-facing services9 volumes: 10 - /var/run/docker.sock:/var/run/docker.sock:ro1112 app: 13 image: myapp:latest14 networks: 15 - frontend # Reachable by traefik16 - backend # Can reach database17 labels: 18 - "traefik.enable=true"19 - "traefik.http.routers.app.rule=Host(`app.example.com`)"2021 database: 22 image: postgres:1623 networks: 24 - backend # Only reachable by app25 volumes: 26 - db_data:/var/lib/postgresql/data27 environment: 28 - POSTGRES_PASSWORD=${DB_PASSWORD}29 # No ports exposed!3031networks: 32 frontend: 33 backend: 3435volumes: 36 db_data: 03Database Isolation Pattern
For multi-service architectures, give each application its own database network. This prevents one compromised app from accessing another app's database.
**Pattern:**
• Each app + its database share a private network
• Apps join the proxy network for external access
• Databases are completely isolated from each other
1services: 2 # Reverse proxy - public network only3 traefik: 4 image: traefik:v3.05 networks: 6 - proxy78 # App 1 with its own database9 nextcloud: 10 image: nextcloud:latest11 networks: 12 - proxy13 - nextcloud_internal14 depends_on: 15 - nextcloud_db1617 nextcloud_db: 18 image: mariadb:1119 networks: 20 - nextcloud_internal # Only nextcloud can reach this21 volumes: 22 - nextcloud_db:/var/lib/mysql23 environment: 24 - MYSQL_ROOT_PASSWORD=${NC_DB_ROOT_PASS}2526 # App 2 with its own database27 gitea: 28 image: gitea/gitea:latest29 networks: 30 - proxy31 - gitea_internal32 depends_on: 33 - gitea_db3435 gitea_db: 36 image: postgres:1637 networks: 38 - gitea_internal # Only gitea can reach this39 volumes: 40 - gitea_db:/var/lib/postgresql/data4142networks: 43 proxy: 44 nextcloud_internal: 45 gitea_internal: 4647volumes: 48 nextcloud_db: 49 gitea_db: Name your networks descriptively. 'nextcloud_internal' is clearer than 'net1' when debugging.
04Management Network Pattern
Monitoring and management tools need access to many services but shouldn't be publicly accessible. Create a dedicated management network.
**Management services:**
• Prometheus (metrics collection)
• Grafana (dashboards)
• Portainer (container management)
• Log aggregators
These join the management network alongside the services they monitor, but don't join the public proxy network (or are protected by authentication).
1services: 2 # Public-facing app3 app: 4 image: myapp:latest5 networks: 6 - proxy7 - management # Exposes metrics8 labels: 9 - "traefik.enable=true"1011 # Metrics collection - not public12 prometheus: 13 image: prom/prometheus:latest14 networks: 15 - management16 volumes: 17 - ./prometheus.yml:/etc/prometheus/prometheus.yml18 - prometheus_data:/prometheus19 # No traefik labels - not publicly accessible2021 # Dashboards - protected access22 grafana: 23 image: grafana/grafana:latest24 networks: 25 - proxy # For external access (with auth)26 - management # To reach prometheus27 labels: 28 - "traefik.enable=true"29 - "traefik.http.routers.grafana.rule=Host(`grafana.example.com`)"30 # Add authentication middleware here3132networks: 33 proxy: 34 management: 3536volumes: 37 prometheus_data: 05Network Aliases for Service Discovery
When a container is on multiple networks, it might need different names on each network. Use network aliases to control how services discover each other.
**Use cases:**
• Same container, different roles on different networks
• Migration: old name on one network, new name on another
• Multiple services behind one container
1services: 2 # API server known by different names3 api: 4 image: myapi:latest5 networks: 6 frontend: 7 aliases: 8 - api9 - api.internal10 backend: 11 aliases: 12 - api-server13 - api.backend.local1415 # Consumer on frontend network16 web: 17 image: myweb:latest18 networks: 19 - frontend20 environment: 21 - API_URL=http://api:8080 # Uses frontend alias2223 # Worker on backend network24 worker: 25 image: myworker:latest26 networks: 27 - backend28 environment: 29 - API_URL=http://api-server:8080 # Uses backend alias3031networks: 32 frontend: 33 backend: Aliases are per-network. The same container can be 'db' on one network and 'postgres-primary' on another.
06External Networks for Cross-Project Communication
When services in different Compose projects need to communicate, use external networks. Create the network once, reference it from multiple projects.
**Common scenarios:**
• Shared reverse proxy serving multiple projects
• Centralized monitoring for all stacks
• Shared database cluster
1# Create shared networks once2docker network create proxy3docker network create monitoring07Using External Networks
Reference the external networks in your Compose files. Services on the same external network can communicate across projects.
1# Project 1: Traefik (reverse proxy)2services: 3 traefik: 4 image: traefik:v3.05 networks: 6 - proxy7 # ...89networks: 10 proxy: 11 external: true1213---1415# Project 2: Web app16services: 17 webapp: 18 image: mywebapp:latest19 networks: 20 - proxy # Reachable by traefik21 - internal # App's own internal network22 labels: 23 - "traefik.enable=true"2425 database: 26 networks: 27 - internal # Not on proxy network2829networks: 30 proxy: 31 external: true32 internal: 33 # This one is project-localExternal networks persist even when you 'docker compose down'. Delete them explicitly with 'docker network rm' when no longer needed.
08Debugging Network Issues
When services can't communicate, use these commands to debug network configuration:
**Common issues:**
• Services on different networks
• Typo in network names
• External network doesn't exist
• Wrong port or hostname
1# List all networks2docker network ls34# Inspect a network (shows connected containers)5docker network inspect proxy67# See which networks a container is on8docker inspect mycontainer --format='{{json .NetworkSettings.Networks}}' | jq910# Test connectivity from inside a container11docker exec webapp ping database12docker exec webapp nc -zv database 54321314# Check DNS resolution15docker exec webapp nslookup database16docker exec webapp cat /etc/hosts1718# Temporarily connect container to network for debugging19docker network connect proxy mycontainer20docker network disconnect proxy mycontainerUse 'docker compose config' to see the fully resolved Compose file, including generated network names.