How to Make Nginx Dynamically Resolve Upstream Server IP After Docker Container Restart


6 views

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:

  1. Docker assigns a new IP to the container
  2. /etc/hosts gets updated (either manually or via Docker's embedded DNS)
  3. Nginx continues using the cached IP from initial resolution
  4. 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