Docker Compose creates isolated network environments where services can communicate securely and efficiently. While the default network configuration works for simple applications, complex architectures require custom networks, DNS configuration, and network aliases to achieve proper service isolation, multi-network connectivity, and flexible service discovery.
Understanding Default Network Behavior
When you run docker-compose up, Docker Compose automatically creates a default network for your application. All services defined in your compose file join this network and can communicate with each other using service names as hostnames.
version: '3.8'
services:
web:
image: nginx:alpine
api:
image: node:18-alpine
command: node server.js
In this setup, the web service can reach the api service at http://api:3000. Docker Compose handles DNS resolution automatically, mapping service names to container IP addresses.
The default network uses the bridge driver and is named based on your project directory. If your project is in a directory called myapp, the network will be named myapp_default.
Creating Custom Networks
Custom networks provide control over network topology, isolation, and communication patterns. Define networks in the top-level networks section of your compose file.
version: '3.8'
services:
web:
image: nginx:alpine
networks:
- frontend
api:
image: node:18-alpine
networks:
- frontend
- backend
database:
image: postgres:14
networks:
- backend
networks:
frontend:
backend:
This configuration creates two isolated networks. The web service can only communicate with api, while database is isolated from web. The api service bridges both networks, acting as a controlled gateway between them.
Specifying Network Drivers
Docker supports multiple network drivers, each optimized for different use cases. The most common driver for Docker Compose is bridge, which creates an isolated network on a single host.
version: '3.8'
services:
app:
image: myapp:latest
networks:
- app-network
networks:
app-network:
driver: bridge
The bridge driver is the default and requires no explicit declaration. However, you can specify it explicitly for clarity or when configuring driver-specific options.
Configuring Bridge Network Options
Bridge networks support various configuration options:
networks:
custom-bridge:
driver: bridge
driver_opts:
com.docker.network.bridge.name: br-custom
com.docker.network.bridge.enable_ip_masquerade: "true"
com.docker.network.bridge.enable_icc: "true"
com.docker.network.bridge.host_binding_ipv4: "0.0.0.0"
ipam:
driver: default
config:
- subnet: 172.28.0.0/16
gateway: 172.28.0.1
Driver options control low-level network behavior:
- com.docker.network.bridge.name: Sets the Linux bridge interface name
- com.docker.network.bridge.enable_ip_masquerade: Enables IP masquerading for external connectivity
- com.docker.network.bridge.enable_icc: Controls inter-container communication
- com.docker.network.bridge.host_binding_ipv4: Specifies the host IP for port bindings
IP Address Management (IPAM)
IPAM configuration controls how IP addresses are assigned to containers within a network. You can define custom subnets, gateways, and IP ranges.
networks:
app-network:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.20.0.0/16
gateway: 172.20.0.1
ip_range: 172.20.5.0/24
This configuration creates a network with:
- Subnet: 172.20.0.0/16 (65,536 IP addresses)
- Gateway: 172.20.0.1
- IP range: 172.20.5.0/24 (containers assigned IPs from this range only)
Assigning Static IP Addresses
You can assign specific IP addresses to services:
version: '3.8'
services:
web:
image: nginx:alpine
networks:
app-network:
ipv4_address: 172.20.0.10
database:
image: postgres:14
networks:
app-network:
ipv4_address: 172.20.0.20
networks:
app-network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
Static IP assignment is useful when services need predictable addresses for configuration or when integrating with external systems that require specific IP whitelisting.
Understanding DNS Resolution
Docker Compose provides automatic DNS resolution within networks. Each service gets a DNS entry matching its service name, and all containers in the same network can resolve these names.
version: '3.8'
services:
api:
image: node:18-alpine
environment:
- DATABASE_HOST=database
- CACHE_HOST=cache
networks:
- app-network
database:
image: postgres:14
networks:
- app-network
cache:
image: redis:alpine
networks:
- app-network
networks:
app-network:
Inside the api container, you can connect to:
- PostgreSQL at database:5432
- Redis at cache:6379
DNS resolution happens automatically through Docker's embedded DNS server, which runs at 127.0.0.11 inside each container.
Using Network Aliases
Network aliases provide alternative DNS names for services. This is particularly useful when you need multiple names for the same service or when migrating from legacy naming schemes.
version: '3.8'
services:
database:
image: postgres:14
networks:
app-network:
aliases:
- db
- postgres
- primary-db
api:
image: node:18-alpine
environment:
- DB_HOST=db
networks:
- app-network
networks:
app-network:
The database service is accessible via four DNS names:
- database (service name)
- db (alias)
- postgres (alias)
- primary-db (alias)
All names resolve to the same container IP address.
Multiple Networks Per Service
Services can connect to multiple networks simultaneously, enabling complex network topologies and security zones.
version: '3.8'
services:
web:
image: nginx:alpine
networks:
- public
api:
image: node:18-alpine
networks:
- public
- private
database:
image: postgres:14
networks:
- private
cache:
image: redis:alpine
networks:
- private
networks:
public:
private:
Network isolation rules:
- web can only reach api
- api can reach all services
- database and cache cannot reach web
- database and cache can reach each other
This pattern implements a three-tier architecture where the presentation layer is isolated from the data layer.
Network Aliases Across Multiple Networks
When a service joins multiple networks, you can define different aliases for each network:
version: '3.8'
services:
api:
image: node:18-alpine
networks:
frontend:
aliases:
- api-gateway
- gateway
backend:
aliases:
- api-internal
- internal-service
web:
image: nginx:alpine
networks:
- frontend
worker:
image: python:3.9
networks:
- backend
networks:
frontend:
backend:
The web service resolves api as api-gateway or gateway, while the worker service resolves the same container as api-internal or internal-service. This allows context-specific naming conventions.
External Networks
You can connect services to networks created outside of your compose file. This is useful when multiple compose projects need to share a network or when integrating with manually created networks.
version: '3.8'
services:
app:
image: myapp:latest
networks:
- shared-network
networks:
shared-network:
external: true
Before running docker-compose up, create the external network:
docker network create shared-network
Multiple compose projects can now reference this network:
Project A:
services:
service-a:
image: app-a:latest
networks:
- shared-network
networks:
shared-network:
external: true
Project B:
services:
service-b:
image: app-b:latest
networks:
- shared-network
networks:
shared-network:
external: true
Both services can communicate because they share the same network.
External Networks with Different Names
You can reference an external network using a different name in your compose file:
version: '3.8'
services:
app:
image: myapp:latest
networks:
- internal
networks:
internal:
external: true
name: company-shared-network
This maps the local name internal to the actual network company-shared-network, allowing you to maintain consistent naming within your compose file while adapting to external network names.
Network Priority and Interface Order
When a service connects to multiple networks, Docker creates multiple network interfaces in the container. You can control the order using the priority option:
version: '3.8'
services:
api:
image: node:18-alpine
networks:
primary:
priority: 1000
secondary:
priority: 500
networks:
primary:
secondary:
Higher priority values result in network interfaces being created first. This affects routing behavior when the container initiates outbound connections.
DNS Configuration Options
You can customize DNS resolution behavior for individual services:
version: '3.8'
services:
app:
image: myapp:latest
dns:
- 8.8.8.8
- 8.8.4.4
dns_search:
- example.com
- internal.local
dns_opt:
- ndots:2
- timeout:3
networks:
- app-network
networks:
app-network:
DNS configuration explained:
- dns: Specifies custom DNS servers (replaces Docker's default DNS)
- dns_search: Sets DNS search domains for unqualified hostname lookups
- dns_opt: Configures resolver options
Warning: Setting custom dns servers bypasses Docker's DNS service, which means service name resolution may not work unless you're using external DNS servers that have entries for your services.
Using DNS Search Domains
DNS search domains automatically append domain suffixes to unqualified hostnames:
version: '3.8'
services:
app:
image: myapp:latest
dns_search:
- internal.company.com
- company.com
networks:
- app-network
api:
image: node:18-alpine
hostname: api
domainname: internal.company.com
networks:
- app-network
networks:
app-network:
Inside the app container, resolving api will try:
- api
- api.internal.company.com
- api.company.com
This enables shorter hostnames in configuration while maintaining fully qualified domain names for actual resolution.
Setting Hostnames and Domain Names
You can explicitly set the hostname and domain name for a service:
version: '3.8'
services:
database:
image: postgres:14
hostname: primary-db
domainname: cluster.local
networks:
- app-network
networks:
app-network:
The container's FQDN becomes primary-db.cluster.local. This appears in the container's /etc/hostname and affects how the container identifies itself in logs and network communications.
Network Mode Configuration
The network_mode option provides alternative networking configurations:
version: '3.8'
services:
# Use another service's network stack
sidecar:
image: monitoring-agent:latest
network_mode: "service:web"
web:
image: nginx:alpine
ports:
- "80:80"
The sidecar container shares the network namespace with web, meaning:
- They share the same IP address
- They share the same network interfaces
- Ports exposed in web are accessible from sidecar via localhost
Other network_mode values:
- bridge: Default bridged networking
- host: Use the host's network stack directly (no isolation)
- none: Disable networking entirely
Link Options for Legacy Compatibility
Links provide an older method of container connectivity, creating environment variables and DNS entries:
version: '3.8'
services:
web:
image: nginx:alpine
links:
- api:backend-api
api:
image: node:18-alpine
networks:
default:
The web container gets:
- Environment variables: BACKEND_API_PORT_3000_TCP_ADDR, BACKEND_API_NAME, etc.
- DNS entry: backend-api resolving to the api container
Note: Links are legacy features. Custom networks with service names and aliases provide better functionality and are the recommended approach.
Inter-Service Communication Patterns
Frontend-Backend-Database Pattern
version: '3.8'
services:
nginx:
image: nginx:alpine
networks:
frontend:
aliases:
- web
- www
application:
image: php:8-fpm
networks:
frontend:
aliases:
- app
backend:
aliases:
- api-server
database:
image: mysql:8
networks:
backend:
aliases:
- db
- mysql
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
The internal: true option prevents the backend network from having external connectivity, enhancing security by isolating database traffic.
Microservices Communication Pattern
version: '3.8'
services:
service-a:
image: service-a:latest
networks:
service-mesh:
aliases:
- user-service
- users
service-b:
image: service-b:latest
networks:
service-mesh:
aliases:
- order-service
- orders
service-c:
image: service-c:latest
networks:
service-mesh:
aliases:
- payment-service
- payments
api-gateway:
image: api-gateway:latest
networks:
public:
service-mesh:
networks:
public:
driver: bridge
service-mesh:
driver: bridge
All microservices communicate through the service-mesh network, while external access is controlled through the api-gateway service bridging both networks.
Internal Networks for Service Isolation
Internal networks prevent containers from accessing external networks while maintaining inter-service communication:
version: '3.8'
services:
api:
image: node:18-alpine
networks:
- external-net
- internal-net
cache:
image: redis:alpine
networks:
- internal-net
database:
image: postgres:14
networks:
- internal-net
networks:
external-net:
driver: bridge
internal-net:
driver: bridge
internal: true
The cache and database services cannot reach the internet or the host network, but can communicate with api and each other. This provides an additional security layer for sensitive backend services.
Network Labels for Organization
Labels help document and organize networks:
networks:
frontend:
driver: bridge
labels:
com.example.tier: "presentation"
com.example.environment: "production"
com.example.description: "Public-facing services"
backend:
driver: bridge
internal: true
labels:
com.example.tier: "data"
com.example.environment: "production"
com.example.description: "Internal data services"
Labels are metadata that don't affect network behavior but are useful for documentation, automation scripts, and monitoring tools.
IPv6 Network Configuration
Enable IPv6 support in your networks:
version: '3.8'
services:
app:
image: myapp:latest
networks:
ipv6-network:
ipv6_address: 2001:db8::10
networks:
ipv6-network:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 172.20.0.0/16
gateway: 172.20.0.1
- subnet: 2001:db8::/64
gateway: 2001:db8::1
This configuration creates a dual-stack network supporting both IPv4 and IPv6. Services can use both protocols simultaneously.
MAC Address Assignment
Assign specific MAC addresses to service network interfaces:
version: '3.8'
services:
app:
image: myapp:latest
mac_address: 02:42:ac:11:00:02
networks:
- app-network
networks:
app-network:
Custom MAC addresses are useful for:
- License validation systems that use MAC addresses
- Network testing scenarios
- Integration with systems expecting specific MAC addresses
Network Attachments Configuration
Fine-grained control over network attachments:
version: '3.8'
services:
api:
image: node:18-alpine
networks:
frontend:
ipv4_address: 172.20.0.10
ipv6_address: 2001:db8::10
aliases:
- api-gateway
priority: 1000
backend:
ipv4_address: 172.21.0.10
aliases:
- api-internal
priority: 500
networks:
frontend:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
backend:
driver: bridge
ipam:
config:
- subnet: 172.21.0.0/16
Each network attachment can have its own IP address, aliases, and priority settings.
Default Network Configuration
Customize the default network instead of creating custom networks:
version: '3.8'
services:
web:
image: nginx:alpine
api:
image: node:18-alpine
networks:
default:
driver: bridge
ipam:
config:
- subnet: 172.25.0.0/16
driver_opts:
com.docker.network.bridge.name: br-custom-default
This approach modifies the automatically created default network while maintaining the simplicity of not explicitly assigning networks to services.
Network Discovery Through DNS
Docker's DNS server provides automatic service discovery. Query the DNS server directly from within containers:
# Inside a container nslookup api dig api getent hosts api
DNS resolution returns all IP addresses for scaled services:
version: '3.8'
services:
api:
image: node:18-alpine
networks:
- app-network
networks:
app-network:
If you scale the API service:
docker-compose up --scale api=3
DNS queries for api return three IP addresses, enabling basic load distribution through DNS round-robin.
Network Name Customization
By default, Docker Compose prefixes network names with the project name. Override this behavior:
version: '3.8'
services:
app:
image: myapp:latest
networks:
- application-tier
networks:
application-tier:
name: my-custom-network-name
driver: bridge
The network is created as my-custom-network-name instead of projectname_application-tier.
Connecting Services Across Compose Projects
Create a shared network in one project and reference it in another:
Project 1 (creates the network):
version: '3.8'
services:
database:
image: postgres:14
networks:
- shared
networks:
shared:
name: cross-project-network
driver: bridge
Project 2 (uses the network):
version: '3.8'
services:
api:
image: node:18-alpine
environment:
- DB_HOST=database
networks:
- shared
networks:
shared:
external: true
name: cross-project-network
The api service in Project 2 can communicate with the database service in Project 1 using the service name database.
Network Gateway Configuration
Specify custom gateway addresses for your networks:
networks:
custom-network:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.30.0.0/16
gateway: 172.30.0.254
The gateway is typically the host's interface on the bridge network. Custom gateways are useful when integrating with existing network infrastructure.
Disabling Network Creation
Prevent Docker Compose from creating networks automatically:
version: '3.8'
services:
app:
image: myapp:latest
network_mode: none
# No networks section - no networks created
With network_mode: none, the container has no network access at all. This is useful for batch processing containers that don't need network connectivity.
Auxiliary Addresses in IPAM
Reserve IP addresses for non-container use:
networks:
app-network:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.28.0.0/16
gateway: 172.28.0.1
aux_addresses:
router: 172.28.0.5
monitoring: 172.28.0.6
vpn: 172.28.0.7
Auxiliary addresses are reserved and not assigned to containers. This prevents IP conflicts with external devices connected to the same network.