Selaa lähdekoodia

Adds support to run processes as a user/group, defined
with PUID and PGID environment variables

- Detects if image is run with a user in docker command and fails if so
- Adds s6 prepare scripts for adding a 'npmuser'
- Split up and refactor the s6 prepare scripts
- Runs nginx and backend node as 'npmuser'
- Changes ownership of files required at startup

Jamie Curnow 2 vuotta sitten
vanhempi
sitoutus
dad3e1da7c

+ 2 - 0
README.md

@@ -70,6 +70,8 @@ services:
       - ./letsencrypt:/etc/letsencrypt
       - ./letsencrypt:/etc/letsencrypt
 ```
 ```
 
 
+This is the bare minimum configuration required. See the [documentation](https://nginxproxymanager.com/setup/) for more.
+
 3. Bring up your stack by running
 3. Bring up your stack by running
 
 
 ```bash
 ```bash

+ 6 - 0
backend/internal/certificate.js

@@ -46,6 +46,8 @@ const internalCertificate = {
 
 
 			const cmd = certbotCommand + ' renew --non-interactive --quiet ' +
 			const cmd = certbotCommand + ' renew --non-interactive --quiet ' +
 				'--config "' + letsencryptConfig + '" ' +
 				'--config "' + letsencryptConfig + '" ' +
+				'--work-dir "/tmp/letsencrypt-lib" ' +
+				'--logs-dir "/tmp/letsencrypt-log" ' +
 				'--preferred-challenges "dns,http" ' +
 				'--preferred-challenges "dns,http" ' +
 				'--disable-hook-validation ' +
 				'--disable-hook-validation ' +
 				(letsencryptStaging ? '--staging' : '');
 				(letsencryptStaging ? '--staging' : '');
@@ -833,6 +835,8 @@ const internalCertificate = {
 
 
 		const cmd = certbotCommand + ' certonly ' +
 		const cmd = certbotCommand + ' certonly ' +
 			'--config "' + letsencryptConfig + '" ' +
 			'--config "' + letsencryptConfig + '" ' +
+			'--work-dir "/tmp/letsencrypt-lib" ' +
+			'--logs-dir "/tmp/letsencrypt-log" ' +
 			'--cert-name "npm-' + certificate.id + '" ' +
 			'--cert-name "npm-' + certificate.id + '" ' +
 			'--agree-tos ' +
 			'--agree-tos ' +
 			'--authenticator webroot ' +
 			'--authenticator webroot ' +
@@ -878,6 +882,8 @@ const internalCertificate = {
 
 
 		let mainCmd = certbotCommand + ' certonly ' +
 		let mainCmd = certbotCommand + ' certonly ' +
 			'--config "' + letsencryptConfig + '" ' +
 			'--config "' + letsencryptConfig + '" ' +
+			'--work-dir "/tmp/letsencrypt-lib" ' +
+			'--logs-dir "/tmp/letsencrypt-log" ' +
 			'--cert-name "npm-' + certificate.id + '" ' +
 			'--cert-name "npm-' + certificate.id + '" ' +
 			'--agree-tos ' +
 			'--agree-tos ' +
 			'--email "' + certificate.meta.letsencrypt_email + '" ' +
 			'--email "' + certificate.meta.letsencrypt_email + '" ' +

+ 2 - 0
docker/docker-compose.dev.yml

@@ -14,6 +14,8 @@ services:
     networks:
     networks:
       - nginx_proxy_manager
       - nginx_proxy_manager
     environment:
     environment:
+      PUID: 1000
+      PGID: 1000
       NODE_ENV: "development"
       NODE_ENV: "development"
       FORCE_COLOR: 1
       FORCE_COLOR: 1
       DEVELOPMENT: "true"
       DEVELOPMENT: "true"

+ 29 - 0
docker/rootfs/bin/common.sh

@@ -0,0 +1,29 @@
+#!/bin/bash
+
+set -e
+
+CYAN='\E[1;36m'
+BLUE='\E[1;34m'
+YELLOW='\E[1;33m'
+RED='\E[1;31m'
+RESET='\E[0m'
+export CYAN BLUE YELLOW RED RESET
+
+log_info () {
+	echo -e "${BLUE}❯ ${CYAN}$1${RESET}"
+}
+
+log_error () {
+	echo -e "${RED}❯ $1${RESET}"
+}
+
+# The `run` file will only execute 1 line so this helps keep things
+# logically separated
+
+log_fatal () {
+	echo -e "${RED}--------------------------------------${RESET}"
+	echo -e "${RED}ERROR: $1${RESET}"
+	echo -e "${RED}--------------------------------------${RESET}"
+	/run/s6/basedir/bin/halt
+	exit 1
+}

+ 0 - 46
docker/rootfs/bin/handle-ipv6-setting

@@ -1,46 +0,0 @@
-#!/bin/bash
-
-# This command reads the `DISABLE_IPV6` env var and will either enable
-# or disable ipv6 in all nginx configs based on this setting.
-
-# Lowercase
-DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]')
-
-CYAN='\E[1;36m'
-BLUE='\E[1;34m'
-YELLOW='\E[1;33m'
-RED='\E[1;31m'
-RESET='\E[0m'
-
-FOLDER=$1
-if [ "$FOLDER" == "" ]; then
-	echo -e "${RED}❯ $0 requires a absolute folder path as the first argument!${RESET}"
-	echo -e "${YELLOW}  ie: $0 /data/nginx${RESET}"
-	exit 1
-fi
-
-FILES=$(find "$FOLDER" -type f -name "*.conf")
-if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ]; then
-	# IPV6 is disabled
-	echo "Disabling IPV6 in hosts"
-	echo -e "${BLUE}❯ ${CYAN}Disabling IPV6 in hosts: ${YELLOW}${FOLDER}${RESET}"
-
-	# Iterate over configs and run the regex
-	for FILE in $FILES
-	do
-		echo -e "  ${BLUE}❯ ${YELLOW}${FILE}${RESET}"
-		sed -E -i 's/^([^#]*)listen \[::\]/\1#listen [::]/g' "$FILE"
-	done
-
-else
-	# IPV6 is enabled
-	echo -e "${BLUE}❯ ${CYAN}Enabling IPV6 in hosts: ${YELLOW}${FOLDER}${RESET}"
-
-	# Iterate over configs and run the regex
-	for FILE in $FILES
-	do
-		echo -e "  ${BLUE}❯ ${YELLOW}${FILE}${RESET}"
-		sed -E -i 's/^(\s*)#listen \[::\]/\1listen [::]/g' "$FILE"
-	done
-
-fi

+ 2 - 3
docker/rootfs/etc/nginx/nginx.conf

@@ -1,7 +1,6 @@
 # run nginx in foreground
 # run nginx in foreground
 daemon off;
 daemon off;
-
-user root;
+pid /run/nginx/nginx.pid;
 
 
 # Set number of worker processes automatically based on number of CPU cores.
 # Set number of worker processes automatically based on number of CPU cores.
 worker_processes auto;
 worker_processes auto;
@@ -57,7 +56,7 @@ http {
 	}
 	}
 
 
 	# Real IP Determination
 	# Real IP Determination
-	
+
 	# Local subnets:
 	# Local subnets:
 	set_real_ip_from 10.0.0.0/8;
 	set_real_ip_from 10.0.0.0/8;
 	set_real_ip_from 172.16.0.0/12; # Includes Docker subnet
 	set_real_ip_from 172.16.0.0/12; # Includes Docker subnet

+ 7 - 4
docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run

@@ -3,17 +3,20 @@
 
 
 set -e
 set -e
 
 
-echo "❯ Starting backend ..."
+. /bin/common.sh
+
+log_info 'Starting backend ...'
+
 if [ "$DEVELOPMENT" == "true" ]; then
 if [ "$DEVELOPMENT" == "true" ]; then
 	cd /app || exit 1
 	cd /app || exit 1
 	# If yarn install fails: add --verbose --network-concurrency 1
 	# If yarn install fails: add --verbose --network-concurrency 1
-	yarn install
-	node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js
+	s6-setuidgid npmuser yarn install
+	exec s6-setuidgid npmuser node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js
 else
 else
 	cd /app || exit 1
 	cd /app || exit 1
 	while :
 	while :
 	do
 	do
-		node --abort_on_uncaught_exception --max_old_space_size=250 index.js
+		s6-setuidgid npmuser node --abort_on_uncaught_exception --max_old_space_size=250 index.js
 		sleep 1
 		sleep 1
 	done
 	done
 fi
 fi

+ 8 - 2
docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run

@@ -6,10 +6,16 @@ set -e
 # This service is DEVELOPMENT only.
 # This service is DEVELOPMENT only.
 
 
 if [ "$DEVELOPMENT" == "true" ]; then
 if [ "$DEVELOPMENT" == "true" ]; then
+	. /bin/common.sh
 	cd /app/frontend || exit 1
 	cd /app/frontend || exit 1
+	log_info 'Starting frontend ...'
+	HOME=/tmp/npmuserhome
+	export HOME
+	mkdir -p /app/frontend/dist
+	chown -R npmuser:npmuser /app/frontend/dist
 	# If yarn install fails: add --verbose --network-concurrency 1
 	# If yarn install fails: add --verbose --network-concurrency 1
-	yarn install
-	yarn watch
+	s6-setuidgid npmuser yarn install
+	exec s6-setuidgid npmuser yarn watch
 else
 else
 	exit 0
 	exit 0
 fi
 fi

+ 5 - 2
docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run

@@ -3,5 +3,8 @@
 
 
 set -e
 set -e
 
 
-echo "❯ Starting nginx ..."
-exec nginx
+. /bin/common.sh
+
+log_info 'Starting nginx ...'
+
+exec s6-setuidgid npmuser nginx

+ 18 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/00-all.sh

@@ -0,0 +1,18 @@
+#!/command/with-contenv bash
+# shellcheck shell=bash
+
+set -e
+
+. /bin/common.sh
+
+if [ "$(id -u)" != "0" ]; then
+	log_fatal "This docker container must be run as root, do not specify a user.\nYou can specify PUID and PGID env vars to run processes as that user and group after initialization."
+fi
+
+. /etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh
+. /etc/s6-overlay/s6-rc.d/prepare/20-paths.sh
+. /etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh
+. /etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh
+. /etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh
+. /etc/s6-overlay/s6-rc.d/prepare/60-secrets.sh
+. /etc/s6-overlay/s6-rc.d/prepare/90-banner.sh

+ 18 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh

@@ -0,0 +1,18 @@
+#!/command/with-contenv bash
+# shellcheck shell=bash
+
+set -e
+
+PUID=${PUID:-911}
+PGID=${PGID:-911}
+
+# Add npmuser user
+log_info 'Creating npmuser ...'
+
+groupmod -g 1000 users || exit 1
+useradd -u "${PUID}" -U -d /data -s /bin/false npmuser || exit 1
+usermod -G users npmuser || exit 1
+groupmod -o -g "$PGID" npmuser || exit 1
+# Home for npmuser
+mkdir -p /tmp/npmuserhome
+chown -R npmuser:npmuser /tmp/npmuserhome

+ 41 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/20-paths.sh

@@ -0,0 +1,41 @@
+#!/command/with-contenv bash
+# shellcheck shell=bash
+
+set -e
+
+log_info 'Checking paths ...'
+
+# Ensure /data is mounted
+if [ ! -d '/data' ]; then
+	log_fatal '/data is not mounted! Check your docker configuration.'
+fi
+# Ensure /etc/letsencrypt is mounted
+if [ ! -d '/etc/letsencrypt' ]; then
+	log_fatal '/etc/letsencrypt is not mounted! Check your docker configuration.'
+fi
+
+# Create required folders
+mkdir -p \
+	/data/nginx \
+	/data/custom_ssl \
+	/data/logs \
+	/data/access \
+	/data/nginx/default_host \
+	/data/nginx/default_www \
+	/data/nginx/proxy_host \
+	/data/nginx/redirection_host \
+	/data/nginx/stream \
+	/data/nginx/dead_host \
+	/data/nginx/temp \
+	/data/letsencrypt-acme-challenge \
+	/run/nginx \
+	/tmp/nginx/body \
+	/var/log/nginx \
+	/var/lib/nginx/cache/public \
+	/var/lib/nginx/cache/private \
+	/var/cache/nginx/proxy_temp
+
+touch /var/log/nginx/error.log || true
+chmod 777 /var/log/nginx/error.log || true
+chmod -R 777 /var/cache/nginx || true
+chmod 644 /etc/logrotate.d/nginx-proxy-manager

+ 21 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh

@@ -0,0 +1,21 @@
+#!/command/with-contenv bash
+# shellcheck shell=bash
+
+set -e
+
+log_info 'Setting ownership ...'
+
+# root
+chown root /tmp/nginx
+
+# npmuser
+chown -R npmuser:npmuser \
+	/data \
+	/etc/letsencrypt \
+	/etc/nginx \
+	/run/nginx \
+	/tmp/nginx \
+	/var/cache/nginx \
+	/var/lib/logrotate \
+	/var/lib/nginx \
+	/var/log/nginx

+ 17 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh

@@ -0,0 +1,17 @@
+#!/command/with-contenv bash
+# shellcheck shell=bash
+
+set -e
+
+log_info 'Dynamic resolvers ...'
+
+DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]')
+
+# Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]`
+# thanks @tfmm
+if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ];
+then
+	echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) ipv6=off valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf
+else
+	echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf
+fi

+ 36 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh

@@ -0,0 +1,36 @@
+#!/bin/bash
+
+# This command reads the `DISABLE_IPV6` env var and will either enable
+# or disable ipv6 in all nginx configs based on this setting.
+
+log_info 'IPv6 ...'
+
+# Lowercase
+DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]')
+
+process_folder () {
+	FILES=$(find "$1" -type f -name "*.conf")
+	SED_REGEX=
+
+	if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ]; then
+		# IPV6 is disabled
+		echo "Disabling IPV6 in hosts in: $1"
+		SED_REGEX='s/^([^#]*)listen \[::\]/\1#listen [::]/g'
+	else
+		# IPV6 is enabled
+		echo "Enabling IPV6 in hosts in: $1"
+		SED_REGEX='s/^(\s*)#listen \[::\]/\1listen [::]/g'
+	fi
+
+	for FILE in $FILES
+	do
+		echo "- ${FILE}"
+		sed -E -i "$SED_REGEX" "$FILE"
+	done
+
+	# ensure the files are still owned by the npmuser
+	chown -R npmuser:npmuser "$1"
+}
+
+process_folder /etc/nginx/conf.d
+process_folder /data/nginx

+ 30 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/60-secrets.sh

@@ -0,0 +1,30 @@
+#!/command/with-contenv bash
+# shellcheck shell=bash
+
+set -e
+
+# in s6, environmental variables are written as text files for s6 to monitor
+# search through full-path filenames for files ending in "__FILE"
+log_info 'Docker secrets ...'
+
+for FILENAME in $(find /var/run/s6/container_environment/ | grep "__FILE$"); do
+	echo "[secret-init] Evaluating ${FILENAME##*/} ..."
+
+	# set SECRETFILE to the contents of the full-path textfile
+	SECRETFILE=$(cat "${FILENAME}")
+	# if SECRETFILE exists / is not null
+	if [[ -f "${SECRETFILE}" ]]; then
+		# strip the appended "__FILE" from environmental variable name ...
+		STRIPFILE=$(echo "${FILENAME}" | sed "s/__FILE//g")
+		# echo "[secret-init] Set STRIPFILE to ${STRIPFILE}"  # DEBUG - rm for prod!
+
+		# ... and set value to contents of secretfile
+		# since s6 uses text files, this is effectively "export ..."
+		printf $(cat "${SECRETFILE}") > "${STRIPFILE}"
+		# echo "[secret-init] Set ${STRIPFILE##*/} to $(cat ${STRIPFILE})"  # DEBUG - rm for prod!"
+		echo "Success: ${STRIPFILE##*/} set from ${FILENAME##*/}"
+
+	else
+		echo "Cannot find secret in ${FILENAME}"
+	fi
+done

+ 17 - 0
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh

@@ -0,0 +1,17 @@
+#!/command/with-contenv bash
+# shellcheck shell=bash
+
+set -e
+
+echo
+echo "-------------------------------------
+ _   _ ____  __  __
+| \ | |  _ \|  \/  |
+|  \| | |_) | |\/| |
+| |\  |  __/| |  | |
+|_| \_|_|   |_|  |_|
+-------------------------------------
+User UID: $(id -u npmuser)
+User GID: $(id -g npmuser)
+-------------------------------------
+"

+ 0 - 93
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/script.sh

@@ -1,93 +0,0 @@
-#!/command/with-contenv bash
-# shellcheck shell=bash
-
-set -e
-
-DATA_PATH=/data
-
-# Ensure /data is mounted
-if [ ! -d "$DATA_PATH" ]; then
-	echo '--------------------------------------'
-	echo "ERROR: $DATA_PATH is not mounted! Check your docker configuration."
-	echo '--------------------------------------'
-	/run/s6/basedir/bin/halt
-	exit 1
-fi
-
-echo "❯ Checking folder structure ..."
-
-# Create required folders
-mkdir -p /tmp/nginx/body \
-	/run/nginx \
-	/var/log/nginx \
-	/data/nginx \
-	/data/custom_ssl \
-	/data/logs \
-	/data/access \
-	/data/nginx/default_host \
-	/data/nginx/default_www \
-	/data/nginx/proxy_host \
-	/data/nginx/redirection_host \
-	/data/nginx/stream \
-	/data/nginx/dead_host \
-	/data/nginx/temp \
-	/var/lib/nginx/cache/public \
-	/var/lib/nginx/cache/private \
-	/var/cache/nginx/proxy_temp \
-	/data/letsencrypt-acme-challenge
-
-touch /var/log/nginx/error.log && chmod 777 /var/log/nginx/error.log && chmod -R 777 /var/cache/nginx
-chown root /tmp/nginx
-
-# Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]`
-# thanks @tfmm
-if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ];
-then
-	echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) ipv6=off valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf
-else
-	echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf
-fi
-
-echo "Changing ownership of /data/logs to $(id -u):$(id -g)"
-chown -R "$(id -u):$(id -g)" /data/logs
-
-# Handle IPV6 settings
-/bin/handle-ipv6-setting /etc/nginx/conf.d
-/bin/handle-ipv6-setting /data/nginx
-
-# ref: https://github.com/linuxserver/docker-baseimage-alpine/blob/master/root/etc/cont-init.d/01-envfile
-
-# in s6, environmental variables are written as text files for s6 to monitor
-# search through full-path filenames for files ending in "__FILE"
-echo "❯ Secrets-init ..."
-for FILENAME in $(find /var/run/s6/container_environment/ | grep "__FILE$"); do
-	echo "[secret-init] Evaluating ${FILENAME##*/} ..."
-
-	# set SECRETFILE to the contents of the full-path textfile
-	SECRETFILE=$(cat "${FILENAME}")
-	# if SECRETFILE exists / is not null
-	if [[ -f "${SECRETFILE}" ]]; then
-		# strip the appended "__FILE" from environmental variable name ...
-		STRIPFILE=$(echo "${FILENAME}" | sed "s/__FILE//g")
-		# echo "[secret-init] Set STRIPFILE to ${STRIPFILE}"  # DEBUG - rm for prod!
-
-		# ... and set value to contents of secretfile
-		# since s6 uses text files, this is effectively "export ..."
-		printf $(cat "${SECRETFILE}") > "${STRIPFILE}"
-		# echo "[secret-init] Set ${STRIPFILE##*/} to $(cat ${STRIPFILE})"  # DEBUG - rm for prod!"
-		echo "[secret-init] Success! ${STRIPFILE##*/} set from ${FILENAME##*/}"
-
-	else
-		echo "[secret-init] cannot find secret in ${FILENAME}"
-	fi
-done
-
-echo
-echo "-------------------------------------
- _   _ ____  __  __
-| \ | |  _ \|  \/  |
-|  \| | |_) | |\/| |
-| |\  |  __/| |  | |
-|_| \_|_|   |_|  |_|
--------------------------------------
-"

+ 1 - 1
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/up

@@ -1,2 +1,2 @@
 # shellcheck shell=bash
 # shellcheck shell=bash
-/etc/s6-overlay/s6-rc.d/prepare/script.sh
+/etc/s6-overlay/s6-rc.d/prepare/00-all.sh

+ 5 - 1
docs/setup/README.md

@@ -20,7 +20,7 @@ services:
 
 
     # Uncomment the next line if you uncomment anything in the section
     # Uncomment the next line if you uncomment anything in the section
     # environment:
     # environment:
-      # Uncomment this if you want to change the location of 
+      # Uncomment this if you want to change the location of
       # the SQLite DB file within the container
       # the SQLite DB file within the container
       # DB_SQLITE_FILE: "/data/database.sqlite"
       # DB_SQLITE_FILE: "/data/database.sqlite"
 
 
@@ -64,6 +64,10 @@ services:
       # Add any other Stream port you want to expose
       # Add any other Stream port you want to expose
       # - '21:21' # FTP
       # - '21:21' # FTP
     environment:
     environment:
+      # Unix user and group IDs, optional
+      PUID: 1000
+      PGID: 1000
+      # Mysql/Maria connection parameters:
       DB_MYSQL_HOST: "db"
       DB_MYSQL_HOST: "db"
       DB_MYSQL_PORT: 3306
       DB_MYSQL_PORT: 3306
       DB_MYSQL_USER: "npm"
       DB_MYSQL_USER: "npm"

+ 1 - 0
docs/upgrading/README.md

@@ -9,3 +9,4 @@ This project will automatically update any databases or other requirements so yo
 any crazy instructions. These steps above will pull the latest updates and recreate the docker
 any crazy instructions. These steps above will pull the latest updates and recreate the docker
 containers.
 containers.
 
 
+See the [list of releases](https://github.com/NginxProxyManager/nginx-proxy-manager/releases) for any upgrade steps specific to each release.