deploy/kubernetes/ in the repository is a Kustomize base: a namespace, a
Deployment, a Service and an Ingress. kubectl applies it as is, no Helm.
Bring Postgres 16 or later and S3-compatible storage: managed ones (RDS, Cloud
SQL, Neon; S3, R2, B2), or in the cluster (CloudNativePG, SeaweedFS, Garage).
Settings
config.env holds the plain settings and becomes a ConfigMap:
config.env
HOSTNAME=0.0.0.0: Kubernetes sets HOSTNAME to the pod’s name, and the
server would listen on that address only. When the pod reaches storage inside
the cluster, set S3_PUBLIC_ENDPOINT to where browsers upload.
secret.env becomes a Secret:
secret.env
BETTER_AUTH_SECRET somewhere safe: a new one signs everyone out and
makes stored secrets unreadable. Both objects’ names carry a hash of their
contents, so a change to either rolls the pods.
Every variable is in Environment.
Domain
Iningress.yaml, set your domain (the one in APP_URL), your
ingressClassName, and your certificate issuer’s annotation. Uploads go from
browsers straight to storage, so the ingress needs no body size limit raised.
Apply
/icon.svg, a static file, so they never query the database. Make the first
account at your domain.
Replicas and resources
The base runs one replica. More work: migrations run under a lock and the sweeper is safe twice. The rate limit counts per pod, so each allowsRATE_LIMIT requests a minute on its own.
It asks for half a CPU and 1 GiB, and may use up to 4 GiB: an upload of up to
512 MB is buffered while its previews are made.
Your own overlay
Keep your changes out of the base with an overlay of your own:kustomization.yaml
artbucket-secret there and drop the secretGenerator from the base.
Upgrading
ChangenewTag in kustomization.yaml ("1" follows the major line) and
apply again. The new pod migrates, then takes over. See
Upgrading.