3.2 KiB
Production deployment
This stack exposes only Caddy on ports 80 and 443. PostgreSQL and the FastAPI backend
share an internal Docker network. The backend additionally uses a non-published egress
network for DNS and outbound requests to Pally and Remnawave. 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. The local Compose override runs backend commands as root only to read host-owned PEM files with mode0600; this override must not be used in production. -
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