siftlode/deploy
npeter83 575bdefd0e chore(deploy): hardened prod compose, rollout script, and CI for asgard
docker-compose.prod.yml targets the public VPS: Postgres is never published, the
app binds to 127.0.0.1 (Caddy proxies it), and every service has a memory/CPU cap
plus no-new-privileges / cap_drop / read-only rootfs. deploy/deploy.sh rolls out
main with a host-side build (migrations run via the entrypoint). CI type-checks
and builds the frontend and byte-compiles the backend on every push to main.
2026-06-14 01:39:43 +02:00
..
deploy.sh chore(deploy): hardened prod compose, rollout script, and CI for asgard 2026-06-14 01:39:43 +02:00
README.md chore(deploy): hardened prod compose, rollout script, and CI for asgard 2026-06-14 01:39:43 +02:00

Deploying Subfeed to the public VPS (asgard)

The public test instance runs on the asgard VPS at https://subfeed.b1fr0st.eu, behind Caddy (which terminates TLS and reverse-proxies to the app on 127.0.0.1:8080).

Layout on asgard

/srv/subfeed/
  src/                 git clone of this repo (build context + compose file)
  .env                 secrets, mode 0600 — NEVER committed

The hardened compose is docker-compose.prod.yml: Postgres is never published, the app binds to localhost only, and every service is memory/CPU-capped for the small VPS.

First-time setup

  1. DNS A/AAAA for subfeed → asgard (done via the porkbun CLI).

  2. Caddy vhost block subfeed.b1fr0st.eu { reverse_proxy 127.0.0.1:8080 } (+ the shared security-headers snippet), then reload Caddy.

  3. git clone this repo to /srv/subfeed/src.

  4. Create /srv/subfeed/.env (mode 0600) with the required keys — see .env.example. Generate the secrets:

    • SECRET_KEY: python -c "import secrets; print(secrets.token_urlsafe(48))"
    • TOKEN_ENCRYPTION_KEY: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
    • POSTGRES_PASSWORD: any long random string.
    • OAUTH_REDIRECT_URL=https://subfeed.b1fr0st.eu/auth/callback
    • GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET / YOUTUBE_API_KEY from Google Cloud Console.
    • ALLOWED_EMAILS / ADMIN_EMAILS = the invited Google accounts.

    The app refuses to start in production (an https redirect URL) if SECRET_KEY is the placeholder/too short or TOKEN_ENCRYPTION_KEY is missing — by design.

Rolling out a new version

/srv/subfeed/src/deploy/deploy.sh

It pulls main, rebuilds the image on the host, and restarts the stack (migrations run automatically via the entrypoint's alembic upgrade head).

CI

.forgejo/workflows/ci.yml type-checks/builds the frontend and byte-compiles the backend on every push to main. It does not auto-deploy; rollout is the script above. (A future improvement: a Forgejo-triggered or watchtower-based auto-rollout.)