stacken.docs

Styrning för en växande portfölj av AI-tjänster

stacken.ai ↗

Ur repot

Hur deploys når prod (GitOps)

För: stacken-utvecklare. Gäller: customer-prod (Hetzner), plattformen-ägt.

Ursprung: skriven av plattformen-sidan 2026-07-26 efter att marknadssajten gått i drift som första app i customer-prod, och överlämnad hit eftersom modellen saknades i stacken-repot. Verifierad mot koden innan den lades in: charterna har mycket riktigt externalSecrets.enabled som grindar externalsecret.yaml, så false avaktiverar ESO-vägen utan att röra något annat.

TL;DR: Du deployar inte. Du uttrycker önskat läge i git — plattformens ArgoCD gör resten. Ändra tjänsten → bumpa appVersion i chartens Chart.yaml → merga main → CI bygger imagen → ArgoCD auto-syncar den till klustret. Ingen helm, ingen kubectl, ingen ESO.

Ägar-uppdelning (kickoff-sömmen)

  • stacken bygger images (GHCR) och definierar Helm-charts (charts/<svc>).
  • plattformen äger klustret och deklarerar vad som körs via ArgoCD.
  • Du har ingen kubeconfig och rör aldrig klustret direkt. Allt går via git.

Så här får du en ändring i drift

  1. Ändra tjänstens kod (services/<svc> / frontends/<svc>) eller charten (charts/<svc>).
  2. Bumpa appVersion i charts/<svc>/Chart.yaml. En innehållsändring = en appVersion-bump, så att det som körs syns i git. (Deploya på appVersion, aldrig latest.)
  3. Öppna PR → merga till main.
  4. CI (frontends.yml för frontends, _template.yml-mönstret för backend-tjänster) bygger + pushar imagen till ghcr.io/digitalist-se/<svc> med taggarna sha / appVersion / latest, med SBOM + provenance, trivy-scannad blockerande på HIGH/CRITICAL.
  5. ArgoCD på customer-prod följer main och auto-syncar (selfHeal + prune). Rullande uppdatering (PDB) → noll nedtid. Alembic-migrationer körs automatiskt som PreSync-hook före ny version rullas ut.

Din ändring är i drift inom någon minut efter merge. Ingen manuell åtgärd.

appVersion-bumpen är inte administration — den ÄR utrullningen

Steg 2 ovan är det enda steget som brukar glömmas, och konsekvensen är obehaglig eftersom ingenting går sönder. Utan bump renderar charten samma manifest som före mergen, ArgoCD ser ingen diff, och ingen utrullning sker. Din fix är byggd, publicerad — och körs inte. imagePullPolicy: IfNotPresent gör dessutom att en nod som redan har taggen cachad aldrig hämtar den nya.

Därtill blir appVersion en rörlig tagg om den aldrig ändras: två olika commits ger olika kod under samma namn, vilket är precis det latest kritiseras för. Det hände under migrations-uppstarten — fixar byggdes, klustret fortsatte köra gammal kod tills plattformen pinnade om för hand.

Grinden: chart-version.yml failar en PR som rör services/<svc>/ eller frontends/<namn>/ utan att motsvarande charts/<svc>/Chart.yaml:s appVersion ändrats i samma PR. Teständringar, docs/ och .md-filer träffar den inte — de hamnar inte i imagen (runtime-steget kopierar det installerade paketet och alembic/, inget annat). Ändringar i libs/** varnar men fäller inte: ett delat libb ligger i alla tjänsteimages, och att kräva elva bumpar per libbändring vore oproportionerligt — men att låta dem passera tyst vore att återskapa buggen.

Två versioner, två betydelser. version är chartens paketversion (bumpa när charten ändras), appVersion är applikationens (bumpa när koden ändras). De följs inte åt.

Secrets: SOPS, INTE external-secrets-operator

Plattformen kör SOPS+age, inte ESO (ADR-0014). Konkret:

  • Charten renderas med externalSecrets.enabled: false (plattformen sätter det). Du behöver ingen ClusterSecretStore / platform-secret-store — den modellen används inte.
  • Varje tjänst läser sin Secret som redan heter <svc> i namespacet.
  • Behöver din tjänst en NY secret-nyckel? Definiera den i charten (env från Secret <svc>) och säg till plattformen — de lägger nyckeln i den SOPS-krypterade secreten. Det är en koordineringspunkt, inte något du provisionerar själv.
  • DB-URL-nyans (per tjänst): SQLAlchemy-async-tjänster vill ha postgresql+asyncpg://, tjänster på rå asyncpg (t.ex. llm-gateway) vill ha plain postgresql://. CNPG genererar plain — plattformen sätter rätt schema per tjänst. Idealt normaliserar appen schemat själv.

Vad du ALDRIG gör

  • Kör inte helm install/upgrade eller kubectl apply mot prod. ArgoCD äger klustret; manuella ändringar självläks bort.
  • Provisionera inte ESO / ClusterSecretStore.
  • Deploya inte på :latest eller mutabla taggar.

Koordineringspunkter med plattformen (säg till, gör inte själv)

  • Ny secret-nyckel → plattformen lägger den i SOPS.

  • Ny publik host → plattformen lägger DNS-post + ingress + cert (Let’s Encrypt).

  • Privat GHCR-paket → plattformen kopplar pull-secret i namespacet.

    Avgjort 2026-07-26 (Fabian): paketen förblir privata. Samtliga paket i organisationen är privata, och att göra ett enda publikt hade gjort det till undantaget — avvikelser i leveranskedjan är där misstag bor. Dessutom hade en publik image skyltat med basimagens version, vilket vi medvetet döljer i HTTP-svaren (server_tokens off).

    Den verkliga luckan är en annan, och den gäller alla tolv charts: ingen av dem stöder imagePullSecrets eller sätter serviceAccountName. Alla förlitar sig på att någon manuellt kopplat secreten till namespacets standardkonto — ett odokumenterat handgrepp som måste upprepas per namespace, och vars fel (ImagePullBackOff med 401) syns i podden och inte i ArgoCD:s app-status.

    charts/marketing-site har nu ett valfritt imagePullSecrets-värde (tomt = renderar ingenting, alltså oförändrat beteende). De elva backend-charterna saknar det fortfarande — en uppföljning att ta gemensamt med plattformen, inte ensidigt i tolv charts.

Vad som är superseded i wave1-bring-up-runbooken

Den interna bring-up-planen för wave 1 skrevs före plattformens beslut och är inaktuell på tre punkter:

  1. Phase 1 (ESO/ClusterSecretStore) → superseded av SOPS+age.
  2. Phase 3 (helm --set image.tag=<sha> + manuell kubectl rollout per tjänst) → superseded av ArgoCD GitOps (ApplicationSet för de 11 + enskild Application för marketing-site).
  3. Phase 4 (frontends på Cloudflare Pages) → superseded av D-66 (frontends flyttas in i k8s).

Runbooken är fortfarande giltig som förstagångs-uppstart-checklista på infra-nivå ([ditt bord]-stegen), men den löpande deploy-modellen är den här filen.

Nuläge (2026-07-26)

  • marketing-site följer appVersion → merge + appVersion-bump auto-deployar (fullt enligt ovan).
  • De 11 backend-tjänsterna är just nu image.tag-pinnade till specifika merge-shas i ArgoCD-ApplicationSet:en (en kontrollåtgärd från migrations-uppstarten). Tills de konvergerar mot appVersion-spårning kräver en backend-release att plattformen pinnar om taggen. Målet är att avpinna dem så att samma auto-flöde gäller alla.