Some checks failed
Deploy to Production / Trigger production update (push) Failing after 11s
compose.prod.yml has always intended to take the app and frontend off the host interface and let nginx front them — "remove port exposure (nginx will handle it)". It did not work: Compose concatenates list-valued fields when merging -f files, so `ports: []` contributed nothing and 8000:8000 and 3001:80 survived from compose.yml. The effect was that an SSL deployment still answered on both ports over plain HTTP, bypassing TLS, HSTS, the security headers and the rate limits — confirmed against the live host from off-box, where the API returned its health payload unencrypted on :8000 and the panel loaded on :3001. Use the !reset tag so the inherited value is dropped rather than extended. Verified in place: the merged config now publishes only 80 and 443, both former ports refuse connections from outside, and the panel still serves over HTTPS.
86 lines
2.9 KiB
YAML
86 lines
2.9 KiB
YAML
# Production Docker Compose with NGINX and SSL
|
|
# Usage: docker compose -f compose.yml -f compose.prod.yml up -d
|
|
# For SSL certificate renewal: docker compose -f compose.yml -f compose.prod.yml --profile ssl up -d
|
|
|
|
services:
|
|
nginx:
|
|
image: nginx:alpine
|
|
container_name: pvemanager-nginx
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes:
|
|
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
- ./nginx/conf.d:/etc/nginx/conf.d:ro
|
|
- ./nginx/ssl:/etc/nginx/ssl:ro
|
|
- ./nginx/certbot/conf:/etc/letsencrypt:ro
|
|
- ./nginx/certbot/www:/var/www/certbot:rw
|
|
# nginx reads the certificate once at startup, so a certificate renewed by
|
|
# the certbot service below would keep being served in its old form until
|
|
# the container restarts — i.e. the site would start serving an expired
|
|
# certificate roughly 30 days after the first successful renewal.
|
|
# Reload every 6h so a renewed certificate is picked up in time.
|
|
command: /bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g "daemon off;"'
|
|
depends_on:
|
|
app:
|
|
condition: service_healthy
|
|
frontend:
|
|
condition: service_started
|
|
networks:
|
|
- pvemanager-network
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD", "nginx", "-t"]
|
|
interval: 30s
|
|
timeout: 3s
|
|
retries: 3
|
|
# nginx.conf asks for 2048 worker_connections, but Docker's default file
|
|
# descriptor limit is 1024 — so the real ceiling was a quarter of the
|
|
# configured one (each proxied connection needs an fd downstream and
|
|
# upstream), and nginx warned about it on every config test.
|
|
ulimits:
|
|
nofile:
|
|
soft: 8192
|
|
hard: 8192
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: '0.5'
|
|
memory: 128M
|
|
|
|
certbot:
|
|
image: certbot/certbot:latest
|
|
container_name: pvemanager-certbot
|
|
volumes:
|
|
- ./nginx/certbot/conf:/etc/letsencrypt
|
|
- ./nginx/certbot/www:/var/www/certbot
|
|
# Renewal loop - only runs if certificates exist
|
|
entrypoint: "/bin/sh -c 'if [ -d /etc/letsencrypt/live ]; then trap exit TERM; while :; do certbot renew --quiet; sleep 12h & wait $${!}; done; else echo \"No certificates found. Exiting.\"; exit 0; fi'"
|
|
depends_on:
|
|
- nginx
|
|
networks:
|
|
- pvemanager-network
|
|
restart: "no"
|
|
profiles:
|
|
- ssl
|
|
|
|
# Override app service - remove port exposure (nginx will handle it).
|
|
# `ports: []` does NOT do that: compose concatenates list fields across -f
|
|
# files, so an empty list contributes nothing and 8000:8000 survived from
|
|
# compose.yml — leaving the API on the public interface over plain HTTP.
|
|
# !reset drops the inherited value outright.
|
|
app:
|
|
ports: !reset []
|
|
expose:
|
|
- "8000"
|
|
volumes:
|
|
- ~/.ssh:/root/.ssh:ro
|
|
- ./logs:/app/logs
|
|
- /etc/localtime:/etc/localtime:ro
|
|
|
|
# Same here: 3001:80 was still published despite `ports: []`.
|
|
frontend:
|
|
ports: !reset []
|
|
expose:
|
|
- "80"
|