01Introduction
Docker networking often confuses beginners. Why can't containers reach each other? Why does localhost not work as expected? This guide demystifies Docker networks and shows you how to configure them properly for self-hosting.
02The Default Network
Docker Compose creates a default network for each project. All services in the same compose file automatically join it and can reach each other by service name.
1# docker-compose.yml in folder "myapp"2services: 3 web: 4 image: nginx5 # Can reach db at hostname "db"67 db: 8 image: postgres9 # Can reach web at hostname "web"1011# Docker automatically creates network "myapp_default"12# Both containers join it automaticallyService names become hostnames. If your service is named db, other containers reach it at db:5432.
03Custom Networks
Define custom networks to control which services can communicate. Services only on the same network can reach each other.
1services: 2 traefik: 3 image: traefik:v3.04 networks: 5 - frontend6 - backend78 app: 9 image: myapp:1.010 networks: 11 - backend12 - database1314 db: 15 image: postgres:1516 networks: 17 - database # Only app can reach db1819networks: 20 frontend: 21 name: traefik-public22 backend: 23 name: myapp-backend24 database: 25 name: myapp-database04Internal Networks
Mark networks as internal to prevent containers from accessing the internet. Ideal for databases and backend services.
1services: 2 app: 3 networks: 4 - external # Can reach internet5 - internal # Can reach db67 db: 8 networks: 9 - internal # Cannot reach internet1011networks: 12 external: 13 driver: bridge14 internal: 15 driver: bridge16 internal: true # No internet accessContainers on internal networks cannot pull updates or reach external APIs. Plan accordingly.
05Cross-Project Communication
Services in different compose files don't share networks by default. Use external networks to connect them.
1# First: Create a shared network2# docker network create shared-services34# traefik/docker-compose.yml5services: 6 traefik: 7 networks: 8 - shared910networks: 11 shared: 12 external: true13 name: shared-services1415# app/docker-compose.yml16services: 17 app: 18 networks: 19 - shared20 labels: 21 - "traefik.enable=true"2223networks: 24 shared: 25 external: true26 name: shared-services2728# Both projects can now communicate on "shared-services"Create the external network before running docker compose up, or the command will fail.
06Network Aliases
Give services multiple hostnames using aliases. Useful when migrating or when different services expect different hostnames.
1services: 2 database: 3 image: postgres:154 networks: 5 backend: 6 aliases: 7 - db8 - postgres9 - database-server1011networks: 12 backend: 1314# Other containers can reach this as:15# - database (service name)16# - db17# - postgres18# - database-server07Static IP Addresses
Usually unnecessary, but sometimes you need a fixed IP. Define a subnet and assign addresses.
1services: 2 app: 3 networks: 4 fixed: 5 ipv4_address: 172.20.0.1067 db: 8 networks: 9 fixed: 10 ipv4_address: 172.20.0.201112networks: 13 fixed: 14 driver: bridge15 ipam: 16 config: 17 - subnet: 172.20.0.0/2418 gateway: 172.20.0.1Avoid static IPs unless absolutely necessary. Service names are more maintainable and work across environments.
08DNS Resolution
Docker provides automatic DNS resolution within networks. Containers find each other by service name. Custom DNS servers can be configured if needed.
1services: 2 app: 3 dns: 4 - 1.1.1.15 - 8.8.8.86 dns_search: 7 - example.com89# Or configure at network level10networks: 11 custom: 12 driver: bridge13 driver_opts: 14 com.docker.network.bridge.enable_dns: "true"09Debugging Network Issues
When containers can't communicate, use these commands to diagnose the problem.
1# List all networks2docker network ls34# Inspect a network (see connected containers)5docker network inspect myapp_default67# Check which networks a container is on8docker inspect myapp --format '{{json .NetworkSettings.Networks}}' | jq910# Test connectivity from inside a container11docker exec app ping db12docker exec app nc -zv db 54321314# Run a debug container on the network15docker run --rm -it --network myapp_default alpine sh16# Then: ping db, nc -zv db 5432, etc.The alpine image is perfect for debugging—small and has basic network tools. Install more with apk add curl bind-tools.
10Network Modes: Bridge vs Host
Bridge mode (default) provides isolation. Host mode removes network isolation—containers share the host's network stack.
1services: 2 # Bridge mode (default) - isolated network3 app: 4 image: myapp:1.05 networks: 6 - app-network7 ports: 8 - "8080:80"910 # Host mode - no isolation, uses host network directly11 # Container binds directly to host ports12 monitoring: 13 image: prometheus:latest14 network_mode: host15 # No port mapping needed - binds to host:9090 directly1617# Host mode use cases:18# - Performance-critical applications19# - Services that need to see all host traffic20# - When you need to bind to many portsHost mode bypasses Docker's network isolation. The container has full access to the host network.