Custom resources¶
The generated CRDs under config/crd/bases/ and JSON schemas under schemas/
are authoritative for validation. This page is an operator-oriented map of the
current API surface, including fields whose safety behavior is easy to miss.
For every served API version, see the generated API field reference.
Its field paths, types, defaults, and descriptions are refreshed from the CRD
schemas. Current versions are generated from Go API types; the served
GarageBucket v1alpha1 schema is preserved for compatibility. Run
make api-reference after changing the types; make verify-generate checks
freshness in CI.
Resource map¶
| Kind | API version | Scope | Purpose |
|---|---|---|---|
GarageCluster |
garage.rajsingh.info/v1beta2 (preferred), v1beta1 (deprecated) |
Namespaced | Garage topology, configuration, layout, federation, health, and operations |
GarageBucket |
garage.rajsingh.info/v1beta1 |
Namespaced | Bucket, aliases, quotas, website, lifecycle, and key grants |
GarageKey |
garage.rajsingh.info/v1beta1 |
Namespaced | S3 key import/generation and permissions |
GarageNode |
garage.rajsingh.info/v1beta1 |
Namespaced | One managed, gateway, external, or node-local Garage identity |
GarageAdminToken |
garage.rajsingh.info/v1beta1 |
Namespaced | Static Admin token Secret template |
GarageReferenceGrant |
garage.rajsingh.info/v1beta1 |
Namespaced | Allow exact or label-selected namespaces/kinds to make cross-namespace references |
Short names include gc, gb, gk, gn, gat, and grg as published in
the CRDs. Confirm them on the target cluster with kubectl api-resources.
References to a GarageCluster use name and an optional namespace. For
GarageBucket and GarageKey, a cross-namespace reference is allowed only
when a GarageReferenceGrant in the destination namespace matches the source
kind plus its exact namespace or Namespace labels, and the target kind/name. GarageAdminToken is
namespace-local: its clusterRef.namespace must be empty or match the token's
namespace, and a GarageReferenceGrant does not override that restriction.
GarageNode does not support cross-namespace cluster references.
GarageCluster v1beta2¶
Topology shapes¶
| Shape | Required fields | Operator-owned workload |
|---|---|---|
| Storage | storage |
One single-replica StatefulSet per managed StatefulSet-backed storage GarageNode; each storage.nodeLocalPools entry adds a DaemonSet with one Garage member per selected Kubernetes Node |
| Unified | storage + gateway |
Storage members (StatefulSets plus any node-local DaemonSet members) plus one persistent-identity gateway GarageNode per Auto gateway replica |
| Edge gateway | gateway + connectTo |
One cluster-level gateway StatefulSet; storage is remote |
| Management handle | connectTo only |
No Garage workload, PVC, Service, or node |
storage and connectTo are mutually exclusive. Federation of independently
managed storage sites uses remoteClusters, not connectTo. gateway.replicas
means Auto gateway identities in a unified cluster, but means replicas in the
single cluster-level StatefulSet for an edge cluster; it has no scaling effect
on user-owned Manual GarageNodes.
The default Auto storage group and ordinary managed GarageNodes are
StatefulSet-backed. storage.nodeLocalPools is additive: each pool uses a
DaemonSet and gives every selected Kubernetes Node its own HostPath-backed
identity. It does not change the workload type of the default group or Manual
GarageNodes.
Spec field matrix¶
| Group | Fields | Contract |
|---|---|---|
| Image and identity | image, imageRepository, imagePullPolicy, imagePullSecrets, serviceAccountName |
image wins over imageRepository; image and metadata changes are identity-bearing rollouts |
| Topology | storage, gateway, connectTo, layoutPolicy, deletionPolicy |
Shapes are disjoint; Auto → Manual is a one-way handoff |
| Placement | zone, zoneFrom, defaultNodeTags |
zone is the fallback; zoneFrom reads a Kubernetes Node label and requires cluster-scoped access |
| Replication | replication.factor, consistencyMode, zoneRedundancyMode, zoneRedundancyMinZones |
Factor is 1–7; factor changes require the destructive migration workflow |
| RPC and Services | network |
RPC bind/public address, shared secret, bootstrap peers, and API Service metadata/type |
| API listeners | s3Api, k2vApi, webApi, admin |
S3 defaults to 3900, web 3902, Admin 3903, and K2V 3904 when enabled |
| Storage engine | database, blocks |
Garage database and block-file tuning; some settings require newer Garage versions |
| Discovery/security | discovery, security, logging |
Kubernetes/Consul discovery, supported security switches, and Rust logging |
| Federation | publicEndpoint, remoteClusters |
RPC reachability and imported remote roles; these do not publish the S3 endpoint |
| Layout | layoutManagement |
Automatic apply threshold, the fail-closed positive-capacity drain policy, siteRole (Writer or Follower; unset means Writer) for federated sites, and redundancyVerification.onTopologyChange (default false: a storage topology change voids the FullyReplicated proof but starts none; true starts a blocks-only proof; ignored on followers) |
| Operations | monitoring, maintenance, workers |
ServiceMonitor/relabeling, reconciliation suspension, and background worker tuning |
Storage, gateway, and pod fields¶
| Field | Meaning and safety rule |
|---|---|
storage.replicas |
Only the default Auto PVC group. Set 0 when using only Manual nodes or node-local pools; omitted defaults to 3. |
storage.metadata |
PVC or EmptyDir containing node_key and Garage metadata. Persistent metadata preserves identity. Optional dataSourceRef restores every Auto metadata PVC from one group source. |
storage.data |
PVC or EmptyDir for object blocks. data.paths is the multi-disk form. Optional dataSourceRef restores every Auto data PVC from one group source. |
storage.data.paths[] |
Each entry has path, capacity, readOnly, and an optional per-path volume; writable paths need capacity. Put dataSourceRef on paths[].volume, not on top-level storage.data. |
gateway.metadata.dataSourceRef |
Same group-source contract for Auto unified and edge gateway metadata PVCs. Gateway data stays EmptyDir. |
storage.metadataSnapshotsDir |
Directory for Garage metadata snapshots; rendered as metadata_snapshots_dir. The corresponding GarageNode.spec.storage.metadataSnapshotsDir overrides it for one node. |
storage.metadataAutoSnapshotInterval |
Enables automatic metadata snapshots. Use Garage duration syntax such as 10m, 6h, or 1h 30m; values must be at least 10m. The corresponding GarageNode field overrides it for one node. |
storage.metadataFsync |
Enables fsync for metadata transactions. GarageNode.spec.storage.metadataFsync can override the cluster value for one node. |
storage.dataFsync |
Enables fsync for data block writes. GarageNode.spec.storage.dataFsync can override the cluster value for one node. |
storage.nodeLocalPools |
Additive selector-driven HostPath identities. Each selected Kubernetes Node gets one Garage role; the pool is not a replication group or failure domain. When selected Nodes equal replication.factor, use node-local-out-of-service then node-local-replace-identity instead of ordinary retirement; see Node-local pools. |
storage.layoutPolicy |
Overrides the cluster policy for the default PVC group only; node-local pools remain operator-owned. |
storage.pvcRetentionPolicy |
Controls claims on StatefulSet deletion and scale-down (Retain or Delete). |
storage.capacityReservePercent |
Reserves 0–50% of advertised capacity for overhead in Auto mode. |
storage.podDisruptionBudget |
One PDB for the storage tier, including node-local pools. |
gateway.replicas |
Auto unified identity count or edge StatefulSet replicas; gateway nodes have no object-block capacity. |
gateway.metadata |
Persistent by default for gateway identity; EmptyDir is an explicit identity-churn exception. |
gateway.rpcPublicAddr |
Address peers use for the gateway identity. One shared edge config/address is safe only for one independently routed edge identity. |
gateway.readinessProbe |
Overrides the default bind-only S3 TCP probe; a cluster-wide /health probe can withdraw all gateways during a quorum loss. |
storage/gateway pod template |
resources, nodeSelector, tolerations, affinity, topology spread, labels, annotations, priority, security contexts, env, and envFrom. |
storage/gateway/pool pod extras |
initContainers (max 16), extraContainers (max 16), and extraVolumes (max 32): standard Kubernetes containers and volumes with operator-reserved names and mounts rejected; see Init containers, sidecars, and extra volumes. GarageCluster.v1beta1 carries the same fields. |
Volumes, selectors, and size growth¶
VolumeConfig.selector is a Kubernetes PV selector for a newly created claim.
It disables dynamic provisioning for that claim; provide a distinct compatible
PV and ensure its storageClassName matches. Selectors, storage classes, access
modes, claim labels, and claim annotations are claim-template inputs, not a way
to reselect an already-bound PVC.
volumeAttributesClassName selects a Kubernetes
VolumeAttributesClass
(IOPS, throughput, or other backend attributes) for the claim. It is accepted
on every volume that accepts storageClassName: storage.metadata,
storage.data, storage.data.paths[].volume, gateway.metadata, and the
metadata, data, and dataPaths[] volumes of a GarageNode. It must be a
DNS-subdomain name (1–253 characters, lowercase alphanumerics, - and .),
has no default, and is rejected on EmptyDir volumes and on a GarageNode
volume that sets existingClaim (the operator does not modify claims it does
not manage). Each volume role takes its own value: storage.data.paths[]
entries do not inherit storage.data.volumeAttributesClassName, and a
top-level value next to paths produces an admission warning.
Unlike storageClassName, the class is mutable on a live identity. The
claim template of a StatefulSet is immutable, so the operator treats the field
as a create-time hint and then patches spec.volumeAttributesClassName on the
bound claim (Kubernetes forbids the change while a claim is Pending). It
never changes size, scheduling, layout, rollouts, drains, or deletion. Once a
class is configured it can be changed but not removed; removing a configured
value is rejected because Kubernetes would otherwise leave the claim on a
class the spec no longer describes. Leaving the field unset means the operator
does not touch the claim's class.
| Field | After the first workload exists |
|---|---|
size |
Growth only |
volumeAttributesClassName |
Change allowed; removal of a configured value rejected |
storageClassName, selector, accessModes, type, existingClaim, mount paths |
Immutable |
Progress is reported through conditions and never feeds Ready or Phase:
| Resource | Condition | Reasons |
|---|---|---|
GarageNode |
VolumeAttributesClassApplied |
Applied, WaitingForBind, ModifyInProgress, Infeasible, Unsupported |
GarageCluster |
StorageVolumeAttributesReady |
Worst reason across the generated GarageNodes and edge-gateway claims; the message lists up to five unfinished workloads |
Infeasible means the CSI driver rejected the class (or it does not exist):
set the previous class in the spec to cancel the modification. Unsupported
means the API server did not accept the field, so the cluster lacks the
VolumeAttributesClass feature; the operator retries about every five minutes
instead of on every reconcile.
The operator supports in-place size growth on the same volume. For a live identity, the volume source, selector, storage class, access modes, mount paths, and single-versus-multi-path topology are immutable safety boundaries. To change one, scale the affected Auto group or edge gateway to zero, wait for its exact Garage roles and workloads to settle, change the template, and scale up in a separate update. Retained claims keep their original template values.
volumeClaimTemplateSpec remains in the schema only for compatibility and is
rejected for new or changed operator-managed workloads. Use the explicit volume
fields above, or GarageNode.spec.storage.*.existingClaim for a pre-provisioned
claim on a Manual node. data.paths is uniform across the default Auto group;
use GarageNode.spec.storage.dataPaths when disks differ per identity.
For GitOps disaster recovery, set dataSourceRef on the volume role you
want populated on a new Auto cluster. Setting the field is the opt-in.
One group Restore is stamped onto every generated PVC of that role; the
populator (Kopiur Restore, Volsync, or similar) must map target ordinal i to
source-group member i, typically by PVC name. A core PVC or single-volume
clone is unsafe because it would give every ordinal the same contents.
apiVersion: garage.rajsingh.info/v1beta2
kind: GarageCluster
metadata:
name: garage
namespace: storage
spec:
replication:
factor: 3
storage:
replicas: 3
metadata:
size: 10Gi
dataSourceRef:
apiGroup: kopiur.example.io
kind: Restore
name: garage-metadata-restore
data:
size: 100Gi
dataSourceRef:
apiGroup: kopiur.example.io
kind: Restore
name: garage-data-restore
Identity restore needs both metadata and data (or per-path data) sources,
and the new cluster should reuse the old GarageCluster name so PVC names
match the snapshot members. Data-only restore mints new node_keys onto old
object blocks and does not bring buckets, keys, or layout back. Metadata-only
restore is admitted with a warning: layout and block-refs will not match empty
disks. Multi-disk clusters set dataSourceRef on each storage.data.paths[].volume.
Gateway identities use spec.gateway.metadata.dataSourceRef.
This is not an escape hatch for volumeClaimTemplateSpec: arbitrary claim
templates remain rejected. Each source must be a non-core same-namespace group
reference and cannot be combined with a selector, EmptyDir, or existingClaim.
Node-local pools are HostPath and are out of scope. Manual GarageNodes do not
inherit cluster volume sources; set GarageNode.spec.storage.*.dataSourceRef
on a new claim or attach a pre-populated existingClaim. If a populator is
absent, fails, or never completes, Kubernetes normally leaves the PVC/Pod
Pending and exposes events. A partial restore can instead reach Garage startup
and fail identity/layout checks; the operator cannot inspect source contents.
Configure populator retry/timeout and alerting so recovery cannot hang silently.
Each volume dataSourceRef is immutable after the GarageCluster is created.
Set it in the initial restore manifest; changing it later cannot repopulate an
existing PVC or change an immutable StatefulSet claim template. To restore from
a different source, create a new cluster identity or use explicitly managed
GarageNode claims after a deliberate migration.
dataSourceRef requires a Kubernetes version and storage implementation that
support PVC data sources and the selected populator kind. VolumeGroupSnapshot
is expected to be generally available in Kubernetes 1.36, but snapshot and
populator support is provided by Kubernetes storage components rather than
this operator.
Garage configuration matrix¶
| Field | Important subfields |
|---|---|
network |
rpcBindPort, rpcBindAddress, rpcPublicAddr, rpcPublicAddrSubnet, rpcBindOutgoing, rpcSecretRef, rpcPingTimeout, rpcTimeout, bootstrapPeers, service |
s3Api |
bindPort, bindAddress, region, rootDomain |
k2vApi |
bindPort, bindAddress; omitting the object disables K2V |
webApi |
enabled, scheme, bindPort, bindAddress, rootDomain, addHostToMetrics |
admin |
bindPort, wildcard bindAddress, adminTokenSecretRef, metricsTokenSecretRef, metricsRequireToken, traceSink |
database |
engine (lmdb, sqlite, fjall), lmdbMapSize, fjallBlockCacheSize |
blocks |
size, ramBufferMax, maxConcurrentReads, maxConcurrentWritesPerRequest, compressionLevel, disableScrub, useLocalTZ |
discovery |
Kubernetes discovery or Consul catalog/agent, TLS credentials, tags, metadata, and datacenters |
security |
allowInsecureSecretPermissions, allowPunycode; security.tls is retained but rejected because Garage removed rpc_tls |
logging |
level, syslog, journald |
workers |
scrubTranquility, resyncWorkerCount (1–8), resyncTranquility |
monitoring |
enabled, interval, additionalLabels, metricRelabelings for the generated ServiceMonitor |
maintenance |
suspended: true pauses reconciliation; it is not the old pause annotation |
K2V requires a Garage image built with the K2V feature; setting k2vApi only
renders the listener and Service port. Worker tranquility reduces I/O at the
cost of longer scrub/resync periods. See Garage configuration
for examples and version caveats.
Reserved environment variables¶
The following names are reserved in env and in prefixes that could inject
them through envFrom:
GARAGE_CONFIG_FILE
GARAGE_RPC_SECRET
GARAGE_RPC_SECRET_FILE
GARAGE_ADMIN_TOKEN
GARAGE_ADMIN_TOKEN_FILE
GARAGE_METRICS_TOKEN
GARAGE_METRICS_TOKEN_FILE
They control the rendered configuration, mesh identity, or credential provenance. Existing released objects that already contain an override enter a fail-closed migration path; follow migration details before removing or changing it.
GarageCluster status¶
| Status field | Use |
|---|---|
phase, replicas, readyReplicas, storageReplicas, storageReadyReplicas, gatewayReplicas, gatewayReadyReplicas |
Workload and identity counts; scaleReplicas/scaleSelector are the narrower Kubernetes Scale projection |
clusterId, buildInfo |
Garage cluster and build identity |
health, storageStats |
Connectivity, quorum, partitions, and disk totals |
layoutVersion, stagedLayoutVersion, stagedRoles, layoutPreview, layoutHistory |
Applied/staged layout and the bounded recent history |
blockErrors, blockErrorDetails, resyncQueueLength |
Block-recovery evidence |
redundancy |
Full-redundancy proof: verification phase, trigger, layout version, proof evidence, and per-storage-node resync, block error, table sync, and repair progress (see below) |
storageRollout |
Exact actor, workload/PVC UIDs, desired hashes, fencing state, and recovery pod evidence for one identity-bearing handoff |
autoModePvcHandoffs |
Exact retained PVC UID and replacement GarageNode authorization after an Auto slot recreation |
storageDrain |
Actor UID, transaction/target hash, removed roles, repair worker IDs, resync baselines, quiet period, and terminal proof |
factorMigration |
Phase and source/target factor for the destructive layout rebuild |
remoteClusters, drainingNodes |
Federation connectivity and draining layout nodes |
endpoints |
Rendered S3, K2V, web, Admin, metrics, and RPC URLs |
pendingGatewayTombstones, gatewayNodesNotInLayout, unreachablePeers, layoutDiagnosis |
Actionable gateway/layout degradation and peer reachability |
lastOperation, observedGeneration, conditions |
Last annotation result, reconciliation generation, and health gates |
resyncQueueLength and blockErrors are absent when the operator could not
observe them on this pass (Admin API unreachable or some node that can hold
blocks did not answer). A silent node whose current layout role has no
capacity (a gateway) and that is not draining an older layout version is
ignored, because it stores no blocks. 0 means every such node answered: the
resync queue is drained, or no block has a sync error. The queue also counts delayed rechecks that Garage has scheduled
for later (for example deletions after its ~10-minute block GC delay), so on an
active cluster it can stay above 0. Neither value alone proves full redundancy: right after a node returns with empty storage the queue
can be near 0 because metadata has not synced yet.
redundancy and the FullyReplicated condition are the operator's answer to
"does every object have all its replicas again?". Garage has no API that
reports this, so the operator proves it: after the layout settles it repairs
one storage node at a time (a full table sync, then a blocks repair scan, then
a two-minute pause), and then requires the block resync workers to stay idle
and error-free through a quiet period. Only that sequence sets
FullyReplicated=True. The operator never starts a proof on upgrade or when
it first sees a cluster: it records a baseline and reports
FullyReplicated=Unknown/NotVerified. A proof starts when the
garage.rajsingh.info/verify-redundancy annotation gets a new value (trigger
Requested). A change in the layout's storage nodes, zones or capacities
after the baseline voids the proof; it starts one (trigger NodeChanged or
LayoutChanged; blocks scans only) only when
spec.layoutManagement.redundancyVerification.onTopologyChange is true.
A proof covers only the storage nodes that run at this site.
redundancy.scope is Cluster when this site runs every storage node of the
layout and Local when other sites run some; redundancy.storageNodes has
total, local, remote and verified (local nodes the current proof has
finished) counts, and a Local proof ends at FullyReplicated=True/VerifiedLocal.
redundancy.coordination is how federated sites take turns: the last
blocks repair worker ID seen per remote storage node
(remoteRepairWorkerIds), and when and on which node a remote blocks repair
was last seen (lastRemoteRepairAt, lastRemoteRepairNodeId); a requested
proof waits (Unknown/WaitingForOtherSite) until a hold-down after it.
redundancy.verification.phase is Idle, Pending, SyncingMetadata,
ScanningBlocks, Settling, Partial, or Verified;
redundancy.verification.trigger says why the current proof started;
redundancy.verification.currentNodeId is the node being repaired and
redundancy.verification.completedNodeIds the nodes already done.
redundancy.verification.topologyHash and layoutVersion are the baseline a
later layout change is compared with. redundancy.verification.verifiedAt
keeps the time of the last successful proof while a new one runs.
redundancy.verification.evidence is the operator's restart-safe proof state;
treat it as internal. redundancy.deferredNodes[] lists storage nodes the
proof skipped, keyed by nodeId, with reason (Down, NotReporting, or
RepairFailed), since, and retryAfter; while it is not empty the proof
ends at Partial. redundancy.nodes[] is keyed by Garage node ID and shows
each storage node's resync queue, block error count, remaining table sync
partitions, and blocks repair progress, so an operator can see which node is
still behind. redundancy.lastProgressAt is the last time any of those
counters moved. For an ETA, graph the Prometheus metrics as described in
Wait for full redundancy.
FullyReplicated does not gate Ready. Gateway-only and connectTo
clusters have neither field. A federation Follower site runs the same
proof for its own storage nodes, only on request. A federated site without
spec.layoutManagement.siteRole runs no proof and reports
FullyReplicated=Unknown/SiteRoleUnset.
The status fields nodes, activeRepairs, workers, workerCount,
workersFailed, scrubStatus, lifecycleStatus, and totalNodes are
deprecated. The operator never writes them; they will be removed in the next
API version. Use redundancy, blockErrors, resyncQueueLength, and the
Garage Admin API instead.
The currently written cluster conditions include Ready,
PublicEndpointReady, ManagementHandleReady, GatewayConnected,
GatewayLayoutDegraded, GatewayTombstones, QuorumAtRisk,
PeerUnreachable, RemoteClustersHealthy, FederationConfigured,
DiscoveryCompatible (only while spec.discovery is enabled),
StorageScaleDownBlocked, StorageTopologyReady, LegacySTSMigrated,
NodeLocalPoolsReady, StorageRolloutReady, StorageDrainReady,
OperatorAdminTokenReady (only with spec.admin.adminTokenSecretRef), FullyReplicated (storage clusters without connectTo), and PodExtrasValid (written only once a cluster uses pod extras).
Older condition constants such as ClusterHealthy, LayoutApplied, and
NodesConnected remain for compatibility but are not emitted as independent
conditions by the current controllers.
The annotations and conditions reference lists their meanings.
GarageBucket¶
Spec¶
clusterRef selects the cluster. deletionPolicy controls whether deleting
the Kubernetes resource deletes the remote bucket: Delete is the default and
preserves the existing behavior; Retain removes Kubernetes management while
leaving the Garage bucket, objects, aliases, permissions, and configuration
untouched. bucketId pins an existing Garage bucket and
prevents replacement; after the first reconciliation the operator records the
resolved ID in status.bucketId, and the field can then be removed from the
manifest — the operator keeps reconciling the recorded bucket. Re-pointing
bucketId at a different bucket remains rejected; a bucket ID can also be
claimed by only one GarageBucket per GarageCluster. globalAlias defaults
from the object name when omitted; localAliases create key-scoped aliases.
quotas supports maxSize and maxObjects. website manages
indexDocument and errorDocument; routing rules and redirect-all behavior
must be configured through S3 APIs. lifecycle supports Garage's subset of
S3 expiration and incomplete-multipart rules and is evaluated asynchronously
by Garage's lifecycle worker. keyPermissions and
GarageKey.spec.bucketPermissions are equivalent declaration directions and
are merged when both describe the same grant. websiteExposure (requires website.enabled) creates an
Ingress or a Gateway API HTTPRoute (exactly one of the two) in the
bucket's namespace that routes the canonical
<globalAlias><webApi.rootDomain> — or the listed hostnames — to the
cluster web API Service (<cluster>-gateway for unified clusters) or an
explicit backendRef override. An HTTPRoute accepts any listed hostname
(non-canonical ones get a URLRewrite back to the canonical host); an
Ingress only accepts the canonical host or the global alias, because it
cannot rewrite the Host header. Ingress is same-namespace only; an
HTTPRoute crosses namespaces through a Gateway API ReferenceGrant in the
cluster's namespace. ingress.tlsSecretName fills the Ingress TLS section
and is ignored for HTTPRoutes.
Bucket and key references can name a namespace; cross-namespace grants must be
approved by a GarageReferenceGrant in the cluster's namespace.
When using Retain, save status.bucketId before deleting the resource. A
future GarageBucket can re-adopt the retained bucket by setting spec.bucketId
to that ID (and drop the field again once it shows up in the new resource's
status). Retain only protects the remote bucket; deleting its underlying
Garage cluster or storage can still make the data unavailable.
Status¶
status.bucketId, phase, globalAlias, and createdAt identify the remote
bucket. size, incomplete-upload counters, quotaUsage, websiteEnabled,
websiteUrl, and websiteConfig report observed state. keys,
localAliases, and lifecycleRules are read-back summaries.
websiteExposure records the exposed resource (type, name, hostnames,
and, for an HTTPRoute, the per-parent Accepted/ResolvedRefs/Ready
states) when spec.websiteExposure is set. The
managedGlobalAlias, pendingGlobalAlias, managedLocalAliases, and
managedKeyGrants fields are controller ownership records used for crash-safe
replacement and revocation; do not edit them. Inspect the Ready,
LifecycleConfigured, and WebsiteExposed conditions, plus
BucketLookupStuck or BucketMetadataDegraded when a bucket is not ready. During a requested
Delete, a non-empty remote bucket sets DeletionBlocked=True with reason
BucketNotEmpty and leaves the finalizer in place until an operator or
administrator removes the content. The older bucket condition
constants (BucketCreated, QuotaConfigured, WebsiteConfigured, and
AliasesConfigured) remain for compatibility and are not emitted independently.
GarageKey¶
Spec¶
clusterRef selects the cluster and name is a Garage-friendly display name.
Use importKey to adopt an existing key (prefer secretRef over inline
credentials; inline values follow the Garage v2.3 key grammar, see
accepted credential formats),
or omit it to generate a key. secretTemplate controls the
generated Secret's name, keys, endpoint/region/bucket fields, type, and extra
data. bucketPermissions grants per-bucket access; allBuckets intentionally
includes buckets created outside Kubernetes; permissions.createBucket grants
S3 bucket creation. expiresAt and neverExpires are mutually exclusive.
Expiry marks the resource and remote key but does not rotate credentials.
secretTemplate supports these fields:
| Field | Meaning and default |
|---|---|
name |
Generated Secret name; defaults to the GarageKey name. |
labels |
Additional labels for the generated Secret. |
annotations |
Annotations for the generated Secret. |
type |
Kubernetes Secret type; defaults to Opaque. |
accessKeyIdKey |
Secret data key for the access-key ID; defaults to access-key-id. |
secretAccessKeyKey |
Secret data key for the secret access key; defaults to secret-access-key. |
endpointKey |
Secret data key for the S3 endpoint, including its scheme; defaults to endpoint. |
hostKey |
Secret data key for the S3 host without a scheme; defaults to host. |
schemeKey |
Secret data key for the endpoint scheme; defaults to scheme. |
regionKey |
Secret data key for the S3 region; defaults to region. |
bucketNameKey |
Secret data key for the bucket name; defaults to bucket. It is used only when includeBucketName is enabled. |
websiteUrlKey |
Secret data key for the referenced bucket's observed website URL; defaults to website-url. It is used only when includeWebsiteUrl is enabled. |
credentialsFileKey |
Secret data key for an AWS shared credentials file; defaults to credentials. It is used only when includeCredentialsFile is enabled. |
credentialsFileProfile |
Profile name in the AWS shared credentials file; defaults to default. It must start with an alphanumeric character and may contain alphanumeric characters, dots, underscores, and hyphens. It is used only when includeCredentialsFile is enabled. |
includeEndpoint |
Include endpoint, host, and scheme fields; defaults to true. |
includeRegion |
Include the region field; defaults to true. |
includeBucketName |
Include a bucket name when the key references exactly one bucket; defaults to false. |
includeWebsiteUrl |
Include the observed website URL when the key references exactly one bucket through bucketRef; defaults to false. |
includeCredentialsFile |
Include an AWS shared credentials file containing the access key ID and secret access key under credentialsFileProfile; defaults to false. Region and endpoint remain separate fields. |
additionalData |
Extra string key/value pairs. Admission rejects entries that collide with generated Secret data keys. |
Status¶
keyId, accessKeyId, phase, createdAt, expiresAt, and secretRef
identify the remote key and generated Secret. clusterWide, permissions, and
buckets report observed access. managedBucketGrants and clusterWide also
record controller ownership before and after remote mutations so a failed
reconcile can revoke only grants it owns. effectivePermissions is retained
for compatibility and is not currently populated; use status.buckets.
Inspect Ready. Expiry is represented by status.phase: Expired, not by a
KeyExpired condition; the older key condition constants remain for compatibility.
GarageNode¶
Spec¶
| Field | Meaning |
|---|---|
clusterRef, nodeId, zone, zoneFrom |
Parent and identity/placement. nodeId is authoritative for external nodes but an expected pin for managed nodes. |
capacity, gateway, tags |
Garage layout role. Gateway nodes omit capacity and store no blocks. |
external |
Address/port for an already-running process; no workload is created. |
backing |
StatefulSet (default) or controller-owned NodeLocalPool; the latter requires kubernetesNodeName and nodeLocalPoolName. |
storage |
Metadata/data or dataPaths, optional existingClaim, selectors, fsync, snapshots, and per-path capacity. |
| Pod overrides | Image, resources, scheduling, labels/annotations, service account, security contexts, topology spread, env, envFrom, and logging. initContainers, extraContainers, and extraVolumes each replace the tier's list when set; [] opts out. |
network, publicEndpoint |
Per-node RPC advertisement and optional RPC Service exposure. |
maintenance |
suspended: true freezes this node's workload, Service, ConfigMap, and layout reconciliation. |
gateway, external, backing, and node-local ownership fields are identity
boundaries. Drain the old identity and create its replacement rather than
changing the role in place.
Status¶
nodeId, zone, phase, inLayout, layoutVersion, connected, lastSeen,
address, hostname, tags, disk partitions, version, and partitions
describe the observed process. managedPVCs records exact PVC UID or pending
reservation evidence. observedPodUid prevents stale process evidence from
being reused. cyclePhase, cycleSiblingName, and cycleSiblingNodeId track
the narrow add-before-remove cycle workflow. clusterAdminEndpoint and
clusterAdminTokenSecretRef preserve delete-time access for an external/edge
parent. parentDeletionRequestGeneration is controller-owned handoff state.
dbEngine and garageFeatures remain in the schema for compatibility but
are not populated by the current Garage Admin API. storedData,
repairInProgress, repairType, repairProgress, and blockErrors are
deprecated: the operator never writes them, and they will be removed in the
next API version. The parent cluster's status.redundancy.nodes[] carries
per-node repair, resync, and block error progress. Inspect Ready, DrainPrepared, Cycling, and the literal
Suspended condition when spec.maintenance.suspended is active. The older
node discovery/layout condition constants are not emitted independently.
GarageAdminToken¶
GarageAdminToken creates static bootstrap bearer material in a Secret. The
referenced cluster must select that Secret through
spec.admin.adminTokenSecretRef. secretTemplate controls the Secret name,
labels, annotations, token key, and optional endpoint key.
spec.secretTemplate supports these fields:
| Field | Meaning and default |
|---|---|
name |
Generated Secret name; defaults to the GarageAdminToken name. |
labels |
Additional labels for the generated Secret. |
annotations |
Additional annotations for the generated Secret. |
tokenKey |
Secret data key containing the admin bearer; defaults to admin-token. |
includeEndpoint |
Include the cluster Admin API endpoint; defaults to true. |
endpointKey |
Secret data key for the Admin API endpoint; defaults to admin-endpoint. |
The resource and its referenced GarageCluster must be in the same namespace:
omit clusterRef.namespace or set it to the token's namespace. The generated
Secret is namespace-local, and GarageCluster.spec.admin.adminTokenSecretRef
cannot consume a Secret from another namespace. Although
GarageReferenceGrant.spec.from includes GarageAdminToken for schema and
status compatibility, a grant cannot make this static credential path
cross-namespace.
This is not a Garage dynamic-token row: name, expiresAt, and
neverExpires are compatibility fields and do not provide server-side scope,
expiry, or revocation. status.tokenId is a short display fingerprint and
status.tokenDigest is the full hash used to detect Secret mutation without
exposing the bearer. status.phase, secretRef, observedGeneration, and the
Ready condition report reconciliation. The older token condition constants
(TokenCreated, TokenSecretCreated, and TokenExpired) remain for
compatibility but are not emitted independently. Deleting the resource does
not revoke bytes already loaded by a running Garage process.
GarageReferenceGrant¶
The grant lives in the destination namespace, where its administrator controls
the trust boundary. Each spec.from entry lists a source kind plus exactly one
of an exact source namespace or a Kubernetes namespaceSelector; selectors
match Namespace labels using standard matchLabels and matchExpressions.
An empty selector matches every namespace, so use it only when that broad grant
is intentional. Namespace labels are authorization input: a namespace that
gains a matching label gains access, and one that loses it no longer matches.
Use labels controlled by a trusted policy; a principal that can change a
selected Namespace label can change who the grant authorizes.
For Helm namespace-scoped installations, the chart adds only a read-only
ClusterRole for Namespace labels; managed Garage resources remain limited to
the configured namespaces while namespaceSelector entries are evaluated.
Allowed source kinds are GarageBucket, GarageKey, and GarageAdminToken.
spec.to narrows
the destination kind and optional name; omitted names allow all resources of
that kind. Omitting spec.to preserves the original grant behavior and allows
only GarageCluster and GarageBucket targets; newer target kinds such as
GarageKey require an explicit entry. GarageAdminToken remains in the source
kind schema and status accounting for compatibility, but its static credential
path is namespace-local and a grant cannot make it cross-namespace.
GarageNode is never allowed cross-namespace.
status.inUseBy is rebuilt on every reconcile and lists the kind, name, and
namespace of current referencing resources. It is safe to remove a grant only
after this list is empty and dependent resources no longer need the reference.
Conditions report whether the grant is in use. Removing a grant makes future
reconciliation fail closed; it does not itself revoke already-issued Garage
permissions.
Validation and compatibility-only fields¶
Some fields remain in schemas to support conversion or old manifests but are
rejected, warned, or ignored. Examples include security.tls (Garage removed
rpc_tls), publicEndpoint.externalIP, remoteClusters[].defaultCapacity,
arbitrary managed volumeClaimTemplateSpec, and
connectTo.clusterRef.kubeConfigSecretRef. Read admission warnings and errors
as the current contract.
For complete field-level descriptions, use the versioned generated schemas: