//Deploy with Helm

Tutorial 07 — Deploy with Helm

This tutorial is the substrate for every deployment-target tutorial that follows. Tutorials 08–13 (the cloud marketplaces) are thin packaging layers around the chart you install here, and 14 (on-prem) is this same chart with the air-gapped values file. If you only read one deployment tutorial, read this one — the rest are distribution mechanics on top of it.

The chart lives in the repo at deploy/helm/buttrbase-backend-rust/. It is real and committed; the commands below run against it as-is. For the why behind the topology modes, see docs/DEPLOYMENT.md and src/deployment.rs (the DeploymentMode enum is the source of truth).

> Honesty note on the tutorial series. Tutorials 01–06 document patterns that are in production today*. 07 and 14 are real, runnable deploy paths grounded in committed chart artifacts. 08–13 are packaging-and-listing guides for marketplaces ButtrBase is *targeting — the chart is real, the vendor-portal listings are forward-looking. 15 (federation) describes a design that is spec-stage. Each tutorial states its own status at the top.

1. What the chart deploys

One Deployment of the Rust backend, one Service, and one ConfigMap — that is the whole footprint. It deliberately does not package Postgres, Redis/Dragonfly, or object storage: those are dependencies you point at, not things the chart owns. This keeps a single chart honest across SaaS (managed Postgres, shared S3) and air-gapped on-prem (in-cluster Postgres, local-fs storage).

| Object | Template | Notes | |--------|----------|-------| | Deployment | templates/deployment.yaml | One container rust-backend, listens on 4300, /health readiness + liveness probes. | | Service | templates/service.yaml | ClusterIP by default, port 4300. | | ConfigMap | templates/configmap.yaml | Renders every BUTTRBASE_RUST_* env var from topology/providers/diagnostics/policy values. |

The container image defaults to ghcr.io/buttrbase/buttrbase-backend-rust:latest. Pin a real tag in production — latest is for the impatient.

2. Prerequisites

  • A Kubernetes cluster (1.25+) and kubectl pointed at it.
  • Helm 3.12+.
  • A reachable Postgres database and its connection URL.
  • Three secret values: the database URL, the app secret-key, and the 32-byte encryption-key. The chart reads these from a Secret you create — it never templates secrets into the ConfigMap. See docs/secrets-architecture.md and docs/encryption-at-rest.md for how the encryption-key is used.
  • Create the Secret first (the default names match values.yaml's env.*SecretRef):

    kubectl create namespace buttrbase

    kubectl -n buttrbase create secret generic buttrbase-backend-rust \ --from-literal=database-url='postgres://buttrbase:…@db.internal:5432/buttrbase' \ --from-literal=secret-key="$(openssl rand -hex 32)" \ --from-literal=encryption-key="$(openssl rand -hex 32)"

    > The encryption-key encrypts tenant secrets at rest. Losing it means losing access to every encrypted value — back it up in your secret manager before you install, not after.

    3. Pick a topology

    The chart ships four values files, one per DeploymentMode. Pick the one that matches what you are running; do not hand-edit values.yaml for a one-off.

    | Values file | topology.mode | When | |-------------|-----------------|------| | values.yaml | saas | The shared multi-tenant control plane (ButtrBase's own hosted product). | | values-single-tenant-managed.yaml | single-tenant-managed | One customer, one isolated install, you operate it. | | values-single-tenant-byoc.yaml | single-tenant-byoc | One customer, their cloud account, you deploy into it. | | values-on-prem.yaml | on-prem | Air-gapped / customer datacenter. See 14-self-host-on-prem.md. |

    The differences are entirely in the values — storageBackend (s3 vs local-fs), networkMode (public/private/air-gapped), providers.ownership (buttrbase-managed/customer-managed/manual-invoice), and the IP allowlist. Each renders into a BUTTRBASE_RUST_* env var the backend reads at startup.

    4. Install

    Dry-run first — --dry-run renders the manifests without touching the cluster, so you can read exactly what the values produced:

    helm template buttrbase ./deploy/helm/buttrbase-backend-rust \
      -f ./deploy/helm/buttrbase-backend-rust/values-single-tenant-managed.yaml
    

    Confirm the rendered ConfigMap carries the topology you expect (BUTTRBASE_RUST_DEPLOYMENT_MODE: "single-tenant-managed", the right BUTTRBASE_RUST_STORAGE_BACKEND, etc.). Then install:

    helm upgrade --install buttrbase ./deploy/helm/buttrbase-backend-rust \
      -n buttrbase \
      -f ./deploy/helm/buttrbase-backend-rust/values-single-tenant-managed.yaml \
      --set app.image.tag=0.1.0
    

    upgrade --install is idempotent: the first run installs, every later run is a rolling upgrade. Override anything from the command line with --set (here, pinning the image tag away from latest).

    5. Verify

    The backend reports readiness on /health, which is exactly what the chart's probes hit. Confirm the pod went Ready, then probe through a port-forward:

    kubectl -n buttrbase rollout status deploy/buttrbase --timeout=120s
    kubectl -n buttrbase port-forward svc/buttrbase 4300:4300 &
    curl -fsS http://localhost:4300/health && echo "  ✓ healthy"
    

    If rollout status hangs, the pod is almost always failing readiness because a secret value is wrong or the database is unreachable. Read the logs:

    kubectl -n buttrbase logs deploy/buttrbase --tail=50
    

    Two failures dominate first installs: a database-url the pod's network can't reach (probe never passes), and an encryption-key that isn't 32 bytes hex (the backend refuses to start rather than run with a weak key). Both are visible in the first ten log lines.

    6. Confirm the deployment mode round-tripped

    The whole point of the topology values is that the running backend knows which mode it is in — federation trust, storage routing, and provisioning all branch on it. Confirm the env reached the process:

    kubectl -n buttrbase exec deploy/buttrbase -- printenv BUTTRBASE_RUST_DEPLOYMENT_MODE
    

    → single-tenant-managed

    If this prints empty or saas when you installed a single-tenant values file, you installed the wrong -f. Re-run the helm upgrade --install with the correct file; Helm reconciles in place.

    Done

    You have a running ButtrBase backend on Kubernetes, installed from the committed chart, with its deployment mode verified end-to-end. From here:

  • Distribute it on a cloud marketplace — the marketplace tutorials (08–13) all start from this chart. Begin with whichever cloud your customers buy through.
  • Self-host air-gapped — 14-self-host-on-prem.md swaps in values-on-prem.yaml and the licensing path.
  • Stand up federation — once two instances exist, 15-set-up-federation.md connects them.