I replaced it with Garage — lightweight, actively maintained, S3-compatible, and with none of that baggage. Longhorn only ever sees an S3 endpoint, so the swap is a bucket, a secret and one Helm value.
You’ll need: an Ubuntu host with Docker and Compose, and a cluster with Longhorn already installed. Check the Garage releases for the current version before you pin one.
Setting up Garage#
Two secrets, generated up front — the RPC secret authenticates nodes to each other, the admin token guards the admin API the WebUI talks to:
sudo mkdir -p /srv/docker/garage/{meta,data}
cd /srv/docker/garage
# Generate RPC secret
openssl rand -hex 32
# Generate admin token
openssl rand -hex 32 | |
replication_factor = 1 is a single copy on a single node. That’s correct here — this bucket is one leg of a backup chain, not the backup itself. If Garage is the only place your data exists, use 3 and more than one node.
Compose#
The WebUI is optional, but it’s the fastest way to see whether a backup actually landed:
| |
docker compose up -dLayout, bucket, key#
A fresh Garage node holds no data until you assign it capacity and apply a layout. Skip layout apply and every write fails with an error that doesn’t mention layouts:
| |
key create already printed the secret; key info --show-secret shows it again whenever you need it. It goes into the Kubernetes secret next.
Pointing Longhorn at it#
The secret keeps the name minio-secret. Renaming it means editing the HelmRelease too, and there’s nothing to gain from making both changes at the same time:
apiVersion: v1
kind: Secret
metadata:
name: minio-secret
namespace: longhorn-system
type: Opaque
stringData:
AWS_ACCESS_KEY_ID: <Key-ID-from-garage>
AWS_SECRET_ACCESS_KEY: <Secret-Key-from-garage>
AWS_ENDPOINTS: http://<GARAGE_SERVER_IP>:3900
AWS_REGION: us-east-1kubectl apply -f minio-secret.yamlThen the backup target itself:
| |
The target reads s3://<bucket>@<region>/, and the region has to match s3_region in garage.toml.
Since Longhorn 1.8 the backup target lives under defaultBackupStore. The older defaultSettings.backupTarget, backupTargetCredentialSecret and backupstorePollInterval keys are silently ignored: Helm accepts the values, and Longhorn either shows no backup target or keeps whatever target it had before the upgrade.
Verify#
Take a real backup — Longhorn UI → Volume → Create Backup — and go looking for it on the Garage side. A backup target that reports healthy while the bucket stays empty is the failure mode worth catching now rather than during a restore:
garage bucket list
garage bucket info longhornThe WebUI at http://<SERVER_IP>:3909 shows the same thing with object counts.
Restoring works exactly as it did before — the procedure is in Restoring from Longhorn Backups, and nothing in it depends on which S3 implementation is underneath.
Troubleshooting#
WebUI returns “Unknown Error”. The [admin] section is missing from garage.toml, or the container started before you added it:
docker compose restart
docker logs garagePort 3903 connection refused. The admin API isn’t bound:
ss -tlnp | grep 3903Longhorn can’t reach Garage. Test from inside the cluster, not from your laptop — the endpoint has to resolve and route from the pods:
kubectl run -it --rm debug --image=amazon/aws-cli --restart=Never -- \
s3 ls s3://longhorn --endpoint-url http://<GARAGE_IP>:3900