fogserv.cloud Deployment Runbook

Status: Active
Last Updated: 2026-08-26
Category: fogserv - Project Reference
Prerequisites: architecture-overview, ../cicd/deployment-automation.md
Time: 30 min read + deploy time
Tags: fogserv, deployment, k3s, caddy, longhorn, pvc, github-actions, secrets, rollback

Summary

How fogserv.cloud goes from a push to main to a running pod: GitHub Actions builds and pushes ghcr.io/fogserv/fogserv-cloud, then deploys over SSH to the node; the k3s manifest (Enterprise/apps/fogserv-cloud.yaml) defines Namespace, Deployment (with PVC-backed SQLite), Service, and Caddy Ingress. Includes go-live sequence, verification steps, and rollback.

Context / Why This Matters

Everything is built but production state lives in three places — Git manifests, on-cluster secrets, and the PVC database. This runbook is the single ordered list for (re)deploying from zero or updating an existing install, so no step (DNS? secret? PVC?) gets forgotten mid-outage. Authoritative sources: repo docs/DEPLOYMENT.md (go-live) and HANDOFF.md.

Implementation / Core Content

The pieces

Artifact Role
.github/workflows/ci.yml lint / type-check / build gate on PRs
.github/workflows/deploy-site.yml build image → GHCR → SSH deploy to node
.github/workflows/deploy-enterprise.yml cluster bootstrap (k3s, Caddy, Longhorn, Flux, Postgres, Forgejo) with run_bootstrap=true
Enterprise/apps/fogserv-cloud.yaml Namespace + Deployment + Service + Ingress (caddy class, host fogserv.cloud)
Secret fogserv-cloud-secrets envFrom into the container (DATABASE_URL, Mailgun keys, signing key)

Required GitHub repo secrets

DEPLOY_SSH_HOST, DEPLOY_SSH_USER, DEPLOY_SSH_KEY — used by the deploy workflow to reach the node.

Go-live sequence (first deploy)

  1. Add the three deploy secrets to the GitHub repo.

  2. Run Deploy Enterprise infrastructure with run_bootstrap=true → installs k3s, Caddy ingress, Longhorn storage, Flux, Postgres, Forgejo.

  3. Create the on-cluster secret:

    kubectl -n fogserv-cloud create secret generic fogserv-cloud-secrets \
      --from-literal=DATABASE_URL=file:/data/prod.db \
      --from-literal=MAILGUN_API_KEY=... \
      --from-literal=MAILGUN_WEBHOOK_SIGNING_KEY=...
    
  4. Point DNS A record for fogserv.cloud at the node IP (Caddy needs it for TLS issuance).

  5. Push to main (or run Deploy website manually). Smoke test runs automatically.

Updating an existing deployment

git push origin main            # pipeline: build → GHCR → SSH deploy
# watch it land:
kubectl -n fogserv-cloud rollout status deployment/fogserv-cloud
curl -fsS https://fogserv.cloud/api/health || curl -fsSI https://fogserv.cloud | head -3

KB content changes ship with the image (kb-browser) — article edits require this rebuild path too.

Persistence: SQLite on a PVC

Production DB is SQLite at /data/prod.db. It must live on a Longhorn PersistentVolume — a plain emptyDir or container filesystem destroys all users/posts/subscribers on every pod restart. Verify before assuming data survives:

kubectl -n fogserv-cloud get pvc
kubectl -n fogserv-cloud exec deploy/fogserv-cloud -- ls -la /data

If the manifest lacks the PVC mount, add it (standalone PVC mounted at /data, matching DATABASE_URL=file:/data/prod.db) before any real content exists.

Practical Examples

Rollback after a bad release — follow ../cicd/rollback-procedures.md: kubectl -n fogserv-cloud rollout undo deployment/fogserv-cloud, then git revert the bad commit so the next pipeline converges.

Verify TLSopenssl s_client -connect fogserv.cloud:443 -servername fogserv.cloud </dev/null 2>/dev/null | openssl x509 -noout -dates (Caddy auto-renews via ACME).

Common Pitfalls & Troubleshooting

Next Steps / Ops Actions

Sources & Related Articles

Change Log

2026-08-26

Choose Theme

Your selection is saved locally.

Neural Cacophony
Aperture v2
Flux v1
Mosaic Chaos
Nexus v1
Nexus Zest
Prism v2
Synapse