Hudu's official Helm Chart lets you install and run self-hosted Hudu on Kubernetes. This article walks through the full lifecycle: getting the chart set up, verifying a healthy install, finding logs when something goes wrong, and keeping your deployment up to date. For the complete option reference (every value the chart accepts), see the chart's README. This article is the narrative, step-by-step version.
If you're installing for the first time, read "Setting up" through once before running any commands. A couple of the defaults, TLS and file storage, need a decision from you up front, and it's easier to decide before your first install than to change it after.
Setup and deployment
Before you start
You'll need:
Version 1.24 or later.
Helm v3 and kubectl installed and pointed at that cluster.
For example nginx-ingress, or the AWS Load Balancer Controller on EKS. The chart creates an Ingress by default, so you'll need a controller in the cluster to adopt it.
Either cert-manager for automatic certificates, or your own certificate and load balancer. Hudu redirects plain HTTP to HTTPS, so this isn't optional for a real deployment. See "A note on TLS" below.
The chart can use a ReadWriteMany Kubernetes volume, or S3 (real S3, an S3-compatible service, or a bundled evaluation-only MinIO for trying the chart out first). More on this in "Choosing file storage" below.
Everything else, database, cache, and app secrets, has a working default or is generated for you on first install.
Adding the chart
The chart is published as an OCI artifact on a public ECR registry, so there's no helm repo add step. You can also browse it on Artifact Hub to see available versions and the full README without pulling anything first.
helm show chart oci://public.ecr.aws/q4n0g0t7/hudu-helm-chart
confirms you can reach the registry and shows the latest published chart version. To download the chart itself, for example to inspect its templates and default values locally, or to install from a local copy:
helm pull oci://public.ecr.aws/q4n0g0t7/hudu-helm-chart --untar
This creates a hudu/ directory with the chart's templates and values.yaml. Add --version <version> to either command to target a specific release instead of the latest one. Neither command needs a login. ECR Public allows anonymous reads.
config.domain is the only value the chart requires. It's the hostname Hudu will be served from, with no scheme or port (hudu.example.com, not https://hudu.example.com). For a deployment that's actually reachable, also set ingress.host to the same hostname. The chart doesn't enforce this, but a blank ingress.host renders an Ingress that doesn't route anywhere.
A minimal install
This relies on the chart's defaults: a bundled single-replica Postgres and Redis, and local-disk file storage on a ReadWriteMany PVC.
helm install my-hudu oci://public.ecr.aws/q4n0g0t7/hudu-helm-chart \ --set config.domain=hudu.example.com \ --set ingress.host=hudu.example.com
The four app secrets, secrets.secretKeyBase, secrets.passwordKey, secrets.twoFactorKey, and the bundled Postgres password, are generated for you automatically the first time you install, if you don't set them yourself. See "Handling secrets" further down. It matters once you get to upgrades.
If you'd rather set them explicitly up front:
helm install my-hudu oci://public.ecr.aws/q4n0g0t7/hudu-helm-chart \ --set config.domain=hudu.example.com \ --set ingress.host=hudu.example.com \ --set secrets.secretKeyBase=$(openssl rand -hex 64) \ --set secrets.passwordKey=$(openssl rand -hex 16) \ --set secrets.twoFactorKey=$(openssl rand -hex 16) \ --set postgresql.auth.password=$(openssl rand -hex 16)
A note on TLS
Hudu runs with HTTPS enforcement on, so plain HTTP traffic gets redirected to HTTPS. In practice this means:
- If you're terminating TLS at your Ingress or load balancer (the supported setup), set
ingress.tls.enabled: truewith cert-manager configured, or bring your own TLS secret. Leaveconfig.behindTlsProxyat its default oftrue. - Only set
config.behindTlsProxy: falseandconfig.disableSsl: trueif there is genuinely no TLS anywhere in front of the pod, for example a throwaway evaluation cluster reached only throughkubectl port-forward.
Getting this wrong on a real deployment causes two separate symptoms: an infinite redirect loop, and, if that's worked around, forms silently failing to submit (sign-up, license validation) with no visible error. That second one is a CSRF check failing because the browser and the app disagree on http versus https.
If you're on EKS behind the AWS Load Balancer Controller rather than nginx, you'll also need:
--set ingress.className=alb \ --set ingress.annotations."alb\.ingress\.kubernetes\.io/scheme"=internet-facing \ --set ingress.annotations."alb\.ingress\.kubernetes\.io/target-type"=ip
kubectl get ingressclass. An Ingress referencing one that doesn't exist won't fail the install, it'll just sit there unadopted.Choosing file storage
storage.type: pvc. Nothing else to configure for a single web replica. If you plan to run more than one web pod, your cluster's StorageClass needs to support ReadWriteMany (NFS, EFS, Longhorn, Azure Files).
storage.type: s3, storage.s3.useLocalS3: true. Spins up a bundled, evaluation-only MinIO and wires up bucket and credentials for you. A good way to try the chart before you've set up real S3 access. Not for production use.
storage.type: s3, useLocalS3: false. Set storage.s3.bucket and storage.s3.region, plus one of: access key and secret, an existing Kubernetes Secret, or an IRSA-annotated ServiceAccount if you're on EKS. Reach out to Hudu support if you'd like a walkthrough of the IRSA setup specifically. It involves an IAM trust policy on your side as well as chart config.
Full option tables for all of the above, including advanced settings like autoscaling, high availability, and resource limits, are in the chart's README.
Running your install
Verify the install
kubectl get pods -l app.kubernetes.io/instance=my-hudu
You should see a web pod and a worker pod reach Running, plus postgres and redis pods if you're using the bundled versions. A one-off migration Job also runs during install or upgrade and should reach Completed.
kubectl rollout status deployment/my-hudu-hudu-web
waits until the web Deployment has finished rolling out. Useful right after an install or upgrade, or in a CI/CD pipeline.
Access the app
Once the Ingress has picked up an address (kubectl get ingress), Hudu is reachable at the hostname you set in ingress.host, over HTTPS. If you haven't wired up DNS yet and just want to confirm the app itself is healthy, port-forward directly to the service:
kubectl port-forward svc/my-hudu-hudu-web 3000:3000
then visit http://localhost:3000. Note this bypasses your Ingress and TLS entirely, so it's for a quick health check only, not a substitute for the real access path.
Everyday commands
| What you want | Command |
|---|---|
| Current release status and revision |
|
| What values a release is running with |
|
| Web pod logs |
|
| Worker pod logs |
|
| Shell into a running pod |
|
| List everything the release created |
|
Maintaining your install
Upgrading
helm upgrade my-hudu oci://public.ecr.aws/q4n0g0t7/hudu-helm-chart \ --version <new-chart-version> \ --reuse-values
--reuse-values carries forward whatever you've already set, so you only need to pass values you're actively changing. Check the chart's release notes for the version you're moving to. Some upgrades introduce a new default that's worth a deliberate decision rather than inheriting silently, for example a memory limit being added to the web pod where none existed before, or config.behindTlsProxy changing default. The chart's README calls these out explicitly under each affected setting.
The migration Job runs automatically as part of helm upgrade, before the new web and worker pods roll out.
Rolling back
helm rollback my-hudu
rolls back to the previous release revision. This rolls back chart values and the application version, but it does not reverse a completed database migration. A schema change from the release you're rolling back from can remain in place.
helm rollback as a tool for reverting configuration, not for undoing a migrated schema.Handling secrets
The app's core secrets, the secret key base, the password encryption key, the 2FA key, and the bundled Postgres password, are generated once on first install and stored in a real Kubernetes Secret (<release>-hudu-generated). They are not regenerated on later upgrades.
If that Secret is ever deleted, don't expect the next helm upgrade to fix it. Pods will fail with a clear CreateContainerConfigError instead of silently getting a new key, which would otherwise make existing encrypted data permanently unreadable.
Back this Secret up:
kubectl get secret my-hudu-hudu-generated -o yaml my-hudu-generated-backup.yaml
and store it somewhere as securely as you'd store the encrypted data it protects. It effectively is the key to that data.
Backup considerations
-
Database: if you're using the bundled Postgres, back up its PVC on whatever schedule your cluster's storage supports (volume snapshots), or point the chart at an externally-managed, already-backed-up Postgres instance instead (
externalDatabase.*). Recommended for anything beyond evaluation. - File storage: back up the uploads PVC, or rely on your S3 bucket's own versioning and replication if you're using S3 storage.
- The generated-secrets Secret: back it up once and keep it somewhere safe. It's not reproducible from anything else.
Changing configuration
Ordinary config changes, resource limits, ingress annotations, feature flags under config.*, are a normal helm upgrade --reuse-values --set <key>=<value>, or by maintaining your own values file and passing -f values.yaml.
A handful of settings are effectively set-once and need care if you change them on a live deployment: the four secrets above, and the bundled Postgres password, since changing them doesn't migrate whatever they were already protecting. When in doubt about whether a setting is safe to change after go-live, check that setting's entry in the chart README before changing it.
FAQ
No, but you do need TLS terminated somewhere in front of the app. cert-manager is the easiest path if you want automatic certificates, but bringing your own certificate or load balancer works too. The only unsupported setup is no TLS at all in front of a real deployment.
You can, but switching storage.type or storage.s3.useLocalS3 doesn't migrate existing files. The new backend starts empty, and you'll need to manually copy your files over (for example with aws s3 sync or mc mirror) before switching over on a live deployment.
No. The four app secrets and the bundled Postgres password are generated once on first install and stored in a Kubernetes Secret. Upgrades reuse them rather than regenerating them.
No. It rolls back chart values and the application version, but a completed schema migration stays in place. Use a database backup restore if you need to undo a migration.
Troubleshooting
Start with pod status, then drill into the specific pod that isn't healthy:
kubectl get pods -l app.kubernetes.io/instance=my-hudu -o wide kubectl describe pod <pod-name> kubectl logs <pod-name> kubectl logs <pod-name> --previous # if the pod already restarted
kubectl describe pod is usually the fastest path to a root cause. It shows scheduling failures, failed probes, and container-start errors, including the exact error for something like a missing Secret, in its Events section at the bottom.
Most often <release>-hudu-generated, the Secret the chart creates on first install to hold the auto-generated app secrets. If it's been deleted, the chart won't silently recreate it. See "Handling secrets" above.
A TLS/behindTlsProxy mismatch. See "A note on TLS" above. Nearly always caused by ingress.tls.enabled being off, or config.behindTlsProxy/config.disableSsl having been set for a no-TLS test setup and left that way.
Check its logs directly. It isn't matched by the component=web or component=worker label selectors:
kubectl logs job/my-hudu-hudu-migrate
A failed migration Job retries up to migrate.backoffLimit times (3 by default) before Helm gives up on the hook.
The HPA needs web.resources.requests.cpu and memory set to compute a utilization percentage against. The chart ships defaults for both, so this only shows up if they were explicitly cleared.
Usually one of: ingress.host left blank, or ingress.className set to an IngressClass your cluster doesn't actually have (check with kubectl get ingressclass). Neither fails the install, the Ingress just sits unadopted.
Switching storage.type or storage.s3.useLocalS3 doesn't migrate existing files. The new backend starts empty. This needs a manual data copy (aws s3 sync, mc mirror) if you change it on a live deployment.
If you're stuck on something not covered here, contact Hudu support with:
- The chart and app version you're running (
helm get values my-hudu,helm list). - The output of
kubectl describe podfor the affected pod. - Recent logs from the affected pod(s).
That's usually enough for us to help diagnose the issue quickly.