From a62c2a6dc3ab51e6fedc34d3ea3506bc9373953f Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Tue, 9 Jun 2026 10:25:10 +1000 Subject: [PATCH] Adds support for custom admin port via env var #3808 and documentation --- docker/dev/Dockerfile | 2 +- docker/dev/squid.conf | 1 + docker/docker-compose.ci.postgres.yml | 4 +++ docker/docker-compose.ci.yml | 1 + ...oduction.conf => production.conf.template} | 4 +-- .../etc/s6-overlay/s6-rc.d/prepare/00-all.sh | 1 + .../s6-rc.d/prepare/45-admin-port.sh | 30 +++++++++++++++++++ .../etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh | 4 --- docker/rootfs/usr/bin/check-health | 2 +- docker/rootfs/usr/bin/common.sh | 4 +++ docs/src/advanced-config/index.md | 18 +++++++++++ scripts/wait-healthy | 2 +- test/cypress/config/ci.mjs | 4 ++- 13 files changed, 67 insertions(+), 10 deletions(-) rename docker/rootfs/etc/nginx/conf.d/{production.conf => production.conf.template} (89%) create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/45-admin-port.sh diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index 8593fd5e..56875fd8 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -25,7 +25,7 @@ RUN curl -sL 'https://taskfile.dev/install.sh' | sh COPY rootfs / COPY scripts/install-s6 /tmp/install-s6 -RUN rm -f /etc/nginx/conf.d/production.conf \ +RUN rm -f /etc/nginx/conf.d/production.conf.template \ && chmod 644 /etc/logrotate.d/nginx-proxy-manager \ && /tmp/install-s6 "${TARGETPLATFORM}" \ && rm -f /tmp/install-s6 \ diff --git a/docker/dev/squid.conf b/docker/dev/squid.conf index cdd749f5..b335f01a 100644 --- a/docker/dev/squid.conf +++ b/docker/dev/squid.conf @@ -28,6 +28,7 @@ acl SSL_ports port 443 acl Safe_ports port 80 # http acl Safe_ports port 81 acl Safe_ports port 443 # https +acl Safe_ports port 8000 # for CI testing with custom admin port # # Recommended minimum Access Permission configuration: diff --git a/docker/docker-compose.ci.postgres.yml b/docker/docker-compose.ci.postgres.yml index b8c42446..90245ed3 100644 --- a/docker/docker-compose.ci.postgres.yml +++ b/docker/docker-compose.ci.postgres.yml @@ -3,6 +3,7 @@ services: cypress: environment: CYPRESS_stack: "postgres" + NPM_ADMIN_PORT: 8000 fullstack: environment: @@ -11,11 +12,14 @@ services: DB_POSTGRES_USER: "npm" DB_POSTGRES_PASSWORD: "npmpass" DB_POSTGRES_NAME: "npm" + NPM_ADMIN_PORT: 8000 depends_on: - db-postgres - authentik - authentik-worker - authentik-ldap + expose: + - "8000/tcp" db-postgres: image: postgres:17 diff --git a/docker/docker-compose.ci.yml b/docker/docker-compose.ci.yml index 6130f82e..1bb3c745 100644 --- a/docker/docker-compose.ci.yml +++ b/docker/docker-compose.ci.yml @@ -105,6 +105,7 @@ services: environment: HTTP_PROXY: "squid:3128" HTTPS_PROXY: "squid:3128" + NPM_ADMIN_PORT: 81 volumes: - "cypress_logs:/test/results" - "./dev/resolv.conf:/etc/resolv.conf:ro" diff --git a/docker/rootfs/etc/nginx/conf.d/production.conf b/docker/rootfs/etc/nginx/conf.d/production.conf.template similarity index 89% rename from docker/rootfs/etc/nginx/conf.d/production.conf rename to docker/rootfs/etc/nginx/conf.d/production.conf.template index 877e51dd..452609bc 100644 --- a/docker/rootfs/etc/nginx/conf.d/production.conf +++ b/docker/rootfs/etc/nginx/conf.d/production.conf.template @@ -1,7 +1,7 @@ # Admin Interface server { - listen 81 default; - listen [::]:81 default; + listen {{NPM_ADMIN_PORT}} default; + listen [::]:{{NPM_ADMIN_PORT}} default; server_name nginxproxymanager; root /app/frontend; 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 index d2e62f3b..172290ea 100755 --- 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 @@ -17,6 +17,7 @@ fi . /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/45-admin-port.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/45-admin-port.sh b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/45-admin-port.sh new file mode 100644 index 00000000..3807c7d7 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/45-admin-port.sh @@ -0,0 +1,30 @@ +#!/command/with-contenv bash +# shellcheck shell=bash + +# This command reads the `NPM_ADMIN_PORT` env var and will fall +# back to 81 if this is not set or is not a number. + +set -e + +log_info 'Admin Port ...' + +NPM_ADMIN_PORT="${NPM_ADMIN_PORT:-81}" +# ensure admin port is a number +if ! [[ "$NPM_ADMIN_PORT" =~ ^[0-9]+$ ]]; then + echo "WARNING: NPM_ADMIN_PORT must be a number. Defaulting to 81" >&2 + NPM_ADMIN_PORT=81 +fi + +PRODFILE="/etc/nginx/conf.d/production.conf" +SED_REGEX="s/\{\{NPM_ADMIN_PORT\}\}/${NPM_ADMIN_PORT}/g" + +if is_mounted "$PRODFILE"; then + echo "WARNING: skipping ${PRODFILE} — mounted file" >&2 +elif [ -f "$PRODFILE.template" ]; then + if sed -E "$SED_REGEX" "$PRODFILE.template" > "$PRODFILE" && [ -s "$PRODFILE" ]; then + # success + log_info "Generated ${PRODFILE} from template" + else + log_fatal "Failed to generate ${PRODFILE} from template" + fi +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 index 36387a98..e37d11cf 100755 --- 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 @@ -8,10 +8,6 @@ set -e log_info 'IPv6 ...' -is_mounted() { - awk -v p="$1" '$5 == p { found=1 } END { exit !found }' /proc/self/mountinfo -} - process_folder () { FILES=$(find "$1" -type f -name "*.conf") SED_REGEX= diff --git a/docker/rootfs/usr/bin/check-health b/docker/rootfs/usr/bin/check-health index bcf5552b..de23c903 100755 --- a/docker/rootfs/usr/bin/check-health +++ b/docker/rootfs/usr/bin/check-health @@ -1,6 +1,6 @@ #!/bin/bash -OK=$(curl --silent http://127.0.0.1:81/api/ | jq --raw-output '.status') +OK=$(curl --silent "http://127.0.0.1:${NPM_ADMIN_PORT:-81}/api/" | jq --raw-output '.status') if [ "$OK" == "OK" ]; then echo "OK" diff --git a/docker/rootfs/usr/bin/common.sh b/docker/rootfs/usr/bin/common.sh index 92bf19d9..32d45704 100644 --- a/docker/rootfs/usr/bin/common.sh +++ b/docker/rootfs/usr/bin/common.sh @@ -70,3 +70,7 @@ is_true () { echo '0' fi } + +is_mounted() { + awk -v p="$1" '$5 == p { found=1 } END { exit !found }' /proc/self/mountinfo +} diff --git a/docs/src/advanced-config/index.md b/docs/src/advanced-config/index.md index 3ab04ce2..a93a1a4d 100644 --- a/docs/src/advanced-config/index.md +++ b/docs/src/advanced-config/index.md @@ -248,3 +248,21 @@ On startup, we generate a resolvers directive for Nginx unless this is defined: In this configuration, all DNS queries performed by Nginx will fall to the `/etc/hosts` file and then the `/etc/resolv.conf`. + + +## Changing the Admin UI port from 81 to something else + +First, add an env var to your docker compose file: +```yml + environment: + NPM_ADMIN_PORT: '8000' +``` + +And you'll probably want to expose that port as well + +```yml + ports: + - '8000:8000' +``` + +Then you'll be able to access admin UI at `http://localhost:8000` diff --git a/scripts/wait-healthy b/scripts/wait-healthy index 503d6a90..7ca01768 100755 --- a/scripts/wait-healthy +++ b/scripts/wait-healthy @@ -19,7 +19,7 @@ echo -e "${BLUE}❯ ${CYAN}Waiting for healthy: ${YELLOW}${SERVICE}${RESET}" until [ "${HEALTHY}" = "healthy" ]; do echo -n "." sleep 1 - HEALTHY="$(docker inspect -f '{{.State.Health.Status}}' $SERVICE)" + HEALTHY="$(docker inspect -f '{{.State.Health.Status}}' "$SERVICE")" ((LOOPCOUNT++)) if [ "$LOOPCOUNT" == "$LIMIT" ]; then diff --git a/test/cypress/config/ci.mjs b/test/cypress/config/ci.mjs index ea203a89..86c0a80d 100644 --- a/test/cypress/config/ci.mjs +++ b/test/cypress/config/ci.mjs @@ -1,6 +1,8 @@ import { defineConfig } from 'cypress'; import pluginSetup from '../plugins/index.mjs'; +const adminPort = process.env.NPM_ADMIN_PORT ?? 81; + export default defineConfig({ allowCypressEnv: false, requestTimeout: 30000, @@ -19,6 +21,6 @@ export default defineConfig({ env: { swaggerBase: `{{baseUrl}}/api/schema?ts=${Date.now()}`, }, - baseUrl: "http://fullstack:81", + baseUrl: `http://fullstack:${adminPort}`, } });