v0.4.0 — v1beta1 API, Lifecycle Policies, Reference Grants
This is a major release that promotes the API to v1beta1 and introduces several breaking changes from v1alpha1. See the migration guide for upgrade steps.
New Features
Bucket Lifecycle Policies (@bhark)
GarageBucket now supports S3 lifecycle rules managed by the operator. Supported rules: Expiration (by age or date) and AbortIncompleteMultipartUpload, with optional prefix and object size filters. The operator maintains an internal per-cluster S3 key to apply rules via the S3 API.
spec:
lifecycle:
rules:
- id: expire-old
status: Enabled
filter:
prefix: "logs/"
expiration:
days: 30GarageReferenceGrant — Cross-Namespace Access Control
New GarageReferenceGrant resource enables multi-tenant setups: a GarageKey or GarageBucket in namespace team-b can reference a GarageCluster in storage-admin only if the storage-admin owner creates a grant. Access is revoked by deleting the grant.
apiVersion: garage.rajsingh.info/v1beta1
kind: GarageReferenceGrant
metadata:
name: allow-team-b
namespace: storage-admin
spec:
from:
- kind: GarageKey
namespace: team-b
to:
- kind: GarageCluster
name: my-clusterMaintenance Mode
Reconciliation can now be suspended via a spec field instead of an annotation — works with GitOps tools and shows up in kubectl get:
spec:
maintenance:
suspended: trueThe operator requeues every 5 minutes but makes no changes while suspended. The old garage.rajsingh.info/pause-reconcile annotation is deprecated.
Worker Tuning
Background worker behavior is now configurable via spec.workers and continuously reconciled (survives pod restarts):
spec:
workers:
scrubTranquility: 4 # default: 2, higher = slower scrub, less disk pressure
resyncWorkerCount: 2 # default: 1, range: 1–8
resyncTranquility: 4 # default: 2, higher = slower resyncCurrent values are visible in status.workers.variables.
New Operational Annotations
Three new one-shot annotations for block-level recovery operations:
| Annotation | Value | Action |
|---|---|---|
garage.rajsingh.info/revert-layout
| "true"
| Discard all staged layout changes (does not undo already-applied versions) |
garage.rajsingh.info/retry-block-resync
| "true" or block hashes
| Clear resync backoff so errored blocks retry immediately |
garage.rajsingh.info/purge-blocks
| block hashes | Irreversible. Delete all S3 objects referencing the listed blocks |
Operation Status
All triggered operations (repair, snapshot, scrub, etc.) now record their outcome in status.lastOperation:
status:
lastOperation:
type: "Repair:Blocks"
triggeredAt: "2026-05-02T10:00:00Z"
succeeded: trueOn failure, succeeded: false and error contains the message. The annotation is kept so the next reconcile retries automatically.
Bidirectional Gateway Connections
External cluster connections from gateway clusters are now bidirectional — gateway nodes can both initiate and receive RPC connections from storage clusters.
Breaking API Changes (v1alpha1 → v1beta1)
| Field | v1alpha1 | v1beta1 |
|---|---|---|
GarageKey.spec.expiration
| string (date)
| expiresAt: metav1.Time
|
GarageCluster.spec.allowWorldReadableSecrets
| — | renamed to allowInsecureSecretPermissions
|
GarageBucket.spec.keys[].bucketRef
| string (name)
| BucketRef object {name, namespace}
|
GarageCluster.spec.storage
| DataStorageConfig with separate metadataPath/dataPath
| merged into VolumeConfig with unified paths
|
GarageCluster.spec.replication.zoneRedundancy
| string enum
| zoneRedundancyMode + optional zoneRedundancyMinZones int
|
GarageCluster.spec.rpcTimeout / rpcPingTimeout
| *int64 (milliseconds)
| *metav1.Duration (e.g. "5s")
|
GarageCluster.spec.admin.enabled
| bool
| removed (admin API always enabled) |
GarageKey.spec.secretTemplate.namespace
| configurable | removed — secrets are always in the key's namespace |
spec.replication
| required | optional — webhook defaults to {factor: 3, consistencyMode: consistent}
|
Bug Fixes
- Operator-internal S3 key leaked on cluster delete — now correctly garbage-collected (@bhark)
- Cross-namespace ownerRef on internal Secret caused Kubernetes GC deletion loops (@bhark)
- Race condition: two concurrent reconciles could miss the secret cache and leak Garage keys (@bhark)
- Malformed secret data caused keys to leak and never be cleaned up (@bhark)
- Bucket reconciler raced cluster finalizer during
DeleteKey(@bhark) GarageKey.spec.expiresAtstatus not cleared when Garage returned a non-RFC3339 expiration (@rajsinghtech)- Lifecycle webhook failed to reject
expirationDatenot at midnight UTC (@bhark) - Extra
PutBucketLifecycleConfigurationcalls on every reconcile when lifecycle was already in sync (@bhark)
Full Changelog: v0.3.16...v0.4.0