From 67f568a4f2a5130a937088bcddb47371a31ac3ca Mon Sep 17 00:00:00 2001 From: Manmohan <66306483+manmohan659@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:41:01 -0400 Subject: [PATCH] fix(nginx): re-resolve upstream IPs so deploys don't break auth (#43) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When docker compose recreates a service, it gets a new internal IP. nginx was resolving upstream hostnames once at startup and serving 502 until someone manually restarted it — which is what broke /api/auth after the last deploy. Uses Docker Compose's embedded DNS (127.0.0.11) and moves each proxy_pass onto a variable so nginx re-resolves every request. Rewrites replace the path-stripping behavior that variable-form proxy_pass doesn't provide out of the box. Also adds a `nginx -t && nginx -s reload` step in the deploy workflow so future nginx.conf edits land without manual ssh. Co-authored-by: Claude Opus 4.7 (1M context) --- .github/workflows/deploy-ec2.yml | 6 ++++++ nginx/nginx.conf | 25 ++++++++++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-ec2.yml b/.github/workflows/deploy-ec2.yml index 9c3b5b02..310d940b 100644 --- a/.github/workflows/deploy-ec2.yml +++ b/.github/workflows/deploy-ec2.yml @@ -66,6 +66,12 @@ jobs: docker compose -f docker-compose.yml -f docker-compose.prod.yml pull docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d + # Reload nginx so it picks up the new nginx.conf and re-resolves + # upstream IPs (auth/chat-api/frontend get fresh IPs after recreate) + docker compose -f docker-compose.yml -f docker-compose.prod.yml exec -T nginx nginx -t \ + && docker compose -f docker-compose.yml -f docker-compose.prod.yml exec -T nginx nginx -s reload \ + || echo "nginx reload skipped (container not running yet)" + # Run migrations (wait for postgres) sleep 8 docker compose exec -T chat-api alembic upgrade head 2>/dev/null || true diff --git a/nginx/nginx.conf b/nginx/nginx.conf index ab820831..af27f5c6 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -22,20 +22,28 @@ http { client_max_body_size 10M; - # Auth service — only direct auth endpoints + # Use Docker Compose's embedded DNS so upstream IPs are re-resolved + # on every request. Without this nginx caches the IP at startup and + # serves 502s after any upstream container is recreated by `compose up`. + resolver 127.0.0.11 valid=10s ipv6=off; + + # Auth service — strip /api prefix, then forward to auth:8001/auth/... location /api/auth/ { limit_req zone=api burst=10 nodelay; - proxy_pass http://auth:8001/auth/; + set $upstream_auth auth; + rewrite ^/api/auth/(.*)$ /auth/$1 break; + proxy_pass http://$upstream_auth:8001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } - # Chat API — direct to backend for conversations + streaming + # Chat API — pass through /api/conversations* unchanged location /api/conversations { limit_req zone=api burst=20 nodelay; - proxy_pass http://chat-api:8002/api/conversations; + set $upstream_chat chat-api; + proxy_pass http://$upstream_chat:8002; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -49,9 +57,11 @@ http { chunked_transfer_encoding off; } - # Grafana (optional) + # Grafana (optional) — strip /grafana/ prefix location /grafana/ { - proxy_pass http://grafana:3000/; + set $upstream_grafana grafana; + rewrite ^/grafana/(.*)$ /$1 break; + proxy_pass http://$upstream_grafana:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -62,7 +72,8 @@ http { # Next.js handles its own /api/* routes (conversations, chat/stream) # and proxies to backend services internally via CHAT_API_URL env var location / { - proxy_pass http://frontend:3000; + set $upstream_frontend frontend; + proxy_pass http://$upstream_frontend:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;