feat: add nginx reverse proxy to docker-compose#423
Conversation
docker-compose.nginx.yml
Outdated
| volumes: | ||
| - ${PWD}/nginx/ssl:/etc/letsencrypt | ||
| - certbot-webroot:/var/www/certbot | ||
| entrypoint: > |
There was a problem hiding this comment.
should this be an entrypoint script instead so that we can keep the compose file neat and tidy?
There was a problem hiding this comment.
Done! Moved it to scripts/certbot_entrypoint.sh :-)
| or, with Nginx reverse proxy and Let's Encrypt SSL: | ||
| ``` | ||
| COMPOSE_FILE=docker-compose.yml:docker-compose.windows.yml | ||
| RELAY_DOMAIN=relay.example.com CERTBOT_EMAIL=you@example.com ./scripts/start_with_nginx | ||
| ``` |
There was a problem hiding this comment.
Pull request overview
Adds an optional Docker Compose overlay that runs Nostream behind an nginx reverse proxy with automated Let’s Encrypt (certbot) TLS, intended to support operator needs like TLS termination and NIP-05 without changing the default startup flow.
Changes:
- Added
docker-compose.nginx.ymldefiningnginx+certbotservices and shared volumes for ACME webroot and certificates. - Added
scripts/start_with_nginxto generate nginx config, bootstrap a temporary cert, and start the merged compose stack. - Updated README quick-start instructions to document the new nginx startup option.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| scripts/start_with_nginx | New startup script to generate nginx config, bootstrap TLS files, and run compose overlay. |
| docker-compose.nginx.yml | New compose overlay defining nginx reverse proxy and certbot issuance/renewal flow. |
| nginx/conf.d/nostream.conf.template | New nginx vhost template for HTTP->HTTPS + websocket proxying to nostream. |
| README.md | Documents how to start with nginx + Let’s Encrypt using env vars. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #!/bin/bash | ||
| PROJECT_ROOT="$(dirname $(readlink -f "${BASH_SOURCE[0]}"))/.." | ||
| DOCKER_COMPOSE_FILE="${PROJECT_ROOT}/docker-compose.yml" | ||
| DOCKER_COMPOSE_NGINX_FILE="${PROJECT_ROOT}/docker-compose.nginx.yml" |
There was a problem hiding this comment.
This script relies on compose files that use ${PWD} for volume mounts; since the script never cds into PROJECT_ROOT, running it from any directory other than the repo root will mount the wrong host paths. Consider cd "${PROJECT_ROOT}" (or the repo root) before invoking docker compose and/or make the directory check enforce running from the root directory.
scripts/start_with_nginx
Outdated
| -f $DOCKER_COMPOSE_FILE \ | ||
| -f $DOCKER_COMPOSE_NGINX_FILE \ | ||
| up --build --remove-orphans $@ |
There was a problem hiding this comment.
The docker compose invocation should quote variables and forward args as "$@" to avoid word-splitting/globbing (e.g., paths with spaces or flags containing spaces). Similarly, -f arguments should be -f "${DOCKER_COMPOSE_FILE}" etc.
| -f $DOCKER_COMPOSE_FILE \ | |
| -f $DOCKER_COMPOSE_NGINX_FILE \ | |
| up --build --remove-orphans $@ | |
| -f "${DOCKER_COMPOSE_FILE}" \ | |
| -f "${DOCKER_COMPOSE_NGINX_FILE}" \ | |
| up --build --remove-orphans "$@" |
| echo "Usage: RELAY_DOMAIN=relay.example.com CERTBOT_EMAIL=you@example.com ./scripts/start_with_nginx" | ||
| exit 1 | ||
| fi | ||
|
|
There was a problem hiding this comment.
RELAY_DOMAIN is used to build filesystem paths and is injected into the generated nginx config; currently it’s only checked for being non-empty. To avoid accidental path traversal (e.g., values containing / or ..) and to fail fast on invalid hostnames, validate RELAY_DOMAIN against an FQDN-safe regex before using it.
| FQDN_REGEX='^([A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$' | |
| if [[ ! "${RELAY_DOMAIN}" =~ ${FQDN_REGEX} ]]; then | |
| echo "Error: RELAY_DOMAIN must be a valid fully-qualified domain name." | |
| echo "Usage: RELAY_DOMAIN=relay.example.com CERTBOT_EMAIL=you@example.com ./scripts/start_with_nginx" | |
| exit 1 | |
| fi |
docker-compose.nginx.yml
Outdated
| default: | ||
|
|
||
| certbot: | ||
| image: certbot/certbot:latest |
There was a problem hiding this comment.
Using certbot/certbot:latest makes deployments non-reproducible and can introduce breaking changes unexpectedly. Pin to a specific version (or digest) so operators get predictable behavior and upgrades are explicit.
| image: certbot/certbot:latest | |
| image: certbot/certbot:v2.11.0 |
docker-compose.nginx.yml
Outdated
| nginx -s reload; | ||
| rm -f /etc/nginx/ssl/reload-nginx; |
There was a problem hiding this comment.
The nginx reload watcher removes the signal file even if nginx -s reload fails, which can leave nginx serving the old/self-signed cert without another reload attempt. Consider testing config (nginx -t) and only deleting the signal file after a successful reload (or retry on failure).
| nginx -s reload; | |
| rm -f /etc/nginx/ssl/reload-nginx; | |
| if nginx -t && nginx -s reload; then | |
| rm -f /etc/nginx/ssl/reload-nginx; | |
| fi; |
nginx/conf.d/nostream.conf.template
Outdated
| # Nginx configuration for Nostream relay | ||
| # Replace ${RELAY_DOMAIN} with your actual domain before use |
There was a problem hiding this comment.
This file is a template that the start script substitutes automatically; the comment suggests manual replacement, which can be confusing for operators editing it directly. Consider updating the header comment to reflect the intended workflow (script-generated config).
| # Nginx configuration for Nostream relay | |
| # Replace ${RELAY_DOMAIN} with your actual domain before use | |
| # Nginx configuration template for Nostream relay | |
| # ${RELAY_DOMAIN} is substituted automatically by the start script/deployment process |
docker-compose.nginx.yml
Outdated
| certbot certonly | ||
| --webroot | ||
| --webroot-path=/var/www/certbot | ||
| --email ${CERTBOT_EMAIL} | ||
| --agree-tos |
There was a problem hiding this comment.
In the certbot entrypoint, if the initial certbot certonly fails, the shell still proceeds into the infinite certbot renew loop (no set -e, and the loop isn’t gated on certonly success). This can leave nginx serving the self-signed cert indefinitely; consider exiting on certonly failure (or enabling set -e).
docker-compose.nginx.yml
Outdated
| if [ ! -f /etc/letsencrypt/renewal/${RELAY_DOMAIN}.conf ]; then | ||
| rm -rf /etc/letsencrypt/live/${RELAY_DOMAIN}; |
There was a problem hiding this comment.
RELAY_DOMAIN is required but if it’s unset, Compose will substitute an empty string here and certbot will operate on /etc/letsencrypt/renewal/.conf / live/, which is easy to miss. Consider using required-variable interpolation (e.g., ${RELAY_DOMAIN:?RELAY_DOMAIN required}) to fail fast.
docker-compose.nginx.yml
Outdated
| certbot certonly | ||
| --webroot | ||
| --webroot-path=/var/www/certbot | ||
| --email ${CERTBOT_EMAIL} |
There was a problem hiding this comment.
CERTBOT_EMAIL is required but if it’s unset, Compose will substitute an empty string and certbot will be invoked with an invalid --email value. Consider required-variable interpolation (e.g., ${CERTBOT_EMAIL:?CERTBOT_EMAIL required}) so misconfiguration fails fast.
| --email ${CERTBOT_EMAIL} | |
| --email ${CERTBOT_EMAIL:?CERTBOT_EMAIL required} |
…tartup validation
Description
Added an nginx reverse proxy with Let's Encrypt SSL as an optional docker-compose overlay. It follows the same pattern as the tor setup-separate compose file, separate start script, doesn't touch the main start flow.
Related Issue
Fixes #36
Motivation and Context
Operators need a reverse proxy for TLS, NIP-05, etc. This gives a ready-to-go nginx + certbot setup without getting in the way of people who already run their own proxy.
How Has This Been Tested?
Screenshots (if appropriate):
Types of changes
Checklist: