Files
malenia-web/deploy

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

  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:

    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:

    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:

    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:

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. The local Compose override runs backend commands as root only to read host-owned PEM files with mode 0600; this override must not be used in production.

  2. 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
    
  3. Build and run the stack:

    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:

    docker compose --env-file .env -f compose.production.yml -f compose.local.yml down