122 lines
5.9 KiB
YAML
122 lines
5.9 KiB
YAML
# ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
# 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:
|