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.

Container Linking User-Defined Networks DNS Resolution
Why Container Communication Matters

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.

For most modern applications, user-defined bridge networks with automatic DNS resolution are the recommended way to enable container communication.
Method 1: Environment Variables (Simple Configuration)

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)
Method 2: Container Linking (Legacy - Deprecated)

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
Container linking is deprecated. Do not use it for new applications. Use user-defined bridge networks instead.
Method 3: User-Defined Bridge Networks (Modern Standard)

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
User-defined bridge networks are the standard for single-host Docker deployments. Docker Compose automatically creates one for you.
DNS Resolution: How Containers Find Each Other

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
Communication Pattern: Web App + Database
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 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
Advanced Pattern: Network Aliases for Service Discovery

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"
Multi-Host Communication: Overlay Networks

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)
Communication Best Practices
  • 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.
Troubleshooting Container Communication
# 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
Frequently Asked Questions
Why can't my containers ping each other by name on the default bridge?
The default bridge network does not have automatic DNS resolution. Use a user-defined bridge network instead, which provides DNS resolution out of the box.
Can containers on different user-defined networks communicate?
By default, no. Containers on different networks are isolated. You can either connect a container to multiple networks (it acts as a bridge), or use an overlay network for multi-network communication.
Does Docker Compose create a custom network automatically?
Yes! When you run `docker-compose up`, it creates a default network named `project_default` where all services can communicate by service name. No additional configuration needed.
How do I make a container reachable from outside the host?
Use port mapping: `docker run -p 8080:80 nginx` maps host port 8080 to container port 80. This is separate from inter-container communication.
What's the difference between container name and network alias?
Container name is unique per host and is automatically resolvable on custom networks. Network aliases are additional names you can assign; multiple containers can share the same alias for load balancing.
Can I use localhost to communicate between containers?
No. On Linux, `localhost` inside a container refers to that container only, not the host. On Docker Desktop, there are special hostnames like `host.docker.internal`, but best practice is to use custom networks.
How do I set a custom hostname for a container?
Use `--hostname` flag: `docker run --hostname myapp.example.com nginx`. This sets the container's internal hostname but doesn't affect DNS resolution on the Docker network.
What happens when a container restarts and gets a new IP?
Docker's internal DNS server updates automatically. Other containers using the container name will still find it. This is why DNS discovery is better than hardcoding IPs.
Previous: Docker Network Drivers Next: Port Mapping & Expose

Modern container communication is built on user-defined networks and DNS resolution. Avoid deprecated linking and embrace custom bridge networks for clean, maintainable architectures.