mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 05:50:35 +00:00
🐳 Serve Penpot and the Admin Console from a subpath (#12358)
* 🐳 Serve the frontend under the path of PENPOT_PUBLIC_URI Requests below the path are rewritten to the root, so a reverse proxy can forward them unchanged. The Admin Console keeps receiving the original path. AI-assisted-by: Claude Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net> * 🐳 Strip the public path before proxying to the Admin Console The Admin Console is served at the root of its own server and applies the public path at runtime, so it needs a matching Admin Console image. AI-assisted-by: Claude Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net> * 📚 Document subpath deployments in docker-compose.yaml AI-assisted-by: Claude Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net> * 📚 Describe subpath deployments without proxy rewrites The frontend now serves the subpath itself, so the reverse proxy must forward it unchanged. AI-assisted-by: Claude Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net> --------- Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
This commit is contained in:
parent
e4da0527fc
commit
d6e1cb23e0
@ -26,6 +26,9 @@
|
||||
x-flags: &penpot-flags
|
||||
PENPOT_FLAGS: disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies enable-mcp enable-admin-console
|
||||
|
||||
## Public URI where Penpot is reached. To serve Penpot from a subpath, include
|
||||
## it with a trailing slash, e.g. https://example.com/penpot/. A reverse proxy in
|
||||
## front must forward the subpath unchanged, without rewriting it.
|
||||
x-uri: &penpot-public-uri
|
||||
PENPOT_PUBLIC_URI: http://localhost:9001
|
||||
|
||||
@ -110,6 +113,8 @@ services:
|
||||
# ## internet with HTTPS using traefik.
|
||||
|
||||
# - "traefik.http.routers.penpot-https.rule=Host(`<DOMAIN_NAME>`)"
|
||||
# ## When Penpot is served from a subpath, match it too:
|
||||
# - "traefik.http.routers.penpot-https.rule=Host(`<DOMAIN_NAME>`) && PathPrefix(`/<SUBPATH>`)"
|
||||
# - "traefik.http.routers.penpot-https.entrypoints=websecure"
|
||||
# - "traefik.http.routers.penpot-https.tls.certresolver=letsencrypt"
|
||||
# - "traefik.http.routers.penpot-https.tls=true"
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
# The Admin Console is served at the root of its own server: strip the public
|
||||
# path and admin-console/ from the original request URI.
|
||||
location /admin-console {
|
||||
set $admin_console_uri "/";
|
||||
if ($request_uri ~ "^${PENPOT_PUBLIC_PATH_REGEX}admin-console(/.*)$") {
|
||||
set $admin_console_uri $1;
|
||||
}
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $http_cf_connecting_ip;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass $PENPOT_ADMIN_CONSOLE_URI$request_uri;
|
||||
proxy_pass $PENPOT_ADMIN_CONSOLE_URI$admin_console_uri;
|
||||
}
|
||||
|
||||
@ -51,6 +51,24 @@ update_oidc_name /var/www/app/js/config.js
|
||||
## Nginx Config
|
||||
#########################################
|
||||
|
||||
# Path of PENPOT_PUBLIC_URI (e.g. /penpot/ for https://example.com/penpot/).
|
||||
# Requests below it are rewritten to the root, so the proxy in front of this
|
||||
# server forwards them unchanged and $request_uri keeps the original path.
|
||||
PENPOT_PUBLIC_PATH="$(sed -E 's|^[A-Za-z][A-Za-z0-9+.-]*://[^/]*||; s|[?#].*$||' <<< "${PENPOT_PUBLIC_URI:-}")"
|
||||
PENPOT_PUBLIC_PATH="/${PENPOT_PUBLIC_PATH#/}"
|
||||
export PENPOT_PUBLIC_PATH="${PENPOT_PUBLIC_PATH%/}/"
|
||||
|
||||
# shellcheck disable=SC2016 # literal regex metacharacters
|
||||
PENPOT_PUBLIC_PATH_REGEX="$(sed -E 's/[][\.*^$(){}+?|]/\\&/g' <<< "$PENPOT_PUBLIC_PATH")"
|
||||
export PENPOT_PUBLIC_PATH_REGEX
|
||||
|
||||
if [ "$PENPOT_PUBLIC_PATH" != "/" ]; then
|
||||
export PENPOT_PUBLIC_PATH_DIRECTIVES="location = ${PENPOT_PUBLIC_PATH%/} { absolute_redirect off; return 301 ${PENPOT_PUBLIC_PATH}\$is_args\$args; }
|
||||
rewrite ^${PENPOT_PUBLIC_PATH_REGEX}(.*)\$ /\$1 last;"
|
||||
else
|
||||
export PENPOT_PUBLIC_PATH_DIRECTIVES=""
|
||||
fi
|
||||
|
||||
export PENPOT_BACKEND_URI=${PENPOT_BACKEND_URI:-http://penpot-backend:6060}
|
||||
export PENPOT_EXPORTER_URI=${PENPOT_EXPORTER_URI:-http://penpot-exporter:6061}
|
||||
export PENPOT_HTTP_SERVER_MAX_BODY_SIZE=${PENPOT_HTTP_SERVER_MAX_BODY_SIZE:-367001600} # Default to 350MiB
|
||||
@ -58,12 +76,12 @@ export PENPOT_IPV6_LISTEN_DIRECTIVE=${PENPOT_IPV6_LISTEN_DIRECTIVE:-"listen [::]
|
||||
if is_truthy "${PENPOT_DISABLE_IPV6_LISTEN:-}"; then
|
||||
export PENPOT_IPV6_LISTEN_DIRECTIVE=""
|
||||
fi
|
||||
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_HTTP_SERVER_MAX_BODY_SIZE,\$PENPOT_IPV6_LISTEN_DIRECTIVE" \
|
||||
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_HTTP_SERVER_MAX_BODY_SIZE,\$PENPOT_IPV6_LISTEN_DIRECTIVE,\$PENPOT_PUBLIC_PATH,\$PENPOT_PUBLIC_PATH_DIRECTIVES" \
|
||||
< /tmp/nginx.conf.template > /etc/nginx/nginx.conf
|
||||
|
||||
if [[ $PENPOT_FLAGS == *"enable-admin-console"* ]]; then
|
||||
export PENPOT_ADMIN_CONSOLE_URI=${PENPOT_ADMIN_CONSOLE_URI:-http://penpot-admin-console:3000}
|
||||
envsubst "\$PENPOT_ADMIN_CONSOLE_URI" \
|
||||
envsubst "\$PENPOT_ADMIN_CONSOLE_URI,\$PENPOT_PUBLIC_PATH_REGEX" \
|
||||
< /tmp/nginx-admin-console-locations.conf.template > /etc/nginx/overrides/server.d/admin-console-locations.conf
|
||||
else
|
||||
rm -f /etc/nginx/overrides/server.d/admin-console-locations.conf
|
||||
|
||||
@ -102,6 +102,8 @@ http {
|
||||
|
||||
root /var/www/app/;
|
||||
|
||||
${PENPOT_PUBLIC_PATH_DIRECTIVES}
|
||||
|
||||
location @handle_redirect {
|
||||
set $redirect_uri "$upstream_http_location";
|
||||
set $redirect_host "$upstream_http_x_host";
|
||||
@ -167,7 +169,7 @@ http {
|
||||
location /readyz {
|
||||
access_log off;
|
||||
proxy_set_header Host $proxy_host;
|
||||
proxy_pass $PENPOT_BACKEND_URI$request_uri;
|
||||
proxy_pass $PENPOT_BACKEND_URI$uri$is_args$args;
|
||||
}
|
||||
|
||||
location /ws/notifications {
|
||||
@ -198,7 +200,7 @@ http {
|
||||
}
|
||||
|
||||
location ~ ^/[^/]+/(.*)$ {
|
||||
return 301 " /404";
|
||||
return 301 " ${PENPOT_PUBLIC_PATH}404";
|
||||
}
|
||||
|
||||
if ($penpot_link_preview_agent) {
|
||||
|
||||
@ -384,16 +384,11 @@ PENPOT_PUBLIC_URI: https://mycompany.com/penpot/
|
||||
|
||||
Your reverse proxy or Ingress must also:
|
||||
|
||||
- Route requests under `/penpot/` to the Penpot frontend.
|
||||
- Remove the `/penpot` prefix before forwarding each request. The frontend
|
||||
continues to serve its internal routes from `/`.
|
||||
- Route requests under `/penpot/` to the Penpot frontend without rewriting the
|
||||
path. The frontend expects to receive the subpath.
|
||||
- Preserve WebSocket upgrade headers for notifications and other services that
|
||||
use WebSockets.
|
||||
|
||||
Changing only `PENPOT_PUBLIC_URI` is not enough. Without the matching proxy
|
||||
rewrite, static assets, API calls, and WebSocket connections will use routes that
|
||||
the frontend does not recognize.
|
||||
|
||||
#### Migrate an existing installation to a subpath
|
||||
|
||||
Moving an installation from `/` to a subpath does not require a database or
|
||||
@ -403,7 +398,7 @@ the existing storage.
|
||||
Before changing the public URL:
|
||||
|
||||
1. Back up the database, assets, and current deployment configuration.
|
||||
2. Configure the proxy or Ingress route and prefix rewrite.
|
||||
2. Configure the proxy or Ingress route for the subpath.
|
||||
3. Update `PENPOT_PUBLIC_URI` for every Penpot component that uses it.
|
||||
4. Keep the existing `PENPOT_SECRET_KEY`, database, and asset storage
|
||||
configuration.
|
||||
|
||||
@ -325,8 +325,7 @@ First, set the complete public URL in `docker-compose.yaml`:
|
||||
PENPOT_PUBLIC_URI: https://mycompany.com/penpot/
|
||||
```
|
||||
|
||||
Then route the subpath to the Penpot frontend and remove the prefix before
|
||||
forwarding the request:
|
||||
Then route the subpath to the Penpot frontend, forwarding the path unchanged:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
@ -344,8 +343,6 @@ server {
|
||||
}
|
||||
|
||||
location /penpot/ {
|
||||
rewrite ^/penpot/(.*)$ /$1 break;
|
||||
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Scheme $scheme;
|
||||
@ -359,9 +356,10 @@ server {
|
||||
}
|
||||
```
|
||||
|
||||
This single location also covers the API and WebSocket endpoints because NGINX
|
||||
forwards them after removing `/penpot`. Keep the trailing slash in
|
||||
`PENPOT_PUBLIC_URI`.
|
||||
This single location covers every Penpot endpoint, WebSockets included. Keep
|
||||
the trailing slash in `PENPOT_PUBLIC_URI`, and do not add a path to `proxy_pass`
|
||||
(such as `http://localhost:9001/`): NGINX would then replace the `/penpot/`
|
||||
prefix instead of forwarding it.
|
||||
|
||||
### Example with CADDY SERVER
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user