diff --git a/README.md b/README.md index 80393647..eaeb122f 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,8 @@ services: - ./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 ```bash diff --git a/backend/internal/certificate.js b/backend/internal/certificate.js index da104a25..e99ebc13 100644 --- a/backend/internal/certificate.js +++ b/backend/internal/certificate.js @@ -46,6 +46,8 @@ const internalCertificate = { const cmd = certbotCommand + ' renew --non-interactive --quiet ' + '--config "' + letsencryptConfig + '" ' + + '--work-dir "/tmp/letsencrypt-lib" ' + + '--logs-dir "/tmp/letsencrypt-log" ' + '--preferred-challenges "dns,http" ' + '--disable-hook-validation ' + (letsencryptStaging ? '--staging' : ''); @@ -833,6 +835,8 @@ const internalCertificate = { const cmd = certbotCommand + ' certonly ' + '--config "' + letsencryptConfig + '" ' + + '--work-dir "/tmp/letsencrypt-lib" ' + + '--logs-dir "/tmp/letsencrypt-log" ' + '--cert-name "npm-' + certificate.id + '" ' + '--agree-tos ' + '--authenticator webroot ' + @@ -878,6 +882,8 @@ const internalCertificate = { let mainCmd = certbotCommand + ' certonly ' + '--config "' + letsencryptConfig + '" ' + + '--work-dir "/tmp/letsencrypt-lib" ' + + '--logs-dir "/tmp/letsencrypt-log" ' + '--cert-name "npm-' + certificate.id + '" ' + '--agree-tos ' + '--email "' + certificate.meta.letsencrypt_email + '" ' + diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 79fbd799..e9d6e8d9 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -14,6 +14,8 @@ services: networks: - nginx_proxy_manager environment: + PUID: 1000 + PGID: 1000 NODE_ENV: "development" FORCE_COLOR: 1 DEVELOPMENT: "true" diff --git a/docker/rootfs/bin/common.sh b/docker/rootfs/bin/common.sh new file mode 100644 index 00000000..b95ff941 --- /dev/null +++ b/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 +} diff --git a/docker/rootfs/bin/handle-ipv6-setting b/docker/rootfs/bin/handle-ipv6-setting deleted file mode 100755 index 2aa0e41a..00000000 --- a/docker/rootfs/bin/handle-ipv6-setting +++ /dev/null @@ -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 diff --git a/docker/rootfs/etc/nginx/nginx.conf b/docker/rootfs/etc/nginx/nginx.conf index 995af00c..438c1bd4 100644 --- a/docker/rootfs/etc/nginx/nginx.conf +++ b/docker/rootfs/etc/nginx/nginx.conf @@ -1,7 +1,6 @@ # run nginx in foreground daemon off; - -user root; +pid /run/nginx/nginx.pid; # Set number of worker processes automatically based on number of CPU cores. worker_processes auto; @@ -57,7 +56,7 @@ http { } # Real IP Determination - + # Local subnets: set_real_ip_from 10.0.0.0/8; set_real_ip_from 172.16.0.0/12; # Includes Docker subnet diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run index 15b55d40..31b59fb0 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run @@ -3,17 +3,20 @@ set -e -echo "❯ Starting backend ..." +. /bin/common.sh + +log_info 'Starting backend ...' + if [ "$DEVELOPMENT" == "true" ]; then cd /app || exit 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 cd /app || exit 1 while : 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 done fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run index ff320a4c..7a80c25a 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run @@ -6,10 +6,16 @@ set -e # This service is DEVELOPMENT only. if [ "$DEVELOPMENT" == "true" ]; then + . /bin/common.sh 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 - yarn install - yarn watch + s6-setuidgid npmuser yarn install + exec s6-setuidgid npmuser yarn watch else exit 0 fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run index 70ca0cb7..044e4d30 100755 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run @@ -3,5 +3,8 @@ set -e -echo "❯ Starting nginx ..." -exec nginx +. /bin/common.sh + +log_info 'Starting nginx ...' + +exec s6-setuidgid npmuser nginx diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/00-all.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/00-all.sh new file mode 100755 index 00000000..1d5899e4 --- /dev/null +++ b/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 diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh new file mode 100755 index 00000000..86b9fc26 --- /dev/null +++ b/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 diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/20-paths.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/20-paths.sh new file mode 100755 index 00000000..2f59ef41 --- /dev/null +++ b/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 diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh new file mode 100755 index 00000000..f415979e --- /dev/null +++ b/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 diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh new file mode 100755 index 00000000..0cb9f126 --- /dev/null +++ b/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 diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh new file mode 100644 index 00000000..bcd64d25 --- /dev/null +++ b/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 diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/60-secrets.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/60-secrets.sh new file mode 100755 index 00000000..faa22acc --- /dev/null +++ b/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 diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh new file mode 100755 index 00000000..af51b46d --- /dev/null +++ b/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) +------------------------------------- +" diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/script.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/script.sh deleted file mode 100755 index 5723ea7a..00000000 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/script.sh +++ /dev/null @@ -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 "------------------------------------- - _ _ ____ __ __ -| \ | | _ \| \/ | -| \| | |_) | |\/| | -| |\ | __/| | | | -|_| \_|_| |_| |_| -------------------------------------- -" diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/up b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/up index b58eed6b..896a01b6 100644 --- a/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/up +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/up @@ -1,2 +1,2 @@ # shellcheck shell=bash -/etc/s6-overlay/s6-rc.d/prepare/script.sh +/etc/s6-overlay/s6-rc.d/prepare/00-all.sh diff --git a/docs/setup/README.md b/docs/setup/README.md index b9c42274..1bd77077 100644 --- a/docs/setup/README.md +++ b/docs/setup/README.md @@ -20,7 +20,7 @@ services: # Uncomment the next line if you uncomment anything in the section # 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 # DB_SQLITE_FILE: "/data/database.sqlite" @@ -64,6 +64,10 @@ services: # Add any other Stream port you want to expose # - '21:21' # FTP environment: + # Unix user and group IDs, optional + PUID: 1000 + PGID: 1000 + # Mysql/Maria connection parameters: DB_MYSQL_HOST: "db" DB_MYSQL_PORT: 3306 DB_MYSQL_USER: "npm" diff --git a/docs/upgrading/README.md b/docs/upgrading/README.md index 0e78e859..f0c86dbb 100644 --- a/docs/upgrading/README.md +++ b/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 containers. +See the [list of releases](https://github.com/NginxProxyManager/nginx-proxy-manager/releases) for any upgrade steps specific to each release.