TL;DR

  • App listens on 127.0.0.1:4001 with GET /__app_ok returning 200.
  • Sidecar NGINX listens on 0.0.0.0:2001 with GET /__gw_ok returning 200 and proxies to the app.
  • App + sidecar join external Docker network net_test; sidecar is not published to the host.
  • Test-proxy routes appX.* to http://appX:2001.
  • Gateway forwards to http://127.0.0.1:7777.

Directory Snapshot (context)

gateway/
 sites-enabled/
   test-proxy-gateway.conf       # forwards to 127.0.0.1:7777
testproxy/
 nginx/
   nginx.conf
   sites-enabled/
     test-wildcard.conf          # maps appX.* to http://appX:2001
 logs/
 docker-compose.yml
apps/
 appX/
   app/nginx.conf                # 127.0.0.1:4001 + /__app_ok
   sidecar/gw.conf               # 0.0.0.0:2001 + /__gw_ok -> 127.0.0.1:4001
   docker-compose.yml            # app + sidecar join net_test
networks/README.md               # name: net_test (external)

A) Add a New App (Sidecar Pattern)

Prep

  • App listens on 127.0.0.1:4001 and serves GET /__app_ok.
  • Sidecar listens on 0.0.0.0:2001 and serves GET /__gw_ok.
  • Sidecar proxy_pass http://127.0.0.1:4001.
  • App + sidecar attach to external Docker network net_test.

Compose up

  • docker compose up -d in the app directory.
  • docker inspect <app> <sidecar> and confirm net_test is attached.

Health checks

  • From sidecar: curl -s http://127.0.0.1:4001/__app_ok -> 200.
  • From test-proxy: curl -s http://appX:2001/__gw_ok -> 200.

Observability

  • Sidecar access and error logs exist and are readable.

Rollback

  • docker compose down in the app directory.

B) Integrate App into Test-Proxy

VHost mapping

  • Update testproxy/nginx/sites-enabled/test-wildcard.conf:
    • Add server_name for the app domain(s).
    • Add proxy_pass http://appX:2001;.
    • Keep location = /__nginx_ok { return 200 'ok'; }.

Validate config

  • Ensure there is a single include path (no nested conf.d/conf.d).
  • docker exec -it testproxy nginx -t returns OK.

Reload and test

  • Recreate or reload the test-proxy container.
  • curl -H "Host: appX.test.privsec.ch" http://127.0.0.1:7777/__nginx_ok -> 200.
  • curl -H "Host: appX.test.privsec.ch" http://127.0.0.1:7777/ -> expected 2xx.

Logs

  • Tail testproxy/logs/access.log and testproxy/logs/error.log.

Rollback

  • Remove vhost block, nginx -t, reload.

C) Integrate App at Main Gateway (host)

Server blocks

  • server_name appX.test.privsec.ch;
  • :80 -> ACME location + 301 to HTTPS.
  • :443 -> correct cert + HSTS.
  • Forward to test-proxy: proxy_pass http://127.0.0.1:7777;.
  • Preserve headers: Host, X-Forwarded-*.

Security defaults

  • Unknown hosts: :80 return 444, :443 ssl_reject_handshake.
  • If needed: set_real_ip_from and real_ip_recursive on.

Reload and test

  • nginx -t && systemctl reload nginx.
  • curl -I https://appX.test.privsec.ch/ -> expected 200/301/302.

Logs

  • Review host access/error logs for upstream status and TLS issues.

Rollback

  • Remove vhost, reload, and verify blackhole defaults.

D) Config Change Checklist (any layer)

  • Single include path, no duplicate conf.d.
  • Only one log_format main definition.
  • Real IP trust matches upstream hop(s).
  • Health endpoints return 200 (/__app_ok, /__gw_ok, /__nginx_ok).
  • Ports consistent: App=4001, Sidecar=2001, Test-proxy=7777.
  • nginx -t clean; reload without warnings.

E) Promotion Checklist (Test -> Production)

  • A/B/C/D checklists pass in test.
  • Gateway vhost points to 127.0.0.1:7777.
  • TLS cert valid + renewal path documented.
  • Monitoring and alerts configured (error rate, p95 latency).
  • Backout plan ready (disable vhost; blackhole defaults active).