nginx-proxy upgrade: jwilder → nginxproxy/nginx-proxy

When this applies

jwilder/nginx-proxy (last released ~2021) does not support the com.github.nginx-proxy.nginx-proxy.network container label. When a backend container is attached to two Docker networks that nginx-proxy is also on, the old image registers two upstream server entries for the same container — causing round-robin to alternate between the container’s two IPs (same process, wrong abstraction). Symptoms: API calls succeed roughly 50 % of the time; intermittent 502/503 or wrong-network timeouts.

Confirm the symptom

# On bms-1 as root
docker exec nginx-proxy grep -A20 'upstream api.w4.pinbox24.com' \
  /etc/nginx/conf.d/default.conf
# BAD: two `server` lines for the same container
# GOOD: one `server` line + comment "(ignored; reachable but redundant)"

Rollback image (before any change)

docker tag jwilder/nginx-proxy jwilder/nginx-proxy:pre-upgrade-$(date +%Y%m%d)
# Verify the tag exists
docker images jwilder/nginx-proxy

Add label to backend container

In the backend’s docker-compose.yml (e.g. pinbox24/p24-back-ts):

services:
  backend:
    labels:
      - "com.github.nginx-proxy.nginx-proxy.network=test-net"
    networks:
      - prod-v-4-net
      - test-net

Commit this to the GitLab repo before the swap — the label is meaningless on the old image but harmless, and it will be live on the new image immediately.

Swap nginx-proxy image (live, ~10 s downtime)

nginx-proxy on bms-1 is NOT managed by docker-compose. All config is in bind mounts under /docker/vol/etc/nginx/; no data loss on container swap.

# 1. Pull new image
docker pull nginxproxy/nginx-proxy:latest
 
# 2. Stop old container; rename so the name is free
docker stop nginx-proxy
docker rename nginx-proxy nginx-proxy-old-jwilder
 
# 3. Start new container with same volumes/ports
DHPARAM_VOL=$(docker inspect nginx-proxy-old-jwilder \
  --format '{{range .Mounts}}{{if eq .Destination "/etc/nginx/dhparam"}}{{.Name}}{{end}}{{end}}')
 
docker run -d \
  --name nginx-proxy \
  -p 80:80 -p 443:443 \
  -v /docker/vol/etc/nginx/certs:/etc/nginx/certs \
  -v /docker/vol/etc/nginx/conf.d:/etc/nginx/conf.d \
  -v /docker/vol/etc/nginx/vhost.d:/etc/nginx/vhost.d \
  -v /docker/vol/usr/share/nginx/html:/usr/share/nginx/html \
  -v /var/run/docker.sock:/tmp/docker.sock:ro \
  -v "${DHPARAM_VOL}:/etc/nginx/dhparam" \
  nginxproxy/nginx-proxy:latest
 
# 4. Reconnect to additional networks (replicate original)
docker network connect test-net     nginx-proxy
docker network connect prod-v-3-net nginx-proxy
docker network connect prod-v-4-net nginx-proxy

Verify

# nginx config valid
docker exec nginx-proxy nginx -t
 
# Single upstream entry for v42-prod
docker exec nginx-proxy grep -A12 'upstream api.w4.pinbox24.com' \
  /etc/nginx/conf.d/default.conf
# Expected: "test-net (ignored; reachable but redundant)" in comments
# Expected: exactly ONE `server <ip>:3000;` line
 
# Traffic flowing
curl -sk -o /dev/null -w '%{http_code}' -H 'Host: api.w4.pinbox24.com' \
  https://localhost/health
# Expected: 200 or 404 (backend-dependent), NOT 502/503

Rollback

If the new container fails:

docker stop nginx-proxy
docker rm   nginx-proxy
docker run -d \
  --name nginx-proxy \
  -p 80:80 -p 443:443 \
  -v /docker/vol/etc/nginx/certs:/etc/nginx/certs \
  -v /docker/vol/etc/nginx/conf.d:/etc/nginx/conf.d \
  -v /docker/vol/etc/nginx/vhost.d:/etc/nginx/vhost.d \
  -v /docker/vol/usr/share/nginx/html:/usr/share/nginx/html \
  -v /var/run/docker.sock:/tmp/docker.sock:ro \
  -v "${DHPARAM_VOL}:/etc/nginx/dhparam" \
  jwilder/nginx-proxy:pre-upgrade-YYYYMMDD   # use your tagged date
 
docker network connect test-net     nginx-proxy
docker network connect prod-v-3-net nginx-proxy
docker network connect prod-v-4-net nginx-proxy

Clean up old container after verification

docker rm nginx-proxy-old-jwilder
# Optional: remove old tag after a few days
# docker rmi jwilder/nginx-proxy:pre-upgrade-YYYYMMDD

bms-1 state after 2026-07-08 swap

  • Image: nginxproxy/nginx-proxy:latest
  • Networks: bridge, test-net, prod-v-3-net, prod-v-4-net
  • Bind mounts: /docker/vol/etc/nginx/{certs,conf.d,vhost.d}, /docker/vol/usr/share/nginx/html
  • Named volume: 5b9ea79f8933.../etc/nginx/dhparam
  • letsencrypt companion: nginx-proxy-letsencrypt (jrcs image) — unaffected by this swap
  • Pending: GitLab MR to commit docker-compose.yml changes (see priorities.md)