Object storage
An object store gives your applications an S3 API inside your cluster: buckets, access keys, presigned URLs, the same client libraries you would use against a cloud provider, with the data on a volume you own. The platform does not provide a shared one. You run yours, in your cluster, from a chart the platform publishes and runs itself.
The chart, and why this one
Section titled “The chart, and why this one”The platform’s own object store is Silo, the maintained community
fork of MinIO. MinIO’s community line stopped receiving updates in early 2026, security fixes
included, while the project no longer ships a chart repository of its own. Silo keeps the S3 API,
the MINIO_* environment variables, the client and the on-disk format, so it is a drop-in
successor, and it is what the platform migrated its own buckets to.
The command line client follows the same path. MinIO’s mc is archived too; Silo maintains it
as mcli, same commands, same flags, same
configuration, only the binary name changes. The examples below use it. Your applications do
not need it: they speak S3 through any SDK, and any S3 tool works against the endpoint, the
aws CLI with --endpoint-url or rclone included. mcli is what you reach for when the
operation is administrative: users, policies, versioning, replication.
Silo publishes no Helm repository either, so h8lio publishes the chart it runs, unchanged, at the version it runs:
oci://registry.h8l.io/charts/siloPulling it needs no account. A published version never changes: the same version always installs the same chart, which is what lets you pin it in your values and in your GitOps repository. See Helm if the CLI is not set up yet.
Install
Section titled “Install”Start from a values file rather than from --set flags. Everything not listed is the chart
default, and the defaults are sized for a sixteen node distributed deployment, which is not what
a first object store in a cluster looks like.
mode: standalone # one pod, one volume; the default is a 16 replica distributed cluster
persistence: size: 50Gi storageClass: eu-west-fr-gra-block-nvme-ec-xfs
# The chart default requests 16Gi of memory. Size it for your cluster's quota.resources: requests: cpu: 100m memory: 512Mi limits: memory: 2Gi
# Root credentials. Prefer a secret you manage over inline values: the secret must carry the# keys `rootUser` and `rootPassword`.existingSecret: silo-root
# Buckets created at startup, and a service user your applications will use.buckets: - name: app-assets policy: noneusers: - accessKey: app existingSecret: silo-app # key `secretKey` holds the secret key existingSecretKey: secretKey policy: readwrite
# Cold backups are opt-in per volume; `export` is the chart's data volume (see below).podAnnotations: backup.velero.io/backup-volumes: exportstorageClass values and what they mean are listed in Volumes. Then:
kubectl create secret generic silo-root -n my-cluster \ --from-literal=rootUser=admin --from-literal=rootPassword='<a long random password>'kubectl create secret generic silo-app -n my-cluster \ --from-literal=secretKey='<another long random secret>'
helm install silo oci://registry.h8l.io/charts/silo --version 7.0.2 \ -n my-cluster -f silo.yamlAlways pass --version. Omit it only to look: helm show chart oci://registry.h8l.io/charts/silo
prints the newest published version and the image it embeds.
Connect your applications
Section titled “Connect your applications”The chart publishes two services in your namespace:
| Service | Port | Use it for |
|---|---|---|
silo | 9000 | the S3 API, http://silo.my-cluster.svc:9000 |
silo-console | 9001 | the web console, sign in with the root credentials |
Point your applications at the service name, with path-style addressing (the bucket in the path, not in the host name), which is what any S3 SDK does when you give it an explicit endpoint:
env: - name: S3_ENDPOINT value: http://silo.my-cluster.svc:9000 - name: S3_ACCESS_KEY value: app - name: S3_SECRET_KEY valueFrom: secretKeyRef: name: silo-app key: secretKeyTo reach the API or the console from outside the cluster, pick the door that matches how much you want to expose:
-
A port-forward, when nothing should be published. It is the right door for the console and for hand operations:
kubectl port-forward svc/silo 9000:9000 -n my-cluster, then the client talks to it as if it were local:Terminal window mcli alias set my-silo http://localhost:9000 admin '<root password>'mcli ls my-silomcli mirror ./local-dir my-silo/app-assets -
A route, when applications outside the cluster need the API: an
IngressRouteon thesiloservice, with a certificate, exactly as the platform publishes its own. The recipe is in Certificates. Publish the API on its own host name and leave the console unpublished unless you protect it: it is the administration surface of your data. -
A gateway, only when the traffic has to enter through a server you operate outside the cluster (Gateway). It is the heaviest of the three and rarely needed for an object store.
Back up the data: replicate it, do not copy the volume
Section titled “Back up the data: replicate it, do not copy the volume”An object store grows into the terabytes, and the platform’s backup tools are built around volumes: a cold backup copies every byte of the volume at each run, a hot snapshot freezes it in the storage cluster. Neither is the right shape for a large store, and the S3 protocol already carries the right one. Replicate your buckets to an object store outside the platform: a bucket at OVHcloud, Scaleway, AWS or any S3 endpoint you hold an account on. The copy is incremental and continuous rather than a nightly full pass, it costs the platform no backup traffic, and it doubles as a failover site your applications can be pointed at.
Two ways to run it, both with tools you already have:
# One-shot or scheduled mirror (a CronJob, or your CI): copies what changed, removes what wentmcli alias set offsite https://s3.<provider>.example '<access key>' '<secret key>'mcli mirror --remove --overwrite my-silo/app-assets offsite/app-assets-replica
# Server-side bucket replication: the store pushes every change itself, as it happens.# Both buckets need versioning enabled.mcli version enable my-silo/app-assetsmcli version enable offsite/app-assets-replicamcli replicate add my-silo/app-assets --remote-bucket offsite/app-assets-replicaThen treat the replica as you would any backup: check that it is complete, and try a restore in the other direction once.
The platform’s cold backups remain an option for a small store, a few tens of gigabytes.
They only capture the volumes you select, through an annotation on the pod; the chart exposes
podAnnotations for that, which is why the values above carry
backup.velero.io/backup-volumes: export. export is the name the chart gives its data volume,
not the name of your claim. Read the Backups guide first: selection is
opt-in, and a backup that reports Completed with no warning proves nothing about volume data
until you have opened it and seen the export volume listed with a non-zero size. Drop the
annotation the day the store outgrows this.
Migrate an existing MinIO release
Section titled “Migrate an existing MinIO release”Silo reads MinIO’s on-disk format as is, its IAM included: users, policies and service accounts stored in the volume come across untouched. The migration is therefore a change of image on the same volume, and the cleanest way to do it is a new release reusing the existing claim.
-
Take a hot snapshot of the MinIO claim first (Backups). Its restore is the rollback.
-
Note the claim name and the secret holding the root credentials of the MinIO release.
-
Tell Helm to leave the claim alone, before anything else. A claim the MinIO chart created belongs to its release, and uninstalling the release deletes it, data included, unless it carries this annotation:
Terminal window kubectl annotate pvc minio -n my-cluster helm.sh/resource-policy=keep -
Uninstall the MinIO release. The Deployment and the services go, the claim and the secret you created yourself stay. Two pods cannot mount the same claim, which is why the old release goes before the new one comes.
-
Install Silo on the same claim, with the same credentials and the same names:
mode: standalonepersistence:existingClaim: minio # the claim MinIO was usingexistingSecret: minio-root # keys rootUser and rootPassword, as beforefullnameOverride: minio # keep the service names, and your applications untouched -
Check that the buckets are all there, then that a write and a read work. Your applications never noticed: same service name, same credentials, same data.
If you run MinIO from your own manifests rather than from the chart, switching the image to
pgsty/silo on the same volume also works, depending on how far behind your MinIO is. One
detail decides whether the pod comes up: the image ships a binary named silo and no minio
alias, so a manifest whose command runs minio server must run silo server instead. The
platform tested the swap and still preferred a clean install on the existing claim, which is
the recipe above.
Two traps, both measured during the platform’s own migration:
- Bucket policies need the bare bucket ARN. A policy that only names
arn:aws:s3:::bucket/*still reads but fails to write, silently, at the first request rather than at startup. Addarn:aws:s3:::bucketnext to it before migrating. - OpenID tokens must be signed asymmetrically. If you authenticate to the console or the API with an OpenID provider, HMAC-signed tokens are no longer accepted; the provider must publish a JWKS.
Upgrade
Section titled “Upgrade”New versions land on the same address. Check what the platform runs before you move, then upgrade with the values file you already have:
helm show chart oci://registry.h8l.io/charts/silo # newest published versionhelm upgrade silo oci://registry.h8l.io/charts/silo --version <new> \ -n my-cluster -f silo.yaml --history-max 3In standalone mode the pod is recreated, not rolled: a single claim cannot be mounted twice,
so expect a few seconds without the API. Plan it outside the hours where your applications
write.