Kubernetes

Run Nixt DNA Server on Kubernetes with its Helm chart — the secrets it needs, the values to set, the first organisation, and upgrades.

The Helm chart runs Nixt DNA Server as groups of pods, each group a StatefulSet running some of the server’s roles. Every pod shares one PostgreSQL database and one S3-compatible bucket.

Before you start

You need:

  • a Kubernetes cluster that can give a Service a public address (type LoadBalancer), for the mail ports;
  • PostgreSQL 15 or later, reachable from the cluster;
  • an S3-compatible bucket;
  • the chart, nixt-server, which comes with each release in the offline bundle.

1. Create the secrets

The chart reads three secrets. Create them in the namespace you install into.

SecretKeyWhat it holds
nixt-storeurlThe PostgreSQL connection URL.
nixt-keyskekThe key-encryption key: 32 random bytes as 64 hexadecimal characters. Keep a copy somewhere safe and apart from your backups: without it, no stored mail can be read.
nixt-tlstls.crt, tls.keyOnly if you supply your own certificate (tls.kind: files). With the default, acme, each pod obtains its own.
kubectl create secret generic nixt-store --from-literal=url='postgres://nixt:<password>@db.example.com/nixt'
kubectl create secret generic nixt-keys --from-literal=kek="$(openssl rand -hex 32)"

If the pods have no cloud identity that can reach the bucket, also create a secret with AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, and name it in blobs.credentialsSecret.

2. Set the values

Write a values file with at least these:

hostname: mail.example.com
blobs:
  bucket: example-mail
  region: eu-central-1
tls:
  contact: postmaster@example.com
ValueDefaultWhat it does
hostnamemail.example.orgThe server’s host name, which every certificate includes.
roleGroupsedge (2 pods), storage (3), control (2)Which roles each group runs and how many pods it has. edge receives and delivers mail; storage serves mailboxes and calendars; control takes submissions and runs the admin API.
store.urlSecretnixt-storeThe secret holding the database URL.
blobs.bucket, blobs.region, blobs.endpointemptyThe bucket. Set endpoint for a store other than Amazon S3.
keys.secretnixt-keysThe secret holding the key-encryption key.
tls.kindacmeacme to obtain certificates automatically, or files to use tls.secret.
tls.namesemptyMore host names for the certificate, beyond hostname.
mtaSts.modetestingYour MTA-STS policy. Move to enforce once TLS reports show nothing failing.
listenersone Service per listenerThe Service type and port for each of mx, submission, submissions, imap, imaps, managesieve, jmap, admin, serve and acme. The admin API is ClusterIP, inside the cluster only.
resolver.sidecartrueRuns a validating DNS resolver beside each pod, which the server trusts for DNSSEC.
resources500m CPU and 512 MiB requested; 2 CPU and 2 GiB limitPer pod.
podDisruptionBudget.maxUnavailable1Keeps a voluntary disruption from taking more than one pod at a time.

3. Install

helm install mail ./nixt-server -f values.yaml

Helm prints which groups it made and what each needs before it is ready.

4. Create the first organisation

Run the admin command line in a control pod. With the release named mail, the first control pod is mail-nixt-server-control-0:

kubectl exec mail-nixt-server-control-0 -- /nixt-server admin post tenants name=example-org
kubectl exec mail-nixt-server-control-0 -- /nixt-server admin post tenants/1/domains name=example.com
kubectl exec mail-nixt-server-control-0 -- /nixt-server admin post tenants/1/accounts address=admin@example.com "displayName=Administrator"
kubectl exec mail-nixt-server-control-0 -- /nixt-server admin put tenants/1/accounts/1/password "password=<a password of 14 or more characters>"

Each answer includes the new record’s id. The commands above assume the first organisation is 1 and its first account 1; use the ids the answers give. Domains and accounts explains each command.

5. Publish DNS

Print the records the domain needs:

kubectl exec mail-nixt-server-control-0 -- /nixt-server dns example.com

Point the domain’s MX record at the address of the mx Service, and the host names in tls.names at the serve Service, with acme on port 80, so that certificates can be obtained. kubectl get services lists each listener’s Service and its address. See DNS records.

Upgrades

Take a snapshot of the database, set upgrade.backup to its name, and run helm upgrade. Before any pod is replaced, a job of the new release prepares the database. If it refuses, Helm stops with nothing changed. See Upgrades.

Something unclear or out of date on this page? Tell us.