fix(nginx): re-resolve upstream IPs so deploys don't break auth (#43)

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) <noreply@anthropic.com>
This commit is contained in:
Manmohan 2026-04-16 20:41:01 -04:00 committed by GitHub
parent 94bec5f2a0
commit 67f568a4f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 24 additions and 7 deletions

View File

@ -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

View File

@ -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;