Deployment¶
Last Updated: 2026-08-11
Dashboard¶
| Property | Value |
|---|---|
| Namespace | heezy |
| Deployment | heezy-finance, 1 replica |
| Image | 025066240222.dkr.ecr.us-east-2.amazonaws.com/heezy-finance:latest, imagePullPolicy: Always |
| Container port | 80 |
| Service | heezy-finance ClusterIP :80, heezy-finance-nodeport NodePort 30860 |
| Public URL | finance.heezy.info via SWAG (192.168.1.25) and Cloudflare Access |
| Strategy | Recreate |
| Resources | requests 100m / 256Mi, limits 500m / 512Mi |
| Volume | heezy-finance-config PVC (2Gi, Longhorn, RWO) at /data |
| Manifests | heezy-k8s/apps/heezy-finance/ |
No node affinity and no nodeSelector. Both were removed after the 2026-06-20 nebula-5 outage. The
pod schedules anywhere. The RWO Longhorn PVC is the reason the strategy is Recreate rather than
RollingUpdate: two pods cannot attach the volume at once, so a rolling deploy would deadlock on a
multi-attach error.
Environment¶
Database credentials come from the heezy-finance-postgres-credentials secret, projected by an
ExternalSecret from OpenBao production/heezy/postgres/heezy-credentials. DB_HOST is
bigboi.heezy.local, resolved by dnsmasq.
The deployment also carries envFrom: heezy-finance-gmail even though the web app makes no Gmail
calls. That is deliberate: running amazon_orders.py by hand inside the pod then picks up the same
OpenBao-backed credential the CronJob uses, instead of the stale file that used to be mounted at
/credentials. The file fallback is gone and a test asserts it stays gone.
Scheduled Jobs¶
All three run the same heezy-finance:latest image with a different command, all with
concurrencyPolicy: Forbid.
| CronJob | Schedule | Command | Purpose |
|---|---|---|---|
heezy-finance-sync |
0 * * * * |
amazon_orders.py --hours 2 |
Gmail order ingest |
heezy-statement-scanner |
30 * * * * |
scan_statements.py |
Parses PDFs from /ingest/new on the nfs-heezy-ingest PVC |
heezy-budget-alerts |
0 14 * * * |
budget_alerts.py |
Posts budget warnings to Discord |
The scanner is offset to :30 so it does not contend with the sync at :00.
heezy-finance-sync carries a podAffinity requiring the same node as the heezy-finance pod. The
other two set affinity: {} to explicitly clear inherited affinity and run anywhere.
heezy-budget-alerts reads DISCORD_WEBHOOK_URL from the heezy-finance-discord secret, marked
optional: true, so a missing webhook degrades to a no-op rather than a crash loop.
Related Deployments¶
| Deployment | Purpose |
|---|---|
statements |
Statement upload web UI at statements.heezy.info. Writes files to the NFS ingest path, does no parsing. NodePort 30851 |
receipts |
Receipt OCR at receipts.heezy.info. NodePort 30850 |
Deploy Pipeline¶
edit dockerfiles/heezy-finance/*
→ git push gitea main (heezy-containers)
→ .gitea/workflows/test-heezy-finance.yml pytest + coverage gate (floor 13)
→ .gitea/workflows/build.yml builds and pushes :latest to ECR
→ .gitea/workflows/deploy-heezy-finance.yml clones heezy-k8s, commits .deploy-trigger
→ heezy-k8s/auto-deploy.yaml kubectl apply -k, rollout restart, rollout status
Push to Gitea, not GitHub. GitHub is a read-only mirror and triggers nothing.
Because :latest is a moving tag, the deploy step relies on rollout restart to force a re-pull.
The three CronJobs pick up the new image on their next scheduled run, no restart needed.
See CI/CD for the full pipeline.
Health Checks¶
kubectl get pods -n heezy -l app=heezy-finance
kubectl logs -n heezy -l app=heezy-finance --tail=50
kubectl get cronjob -n heezy | grep heezy
curl -s http://192.168.1.15:30860/api/latest-data-period
There are no liveness or readiness probes on the deployment. A pod that starts but fails to reach
Postgres reports Running and returns 500s.
Open Infrastructure Items¶
| Item | Notes |
|---|---|
| No liveness or readiness probe | A broken DB connection looks healthy to k8s |
| PVC is RWO | Recreate strategy means a few seconds of downtime on every deploy. RWX or dropping the PVC entirely would remove it |
| PVC necessity unaudited | /data is mounted but the app's state lives in Postgres. Worth checking whether the volume is needed at all |