# nginx-proxy — TLS reverse proxy (HTTPS + MQTT-TLS stream proxy) # Stock nginx + certbot sidecar for Let's Encrypt automation. # Networks: edge (port publisher) + app (proxy targets) # Publishes: 80, 443, 8883 on the host services: # One-shot init: generate a self-signed dummy cert if /etc/letsencrypt/live/infra/ # doesn't already have one. Lets nginx start on a fresh deploy before certbot has # issued the real cert via HTTP-01. Subsequent runs are no-ops. nginx-init: image: alpine/openssl:latest restart: "no" volumes: - nginx-certs:/etc/letsencrypt entrypoint: ["/bin/sh", "-c"] command: - | set -eu d=/etc/letsencrypt/live/infra if [ ! -s "$$d/fullchain.pem" ] || [ ! -s "$$d/privkey.pem" ]; then echo "nginx-init: generating self-signed bootstrap cert at $$d" mkdir -p "$$d" openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout "$$d/privkey.pem" -out "$$d/fullchain.pem" -subj '/CN=bootstrap-infra' else echo "nginx-init: cert already present at $$d, skipping" fi nginx: image: nginx:1.27-alpine restart: unless-stopped networks: [edge, app] ports: - "80:80" - "443:443" - "8883:8883" # MQTT-TLS via stream{} block, SNI route to rabbitmq volumes: - ./config/nginx.conf:/etc/nginx/nginx.conf:ro - ./config/conf.d:/etc/nginx/conf.d:ro - ./config/stream.d:/etc/nginx/stream.d:ro - nginx-certs:/etc/letsencrypt:ro - nginx-acme-challenge:/var/www/certbot:ro depends_on: nginx-init: condition: service_completed_successfully certbot: condition: service_started certbot: image: certbot/certbot:latest restart: unless-stopped volumes: - nginx-certs:/etc/letsencrypt - nginx-acme-challenge:/var/www/certbot entrypoint: /bin/sh -c command: > "trap exit TERM; while :; do certbot renew --webroot -w /var/www/certbot --quiet; sleep 12h & wait $${!}; done" # Initial issuance is manual: # docker compose run --rm certbot certonly \ # --webroot -w /var/www/certbot \ # --email "$LETSENCRYPT_EMAIL" --agree-tos --no-eff-email \ # -d -d ... networks: edge: app: volumes: nginx-certs: nginx-acme-challenge: