first commit
This commit is contained in:
@@ -0,0 +1,121 @@
|
|||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# sitomat — CONTROL PLANE (ADR-0009 §10, step 1)
|
||||||
|
#
|
||||||
|
# api (Elysia/Bun) :13000 ─┐
|
||||||
|
# web (Next.js/Bun) :13001 ─┼─ one compose stack, one host
|
||||||
|
# volume `sitomat-data` ─┘ (SQLite + uploads)
|
||||||
|
#
|
||||||
|
# The DATA PLANE (Airflow, MinIO, Trino/StarRocks, OpenMetadata) is a SEPARATE
|
||||||
|
# stack on a SEPARATE host — see ADR-0009 §9. Do not add it here.
|
||||||
|
#
|
||||||
|
# DEPLOY — NO BUILD: the api/web images are pulled from a private registry
|
||||||
|
# (SITOMAT_IMAGE_REGISTRY / SITOMAT_IMAGE_TAG). A deploy host needs only this
|
||||||
|
# file + docker/ + docker/.env — no source, no toolchain.
|
||||||
|
#
|
||||||
|
# cp docker/env.example docker/.env # then edit it
|
||||||
|
# docker compose --env-file docker/.env -f docker-compose.control-plane.yml pull
|
||||||
|
# docker compose --env-file docker/.env -f docker-compose.control-plane.yml \
|
||||||
|
# run --rm api bun run --cwd apps/api db:migrate # first boot only
|
||||||
|
# docker compose --env-file docker/.env -f docker-compose.control-plane.yml up -d
|
||||||
|
#
|
||||||
|
# Optional reverse proxy (HTTP; add TLS when the real domain exists):
|
||||||
|
# docker compose --env-file docker/.env -f docker-compose.control-plane.yml \
|
||||||
|
# --profile proxy up -d
|
||||||
|
#
|
||||||
|
# BUILD + PUSH (maintainer host, WITH the source) — see docker/build-push.sh,
|
||||||
|
# which layers docker-compose.build.yml on top of this file:
|
||||||
|
# docker/build-push.sh
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
name: sitomat-control-plane
|
||||||
|
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
# PULL-ONLY: this file is the DEPLOYMENT file. It has NO `build:` section, so
|
||||||
|
# a deploy host needs neither the source nor a toolchain — only the image.
|
||||||
|
# Build + push live in docker-compose.build.yml (see docker/build-push.sh).
|
||||||
|
image: ${SITOMAT_IMAGE_REGISTRY:-sitomat}/api:${SITOMAT_IMAGE_TAG:-latest}
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
PORT: "13000"
|
||||||
|
DB_PATH: /data/sitomat.db
|
||||||
|
WORKSPACE_UPLOAD_DIR: /data/uploads
|
||||||
|
# ── Auth / origin ──────────────────────────────────────────────────────
|
||||||
|
# `BETTER_AUTH_SECRET` is REQUIRED; the `:?` guard makes compose refuse to
|
||||||
|
# start without it instead of silently using a dev fallback.
|
||||||
|
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?set BETTER_AUTH_SECRET in docker/.env}
|
||||||
|
BETTER_AUTH_URL: ${API_PUBLIC_URL:-http://localhost:13000}
|
||||||
|
WEB_URL: ${WEB_PUBLIC_URL:-http://localhost:13001}
|
||||||
|
# Empty = host-only session cookie (single-host). Set to the shared parent
|
||||||
|
# domain (e.g. `.instansi.go.id`) only when web & API are on subdomains.
|
||||||
|
SESSION_COOKIE_DOMAIN: ${SESSION_COOKIE_DOMAIN:-}
|
||||||
|
# ── Keycloak SSO — OPTIONAL: leave all three empty to disable it ───────
|
||||||
|
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-}
|
||||||
|
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-}
|
||||||
|
OIDC_ISSUER_URL: ${OIDC_ISSUER_URL:-}
|
||||||
|
volumes:
|
||||||
|
# SQLite + uploads live here. Keep it a LOCAL named volume: the DB runs in
|
||||||
|
# WAL mode and SQLite locking breaks on network filesystems (NFS/SMB).
|
||||||
|
- sitomat-data:/data
|
||||||
|
ports:
|
||||||
|
- "${API_PORT:-13000}:13000"
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- CMD
|
||||||
|
- bun
|
||||||
|
- -e
|
||||||
|
- "fetch('http://127.0.0.1:13000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 20s
|
||||||
|
|
||||||
|
web:
|
||||||
|
# PULL-ONLY (see the `api` note above). The web build args — including the
|
||||||
|
# DOMAIN-AGNOSTIC `NEXT_PUBLIC_API_URL=""` — are baked when the image is
|
||||||
|
# BUILT (docker-compose.build.yml), not here.
|
||||||
|
image: ${SITOMAT_IMAGE_REGISTRY:-sitomat}/web:${SITOMAT_IMAGE_TAG:-latest}
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
# Server-side Eden client (Server Components / Actions) reaches the API by
|
||||||
|
# its compose service name — this is NOT the browser-facing origin.
|
||||||
|
API_URL: http://api:13000
|
||||||
|
# The web app's OWN public origin. The demo-login Server Action calls the
|
||||||
|
# API server-side and must present the SAME `Origin` the API trusts —
|
||||||
|
# better-auth rejects a mismatched one with 403 INVALID_ORIGIN. Without
|
||||||
|
# this the action falls back to `http://localhost:13001` and demo login
|
||||||
|
# fails on any host that is not localhost. MUST be a SINGLE origin (the
|
||||||
|
# action puts it straight into the Origin header); if WEB_PUBLIC_URL is a
|
||||||
|
# comma-separated trust list, override this on the web service.
|
||||||
|
WEB_URL: ${WEB_PUBLIC_URL:-http://localhost:13001}
|
||||||
|
depends_on:
|
||||||
|
api:
|
||||||
|
condition: service_healthy
|
||||||
|
ports:
|
||||||
|
- "${WEB_PORT:-13001}:13001"
|
||||||
|
|
||||||
|
# ── OPTIONAL reverse proxy (profile "proxy") — SINGLE DOMAIN (Opsi A) ───────
|
||||||
|
# One origin on :8080 serves BOTH the web and the API, split by PATH: a small
|
||||||
|
# set of API prefixes (`/api/`, `/public/`, `/ingest/`, `/docs`, `/health`,
|
||||||
|
# the `/datasets/…/download` regex) goes to the API, everything else to the
|
||||||
|
# web. Add TLS + the real domain when it exists — see
|
||||||
|
# docker/nginx/templates/default.conf.template.
|
||||||
|
proxy:
|
||||||
|
image: nginx:1.27-alpine
|
||||||
|
profiles: ["proxy"]
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
- web
|
||||||
|
environment:
|
||||||
|
# The SINGLE public hostname the proxy answers for (envsubst → server_name).
|
||||||
|
# One origin serves both the web UI and the API, split by path.
|
||||||
|
PUBLIC_HOST: ${PUBLIC_HOST:-localhost}
|
||||||
|
volumes:
|
||||||
|
- ./docker/nginx/templates:/etc/nginx/templates:ro
|
||||||
|
ports:
|
||||||
|
- "${PROXY_PORT:-8080}:80"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
sitomat-data:
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# sitomat control plane — copy to `docker/.env` and edit.
|
||||||
|
# cp docker/env.example docker/.env
|
||||||
|
#
|
||||||
|
# (Named `env.example`, not `.env.example`, on purpose: the tooling policy in
|
||||||
|
# this repo blocks files matching `*.env*`. The copy target `docker/.env` IS
|
||||||
|
# gitignored and is excluded from the image build context.)
|
||||||
|
#
|
||||||
|
# SINGLE DOMAIN (Opsi A): the reverse proxy serves the web UI AND the API on ONE
|
||||||
|
# origin, split by path. See docs/deployment.md → "Docker" and the nginx
|
||||||
|
# template at docker/nginx/templates/default.conf.template.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# ── REQUIRED ─────────────────────────────────────────────────────────────────
|
||||||
|
# Generate a fresh one — never reuse an example value:
|
||||||
|
# openssl rand -base64 32
|
||||||
|
BETTER_AUTH_SECRET=aLNwbyLRHByb9T9DWFpR4Ck88WAk9kuMGXAwLu6eV6g=
|
||||||
|
|
||||||
|
# ── REGISTRY IMAGES ──────────────────────────────────────────────────────────
|
||||||
|
# Which images the DEPLOY file pulls. Set BOTH here on the build host (used by
|
||||||
|
# docker/build-push.sh) and on EVERY deploy host. Example:
|
||||||
|
# SITOMAT_IMAGE_REGISTRY=registry.contoh.go.id/sitomat
|
||||||
|
# SITOMAT_IMAGE_TAG=1.0.0
|
||||||
|
# Deploy host then only needs: docker compose … pull && docker compose … up -d
|
||||||
|
SITOMAT_IMAGE_REGISTRY=registry.sicabe.com/sitomat
|
||||||
|
SITOMAT_IMAGE_TAG=1.0.0
|
||||||
|
|
||||||
|
# ── PUBLIC ORIGIN — ONE domain for web + API (Opsi A) ────────────────────────
|
||||||
|
# Both values are the SAME single origin; the proxy splits it by path.
|
||||||
|
# • no domain yet → server IP + proxy port, e.g. http://192.168.1.10:8080
|
||||||
|
# • with TLS → https://sitomat.contoh.go.id
|
||||||
|
# Exact origin: scheme + host + port, NO trailing slash.
|
||||||
|
#
|
||||||
|
# These are RUNTIME values (BETTER_AUTH_URL / WEB_URL) — change + `up -d` only.
|
||||||
|
API_PUBLIC_URL=http://localhost:8080
|
||||||
|
WEB_PUBLIC_URL=http://localhost:8080
|
||||||
|
|
||||||
|
# ── WEB API BASE (build-time) — LEAVE EMPTY ──────────────────────────────────
|
||||||
|
# Empty (recommended) = same-origin: the browser client resolves the API against
|
||||||
|
# `window.location.origin` and download links are relative, so the web image is
|
||||||
|
# DOMAIN-AGNOSTIC — changing the public domain needs NO rebuild.
|
||||||
|
# Set an absolute origin ONLY for the two-domain (split) setup; then a domain
|
||||||
|
# change requires: docker compose … build web && docker compose … up -d web
|
||||||
|
WEB_API_BASE_URL=
|
||||||
|
|
||||||
|
# ── HOST PORTS (optional) ────────────────────────────────────────────────────
|
||||||
|
# The single public entry is the PROXY. The api/web ports are for direct
|
||||||
|
# debugging only — the proxy reaches them on the compose network.
|
||||||
|
API_PORT=13000
|
||||||
|
WEB_PORT=13001
|
||||||
|
PROXY_PORT=8080
|
||||||
|
|
||||||
|
# ── SESSION COOKIE DOMAIN — leave EMPTY in single-domain mode ────────────────
|
||||||
|
# One origin ⇒ host-only cookie, which is exactly right. Set this ONLY if you
|
||||||
|
# deliberately put the web and API on DIFFERENT subdomains (not Opsi A).
|
||||||
|
#SESSION_COOKIE_DOMAIN=
|
||||||
|
|
||||||
|
# ── KEYCLOAK SSO (optional) ──────────────────────────────────────────────────
|
||||||
|
# Leave ALL THREE empty to disable Keycloak entirely. Setting OIDC_CLIENT_ID
|
||||||
|
# without OIDC_ISSUER_URL makes the API refuse to boot (fail fast).
|
||||||
|
#OIDC_CLIENT_ID=
|
||||||
|
#OIDC_CLIENT_SECRET=
|
||||||
|
#OIDC_ISSUER_URL=
|
||||||
|
|
||||||
|
# ── WEB BUILD FLAGS ──────────────────────────────────────────────────────────
|
||||||
|
# 🚨 NEXT_PUBLIC_DEMO_LOGIN must stay `false` on any server others can reach:
|
||||||
|
# `true` lets anyone sign in as any role WITHOUT a password.
|
||||||
|
NEXT_PUBLIC_DEMO_LOGIN=true
|
||||||
|
NEXT_PUBLIC_KEYCLOAK_ENABLED=false
|
||||||
|
|
||||||
|
# ── REVERSE PROXY (profile "proxy") ──────────────────────────────────────────
|
||||||
|
# The single hostname the proxy answers for (envsubst → nginx `server_name`).
|
||||||
|
# With a real domain: PUBLIC_HOST=sitomat.contoh.go.id
|
||||||
|
PUBLIC_HOST=localhost
|
||||||
Executable
+55
@@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Build + push the sitomat control-plane images to a PRIVATE REGISTRY.
|
||||||
|
#
|
||||||
|
# Run from anywhere in the repo, on a host WITH the source. The DEPLOY host does
|
||||||
|
# not need this script (nor the source) — it only pulls.
|
||||||
|
#
|
||||||
|
# Requires a prior `docker login <registry>`.
|
||||||
|
#
|
||||||
|
# Env (usually from docker/.env — loaded automatically when present):
|
||||||
|
# SITOMAT_IMAGE_REGISTRY e.g. registry.contoh.go.id/sitomat
|
||||||
|
# SITOMAT_IMAGE_TAG e.g. 1.0.0 (or demo / prod / latest)
|
||||||
|
# NEXT_PUBLIC_DEMO_LOGIN true|false — BAKED into the web image
|
||||||
|
# NEXT_PUBLIC_KEYCLOAK_ENABLED true|false — BAKED into the web image
|
||||||
|
# WEB_API_BASE_URL empty = same-origin (recommended); set an
|
||||||
|
# absolute origin only for two-domain setups
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# SITOMAT_IMAGE_REGISTRY=registry.contoh.go.id/sitomat SITOMAT_IMAGE_TAG=1.0.0 \
|
||||||
|
# NEXT_PUBLIC_DEMO_LOGIN=false docker/build-push.sh
|
||||||
|
#
|
||||||
|
# # a DEMO image (demo login ON) — separate tag, because the flag is baked:
|
||||||
|
# SITOMAT_IMAGE_REGISTRY=registry.contoh.go.id/sitomat SITOMAT_IMAGE_TAG=demo \
|
||||||
|
# NEXT_PUBLIC_DEMO_LOGIN=true docker/build-push.sh
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
ENV_FILE="docker/.env"
|
||||||
|
ENV_FLAG=""
|
||||||
|
if [ -f "$ENV_FILE" ]; then
|
||||||
|
ENV_FLAG="--env-file $ENV_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
: "${SITOMAT_IMAGE_REGISTRY:?set SITOMAT_IMAGE_REGISTRY, e.g. registry.contoh.go.id/sitomat}"
|
||||||
|
: "${SITOMAT_IMAGE_TAG:?set SITOMAT_IMAGE_TAG, e.g. 1.0.0}"
|
||||||
|
|
||||||
|
FILES="-f docker-compose.control-plane.yml -f docker-compose.build.yml"
|
||||||
|
|
||||||
|
echo "==> building api + web (tag ${SITOMAT_IMAGE_TAG})"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
docker compose $ENV_FLAG $FILES build api web
|
||||||
|
|
||||||
|
echo "==> pushing api + web to ${SITOMAT_IMAGE_REGISTRY}"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
docker compose $ENV_FLAG $FILES push api web
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==> pushed:"
|
||||||
|
echo " ${SITOMAT_IMAGE_REGISTRY}/api:${SITOMAT_IMAGE_TAG}"
|
||||||
|
echo " ${SITOMAT_IMAGE_REGISTRY}/web:${SITOMAT_IMAGE_TAG}"
|
||||||
|
echo
|
||||||
|
echo "On a deploy host, set the SAME SITOMAT_IMAGE_REGISTRY/TAG in docker/.env,"
|
||||||
|
echo "then: docker compose --env-file docker/.env -f docker-compose.control-plane.yml pull && ... up -d"
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# sitomat control plane — copy to `docker/.env` and edit.
|
||||||
|
# cp docker/env.example docker/.env
|
||||||
|
#
|
||||||
|
# (Named `env.example`, not `.env.example`, on purpose: the tooling policy in
|
||||||
|
# this repo blocks files matching `*.env*`. The copy target `docker/.env` IS
|
||||||
|
# gitignored and is excluded from the image build context.)
|
||||||
|
#
|
||||||
|
# SINGLE DOMAIN (Opsi A): the reverse proxy serves the web UI AND the API on ONE
|
||||||
|
# origin, split by path. See docs/deployment.md → "Docker" and the nginx
|
||||||
|
# template at docker/nginx/templates/default.conf.template.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# ── REQUIRED ─────────────────────────────────────────────────────────────────
|
||||||
|
# Generate a fresh one — never reuse an example value:
|
||||||
|
# openssl rand -base64 32
|
||||||
|
BETTER_AUTH_SECRET=
|
||||||
|
|
||||||
|
# ── REGISTRY IMAGES ──────────────────────────────────────────────────────────
|
||||||
|
# Which images the DEPLOY file pulls. Set BOTH here on the build host (used by
|
||||||
|
# docker/build-push.sh) and on EVERY deploy host. Example:
|
||||||
|
# SITOMAT_IMAGE_REGISTRY=registry.contoh.go.id/sitomat
|
||||||
|
# SITOMAT_IMAGE_TAG=1.0.0
|
||||||
|
# Deploy host then only needs: docker compose … pull && docker compose … up -d
|
||||||
|
SITOMAT_IMAGE_REGISTRY=sitomat
|
||||||
|
SITOMAT_IMAGE_TAG=latest
|
||||||
|
|
||||||
|
# ── PUBLIC ORIGIN — ONE domain for web + API (Opsi A) ────────────────────────
|
||||||
|
# Both values are the SAME single origin; the proxy splits it by path.
|
||||||
|
# • no domain yet → server IP + proxy port, e.g. http://192.168.1.10:8080
|
||||||
|
# • with TLS → https://sitomat.contoh.go.id
|
||||||
|
# Exact origin: scheme + host + port, NO trailing slash.
|
||||||
|
#
|
||||||
|
# These are RUNTIME values (BETTER_AUTH_URL / WEB_URL) — change + `up -d` only.
|
||||||
|
API_PUBLIC_URL=http://localhost:8080
|
||||||
|
WEB_PUBLIC_URL=http://localhost:8080
|
||||||
|
|
||||||
|
# ── WEB API BASE (build-time) — LEAVE EMPTY ──────────────────────────────────
|
||||||
|
# Empty (recommended) = same-origin: the browser client resolves the API against
|
||||||
|
# `window.location.origin` and download links are relative, so the web image is
|
||||||
|
# DOMAIN-AGNOSTIC — changing the public domain needs NO rebuild.
|
||||||
|
# Set an absolute origin ONLY for the two-domain (split) setup; then a domain
|
||||||
|
# change requires: docker compose … build web && docker compose … up -d web
|
||||||
|
WEB_API_BASE_URL=
|
||||||
|
|
||||||
|
# ── HOST PORTS (optional) ────────────────────────────────────────────────────
|
||||||
|
# The single public entry is the PROXY. The api/web ports are for direct
|
||||||
|
# debugging only — the proxy reaches them on the compose network.
|
||||||
|
API_PORT=13000
|
||||||
|
WEB_PORT=13001
|
||||||
|
PROXY_PORT=8080
|
||||||
|
|
||||||
|
# ── SESSION COOKIE DOMAIN — leave EMPTY in single-domain mode ────────────────
|
||||||
|
# One origin ⇒ host-only cookie, which is exactly right. Set this ONLY if you
|
||||||
|
# deliberately put the web and API on DIFFERENT subdomains (not Opsi A).
|
||||||
|
#SESSION_COOKIE_DOMAIN=
|
||||||
|
|
||||||
|
# ── KEYCLOAK SSO (optional) ──────────────────────────────────────────────────
|
||||||
|
# Leave ALL THREE empty to disable Keycloak entirely. Setting OIDC_CLIENT_ID
|
||||||
|
# without OIDC_ISSUER_URL makes the API refuse to boot (fail fast).
|
||||||
|
#OIDC_CLIENT_ID=
|
||||||
|
#OIDC_CLIENT_SECRET=
|
||||||
|
#OIDC_ISSUER_URL=
|
||||||
|
|
||||||
|
# ── WEB BUILD FLAGS ──────────────────────────────────────────────────────────
|
||||||
|
# 🚨 NEXT_PUBLIC_DEMO_LOGIN must stay `false` on any server others can reach:
|
||||||
|
# `true` lets anyone sign in as any role WITHOUT a password.
|
||||||
|
NEXT_PUBLIC_DEMO_LOGIN=false
|
||||||
|
NEXT_PUBLIC_KEYCLOAK_ENABLED=false
|
||||||
|
|
||||||
|
# ── REVERSE PROXY (profile "proxy") ──────────────────────────────────────────
|
||||||
|
# The single hostname the proxy answers for (envsubst → nginx `server_name`).
|
||||||
|
# With a real domain: PUBLIC_HOST=sitomat.contoh.go.id
|
||||||
|
PUBLIC_HOST=localhost
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# sitomat — SINGLE-DOMAIN reverse proxy (Opsi A).
|
||||||
|
#
|
||||||
|
# ONE public origin serves BOTH the web UI and the API; routing is by PATH. A
|
||||||
|
# small, explicit set of API prefixes goes to the API, everything else to the
|
||||||
|
# web — which is what lets the deployment expose a single port / domain.
|
||||||
|
#
|
||||||
|
# WHY ONLY THESE PREFIXES: most API routes (`/datasets`, `/submissions`,
|
||||||
|
# `/services`, `/standards`, …) COLLIDE with web pages of the same name. They do
|
||||||
|
# not need a public path because the web calls them SERVER-SIDE (Eden →
|
||||||
|
# `http://api:13000`) over the internal network. Only endpoints the BROWSER — or
|
||||||
|
# an external M2M client — hits directly are routed here:
|
||||||
|
#
|
||||||
|
# /api/auth/* better-auth (login/session) ← NEXT_PUBLIC_API_URL
|
||||||
|
# /api/v1/* documented read API (F09-A)
|
||||||
|
# /public/* open-data downloads (F07)
|
||||||
|
# /ingest/v1/* push ingest (F09-B)
|
||||||
|
# /docs swagger
|
||||||
|
# /health health check
|
||||||
|
# /datasets/<id>/versions/<v>/download browser download (F02)
|
||||||
|
#
|
||||||
|
# The last one is a REGEX on purpose: it is the ONLY browser-facing API path at
|
||||||
|
# the ROOT, and a plain prefix would swallow the web's `/datasets/[id]` page.
|
||||||
|
#
|
||||||
|
# With web + API on ONE origin there is no cross-origin request, so CORS is moot
|
||||||
|
# and the session cookie stays host-only (no `SESSION_COOKIE_DOMAIN`).
|
||||||
|
#
|
||||||
|
# `${PUBLIC_HOST}` is substituted by the nginx image's envsubst step.
|
||||||
|
# This is HTTP; add `listen 443 ssl` + certificates when TLS terminates here.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Uploads (F02) can be several MB — nginx's 1m default would answer 413.
|
||||||
|
client_max_body_size 100m;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name ${PUBLIC_HOST};
|
||||||
|
|
||||||
|
# Inherited by every `location` below.
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
# better-auth checks the browser's Origin against WEB_URL; forward it as-is.
|
||||||
|
proxy_set_header Origin $http_origin;
|
||||||
|
|
||||||
|
# ── API ──────────────────────────────────────────────────────────────────
|
||||||
|
location = /health { proxy_pass http://api:13000; }
|
||||||
|
location /docs { proxy_pass http://api:13000; }
|
||||||
|
location /api/ { proxy_pass http://api:13000; }
|
||||||
|
location /public/ { proxy_pass http://api:13000; }
|
||||||
|
location /ingest/ { proxy_pass http://api:13000; }
|
||||||
|
|
||||||
|
# The one root-level browser API path (see the header note). Regex locations
|
||||||
|
# are evaluated BEFORE the prefix `location /` below, so this wins.
|
||||||
|
location ~ ^/datasets/[^/]+/versions/[^/]+/download$ {
|
||||||
|
proxy_pass http://api:13000;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Web (everything else) ────────────────────────────────────────────────
|
||||||
|
location / { proxy_pass http://web:13001; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user