# 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 1. Point the `malenia.space` A record to the VPS public IPv4 address. 2. Install Docker Engine and the Docker Compose plugin on the VPS. Allow only TCP 22, 80, and 443 through the host firewall. 3. Clone the repository to `/opt/malenia` and create `/opt/malenia/.env` from `.env.example`. Set file mode `600` and replace all placeholder values. 4. Put `private.pem` and `public.pem` in `/opt/malenia/keys`, set `KEYS_DIR=/opt/malenia/keys` in `.env`, then grant access only to the backend container user: ```bash sudo chown -R 10001:10001 /opt/malenia/keys sudo chmod 700 /opt/malenia/keys sudo chmod 600 /opt/malenia/keys/*.pem ``` 5. Build and start PostgreSQL, then apply migrations: ```bash 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 ``` 6. Start the application: ```bash docker compose --env-file .env -f compose.production.yml up -d --build ``` 7. 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: ```bash 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. 1. Create a local `.env` from `.env.example` and fill the required backend integration settings. `POSTGRES_HOST=postgres`, `PRIVATE_KEY_FP=/run/secrets/keys/private.pem`, and `PUBLIC_KEY_FP=/run/secrets/keys/public.pem` must remain unchanged. The local `keys/` directory must contain both PEM files. 2. Start PostgreSQL and apply migrations: ```bash 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 ``` 3. Build and run the stack: ```bash docker compose --env-file .env -f compose.production.yml -f compose.local.yml up -d --build ``` 4. Check the frontend at `http://localhost:8080` and the backend through Caddy at `http://localhost:8080/api/health/`. 5. Stop the local stack while preserving its database volume: ```bash docker compose --env-file .env -f compose.production.yml -f compose.local.yml down ```