Add prod nginx image with clearnet, onion, and SPA build.

Multi-stage frontend embed, envsubst entrypoint for TLS vhosts and upload body limits, shared locations with minimal response headers and zero access logs.
This commit is contained in:
2026-10-28 20:56:21 +01:00
parent 4385a024a8
commit cb37a1e23c
6 changed files with 177 additions and 0 deletions
View File
+46
View File
@@ -0,0 +1,46 @@
FROM node:22.23.4-alpine AS frontend-builder
WORKDIR /frontend
COPY frontend/package*.json .
COPY frontend/patches ./patches
RUN npm ci
COPY frontend/ .
ARG VITE_API_BASE_URL
ARG VITE_CLEARNET_SCHEME
ARG VITE_CLEARNET_DOMAIN
ARG VITE_ONION_HOSTNAME
ARG VITE_ALTCHA_ENABLED
ARG VITE_ALTCHA_MAX_NUMBER
ARG VITE_UPLOAD_MAX_QUEUE_SIZE
ARG VITE_VALIDATION_UPLOAD_MAX_VIEWS_MAX
ARG VITE_VALIDATION_UPLOAD_MAX_IMAGE_BYTES
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL \
VITE_CLEARNET_SCHEME=$VITE_CLEARNET_SCHEME \
VITE_CLEARNET_DOMAIN=$VITE_CLEARNET_DOMAIN \
VITE_ONION_HOSTNAME=$VITE_ONION_HOSTNAME \
VITE_ALTCHA_ENABLED=$VITE_ALTCHA_ENABLED \
VITE_ALTCHA_MAX_NUMBER=$VITE_ALTCHA_MAX_NUMBER \
VITE_UPLOAD_MAX_QUEUE_SIZE=$VITE_UPLOAD_MAX_QUEUE_SIZE \
VITE_VALIDATION_UPLOAD_MAX_VIEWS_MAX=$VITE_VALIDATION_UPLOAD_MAX_VIEWS_MAX \
VITE_VALIDATION_UPLOAD_MAX_IMAGE_BYTES=$VITE_VALIDATION_UPLOAD_MAX_IMAGE_BYTES
RUN npm run build
FROM nginx:1.29.4-alpine
RUN apk add --no-cache gettext
COPY --from=frontend-builder /frontend/dist /usr/share/nginx/html
COPY nginx/conf.d/ /etc/nginx/templates/conf.d/
COPY nginx/snippets/private-image-locations.conf.template /etc/nginx/templates/snippets/
COPY nginx/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]
+28
View File
@@ -0,0 +1,28 @@
server {
listen 80;
server_name ${CLEARNET_DOMAIN} www.${CLEARNET_DOMAIN};
access_log off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name ${CLEARNET_DOMAIN} www.${CLEARNET_DOMAIN};
access_log off;
ssl_certificate /etc/nginx/certs/live/${CLEARNET_DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/live/${CLEARNET_DOMAIN}/privkey.pem;
${ONION_LOCATION_HEADER}
include /etc/nginx/snippets/private-image-locations.conf;
}
+8
View File
@@ -0,0 +1,8 @@
server {
listen 8080;
server_name _;
access_log off;
include /etc/nginx/snippets/private-image-locations.conf;
}
+47
View File
@@ -0,0 +1,47 @@
#!/bin/sh
set -eu
max_image_bytes="${VALIDATION_UPLOAD_MAX_IMAGE_BYTES:?}"
json_padding="${VALIDATION_UPLOAD_JSON_BODY_PADDING_BYTES:?}"
UPLOAD_MAX_REQUEST_BODY_BYTES=$(( (max_image_bytes * 4 + 2) / 3 + json_padding ))
export UPLOAD_MAX_REQUEST_BODY_BYTES BACKEND_PORT
render_locations() {
envsubst '${BACKEND_PORT} ${UPLOAD_MAX_REQUEST_BODY_BYTES}' \
< /etc/nginx/templates/snippets/private-image-locations.conf.template \
> /etc/nginx/snippets/private-image-locations.conf
}
render_server() {
template_path="$1"
output_path="$2"
surface="$3"
if [ "$surface" = "clearnet" ]; then
if [ -n "${ONION_HOSTNAME:-}" ]; then
export ONION_LOCATION_HEADER="add_header Onion-Location \"http://${ONION_HOSTNAME}\$request_uri\";"
else
export ONION_LOCATION_HEADER=""
fi
export CLEARNET_DOMAIN
envsubst '${CLEARNET_DOMAIN} ${ONION_LOCATION_HEADER}' \
< "$template_path" \
> "$output_path"
return
fi
cp "$template_path" "$output_path"
}
mkdir -p /etc/nginx/snippets
render_locations
render_server /etc/nginx/templates/conf.d/clearnet.conf.template /etc/nginx/conf.d/clearnet.conf clearnet
render_server /etc/nginx/templates/conf.d/onion.conf.template /etc/nginx/conf.d/onion.conf onion
exec nginx -g 'daemon off;'
@@ -0,0 +1,48 @@
location ^~ /i/ {
add_header X-Content-Type-Options "nosniff" always;
add_header Cross-Origin-Resource-Policy "cross-origin" always;
add_header Cache-Control "private, no-store" always;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://backend:${BACKEND_PORT};
}
location /api {
client_max_body_size ${UPLOAD_MAX_REQUEST_BODY_BYTES};
add_header Cache-Control "no-store" always;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
proxy_pass http://backend:${BACKEND_PORT};
}
location /pgadmin {
proxy_set_header X-Script-Name /pgadmin;
proxy_set_header Host $host;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass_header Set-Cookie;
proxy_pass http://pgadmin;
proxy_redirect off;
}
location / {
add_header Referrer-Policy "no-referrer" always;
add_header Cache-Control "no-store" always;
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}