Skip to main content
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
Keep 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
Keep 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

In ingress.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

The pod migrates the database before it answers; its probes read /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 allows RATE_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
With a secrets manager (External Secrets, Sealed Secrets), make a Secret named artbucket-secret there and drop the secretGenerator from the base.

Upgrading

Change newTag in kustomization.yaml ("1" follows the major line) and apply again. The new pod migrates, then takes over. See Upgrading.