3.0 KiB
Production deployment
This stack exposes only Caddy on ports 80 and 443. PostgreSQL and the FastAPI backend
are isolated in the private Docker network. Caddy serves the Vite SPA and proxies public
/api/* requests to the backend after removing the /api prefix.
First deployment
-
Point the
malenia.spaceA record to the VPS public IPv4 address. -
Install Docker Engine and the Docker Compose plugin on the VPS. Allow only TCP 22, 80, and 443 through the host firewall.
-
Clone the repository to
/opt/maleniaand create/opt/malenia/.envfrom.env.example. Set file mode600and replace all placeholder values. -
Put
private.pemandpublic.pemin/opt/malenia/keys, setKEYS_DIR=/opt/malenia/keysin.env, then grant access only to the backend container user:sudo chown -R 10001:10001 /opt/malenia/keys sudo chmod 700 /opt/malenia/keys sudo chmod 600 /opt/malenia/keys/*.pem -
Build and start PostgreSQL, then apply migrations:
docker compose --env-file .env -f compose.production.yml up -d postgres docker compose --env-file .env -f compose.production.yml --profile tools run --rm migrate -
Start the application:
docker compose --env-file .env -f compose.production.yml up -d --build -
Verify
https://malenia.space/api/health/and the primary frontend flows.
Releases
Before each release, run frontend checks locally. On the VPS, pull the intended revision, apply migrations, then rebuild and recreate services:
git pull --ff-only
docker compose --env-file .env -f compose.production.yml --profile tools run --rm migrate
docker compose --env-file .env -f compose.production.yml up -d --build
The Pally callback URL is https://malenia.space/api/payments/pally/result.
Local production-like test
This test uses the same images, API proxying, and rate-limit plugin as production, but
serves HTTP on http://localhost:8080. It does not need a domain, TLS certificate, or VPS.
-
Create a local
.envfrom.env.exampleand fill the required backend integration settings.POSTGRES_HOST=postgres,PRIVATE_KEY_FP=/run/secrets/keys/private.pem, andPUBLIC_KEY_FP=/run/secrets/keys/public.pemmust remain unchanged. The localkeys/directory must contain both PEM files. -
Start PostgreSQL and apply migrations:
docker compose --env-file .env -f compose.production.yml -f compose.local.yml up -d postgres docker compose --env-file .env -f compose.production.yml -f compose.local.yml --profile tools run --rm migrate -
Build and run the stack:
docker compose --env-file .env -f compose.production.yml -f compose.local.yml up -d --build -
Check the frontend at
http://localhost:8080and the backend through Caddy athttp://localhost:8080/api/health/. -
Stop the local stack while preserving its database volume:
docker compose --env-file .env -f compose.production.yml -f compose.local.yml down