Deploy on Hetzner with CloudFleet
Use the Kubernetes deployment with a provider overlay for CloudFleet compute and Hetzner Object Storage in Helsinki. Match compute architecture to the release, configure the storage endpoint and scope secrets to the deployment. Follow the same quorum, TLS, key-preservation and recovery rules as the base installation.
This guide adapts the standard Kubernetes deployment to a CloudFleet Kubernetes cluster in Helsinki, with Hetzner ARM64 compute and Hetzner Object Storage. The storage bucket holds published data and completed recovery backups. Hydrated files on node-local NVMe are caches; unpublished hot rows have a separate durability boundary. See Reliability and completion.
1. prepare compute and object storage
Section titled “1. prepare compute and object storage”Create a Hetzner Object Storage bucket in hel1 and credentials scoped to that bucket.
Use the endpoint shown by Hetzner, for example:
https://hel1.your-objectstorage.comCreate a CloudFleet node pool in the same region with at least three nodes. Choose instances matching the release architecture and size CPU, memory and local storage for your ingestion and query workload. Label the pool so the chart can select it, for example:
boilstream.com/nodepool=arm64-helSpread the three BoilStream replicas across nodes when capacity permits. The example uses preferred anti-affinity so temporary capacity pressure does not deadlock scheduling.
2. install cluster prerequisites
Section titled “2. install cluster prerequisites”Install cert-manager and Envoy Gateway before BoilStream:
helm install cert-manager jetstack/cert-manager \ --version v1.16.2 \ --namespace cert-manager --create-namespace \ --set crds.enabled=true
helm install eg oci://docker.io/envoyproxy/gateway-helm \ --version v1.2.1 \ --namespace envoy-gateway-system --create-namespaceCreate the ClusterIssuer named by your values. Because the public listeners use TLS
passthrough, use a DNS-01 issuer for automated public certificates. The internal cluster
certificate must use a private trust root and is never shared with external clients.
3. create secrets
Section titled “3. create secrets”Keep credentials and keys out of Helm values:
kubectl create namespace boilstream
kubectl -n boilstream create secret generic boilstream-s3-creds \ --from-literal=access_key='<HETZNER_S3_ACCESS_KEY>' \ --from-literal=secret_key='<HETZNER_S3_SECRET_KEY>'
kubectl -n boilstream create secret generic boilstream-superadmin \ --from-literal=password='<STRONG_PASSWORD>'
kubectl -n boilstream create secret generic boilstream-encryption \ --from-literal=encryption_key="$(openssl rand -hex 32)" \ --from-literal=token_signing_key="$(openssl rand -base64 32)" \ --from-literal=cluster_token="$(openssl rand -base64 32)"Back up both generated keys in an external secret manager. Every replica and restore incarnation needs the same values. If you automate superadmin login, also pre-create the optional MFA Secret described in the chart values.
4. create the values overlay
Section titled “4. create the values overlay”Copy charts/boilstream/values-hetzner-example.yaml and replace the example values:
image: repository: <release-registry>/boilstream tag: <release-version> # Replace this non-resolving example with the published ARM64 release digest. digest: sha256:0000000000000000000000000000000000000000000000000000000000000000 pullPolicy: IfNotPresent
configRenderer: image: # Replace with the published Python multi-architecture manifest digest. digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
domain: data.your-company.example
s3: endpoint: https://hel1.your-objectstorage.com bucket: boilstream-state region: hel1 prefix: production/ forcePathStyle: true existingCredsSecret: boilstream-s3-creds accessKey: "" secretKey: ""
encryptionKey: existingSecret: boilstream-encryption
superadmin: existingSecret: boilstream-superadmin
# Explicit operator acceptance: use only with a continuous-clock rate of 0.9–1.1.# The chart default stays untrusted and rejects clustered startup.quack: sharedSupport: profile: clock-profile-1
cpMetadata: enabled: true ducklakeMultiRaft: true usersMultiRaft: true tokenSecretKey: cluster_token s3Dr: enabled: true clusterId: company-prod-hel1 backend: primary-s3 prefix: quack-dr durabilityMode: strict bootstrapMode: new recoveryEpoch: 1 recoveryId: company-prod-hel1-first-install
clusterTls: enabled: true issuer: name: boilstream-ca-issuer kind: ClusterIssuer
tls: issuer: create: false name: letsencrypt-prod kind: ClusterIssuer
httpIngestion: enabled: true port: 8446 allowedOrigins: - https://data.your-company.example
nodeSelector: boilstream.com/nodepool: arm64-hel
persistence: enabled: falseHetzner Object Storage uses path-style addressing in the tested chart profile. Keep a
deployment-specific prefix and never share its cluster identity with another installation.
After the first completed recovery backup, change bootstrapMode to restore.
The release digest selects the exact ARM64 image bytes; the tag is not used for pulling while
image.digest is set. Pin the config-renderer image by digest as well.
5. install and configure DNS
Section titled “5. install and configure DNS”helm lint ./charts/boilstream \ --values ./charts/boilstream/values-hetzner-example.yaml \ --values my-values.yaml
helm install boilstream ./charts/boilstream \ --namespace boilstream \ --values ./charts/boilstream/values-hetzner-example.yaml \ --values my-values.yaml \ --set-string image.digest='sha256:<release-image-digest>' \ --set-string configRenderer.image.digest='sha256:<python-image-digest>'
kubectl -n boilstream rollout status statefulset/boilstreamkubectl -n boilstream get gateway boilstream \ -o jsonpath='{.status.addresses[0].value}'Point both the base domain and its wildcard at the Gateway address:
| Record | Type | Target |
|---|---|---|
data.your-company.example |
A/AAAA | Gateway address |
*.data.your-company.example |
A/AAAA | Gateway address |
Wait for the public certificate to become ready before testing clients.
6. verify before use
Section titled “6. verify before use”kubectl -n boilstream get pods,gateway,certificateboilstream-admin auth login \ --server https://data.your-company.example \ --email admin@example.comboilstream-admin cluster statusUse the credentials and endpoints supplied by BoilStream. Verify managed DuckDB, PGWire, Kafka, HTTP/2, FlightRPC, and FlightSQL independently. Then delete the current leader pod, confirm clients can reconnect and write, and complete a rolling restart.
Before production, run the all-node-local-state-loss drill in staging: fence the previous incarnation, increment the recovery epoch once, assign a new recovery ID, restore solely from the same S3 prefix and encryption key, and verify catalog access plus writes after a leader failover. A PVC must not be part of the recovery procedure.
Operations
Section titled “Operations”- Scale infrastructure before increasing replicas; retain a quorum throughout changes.
- Upgrade one replica at a time and wait for readiness.
- Monitor object-storage recovery publication as well as pod health.
- Rebuild hydrated historical files from object storage; do not treat unpublished recent rows as disposable cache.
- Retain the previous release artifacts, values, keys, and recovery identity for rollback.