v0.4.0
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