Skip to content

Ship a first-party Helm chart (migration hook, values schema, OCI release) #545

Description

@joryirving

Why

Dispatch currently deploys via bjw-s app-template in home-ops. That works, but three problems are chart-shaped:

  1. Boot-time migrations block HA. docker-entrypoint.sh runs prisma migrate deploy on every pod start — two replicas racing migrations is the audit's latent multi-replica hazard. A chart moves migrations to a pre-install,pre-upgrade hook Job; pods just run node server.js (SKIP_DB_MIGRATIONS=true).
  2. The env contract is stringly-typed. DISPATCH_LANE_CONFIG_JSON is a giant quoted JSON blob in a values file; scheduler intervals, auth mode, and groomer knobs are undocumented free-form env. A chart gives them structured values + values.schema.json validation, documented next to the code that reads them.
  3. Chart/app version skew. Deploy config lives in a different repo from the app, so image bumps and config changes land separately (recent examples: the HOSTNAME loopback workaround shipping before the image fix; scheduler env landing before 0.5.16). Chart version = app version kills the skew class.

Scope (thin chart, not a framework)

charts/dispatch/
  Chart.yaml              # version == appVersion == release tag
  values.yaml
  values.schema.json      # validates lanes, intervals, auth mode, etc.
  templates/
    deployment.yaml       # node server.js, SKIP_DB_MIGRATIONS=true
    migrate-job.yaml      # helm.sh/hook: pre-install,pre-upgrade — prisma migrate deploy
    service.yaml
    httproute.yaml        # optional (gateway API), off by default
    serviceaccount.yaml
    secret-contract.md    # documented expected Secret keys (DISPATCH_AGENT_TOKEN, GITHUB_*, LLM key) — chart references, never creates

Values sketch:

image: {repository: ghcr.io/misospace/dispatch, tag: ""}  # tag defaults to appVersion
env: {}                        # escape hatch, merged last
config:
  authMode: basic              # basic|oidc|disabled
  excludedLabels: [renovate]
  llmBaseUrl: ""
  groomer: {enabled: true, model: "", dryRun: false, repoContext: true}
  scheduler:
    enabled: true
    intervals: {syncMs: 900000, groomerMs: 600000, prFollowupMs: 900000, pruneClosedMs: 86400000}
  lanes:                       # structured -> templated into DISPATCH_LANE_CONFIG_JSON
    - {id: local, title: Local, claimable: true, role: default}
    - {id: frontier, title: Frontier, claimable: true, role: escalation}
    - {id: backlog, title: Backlog, claimable: false}
  laneAliases: {normal: local, escalated: frontier}
existingSecret: dispatch       # envFrom
database: {existingSecret: "", key: uri}
migrations: {enabled: true}    # hook Job; disable for platforms handling it elsewhere

Release wiring

  • helm package + helm push oci://ghcr.io/misospace/charts as a step in the existing manual-release workflow (after the image build succeeds), chart version stamped from the release version.
  • Lint/template in CI (helm lint, helm template against schema, kubeconform).
  • Renovate: home-ops already has an OCIRepository per app — point dispatch's at the chart artifact; one bump updates chart+app atomically.

home-ops migration path

  1. Ship chart in release N; keep app-template values as-is.
  2. Swap home-ops OCIRepository from app-template to ghcr.io/misospace/charts/dispatch, translate values (lanes JSON → structured), set SKIP_DB_MIGRATIONS path via migrations.enabled: true.
  3. Verify hook Job runs migrations before rollout; delete the entrypoint migration once stable (follow-up release).

Non-goals

  • No bundled Postgres (CNPG/postgres component stays platform-side).
  • No secret creation (ExternalSecret/SOPS stays platform-side).
  • No ingress-controller matrix — HTTPRoute + plain Service only.

Acceptance

  • helm install dispatch oci://ghcr.io/misospace/charts/dispatch + a Secret + a DATABASE_URL yields a working instance on a kind cluster.
  • Migration hook Job runs before pod rollout on upgrades; pods never migrate.
  • values.schema.json rejects an invalid lane config at install time.
  • home-ops runs on the chart with no behavior change.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions