mirror of
https://github.com/penpot/penpot.git
synced 2026-10-30 14:30:37 +00:00
* 🐳 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>
235 lines
8.0 KiB
Plaintext
235 lines
8.0 KiB
Plaintext
worker_processes auto;
|
|
pid /tmp/nginx.pid;
|
|
include /etc/nginx/overrides/main.d/*.conf;
|
|
|
|
events {
|
|
worker_connections 65535;
|
|
multi_accept on;
|
|
}
|
|
|
|
http {
|
|
client_body_temp_path /tmp/client_temp;
|
|
proxy_temp_path /tmp/proxy_temp_path;
|
|
fastcgi_temp_path /tmp/fastcgi_temp;
|
|
uwsgi_temp_path /tmp/uwsgi_temp;
|
|
scgi_temp_path /tmp/scgi_temp;
|
|
|
|
sendfile on;
|
|
tcp_nopush on;
|
|
tcp_nodelay on;
|
|
keepalive_requests 30;
|
|
keepalive_timeout 65;
|
|
types_hash_max_size 2048;
|
|
|
|
server_tokens off;
|
|
|
|
reset_timedout_connection on;
|
|
client_body_timeout 30s;
|
|
client_header_timeout 30s;
|
|
|
|
include /etc/nginx/mime.types;
|
|
default_type application/octet-stream;
|
|
|
|
error_log /dev/stderr;
|
|
|
|
# Access log enriched with upstream diagnostics: what the backend
|
|
# answered (us), how long it took to connect to it (uct), how long it
|
|
# took to answer (urt) and which internal address served the request
|
|
# (ua). With proxy_pass on variables there is no upstream keepalive,
|
|
# so uct measures one real TCP connection per request.
|
|
log_format penpot_upstream '$remote_addr - $remote_user [$time_local] "$request" '
|
|
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
|
|
'us=$upstream_status uct=$upstream_connect_time '
|
|
'urt=$upstream_response_time ua=$upstream_addr';
|
|
access_log /dev/stdout penpot_upstream;
|
|
|
|
proxy_connect_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
proxy_read_timeout 300s;
|
|
send_timeout 300s;
|
|
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_proxied any;
|
|
gzip_static on;
|
|
gzip_comp_level 6;
|
|
gzip_buffers 16 8k;
|
|
gzip_http_version 1.1;
|
|
|
|
gzip_types text/plain text/css text/javascript application/javascript application/json application/transit+json image/svg+xml application/wasm;
|
|
|
|
proxy_buffer_size 16k;
|
|
proxy_busy_buffers_size 24k; # essentially, proxy_buffer_size + 2 small buffers of 4k
|
|
proxy_buffers 32 4k;
|
|
|
|
map $http_upgrade $connection_upgrade {
|
|
default upgrade;
|
|
'' close;
|
|
}
|
|
|
|
# Link preview crawlers; their requests for the application root
|
|
# are served with dynamic Open Graph metadata from the backend.
|
|
map $http_user_agent $penpot_link_preview_agent {
|
|
default 0;
|
|
"~*(slackbot|discordbot|twitterbot|facebookexternalhit|facebookcatalog|whatsapp|telegrambot|linkedinbot|skypeuripreview|pinterestbot|redditbot|embedly|iframely|mastodon|bluesky|applebot|googlebot|bingbot|bingpreview|duckduckbot|mattermost-bot|opengraph[.]xyz|twitter card validator)" 1;
|
|
}
|
|
|
|
proxy_cache_path /tmp/cache/ levels=2:2 keys_zone=penpot:20m;
|
|
proxy_cache_methods GET HEAD;
|
|
proxy_cache_valid any 48h;
|
|
proxy_cache_key "$host$request_uri";
|
|
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $http_host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Scheme $scheme;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
|
|
include /etc/nginx/overrides/http.d/*.conf;
|
|
|
|
server {
|
|
listen 8080 default_server reuseport backlog=16384;
|
|
${PENPOT_IPV6_LISTEN_DIRECTIVE}
|
|
server_name _;
|
|
|
|
client_max_body_size $PENPOT_HTTP_SERVER_MAX_BODY_SIZE;
|
|
charset utf-8;
|
|
|
|
etag off;
|
|
proxy_hide_header X-Powered-By;
|
|
include /etc/nginx/nginx-security-headers.conf;
|
|
|
|
root /var/www/app/;
|
|
|
|
${PENPOT_PUBLIC_PATH_DIRECTIVES}
|
|
|
|
location @handle_redirect {
|
|
set $redirect_uri "$upstream_http_location";
|
|
set $redirect_host "$upstream_http_x_host";
|
|
set $redirect_cache_control "$upstream_http_cache_control";
|
|
set $real_mtype "$upstream_http_x_mtype";
|
|
|
|
proxy_buffering off;
|
|
|
|
proxy_set_header Host "$redirect_host";
|
|
proxy_set_header Authorization "";
|
|
proxy_hide_header etag;
|
|
proxy_hide_header x-amz-id-2;
|
|
proxy_hide_header x-amz-request-id;
|
|
proxy_hide_header x-amz-meta-server-side-encryption;
|
|
proxy_hide_header x-amz-server-side-encryption;
|
|
proxy_ssl_server_name on;
|
|
proxy_pass $redirect_uri;
|
|
|
|
include /etc/nginx/nginx-security-headers.conf;
|
|
add_header x-internal-redirect "$redirect_uri";
|
|
add_header x-cache-control "$redirect_cache_control";
|
|
add_header cache-control "$redirect_cache_control";
|
|
add_header content-type "$real_mtype";
|
|
}
|
|
|
|
location /assets {
|
|
proxy_set_header Host $proxy_host;
|
|
proxy_pass $PENPOT_BACKEND_URI/assets;
|
|
recursive_error_pages on;
|
|
proxy_intercept_errors on;
|
|
error_page 301 302 307 = @handle_redirect;
|
|
|
|
include /etc/nginx/overrides/assets.d/*.conf;
|
|
}
|
|
|
|
location /internal/assets {
|
|
internal;
|
|
alias /opt/data/assets;
|
|
include /etc/nginx/nginx-security-headers.conf;
|
|
add_header x-internal-redirect "$upstream_http_x_accel_redirect";
|
|
}
|
|
|
|
location = /link-preview {
|
|
proxy_pass $PENPOT_BACKEND_URI/link-preview$is_args$args;
|
|
}
|
|
|
|
location /api/export {
|
|
proxy_set_header Host $proxy_host;
|
|
proxy_pass $PENPOT_EXPORTER_URI;
|
|
}
|
|
|
|
location /api {
|
|
proxy_set_header Host $proxy_host;
|
|
proxy_pass $PENPOT_BACKEND_URI/api;
|
|
proxy_buffering off;
|
|
}
|
|
|
|
location /plugins {
|
|
alias /var/www/app/plugins;
|
|
proxy_http_version 1.1;
|
|
}
|
|
|
|
location /readyz {
|
|
access_log off;
|
|
proxy_set_header Host $proxy_host;
|
|
proxy_pass $PENPOT_BACKEND_URI$uri$is_args$args;
|
|
}
|
|
|
|
location /ws/notifications {
|
|
proxy_set_header Host $proxy_host;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection 'upgrade';
|
|
proxy_pass $PENPOT_BACKEND_URI/ws/notifications;
|
|
}
|
|
|
|
include /etc/nginx/overrides/server.d/*.conf;
|
|
|
|
location / {
|
|
include /etc/nginx/overrides/location.d/*.conf;
|
|
|
|
# Regenerated from the environment on every container start
|
|
# (see nginx-entrypoint.sh) while its URL is only versioned by
|
|
# the build, so a flags only restart leaves the URL untouched.
|
|
# Caching it like a build asset would keep returning users on
|
|
# the previous PENPOT_FLAGS for up to a week.
|
|
location = /js/config.js {
|
|
include /etc/nginx/nginx-security-headers.conf;
|
|
add_header Cache-Control "no-store, no-cache, max-age=0" always;
|
|
}
|
|
|
|
location ~* \.(js|css|jpg|png|svg|gif|ttf|woff|woff2|wasm|map)$ {
|
|
include /etc/nginx/nginx-security-headers.conf;
|
|
add_header Cache-Control "public, max-age=604800" always; # 7 days
|
|
}
|
|
|
|
location ~ ^/[^/]+/(.*)$ {
|
|
return 301 " ${PENPOT_PUBLIC_PATH}404";
|
|
}
|
|
|
|
if ($penpot_link_preview_agent) {
|
|
rewrite ^/$ /link-preview last;
|
|
}
|
|
|
|
include /etc/nginx/nginx-security-headers.conf;
|
|
add_header Cache-Control "no-store, no-cache, max-age=0" always;
|
|
try_files $uri /index.html$is_args$args /index.html =404;
|
|
|
|
}
|
|
}
|
|
|
|
# Dedicated health endpoint consumed by the nginx-prometheus-exporter
|
|
# sidecar (deployed by ops, scraping http://127.0.0.1:8082/stub_status).
|
|
# `listen 8082` binds every interface and sits out of the public server:
|
|
# a compose port mapping delivers traffic to the container address, never
|
|
# to loopback, so only the published port decides who gets in from
|
|
# outside the host. Counts client-side connections only; it says nothing
|
|
# about the upstream pools.
|
|
server {
|
|
listen 8082;
|
|
server_name _;
|
|
|
|
access_log off;
|
|
|
|
location = /stub_status {
|
|
stub_status;
|
|
}
|
|
}
|
|
}
|