When working with Docker containers where IP addresses change frequently (especially during container restarts), Nginx's default behavior of resolving upstream server names only at startup becomes problematic. Here's what happens under the hood:
# Standard upstream configuration (problematic with dynamic IPs)
upstream main_web {
server web:8000; # Resolves only during Nginx start
}
When your Django container restarts:
- Docker assigns a new IP to the container
- /etc/hosts gets updated (either manually or via Docker's embedded DNS)
- Nginx continues using the cached IP from initial resolution
- Connection attempts fail because the old IP is no longer valid
1. Using variables with resolver (Recommended)
The most robust solution involves using Nginx variables with a resolver directive:
resolver 127.0.0.11 valid=10s; # Docker's internal DNS
server {
set $upstream_web web;
location / {
proxy_pass http://$upstream_web:8000;
proxy_set_header Host $host;
# Other proxy settings...
}
}
2. Forcing Nginx to Re-resolve
For older Nginx versions without variable support in proxy_pass:
upstream main_web {
server web:8000 resolve;
keepalive 16;
}
server {
location / {
proxy_pass http://main_web;
# Important headers
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
While these solutions work, they impact performance:
- DNS resolution adds ~100-300ms latency per new connection
- Keepalive connections become less effective
- Consider increasing keepalive_timeout to compensate
For production environments, consider:
# Option 1: Docker network aliases
version: '3'
services:
web:
networks:
default:
aliases:
- myapp-web
# Option 2: Service discovery tools
# (Consul, etcd, or Docker Swarm's native DNS)
When troubleshooting:
# Check current DNS resolution
docker exec nginx-container nslookup web
# Verify Nginx is using fresh DNS
tail -f /var/log/nginx/error.log | grep 'host not found'
upstream main_web {
server web:8000;
}
server {
location / {
proxy_pass http://main_web;
#...
}
}
When working with Docker containers and nginx, we often encounter situations where container IPs change after restarts. While the /etc/hosts
file gets updated automatically through Docker's embedded DNS, nginx maintains its own DNS cache and won't re-resolve hostnames until restarted.
The key issue is that nginx resolves upstream hostnames only once during startup. When your Django container restarts and gets a new IP:
- Docker updates its internal DNS and
/etc/hosts
- But nginx continues using the old cached IP
- All requests to the stale IP result in 502 errors
1. Using Variables in proxy_pass (Nginx Commercial or OpenResty)
server {
location / {
resolver 127.0.0.11 valid=10s; # Docker's DNS server
set $upstream web:8000;
proxy_pass http://$upstream;
}
}
2. Implementing Health Checks with DNS Refresh
upstream main_web {
server web:8000 resolve;
zone upstream_zone 64k;
}
server {
location / {
proxy_pass http://main_web;
health_check interval=5s;
}
}
3. The Zero-Downtime Reload Approach
For standard nginx installations without commercial features:
# Create a reload script
#!/bin/bash
while true; do
inotifywait -e modify /etc/hosts
nginx -t && nginx -s reload
done
Instead of relying on hostname resolution, use Docker's network aliases for stable addressing:
# docker-compose.yml
services:
web:
networks:
app_network:
aliases:
- django-app
nginx:
networks:
- app_network
networks:
app_network:
driver: bridge
Each solution has trade-offs:
- Variable-based approach adds slight latency
- Health checks consume additional resources
- Frequent reloads may impact performance