Ticket: k8s overlay — Deployment, Service, private Tailscale ingress, SOPS secrets
Ticket: k8s overlay — Deployment, Service, private Tailscale ingress, SOPS secrets
Story:
Architecture:
Labels:
Blocks: Marcus onboarding (needs a live URL)
Blocked by: Postgres role, services-entry, Woodpecker pipeline (needs an image in Harbor)
story-westside-ops-spreadsheet-accessArchitecture:
arch-deployment-westside-opsLabels:
story:spreadsheet-access,arch:k8s-overlay,type:infra,track:devops,scope:plannedBlocks: Marcus onboarding (needs a live URL)
Blocked by: Postgres role, services-entry, Woodpecker pipeline (needs an image in Harbor)
Purpose
Write the k8s manifests inside
~/westside-ops/k8s/ that ArgoCD syncs: Deployment (pulling the Streamlit image from Harbor), Service, private Tailscale Ingress (tailscale.com/expose: "true" — not funnel), and SOPS-encrypted secrets. ArgoCD's Application resource already exists (created by services-entry) and is pointed at westside-ops repo's k8s path.Scope
- Write 5 files under
~/westside-ops/k8s/:kustomization.yaml,deployment.yaml,service.yaml,ingress.yaml,secrets.enc.yaml(SOPS-encrypted) - Use SOPS age encryption matching the existing
overlays/basketball-api/prod/harbor-creds.enc.yamlpattern — same recipient, samesopsconfig - Populate
secrets.enc.yamlwith:WESTSIDE_OPS_DATABASE_URL,KEYCLOAK_CLIENT_SECRET,COOKIE_SECRET(random 32 bytes) - Commit and push to Forgejo — ArgoCD will auto-sync within seconds
- Verify: pod starts, becomes ready, Ingress gets a Tailscale address,
curl -k https://westside-ops.tail5b443a.ts.netreturns a Streamlit HTML page (from within the tailnet)
Exact manifests
kustomization.yaml
deployment.yaml
service.yaml
ingress.yaml — the load-bearing file
secrets.enc.yaml (plaintext, to be encrypted with sops -e)
After populating the plaintext, encrypt:
sops -e -i secrets.enc.yaml. Verify with sops -d secrets.enc.yaml that it round-trips. Only the .enc.yaml version is committed.Acceptance Criteria
- [ ] All 5 files exist in
~/westside-ops/k8s/, committed and pushed to Forgejomain - [ ]
secrets.enc.yamlis SOPS-encrypted (not plaintext);sops -dround-trips successfully - [ ] ArgoCD application
westside-opsreportsSyncedandHealthy - [ ]
kubectl get pod -n westside-opsshows the pod inRunningstatus - [ ]
kubectl logs -n westside-ops deploy/westside-opsshows Streamlit bound to 0.0.0.0:8501 - [ ]
kubectl get ingress -n westside-opsshows the ingress with a Tailscale address assigned - [ ]
curl -k https://westside-ops.tail5b443a.ts.netfrom a tailnet member returns a Streamlit HTML page (redirect to Keycloak login) - [ ]
curl -k https://westside-ops.tail5b443a.ts.netfrom a non-tailnet member returns an error (DNS resolution or connection refused) — confirms the private mode is working - [ ] The pod's DB connection to basketball-api works (verify by logging in and loading the Players page — this moves into the Marcus onboarding ticket for end-to-end verification)
Files touched
~/westside-ops/k8s/kustomization.yaml~/westside-ops/k8s/deployment.yaml~/westside-ops/k8s/service.yaml~/westside-ops/k8s/ingress.yaml~/westside-ops/k8s/secrets.enc.yaml
Rollback
Revert the commit. ArgoCD syncs back to the empty state (the prior commit had
k8s/.gitkeep only). Pod is torn down. Namespace and Harbor project persist (owned by services-entry). Full rollback requires services-entry rollback.Out of scope
- NetworkPolicy — the existing
bases/standard/networkpolicy.yamlis disabled per the kube-router ipset bug comment in existing overlays. Don't add a NetworkPolicy until that bug is fixed cluster-wide. - HorizontalPodAutoscaler — single replica is fine for v1, Marcus is the only user
- ServiceMonitor / Prometheus scraping — Streamlit doesn't expose
/metricsby default, and telemetry for Marcus-use isn't load-bearing. Follow-up ticket if needed. - Cert-manager or any TLS setup — Tailscale operator handles TLS automatically
Dependencies
Blocked by: services-entry (namespace + harbor-creds secret), Postgres role (database URL password), streamlit-app implementation (the code), Woodpecker pipeline (the image in Harbor).