Skip to content

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.

See GitOps volume restore.

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: