Container Communication
In a containerized world, applications rarely run in isolation. This guide covers all the ways containers communicate: container linking (legacy), user-defined bridge networks (modern), DNS resolution, and practical communication patterns for microservices.
Modern applications are built as collections of small, focused services rather than monolithic codebases. A typical web application might have a web server container, an application server container, a database container, a cache container (Redis), and a message queue container. These containers need to communicate with each other to function as a complete application.
Docker provides several ways for containers to communicate: environment variables (for simple configurations), container linking (legacy), custom bridge networks (modern standard), and overlay networks (multi-host). Understanding these options helps you design secure, maintainable container architectures.
The simplest way for containers to communicate is through environment variables. When you know a service's address ahead of time, you can pass it as an environment variable. This works well for database connections where the database container's name or IP is known at container startup.
However, environment variables have limitations: they don't update if a container restarts with a different IP, and they require restarting dependent containers when addresses change. For dynamic environments, use DNS-based discovery instead.
# Run database with fixed name
docker run -d --name postgres_db -e POSTGRES_PASSWORD=secret postgres:15
# Get the database container's IP
DB_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' postgres_db)
# Run app with database IP as environment variable
docker run -d --name myapp -e DATABASE_HOST=$DB_IP -p 8080:3000 myapp
# Inside app, access via process.env.DATABASE_HOST (Node.js) or os.getenv() (Python)
Container linking was the original way Docker allowed containers to discover each other. When you link a container using --link, Docker adds environment variables to the child container with the parent's connection information and updates the child's /etc/hosts file. Links are one-way and only work on the default bridge network.
Important: Container linking is deprecated. Docker recommends using user-defined bridge networks instead, which provide better isolation and automatic DNS resolution. Links are still supported for backward compatibility but should not be used for new applications.
# Legacy linking (deprecated - not recommended)
docker run -d --name database postgres
docker run -d --name app --link database:db myapp
# Inside app, you can reference "db" as hostname
# Environment variables like DB_PORT_5432_TCP_ADDR are also added
# View links (legacy)
docker inspect app | grep -A 5 Links
User-defined bridge networks are the recommended way to enable container communication. When you create a custom bridge network and connect containers to it, Docker automatically provides automatic DNS resolution—containers can ping each other by container name. This is the foundation of Docker Compose networking.
Key benefits over default bridge: automatic DNS resolution (containers can reach each other by name), better isolation (only containers on the same custom network communicate), and the ability to connect/disconnect containers dynamically.
# Create custom bridge network
docker network create mynetwork
# Run containers on the same network
docker run -d --name database --network mynetwork postgres:15
docker run -d --name redis --network mynetwork redis:alpine
docker run -d --name app --network mynetwork -p 8080:3000 myapp
# Inside app container - can reach other containers by name
# http://database:5432 (Postgres)
# redis:6379 (Redis)
# Test connectivity
docker exec app ping database
docker exec app ping redis
# Connect an existing container to network
docker network connect mynetwork existing-container
# Disconnect container from network
docker network disconnect mynetwork container-name
Docker provides an internal DNS server at 127.0.0.11 for user-defined networks. When a container tries to resolve a hostname, Docker's DNS server returns the IP address of any container with that name on the same network.
This works across container restarts—if a container stops and restarts with a new IP address, DNS resolution automatically updates. This is a key advantage over environment variables or static IPs. Containers can also have aliases (multiple names for the same container).
# Check Docker's internal DNS configuration
docker exec app cat /etc/resolv.conf
# nameserver 127.0.0.11
# options ndots:0
# Test DNS resolution from inside container
docker exec app nslookup database
docker exec app ping -c 2 database
# Create container with network alias
docker run -d --name web --network mynetwork --network-alias www --network-alias website nginx
# Now reachable as "web", "www", and "website"
# Custom DNS search domains
docker network create --dns 8.8.8.8 --dns 1.1.1.1 mynetwork
# Inspect DNS settings
docker network inspect mynetwork | grep -A 10 DNS
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ nginx │ │ Node.js │ │ PostgreSQL │
│ (web) │─────▶│ (app) │─────▶│ (database) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
└─────────────────────┴─────────────────────┘
mynetwork (bridge)
# Create network
docker network create app-network
# Run PostgreSQL
docker run -d \
--name postgres \
--network app-network \
-e POSTGRES_DB=myapp \
-e POSTGRES_USER=myuser \
-e POSTGRES_PASSWORD=mypassword \
postgres:15
# Run Redis
docker run -d \
--name redis \
--network app-network \
redis:alpine
# Run Node.js app (connects to postgres:5432 and redis:6379)
docker run -d \
--name app \
--network app-network \
-e DB_HOST=postgres \
-e REDIS_HOST=redis \
myapp:latest
# Run nginx as reverse proxy
docker run -d \
--name nginx \
--network app-network \
-p 80:80 \
nginx:alpine
# All containers communicate by name within the network
Network aliases allow a single container to be reachable by multiple hostnames. This is useful for blue-green deployments, A/B testing, or when you want to abstract service names from implementation.
# Run API v1 with alias "api"
docker run -d --name api-v1 --network mynetwork --network-alias api myapi:1.0
# Run API v2 with same alias "api" (on different port, another container)
docker run -d --name api-v2 --network mynetwork --network-alias api myapi:2.0 -p 8081
# Now both containers respond to the name "api"
# Docker's DNS will load balance between them (round-robin)
# For blue-green deployment, remove v1's alias
docker network disconnect mynetwork api-v1
docker network connect --alias api mynetwork api-v2 # Promote v2 to "api"
When containers run on different physical hosts, user-defined bridge networks aren't enough. Overlay networks (used with Docker Swarm) span multiple hosts and provide the same automatic DNS resolution across hosts. Containers on the same overlay network can communicate by name regardless of which host they run on.
This is essential for production deployments where applications scale across multiple servers. Kubernetes uses a similar concept with its own networking model (CNI plugins).
# Initialize Swarm mode (manager node)
docker swarm init --advertise-addr 192.168.1.10
# Create overlay network
docker network create -d overlay --attachable my-overlay
# Deploy service on overlay network
docker service create --name web --network my-overlay --replicas 3 nginx
# Services can communicate by name across hosts
# web service resolves to any replica (load balanced)
- Use user-defined bridge networks - Not the default bridge. Custom networks provide automatic DNS resolution.
- Never use --link - It's deprecated. Use custom networks instead.
- Use container names as hostnames - In a custom network, "ping postgres" works. Use that connection string.
- Don't rely on container IP addresses - IPs change when containers restart. Use DNS names instead.
- Use environment variables for configuration - Pass database credentials and service names via env vars, but rely on DNS for discovery.
- Use network aliases for service abstraction - Allows swapping implementations without changing client configuration.
- For production, consider orchestration - Docker Swarm or Kubernetes provide built-in service discovery and load balancing.
# Check if containers are on same network
docker network inspect mynetwork
# Test connectivity between containers
docker exec app ping -c 3 database
# Check DNS resolution
docker exec app nslookup database
# View container's /etc/hosts (DNS overrides)
docker exec app cat /etc/hosts
# View all networks a container is connected to
docker inspect container-name | grep -A 10 Networks
# Check if ports are correctly exposed
docker port app
# Run temporary debug container on same network
docker run -it --rm --network mynetwork alpine sh
# Then test: ping app, telnet app 3000, wget -O- http://app:3000/health
Modern container communication is built on user-defined networks and DNS resolution. Avoid deprecated linking and embrace custom bridge networks for clean, maintainable architectures.