Deploy with Kustomize and GitOps

Keep shared deployment configuration in a base and environment-specific chart values in overlays. The supplied example uses Kustomize's Helm chart inflation. Argo CD and Flux need different execution paths; do not point Flux's Kustomize controller at helmCharts and expect it to run Helm.

Choose the controller path

PathHow the chart is rendered
Local Kustomizekustomize build --enable-helm, with Helm available.
Argo CDEnable Helm inflation in the repo-server's Kustomize build options, then use the overlay Application.
FluxReconcile an OCIRepository and HelmRelease through Flux's source/Helm controllers. Use Kustomize to compose those resources.

Before deploying, complete the Helm prerequisites, image access, namespace storage and TLS setup. Select an exact published chart version and matching values; the example pin is an example, not a promise that it is the latest release.

Adapt the base and overlay

Download the complete example. It includes a namespace base, production overlay, values file and an Azure Blob CSI static volume example:

base/kustomization.yaml

yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - namespace.yaml

base/namespace.yaml

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: stackweaver

overlays/production/kustomization.yaml

yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base
  - pv-pvc.yaml

namespace: stackweaver

helmCharts:
  - name: stackweaver
    repo: oci://ghcr.io/vhco-pro/charts
    version: 0.13.4
    releaseName: stackweaver
    namespace: stackweaver
    valuesFile: values.yaml

overlays/production/pv-pvc.yaml

yaml
# Static provisioning for ReadWriteMany volumes using Azure Blob CSI driver.
# Uses managed identity of the cluster for authentication - no explicit clientID needed (driver >= v1.24).
# https://github.com/kubernetes-sigs/blob-csi-driver/tree/master/deploy/example/blobfuse-mi
#
# Replace the following placeholders before applying:
#   <STORAGE_ACCOUNT>              - Azure storage account name (e.g. mystorageaccount)
#   <RESOURCE_GROUP>               - Resource group containing the storage account (e.g. my-cluster-rg)
#   <RUNNER_WORKSPACES_CONTAINER>  - Blob container name for runner workspaces (e.g. stackweaver-runner-workspaces)
#
# For other storage providers (AWS EFS, NFS, etc.) replace the csi block with the appropriate
# driver configuration. The PVC name (runner-workspaces-pvc) must stay the same: values.yaml
# names it in runnerWorkspaces.existingClaim.

# ── Runner Workspaces ─────────────────────────────────────────────────────────

apiVersion: v1
kind: PersistentVolume
metadata:
  name: runner-workspaces-pv
spec:
  accessModes:
    - ReadWriteMany
  capacity:
    storage: 10Gi
  persistentVolumeReclaimPolicy: Retain
  storageClassName: "" # Static provisioning - must be empty
  csi:
    driver: blob.csi.azure.com
    volumeAttributes:
      storageAccount: <STORAGE_ACCOUNT>
      containerName: <RUNNER_WORKSPACES_CONTAINER>
    volumeHandle: "<RESOURCE_GROUP>#<STORAGE_ACCOUNT>#<RUNNER_WORKSPACES_CONTAINER>"
  mountOptions:
    - -o allow_other
    - --file-cache-timeout-in-seconds=120
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: runner-workspaces-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Gi
  storageClassName: "" # Static provisioning - must be empty
  volumeName: runner-workspaces-pv

# ── Zitadel PAT ───────────────────────────────────────────────────────────────

overlays/production/values.yaml

yaml
# StackWeaver Helm Chart - values.yaml
# ─────────────────────────────────────
# Override with --set or -f custom-values.yaml at install time.
#
# SECRETS:
#   All secrets must be provided as pre-existing Kubernetes Secrets (via External
#   Secrets Operator, Sealed Secrets, or kubectl create secret). The chart only
#   references them by name - it never creates Secret resources containing
#   user-provided credentials.
#
#   Any secret you do not supply is generated in-cluster by the chart's
#   secrets-init bootstrap Job, which runs before every other resource and only
#   ever creates what is missing. The chart itself never renders Secret manifests.

# ── Global ────────────────────────────────────────────────────────────────────

global:
  imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
# ── Service Account ───────────────────────────────────────────────────────────
serviceAccount:
  create: true
  name: ""
  annotations: {}
# ── Ingress ───────────────────────────────────────────────────────────────────
#
# Two-domain layout:
#   - app host:  Frontend SPA (login UI bundled via /login/* routes) + API (with auth proxy at /auth/*)
#   - auth host: Zitadel OIDC (only when zitadel.bundled=true)
#
# The API is routed via /api/v2, /.well-known/terraform.json, /v1/modules, /health.
# Everything else on the app host goes to the frontend.
ingress:
  enabled: true
  className: nginx
  # Ingress controller provider - selects annotation presets.
  # Options: nginx-inc | community-nginx | traefik | none
  #   nginx-inc:        NGINX Inc ingress controller (nginx.org/* annotations) - default
  #   community-nginx:  Community NGINX ingress controller (nginx.ingress.kubernetes.io/*) - sunset project
  #   traefik:          Traefik ingress controller (minimal annotations; use middleware CRDs for advanced config)
  #   none:             No controller-specific annotations - supply everything via annotations/appAnnotations/authAnnotations
  provider: nginx-inc
  annotations: {}
  # Per-ingress annotation overrides (merged on top of provider presets + annotations)
  appAnnotations: {}
  authAnnotations: {}
  tls:
    enabled: true
    secretName: stackweaver-tls
    authSecretName: stackweaver-auth-tls
  hosts:
    app: stackweaver.example.com
    auth: auth.stackweaver.example.com
# ── External Secrets ──────────────────────────────────────────────────────────
#
# For each component, you can either:
#   1. Leave secretName empty → the secrets-init bootstrap Job creates the Secret
#      with random values on first install and skips it on every later run, so the
#      value is preserved across upgrades. Zero config, works out of the box.
#   2. Set secretName to a pre-existing K8s Secret → chart uses it as-is.
#      Use this for production / GitOps (External Secrets Operator, Sealed Secrets, etc.).
#
# The "keys" mapping tells the chart which key name to read from the Secret.
# Only change these if your external Secret uses different key names.
secrets:
  # PostgreSQL credentials
  # secrets-init generates a random 24-char password when secretName is empty.
  postgresql:
    secretName: ""
    keys:
      password: password
  # Redis credentials (optional - only needed if Redis auth is enabled)
  redis:
    secretName: ""
    keys:
      password: password
  # Storage / S3 credentials (only used when storage.auth=credentials)
  # When garage.enabled=true AND secretName is empty, secrets-init generates
  # credentials that match the bundled Garage init. When using external S3, provide
  # your own secret.
  # Ignored entirely when storage.auth=pod-identity.
  storage:
    secretName: ""
    keys:
      accessKey: access-key
      secretKey: secret-key
  # Encryption key - 32-byte hex string
  # secrets-init generates a random key when secretName is empty.
  # Used by API, runner, and ansible-runner for encrypting variables/credentials.
  encryption:
    secretName: ""
    keys:
      key: encryption-key
  # Zitadel credentials
  # secrets-init generates the masterkey + admin password when empty.
  # After zitadel-init runs, it populates clientId, clientSecret, etc.
  # For external Zitadel: supply all keys directly.
  zitadel:
    secretName: ""
    keys:
      masterkey: masterkey
      adminPassword: admin-password
      adminUsername: admin-username
      clientId: client-id
      clientSecret: client-secret
      loginServiceUserToken: login-service-user-token
      webhookIdpSyncKey: webhook-idp-sync-key
      webhookComplementTokenKey: webhook-complement-token-key
  # OIDC workload identity signing key (optional - for Azure OIDC / inventory sync)
  # When secretName is empty, secrets-init generates an RSA-2048 private key.
  oidc:
    secretName: ""
    keys:
      signingKey: signing-key
  # GitHub App credentials (optional - for GitHub VCS integration)
  githubApp:
    secretName: ""
    keys:
      privateKey: private-key
      webhookSecret: webhook-secret
# ── Core Components ───────────────────────────────────────────────────────────

# -- API (Go backend, port 8022) --
api:
  image:
    repository: ghcr.io/vhco-pro/stackweaver-api
    tag: latest
    pullPolicy: IfNotPresent
  replicas: 1
  port: 8022
  server:
    host: "0.0.0.0"
    readTimeout: "30s"
    writeTimeout: "30s"
  corsExtraOrigins: ""
  resources:
    requests:
      cpu: 100m
      memory: 128Mi
    limits:
      cpu: 500m
      memory: 512Mi
  # Extra environment variables (map of NAME: value).
  # These are added last and can override any auto-generated env var.
  env: {}
  podAnnotations: {}
  nodeSelector: {}
  tolerations: []
  affinity: {}
  # GitHub App integration (non-secret config)
  githubApp:
    id: ""
    name: ""
# -- Frontend (Chainguard nginx SPA, port 8080 - non-root) --
frontend:
  image:
    repository: ghcr.io/vhco-pro/stackweaver-frontend
    tag: latest
    pullPolicy: IfNotPresent
  replicas: 1
  port: 8080
  resources:
    requests:
      cpu: 50m
      memory: 64Mi
    limits:
      cpu: 200m
      memory: 256Mi
  env: {}
  podAnnotations: {}
  nodeSelector: {}
  tolerations: []
  affinity: {}
# -- Orchestrator (background job scheduler) --
orchestrator:
  image:
    repository: ghcr.io/vhco-pro/stackweaver-orchestrator
    tag: latest
    pullPolicy: IfNotPresent
  replicas: 1
  resources:
    requests:
      cpu: 100m
      memory: 128Mi
    limits:
      cpu: 500m
      memory: 512Mi
  env: {}
  podAnnotations: {}
  nodeSelector: {}
  tolerations: []
  affinity: {}
  githubApp:
    id: ""
    name: ""
# -- OpenTofu Runner --
runner:
  image:
    repository: ghcr.io/vhco-pro/stackweaver-opentofu-runner
    tag: latest
    pullPolicy: IfNotPresent
  replicas: 1
  resources:
    requests:
      cpu: 200m
      memory: 256Mi
    limits:
      cpu: "1"
      memory: 1Gi
  env: {}
  podAnnotations: {}
  nodeSelector: {}
  tolerations: []
  affinity: {}
# -- Ansible Runner --
ansibleRunner:
  image:
    repository: ghcr.io/vhco-pro/stackweaver-ansible-runner
    tag: latest
    pullPolicy: IfNotPresent
  replicas: 1
  resources:
    requests:
      cpu: 200m
      memory: 256Mi
    limits:
      cpu: "1"
      memory: 1Gi
  env: {}
  podAnnotations: {}
  nodeSelector: {}
  tolerations: []
  affinity: {}
  # Size limit for the writable /tmp emptyDir (Ansible local/remote temp, SSH
  # control sockets) under the read-only root filesystem.
  tmpSizeLimit: 1Gi
  # Per-job scratch (playbook checkout, inventory, staged credentials) runs on an
  # ephemeral emptyDir isolated from the OpenTofu runner - it holds no run
  # history, so nothing is lost when a pod reschedules. Bound its size here.
  workspacesSizeLimit: 5Gi
  # The Ansible Galaxy cache is a regenerable performance cache. This static-PV
  # example keeps it on an ephemeral emptyDir (enabled: false) so it needs no
  # extra pre-provisioned volume - collections simply re-download on a cold pod.
  # To keep it warm across restarts, set enabled: true and point existingClaim at
  # a pre-created PVC (and add it to pv-pvc.yaml).
  galaxyCache:
    enabled: false
    sizeLimit: 5Gi
# ── Storage ───────────────────────────────────────────────────────────────────
# All data (state, configs, registry, logs, ansible artifacts) is stored in a
# single S3-compatible bucket. Any S3-compatible service works: AWS S3, R2, B2,
# MinIO, Garage, DigitalOcean Spaces, etc.
# When garage.enabled=true (default), the chart deploys a bundled Garage instance
# and auto-configures storage to point to it. Set garage.enabled=false to use an
# external S3-compatible service.
storage:
  bucket: stackweaver
  region: garage
  endpoint: ""
  useSSL: false
  forcePathStyle: true
  # Authentication: "credentials" (default) or "pod-identity" (IRSA/Workload Identity)
  auth: credentials
  serviceAccountAnnotations: {}
# ── Runner Workspaces PVC ─────────────────────────────────────────────────────
runnerWorkspaces:
  size: 10Gi
  storageClass: ""
  accessMode: ReadWriteMany
  # The statically provisioned claim defined in pv-pvc.yaml. Set to "" to let the chart create
  # a claim from storageClass instead.
  existingClaim: runner-workspaces-pvc
# ── Dependencies ──────────────────────────────────────────────────────────────
#
# For each dependency, set enabled=true to deploy it in-cluster (bundled),
# or enabled=false to use an external instance. When external, configure the
# external.* fields and provide credentials in the secrets section above.

# -- PostgreSQL 17 --
postgresql:
  enabled: true
  image:
    repository: postgres
    tag: "17"
  auth:
    username: iac
    database: iac_platform
  storage:
    size: 10Gi
    storageClass: ""
    existingClaim: ""  # if set, skip volumeClaimTemplate and use this pre-existing PVC
    volumeName: ""     # if set, bind the new PVC to this specific pre-existing PV
  resources:
    requests:
      cpu: 100m
      memory: 256Mi
    limits:
      cpu: 500m
      memory: 512Mi
  external:
    host: ""
    port: 5432
    username: ""
    database: ""
    sslmode: disable
# -- Redis 8 --
redis:
  enabled: true
  image:
    repository: redis
    tag: 8-alpine
  resources:
    requests:
      cpu: 50m
      memory: 64Mi
    limits:
      cpu: 200m
      memory: 256Mi
  external:
    host: ""
    port: 6379
# -- Garage (bundled S3-compatible storage) --
# Lightweight, self-hosted, S3-compatible object store. Deployed by default.
# Set enabled=false when using AWS S3, Cloudflare R2, or another external service.
garage:
  enabled: true
  image:
    repository: dxflrs/garage
    tag: v2.4.1
  storage:
    size: 10Gi
    storageClass: ""
    existingClaim: ""
    volumeName: ""
  resources:
    requests:
      cpu: 100m
      memory: 256Mi
    limits:
      cpu: 500m
      memory: 512Mi
# -- Zitadel (OIDC Identity Provider) --
# Zitadel is REQUIRED - the stack cannot function without it.
# Set bundled=true to deploy Zitadel in this chart, or bundled=false to use
# an external Zitadel instance. In both cases, provide credentials in secrets.zitadel.
zitadel:
  bundled: true
  image:
    repository: ghcr.io/zitadel/zitadel
    tag: latest
  resources:
    requests:
      cpu: 500m
      memory: 512Mi
    limits:
      cpu: "2"
      memory: 1Gi
  config:
    accessTokenLifetime: 1h
    idTokenLifetime: 12h
    refreshTokenIdleExpiration: 720h # 30 days
    refreshTokenExpiration: 2160h # 90 days
  init:
    image:
      repository: ghcr.io/vhco-pro/stackweaver-zitadel-init
      tag: latest
      pullPolicy: IfNotPresent
    # Email address for the initial Zitadel admin account.
    # Written to the zitadel secret under "admin-username" so operators can discover it
    # without reading the docs. Only effective on first install - changing after
    # install has no effect (Zitadel FirstInstance config only runs once).
    adminUsername: "[email protected]"
    resources:
      requests:
        cpu: 50m
        memory: 64Mi
      limits:
        cpu: 200m
        memory: 256Mi
  # External Zitadel settings (when bundled=false)
  external:
    # Public issuer URL (e.g. https://auth.example.com)
    issuer: ""
    # In-cluster address for gRPC/HTTP calls (e.g. zitadel.other-namespace.svc:8080)
    internalAddr: ""
# ── OIDC Workload Identity ───────────────────────────────────────────────────
oidc:
  # Issuer URL for OIDC workload identity tokens (non-secret, can be left empty)
  issuerUrl: ""
# ── SSO Configuration ─────────────────────────────────────────────────────────
#
# Configure an external identity provider for SSO login.
# Only one of azureAd or oidcProvider can be active at a time (oidcProvider
# covers Okta, AWS Cognito, and any generic OIDC provider).
# Azure AD can be used alongside one oidcProvider.
#
# Non-secret values (client IDs, tenant ID, issuer URL) are set inline.
# Secret values (client secrets) must be provided in a pre-existing K8s Secret.
sso:
  enableOidcTeamSync: false
  oidcRemoveFromNonSsoTeams: false
  # Azure AD / Entra ID
  azureAd:
    clientId: ""
    tenantId: ""
  # Generic OIDC provider (Okta, AWS Cognito, Keycloak, etc.)
  oidcProvider:
    name: ""       # Display name on the login button (e.g. "Okta", "AWS Cognito")
    issuer: ""     # OIDC issuer URL (must serve /.well-known/openid-configuration)
    clientId: ""
  # Pre-existing K8s Secret containing SSO client secrets.
  # Both keys are needed if you configure Azure AD and a Generic OIDC provider simultaneously.
  secretName: ""
  # Key names within the Secret. Override these if your existing Secret uses
  # different key names (e.g. from External Secrets Operator or Sealed Secrets).
  keys:
    azureAdClientSecret: azure-ad-client-secret
    oidcProviderClientSecret: oidc-idp-client-secret
# ── Platform administration ───────────────────────────────────────────────────
#
# A platform administrator manages the installation itself: the OpenTofu
# versions catalogue, the platform administrators, and the list of every
# organization. It has no access inside an organization it is not a member of.
platform:
  # Email addresses of the deployment-declared platform administrators. Empty
  # means the bundled Zitadel's zitadel.init.adminUsername; with an external
  # Zitadel (zitadel.bundled: false) set this, or nobody is a platform
  # administrator. Declare only addresses you control at every identity
  # provider this installation signs people in with.
  admins: []
  # Who may create organizations: everyone or platform-admins
  organizationCreation: everyone
# ── Custom CA Certificates ─────────────────────────────────────────────────
#
# Mount a custom CA certificate bundle into all Go service containers.
# Required when an upstream service (e.g. an external Zitadel instance) is
# signed by an internal/corporate CA not present in the default system trust
# store - which causes "x509: certificate signed by unknown authority" errors.
#
# Three source options (evaluated in priority order):
#
#   existingSecret    - name of a pre-existing Kubernetes Secret
#   existingConfigMap - name of a pre-existing Kubernetes ConfigMap
#   cert              - PEM-encoded certificate(s) pasted inline; the chart
#                       creates a ConfigMap from this value automatically
#
# The cert is mounted at /etc/ssl/certs/custom-ca.crt via subPath (so the
# system cert directory is not replaced). Go's crypto/x509 package scans
# /etc/ssl/certs/ automatically on Linux, so no extra env vars are needed.
customCA:
  # Inline PEM-encoded CA certificate(s). The chart creates a ConfigMap from
  # this value. Ignored when existingSecret or existingConfigMap is set.
  # Example (use | to preserve newlines in YAML):
  #   cert: |
  #     -----BEGIN CERTIFICATE-----
  #     ...
  #     -----END CERTIFICATE-----
  cert: ""
  # Name of a pre-existing Kubernetes ConfigMap containing the CA cert.
  # Ignored when existingSecret is set.
  existingConfigMap: ""
  # Name of a pre-existing Kubernetes Secret containing the CA cert.
  # Takes highest priority when set.
  existingSecret: ""
  # Key within the Secret or ConfigMap that holds the PEM-encoded cert(s).
  key: ca.crt

Change these inputs before a sync:

  • overlays/production/kustomization.yaml: reviewed chart version and release namespace.
  • overlays/production/values.yaml: app/auth hosts, ingress provider/class, TLS Secret names, image tags/digests and global.imagePullSecrets when needed.
  • overlays/production/pv-pvc.yaml: storage driver, volume identity and claim for your cluster. The Azure-specific identifiers are placeholders, not portable storage defaults.
  • runnerWorkspaces.existingClaim: must match the runner claim, runner-workspaces-pvc in this example. Replace the static volume resources with your storage path if different.

Keep search indexing disabled for private environments. If an overlay publishes public pages, configure frontend.publicSite in its chart values and follow the public-site checks before submitting a sitemap.

This example disables the persistent Galaxy cache, so a restarted Ansible runner downloads collections again. PostgreSQL and Garage still need durable storage; a runner scratch claim is not a database backup.

Diagram definition
flowchart LR
    G["Git: base and environment values"] --> A["Argo CD: Kustomize with Helm enabled"]
    G --> F["Flux: Kustomize of HelmRelease resources"]
    A --> K["Kubernetes resources and hooks"]
    F --> H["Flux Helm controller"]
    H --> K

Render locally before syncing

With an OCI-capable Kustomize (for example 5.7.1) and Helm installed, from the extracted example root:

kustomize build overlays/production --enable-helm > rendered.yaml

Use a Kustomize version that supports your OCI chart source, and verify its Helm command can pull the selected version. Review the output's namespace, hosts, image references, PVCs and hook annotations. This renders files; it does not create resources. Protect rendered output if you add secrets or sensitive env values.

Argo CD

An Argo administrator must enable Helm inflation in the argocd-cm ConfigMap. Merge this setting with existing build options rather than replacing unrelated configuration:

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
  namespace: argocd
data:
  kustomize.buildOptions: --enable-helm

This is a repo-server/global build setting, not a per-Application kustomize.enableHelm field. A custom config-management plugin is another option when a global setting is unsuitable. See Argo CD's Kustomize guidance.

Save an Application in your deployment repository, substituting its URL and overlay path:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: stackweaver
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/your-gitops-repo
    targetRevision: main
    path: overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: stackweaver
  syncPolicy:
    syncOptions:
      - CreateNamespace=true

Review the diff and perform the first sync manually. The chart uses PreSync secret bootstrap and PostSync identity-policy reconciliation. Do not assume a selective resource sync executes all hooks. Enable automated pruning/self-healing only after agreeing how deletions and secret ownership should be handled.

Flux

Flux's Kustomize controller does not execute the Helm inflation plugin. Use its native Helm resources. The following belongs in a directory referenced by your existing Flux GitRepository/Kustomization; create namespace stackweaver and prerequisite Secrets first. Replace REPLACE_WITH_CHART_VERSION with the exact selected chart tag:

apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
  name: stackweaver
  namespace: stackweaver
spec:
  interval: 10m
  url: oci://ghcr.io/vhco-pro/charts/stackweaver
  ref:
    tag: REPLACE_WITH_CHART_VERSION
  layerSelector:
    mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
    operation: copy
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: stackweaver
  namespace: stackweaver
spec:
  interval: 10m
  releaseName: stackweaver
  chartRef:
    kind: OCIRepository
    name: stackweaver
  values:
    global:
      imagePullSecrets:
        - name: ghcr-pull-secret
    ingress:
      provider: nginx-inc
      className: nginx
      hosts:
        app: stackweaver.example.com
        auth: auth.stackweaver.example.com
      tls:
        enabled: true
        secretName: stackweaver-tls
        authSecretName: stackweaver-auth-tls

Copy your complete environment values into spec.values, or reference an appropriately managed values source. The local Kustomize valuesFile does not automatically become Flux HelmRelease values. Include your storage/secret overrides as well. For full source and release options, use Flux's Helm guide.

Browser-session upgrades

Before a GitOps upgrade with external Zitadel, create the confidential BFF client and populate its required Secret keys. Pin the reviewed per-component image versions or use Always so a cached initializer cannot leave the new API without credentials. Follow the Helm upgrade sequence, including readiness checks and rollback prerequisites; do not let an automatic rollback conceal missing keys.

Secrets and first-sync checks

The chart's bootstrap Job creates missing Secrets without overwriting existing values. For production lifecycle control, provision credentials through your secret store/operator and reference them through secrets.<component>.secretName. To omit bootstrap entirely, supply all five credential Secrets and disable bundled Garage; see Helm secret ownership. Kubernetes RBAC and hook ordering still matter.

Do not commit live passwords or unencrypted pull credentials in Git. The chart's credential references do not make arbitrary values you add to manifests secret.

After syncing, check:

kubectl get pods,pvc --namespace stackweaver
kubectl logs --namespace stackweaver job/stackweaver-secrets-init
kubectl logs --namespace stackweaver deployment/stackweaver-zitadel -c zitadel-init

If you intentionally omitted bootstrap or use external identity, skip the absent Job/container and verify that path's supplied credentials. Check the controller's Ready/health conditions, then sign in through the configured HTTPS app hostname. On a failed sync, inspect image pulls, PVC binding and hook logs before retrying; do not delete Secrets to force a clean sync.