From 81c64c876cc016793ef0eeaceef6cc5c2220669e Mon Sep 17 00:00:00 2001 From: Simon Planinschek Date: Thu, 9 Jul 2026 19:03:35 +0200 Subject: [PATCH 01/12] init helm chart for garage --- .github/workflows/release.yml | 48 +++++ .gitignore | 4 + garage/.helmignore | 23 +++ garage/Chart.yaml | 13 ++ garage/templates/NOTES.txt | 53 ++++++ garage/templates/_helpers.tpl | 64 +++++++ garage/templates/httproute.yaml | 35 ++++ garage/templates/ingress.yaml | 46 +++++ garage/templates/secret.yaml | 57 ++++++ garage/templates/service.yaml | 46 +++++ garage/templates/serviceaccount.yaml | 12 ++ garage/templates/servicemonitor.yaml | 39 ++++ garage/templates/statefulset.yaml | 133 ++++++++++++++ garage/values.reference.yaml | 258 +++++++++++++++++++++++++++ garage/values.yaml | 139 +++++++++++++++ license.md | 7 + 16 files changed, 977 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 garage/.helmignore create mode 100644 garage/Chart.yaml create mode 100644 garage/templates/NOTES.txt create mode 100644 garage/templates/_helpers.tpl create mode 100644 garage/templates/httproute.yaml create mode 100644 garage/templates/ingress.yaml create mode 100644 garage/templates/secret.yaml create mode 100644 garage/templates/service.yaml create mode 100644 garage/templates/serviceaccount.yaml create mode 100644 garage/templates/servicemonitor.yaml create mode 100644 garage/templates/statefulset.yaml create mode 100644 garage/values.reference.yaml create mode 100644 garage/values.yaml create mode 100644 license.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..43d54bd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,48 @@ +name: Release Package + +on: + workflow_dispatch: + inputs: + version: + description: "Version (eg: x.x.x)" + type: string + required: true + +env: + CHART_NAME: garage + OCI_IMAGE: oci://ghcr.io/${{ github.repository }} + +jobs: + build-and-publish: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@v5 + with: + token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} + - uses: aboutbits/github-actions-base/git-setup@v2 + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + version: 3.17.1 + - name: Set version of Chart.yaml file + run: yq -i '.version = "${{ github.event.inputs.version }}"' ${{ env.CHART_NAME }}/Chart.yaml + shell: bash + - name: Package Helm chart + run: helm package ${{ env.CHART_NAME }} + shell: bash + - name: Push to OCI registry + run: | + helm registry login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} + helm push ${{ env.CHART_NAME }}-${{ github.event.inputs.version }}.tgz ${{ env.OCI_IMAGE }} + shell: bash + - uses: aboutbits/github-actions-base/git-commit-and-push-all@v2 + with: + message: '${{ github.event.inputs.version }}' + - uses: aboutbits/github-actions-base/git-create-or-update-tag@v2 + with: + tag-name: 'v${{ github.event.inputs.version }}' + - uses: aboutbits/github-actions-base/github-create-release@v2 + with: + tag-name: 'v${{ github.event.inputs.version }}' + release-notes-generation: 'true' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0da2a5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +*.iml +*.tgz +.DS_Store diff --git a/garage/.helmignore b/garage/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/garage/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/garage/Chart.yaml b/garage/Chart.yaml new file mode 100644 index 0000000..ebf79b3 --- /dev/null +++ b/garage/Chart.yaml @@ -0,0 +1,13 @@ +apiVersion: v2 +name: garage +description: AboutBits single-node Garage (S3-compatible object storage) Helm chart +type: application +version: 0.1.0 +appVersion: "v2.3.0" +keywords: + - garage + - s3 + - object-storage +home: https://garagehq.deuxfleurs.fr +sources: + - https://git.deuxfleurs.fr/Deuxfleurs/garage diff --git a/garage/templates/NOTES.txt b/garage/templates/NOTES.txt new file mode 100644 index 0000000..724d361 --- /dev/null +++ b/garage/templates/NOTES.txt @@ -0,0 +1,53 @@ +Garage single-node deployment "{{ include "garage.fullname" . }}" is starting up. + +{{- if .Values.bootstrap.enabled }} + +It bootstraps itself on first boot via `garage server --single-node --default-bucket` +(no manual layout/bucket setup needed). This is a one-time bootstrap: adding a +second bucket or rotating the default key later means calling the Admin API +directly, not editing values.yaml. + +Bucket: {{ .Values.bootstrap.defaultBucket }} +Access key: (value of bootstrap.accessKey in your values) +Secret key: (value of bootstrap.secretKey in your values) +{{- else }} + +No bucket/key is auto-created (bootstrap.enabled=false). Create buckets and +keys yourself via the Admin API or the `garage` CLI, e.g.: + kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage layout assign -z dc1 -c 1G + kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage layout apply --version 1 + kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage bucket create + kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage key create + kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage bucket allow --read --write --owner --key +{{- end }} + +S3 endpoint (from inside the cluster): + http://{{ include "garage.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.s3.port }} + +Region: {{ .Values.s3.region }} + +{{- if .Values.ingress.enabled }} + +External S3 endpoint (via Ingress): +{{- range .Values.ingress.hosts }} + {{- $host := .host }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host }}{{ .path }} + {{- end }} +{{- end }} +{{- end }} +{{- if .Values.httpRoute.enabled }} + +External S3 endpoint (via Gateway API HTTPRoute): +{{- if .Values.httpRoute.hostnames }} +{{- range .Values.httpRoute.hostnames }} + https://{{ . }}/ +{{- end }} +{{- else }} + (matches all hostnames on the referenced Gateway) +{{- end }} +{{- end }} + +Check status: + kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage status + kubectl -n {{ .Release.Namespace }} exec -it {{ include "garage.fullname" . }}-0 -- /garage bucket list diff --git a/garage/templates/_helpers.tpl b/garage/templates/_helpers.tpl new file mode 100644 index 0000000..47bbd66 --- /dev/null +++ b/garage/templates/_helpers.tpl @@ -0,0 +1,64 @@ +{{/* +Chart name and fullname +*/}} +{{- define "garage.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "garage.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{- define "garage.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "garage.labels" -}} +helm.sh/chart: {{ include "garage.chart" . }} +{{ include "garage.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{- define "garage.selectorLabels" -}} +app.kubernetes.io/name: {{ include "garage.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Name of the ServiceAccount to use. +*/}} +{{- define "garage.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "garage.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Stable per-release RPC secret (inter-node clustering auth; loopback-only on a +single node). `lookup` is used so that on `helm upgrade` we keep the previously +generated value instead of rotating it (which would orphan the existing +single-node cluster layout). The admin_token, by contrast, is a required, +operator-provided value (see admin.token) so it is known up front. +*/}} +{{- define "garage.rpcSecret" -}} +{{- $existing := (lookup "v1" "Secret" .Release.Namespace (include "garage.fullname" .)) -}} +{{- if $existing -}} +{{- index $existing.data "rpc_secret" | b64dec -}} +{{- else -}} +{{- sha256sum (printf "%s-rpc-%s" .Release.Name (randAlphaNum 32)) -}} +{{- end -}} +{{- end }} diff --git a/garage/templates/httproute.yaml b/garage/templates/httproute.yaml new file mode 100644 index 0000000..82be1fc --- /dev/null +++ b/garage/templates/httproute.yaml @@ -0,0 +1,35 @@ +{{- if and .Values.ingress.enabled .Values.httpRoute.enabled -}} +{{- fail "Enable only one of ingress.enabled or httpRoute.enabled, not both" -}} +{{- end -}} +{{- if .Values.httpRoute.enabled -}} +{{- if not .Values.httpRoute.parentRefs -}} +{{- fail "httpRoute.parentRefs is required when httpRoute.enabled=true" -}} +{{- end -}} +{{- $fullName := include "garage.fullname" . -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ $fullName }} + labels: + {{- include "garage.labels" . | nindent 4 }} + {{- with .Values.httpRoute.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- toYaml .Values.httpRoute.parentRefs | nindent 4 }} + {{- with .Values.httpRoute.hostnames }} + hostnames: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + - matches: + {{- toYaml .Values.httpRoute.matches | nindent 8 }} + backendRefs: + - name: {{ $fullName }} + port: {{ .Values.service.s3.port }} +{{- end }} diff --git a/garage/templates/ingress.yaml b/garage/templates/ingress.yaml new file mode 100644 index 0000000..4b08932 --- /dev/null +++ b/garage/templates/ingress.yaml @@ -0,0 +1,46 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "garage.fullname" . -}} +{{- $svcPort := .Values.service.s3.port -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "garage.labels" . | nindent 4 }} + {{- with .Values.ingress.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- end }} + {{- end }} +{{- end }} diff --git a/garage/templates/secret.yaml b/garage/templates/secret.yaml new file mode 100644 index 0000000..5fb152f --- /dev/null +++ b/garage/templates/secret.yaml @@ -0,0 +1,57 @@ +{{- $rpcSecret := include "garage.rpcSecret" . -}} +{{- $adminToken := "" -}} +{{- if .Values.admin.enabled -}} +{{- $adminToken = required "admin.token is required when admin.enabled=true" .Values.admin.token -}} +{{- end -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "garage.fullname" . }} + labels: + {{- include "garage.labels" . | nindent 4 }} +type: Opaque +stringData: + rpc_secret: {{ $rpcSecret | quote }} + {{- if .Values.admin.enabled }} + admin_token: {{ $adminToken | quote }} + {{- end }} + {{- if .Values.bootstrap.enabled }} + default_access_key: {{ required "bootstrap.accessKey is required when bootstrap.enabled=true (e.g. GK + 16 hex chars)" .Values.bootstrap.accessKey | quote }} + default_secret_key: {{ required "bootstrap.secretKey is required when bootstrap.enabled=true" .Values.bootstrap.secretKey | quote }} + {{- end }} + garage.toml: | + metadata_dir = "/var/lib/garage/meta" + data_dir = "/var/lib/garage/data" + db_engine = {{ .Values.garage.dbEngine | quote }} + block_size = {{ .Values.garage.blockSize | quote }} + {{- if eq (toString .Values.garage.compressionLevel) "none" }} + compression_level = "none" + {{- else }} + compression_level = {{ .Values.garage.compressionLevel | int }} + {{- end }} + + replication_factor = 1 + + {{- if .Values.garage.metadataAutoSnapshotInterval }} + metadata_auto_snapshot_interval = {{ .Values.garage.metadataAutoSnapshotInterval | quote }} + {{- end }} + + rpc_bind_addr = "[::]:3901" + rpc_public_addr = "127.0.0.1:3901" + rpc_secret = {{ $rpcSecret | quote }} + + {{- if .Values.garage.additionalTopLevelConfig }} + {{ .Values.garage.additionalTopLevelConfig | nindent 4 }} + {{- end }} + + [s3_api] + s3_region = {{ .Values.s3.region | quote }} + api_bind_addr = "[::]:{{ .Values.service.s3.port }}" + root_domain = {{ .Values.s3.rootDomain | quote }} + + {{- if .Values.admin.enabled }} + + [admin] + api_bind_addr = "[::]:{{ .Values.admin.port }}" + admin_token = {{ $adminToken | quote }} + {{- end }} diff --git a/garage/templates/service.yaml b/garage/templates/service.yaml new file mode 100644 index 0000000..fd7b68d --- /dev/null +++ b/garage/templates/service.yaml @@ -0,0 +1,46 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "garage.fullname" . }} + labels: + {{- include "garage.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + # Not headless: single-node Garage has no peers to discover via pod DNS, so + # this plain ClusterIP Service is used both as the StatefulSet's + # `serviceName` and as the address clients connect to for the S3 API. + type: {{ .Values.service.type }} + selector: + {{- include "garage.selectorLabels" . | nindent 4 }} + ports: + - name: s3 + port: {{ .Values.service.s3.port }} + targetPort: s3 + {{- if .Values.admin.enabled }} + - name: admin + port: {{ .Values.admin.port }} + targetPort: admin + {{- end }} +{{- if and .Values.admin.enabled .Values.monitoring.metrics.enabled }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "garage.fullname" . }}-metrics + labels: + {{- include "garage.labels" . | nindent 4 }} + annotations: + prometheus.io/scrape: "true" +spec: + type: ClusterIP + clusterIP: None + ports: + - name: metrics + port: {{ .Values.admin.port }} + targetPort: admin + selector: + {{- include "garage.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/garage/templates/serviceaccount.yaml b/garage/templates/serviceaccount.yaml new file mode 100644 index 0000000..a0a89a3 --- /dev/null +++ b/garage/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "garage.serviceAccountName" . }} + labels: + {{- include "garage.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/garage/templates/servicemonitor.yaml b/garage/templates/servicemonitor.yaml new file mode 100644 index 0000000..bd09249 --- /dev/null +++ b/garage/templates/servicemonitor.yaml @@ -0,0 +1,39 @@ +{{- if and .Values.admin.enabled .Values.monitoring.metrics.enabled .Values.monitoring.metrics.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "garage.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "garage.labels" . | nindent 4 }} + {{- with .Values.monitoring.metrics.serviceMonitor.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: metrics + {{- with .Values.monitoring.metrics.serviceMonitor.interval }} + interval: {{ . }} + {{- end }} + {{- with .Values.monitoring.metrics.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ . }} + {{- end }} + honorLabels: true + path: {{ .Values.monitoring.metrics.serviceMonitor.path }} + scheme: {{ .Values.monitoring.metrics.serviceMonitor.scheme }} + {{- with .Values.monitoring.metrics.serviceMonitor.tlsConfig }} + tlsConfig: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.monitoring.metrics.serviceMonitor.relabelings }} + relabelings: + {{- toYaml . | nindent 8 }} + {{- end }} + jobLabel: "{{ .Release.Name }}" + selector: + matchLabels: + {{- include "garage.selectorLabels" . | nindent 6 }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} +{{- end }} diff --git a/garage/templates/statefulset.yaml b/garage/templates/statefulset.yaml new file mode 100644 index 0000000..bee84c8 --- /dev/null +++ b/garage/templates/statefulset.yaml @@ -0,0 +1,133 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "garage.fullname" . }} + labels: + {{- include "garage.labels" . | nindent 4 }} +spec: + serviceName: {{ include "garage.fullname" . }} + replicas: 1 + podManagementPolicy: OrderedReady + selector: + matchLabels: + {{- include "garage.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "garage.selectorLabels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "garage.serviceAccountName" . }} + {{- with .Values.priorityClassName }} + priorityClassName: {{ . }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: garage + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + - /garage + - server + - --single-node + {{- if .Values.bootstrap.enabled }} + - --default-bucket + {{- end }} + {{- if or .Values.bootstrap.enabled .Values.environment }} + env: + {{- if .Values.bootstrap.enabled }} + - name: GARAGE_DEFAULT_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ include "garage.fullname" . }} + key: default_access_key + - name: GARAGE_DEFAULT_SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ include "garage.fullname" . }} + key: default_secret_key + - name: GARAGE_DEFAULT_BUCKET + value: {{ .Values.bootstrap.defaultBucket | quote }} + {{- end }} + {{- with .Values.environment }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- end }} + ports: + - name: s3 + containerPort: {{ .Values.service.s3.port }} + - name: rpc + containerPort: 3901 + {{- if .Values.admin.enabled }} + - name: admin + containerPort: {{ .Values.admin.port }} + {{- end }} + volumeMounts: + - name: config + mountPath: /etc/garage.toml + subPath: garage.toml + readOnly: true + - name: meta + mountPath: /var/lib/garage/meta + - name: data + mountPath: /var/lib/garage/data + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + livenessProbe: + {{- toYaml .Values.probes.liveness | nindent 12 }} + readinessProbe: + {{- toYaml .Values.probes.readiness | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumes: + - name: config + secret: + secretName: {{ include "garage.fullname" . }} + {{- with .Values.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + volumeClaimTemplates: + - metadata: + name: meta + spec: + accessModes: + {{- toYaml .Values.persistence.meta.accessModes | nindent 10 }} + storageClassName: {{ .Values.persistence.meta.storageClassName }} + resources: + requests: + storage: {{ .Values.persistence.meta.size }} + - metadata: + name: data + spec: + accessModes: + {{- toYaml .Values.persistence.data.accessModes | nindent 10 }} + storageClassName: {{ .Values.persistence.data.storageClassName }} + resources: + requests: + storage: {{ .Values.persistence.data.size }} diff --git a/garage/values.reference.yaml b/garage/values.reference.yaml new file mode 100644 index 0000000..e8723a8 --- /dev/null +++ b/garage/values.reference.yaml @@ -0,0 +1,258 @@ +# Reference values for the garage chart. +# +# This chart deploys a single-node Garage (S3-compatible object store): one +# StatefulSet replica, replication_factor=1, no node discovery/clustering. It +# keeps the upstream chart's security hardening and optional Prometheus +# monitoring, and adds an optional first-boot bucket + access key bootstrap. + +# -- Override the name of the chart +nameOverride: "" +# -- Override the full name of the chart +fullnameOverride: "" + +# -- Container image configuration +image: + # -- Repository of the container image + repository: dxflrs/garage + # -- Tag of the container image + tag: v2.3.0 + # -- Image pull policy. Accepted values: Always, Never, IfNotPresent + pullPolicy: IfNotPresent + +# -- Image pull secrets for private registries +imagePullSecrets: [] + +# -- Garage configuration. These values are rendered into garage.toml. +garage: + # -- Metadata database engine. https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#db_engine + dbEngine: lmdb + # -- Block size in bytes (default 1MB). https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#block_size + blockSize: "1048576" + # -- zstd compression level of stored blocks (integer 1-19), or "none" to + # disable. https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#compression_level + compressionLevel: "1" + # -- If set, Garage snapshots the metadata DB at this interval into the metadata dir. + metadataAutoSnapshotInterval: "" + # -- Additional lines appended verbatim to garage.toml. + additionalTopLevelConfig: "" + +# -- S3 API settings. Garage only speaks plain HTTP; terminate TLS at the +# ingress/gateway if exposing it outside the cluster. +s3: + # -- S3 region name Garage responds to. Arbitrary, but the client must sign + # with the same value (e.g. AWS_DEFAULT_REGION=garage) or requests fail with + # AuthorizationHeaderMalformed. Change only if a tool hardcodes a region. + region: garage + # -- Domain suffix (with leading dot) for virtual-hosted-style bucket access + # (.). Only relevant when exposing Garage externally with + # bucket-name-in-hostname URLs; requires wildcard DNS and a matching ingress + # host. Unused for in-cluster / path-style clients (AWS_S3_FORCE_PATH_STYLE=true). + rootDomain: ".s3.garage" + +# -- Admin API: bucket/key management, health checks, and Prometheus metrics. +# Kept ClusterIP-only; never exposed via ingress. +admin: + # -- Enable the admin API. Required for the /health probes and monitoring below. + enabled: true + # -- Port the admin API listens on inside the container. + port: 3903 + # -- Bearer token protecting the admin API. Required when admin.enabled=true. + # Set your own (any sufficiently random string) so it is known up front for + # managing buckets/keys via the admin API. Prefer --set / Terraform over + # committing it to git. + token: "" + +# -- Optional bootstrap of a single default bucket + access key on first boot, +# via `garage server --default-bucket`. Disabled by default: most deployments +# provision buckets/keys out-of-band (Admin API, Terraform, ...). This is a +# one-time first-boot action; later buckets/keys or key rotation go through the +# Admin API, not values. +bootstrap: + # -- Enable first-boot bucket + key creation. + enabled: false + # -- Name of the single bucket auto-created on first boot. + defaultBucket: default-bucket + # -- Access key ID for the default bucket. Required when bootstrap.enabled=true. + # Must start with "GK" by Garage convention (GK + 16 hex chars). + accessKey: "" + # -- Secret access key for the default bucket. Required when bootstrap.enabled=true. + secretKey: "" + +# -- Service configuration (ClusterIP by default; this is the in-cluster S3 endpoint). +service: + # -- Kubernetes Service type. + type: ClusterIP + s3: + # -- Port the S3 API is exposed on. + port: 3900 + # -- Extra annotations for the Service. + annotations: {} + +# -- Classic Ingress for the S3 API (networking.k8s.io/v1; requires an ingress +# controller). Only needed to reach Garage from OUTSIDE the cluster. In-cluster +# clients use the Service directly. Enable AT MOST ONE of ingress / httpRoute. +ingress: + # -- Enable the Ingress. + enabled: false + # -- Ingress class name. + className: "" + # -- Ingress annotations. + annotations: {} + # cert-manager.io/cluster-issuer: letsencrypt + # -- Extra labels for the Ingress. + labels: {} + # -- Ingress hosts configuration. + hosts: + - host: s3.example.com + paths: + - path: / + pathType: Prefix + # -- TLS configuration. + tls: [] + # - secretName: garage-tls + # hosts: + # - s3.example.com + +# -- Gateway API HTTPRoute for the S3 API (gateway.networking.k8s.io/v1) — the +# modern alternative to ingress. Attaches to an existing Gateway via parentRefs +# (the Gateway itself is shared infra managed outside this chart). Requires the +# Gateway API CRDs installed. Enable AT MOST ONE of ingress / httpRoute. +httpRoute: + # -- Enable the HTTPRoute. + enabled: false + # -- Gateway(s) this route attaches to. Required when httpRoute.enabled=true. + parentRefs: [] + # - name: my-gateway + # namespace: gateway-system + # sectionName: https + # -- Hostnames this route matches. Empty = match all hostnames on the Gateway. + hostnames: [] + # - s3.example.com + # -- HTTPRoute annotations. + annotations: {} + # -- Extra labels for the HTTPRoute. + labels: {} + # -- Path matches for the route. + matches: + - path: + type: PathPrefix + value: / + +# -- Persistent storage. Single-node with replication_factor=1: there is no +# in-cluster replica, so durability comes from the underlying volumes and their +# backups. Make sure both the meta and data volumes are covered by the node's +# backup/snapshot schedule (e.g. daily disk snapshots). +persistence: + # -- Metadata volume (LMDB database, indexes). + meta: + storageClassName: local-path + size: 2Gi + accessModes: + - ReadWriteOnce + # -- Data volume (object blocks). + data: + storageClassName: local-path + size: 20Gi + accessModes: + - ReadWriteOnce + +# -- Resource requests and limits. Starting point for a small single-node +# deployment; tune to your workload. LMDB benefits from memory for its page +# cache. No CPU limit by default to avoid throttling under bursty S3 traffic. +resources: + requests: + cpu: 100m + memory: 256Mi + limits: + memory: 1Gi + +# -- Node selector for pod scheduling. +nodeSelector: {} +# -- Tolerations for pod scheduling. +tolerations: [] +# -- Affinity rules for pod scheduling. +affinity: {} + +# -- Optional priority class name to assign to the pod. +priorityClassName: "" + +# -- Extra annotations for the pod. +podAnnotations: {} +# -- Extra labels for the pod. +podLabels: {} + +# -- Pod-level security context. +podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + fsGroupChangePolicy: OnRootMismatch + +# -- Container-level hardening. If a custom image needs to write outside the +# mounted meta/data dirs, add a writable volume via extraVolumes/extraVolumeMounts +# rather than loosening this. +securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + +# -- Liveness/readiness probes. Default to the admin API's /health endpoint +# (200 only when the node can serve requests). NOTE: requires admin.enabled=true. +# If you disable the admin API, switch these to a tcpSocket check on the `s3` port. +probes: + liveness: + httpGet: + path: /health + port: admin + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 6 + readiness: + httpGet: + path: /health + port: admin + initialDelaySeconds: 5 + periodSeconds: 5 + +# -- Extra environment variables for the garage container. +environment: {} + +# -- Extra volumes, e.g. for custom TLS trust or a writable scratch dir. +extraVolumes: [] +# -- Extra volume mounts matching extraVolumes. +extraVolumeMounts: [] + +# -- Service account configuration. Not required for single-node operation (no +# Kubernetes API access needed); off by default. +serviceAccount: + # -- Create a ServiceAccount for the pod. + create: false + # -- Annotations for the ServiceAccount. + annotations: {} + # -- Name of the ServiceAccount (generated from the fullname template if empty). + name: "" + +# -- Prometheus monitoring (requires admin.enabled=true). +monitoring: + metrics: + # -- Expose the admin API's Prometheus metrics on a separate headless Service. + enabled: false + serviceMonitor: + # -- Create a ServiceMonitor CRD for a prometheus-operator setup. + enabled: false + # -- Metrics path to scrape. + path: /metrics + # -- Extra labels for the ServiceMonitor. + labels: {} + # -- Scrape interval. + interval: 15s + # -- Scrape scheme. + scheme: http + # -- TLS config for scraping. + tlsConfig: {} + # -- Scrape timeout. + scrapeTimeout: 10s + # -- Metric relabeling rules. + relabelings: [] diff --git a/garage/values.yaml b/garage/values.yaml new file mode 100644 index 0000000..85be388 --- /dev/null +++ b/garage/values.yaml @@ -0,0 +1,139 @@ +nameOverride: "" +fullnameOverride: "" + +image: + repository: dxflrs/garage + tag: v2.3.0 + pullPolicy: IfNotPresent + +imagePullSecrets: [] + +garage: + dbEngine: lmdb + blockSize: "1048576" + compressionLevel: "1" + metadataAutoSnapshotInterval: "" + additionalTopLevelConfig: "" + +s3: + region: garage + rootDomain: ".s3.garage" + +admin: + enabled: true + port: 3903 + token: "" + +bootstrap: + enabled: false + defaultBucket: default-bucket + accessKey: "" + secretKey: "" + +service: + type: ClusterIP + s3: + port: 3900 + annotations: {} + +ingress: + enabled: false + className: "" + annotations: {} + labels: {} + hosts: + - host: s3.example.com + paths: + - path: / + pathType: Prefix + tls: [] + +httpRoute: + enabled: false + parentRefs: [] + hostnames: [] + annotations: {} + labels: {} + matches: + - path: + type: PathPrefix + value: / + +persistence: + meta: + storageClassName: local-path + size: 2Gi + accessModes: + - ReadWriteOnce + data: + storageClassName: local-path + size: 20Gi + accessModes: + - ReadWriteOnce + +resources: + requests: + cpu: 100m + memory: 256Mi + limits: + memory: 1Gi + +nodeSelector: {} +tolerations: [] +affinity: {} + +priorityClassName: "" + +podAnnotations: {} +podLabels: {} + +podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + fsGroupChangePolicy: OnRootMismatch + +securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + +probes: + liveness: + httpGet: + path: /health + port: admin + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 6 + readiness: + httpGet: + path: /health + port: admin + initialDelaySeconds: 5 + periodSeconds: 5 + +environment: {} + +extraVolumes: [] +extraVolumeMounts: [] + +serviceAccount: + create: false + annotations: {} + name: "" + +monitoring: + metrics: + enabled: false + serviceMonitor: + enabled: false + path: /metrics + labels: {} + interval: 15s + scheme: http + tlsConfig: {} + scrapeTimeout: 10s + relabelings: [] diff --git a/license.md b/license.md new file mode 100644 index 0000000..21586f5 --- /dev/null +++ b/license.md @@ -0,0 +1,7 @@ +Copyright About Bits GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 3ff2e06c5a2f98e0372dbc54228cce025a55cac7 Mon Sep 17 00:00:00 2001 From: Simon Planinschek Date: Thu, 9 Jul 2026 19:03:42 +0200 Subject: [PATCH 02/12] update readme --- readme.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/readme.md b/readme.md index 2ced57e..56721ad 100644 --- a/readme.md +++ b/readme.md @@ -1 +1,43 @@ # Garage S3 Helm Chart + +Helm chart to deploy a single-node [Garage](https://garagehq.deuxfleurs.fr) +(S3-compatible object storage) to Kubernetes. It runs one node with +`replication_factor=1` (no clustering/node discovery), keeps upstream security +hardening and optional Prometheus monitoring, and can optionally bootstrap a +bucket + access key on first boot. + +## Configuration + +The default configuration for this Helm chart is defined in the [values.yaml](./garage/values.yaml) file. +For detailed descriptions of all the configurable parameters, refer to the provided [values.reference.yaml](./garage/values.reference.yaml) file. +This file serves as the reference for all available settings and their default values. Do not use this for deployment. + +## Manual Installation + +You can pull and install the Helm chart directly from the OCI registry. + +### Install Using OCI Registry + +```bash +helm install oci://ghcr.io/aboutbits/helm-garage/garage --version --values values-.yaml --namespace +``` + +## Build & Publish + +To build and publish the chart, visit the GitHub Actions page of the repository and trigger the workflow "Release Package" manually. + +## Information + +About Bits is a company based in South Tyrol, Italy. You can find more information about us on [our website](https://aboutbits.it). + +### Support + +For support, please contact [info@aboutbits.it](mailto:info@aboutbits.it). + +### Credits + +- [All Contributors](../../contributors) + +### License + +The MIT License (MIT). Please see the [license file](license.md) for more information. From 9f31d5a370fa01daa00816b948801599e85b0193 Mon Sep 17 00:00:00 2001 From: Simon Planinschek Date: Thu, 9 Jul 2026 19:14:49 +0200 Subject: [PATCH 03/12] fix comments --- garage/templates/_helpers.tpl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/garage/templates/_helpers.tpl b/garage/templates/_helpers.tpl index 47bbd66..c0b13ba 100644 --- a/garage/templates/_helpers.tpl +++ b/garage/templates/_helpers.tpl @@ -48,10 +48,12 @@ Name of the ServiceAccount to use. {{- end }} {{/* -Stable per-release RPC secret (inter-node clustering auth; loopback-only on a -single node). `lookup` is used so that on `helm upgrade` we keep the previously -generated value instead of rotating it (which would orphan the existing -single-node cluster layout). The admin_token, by contrast, is a required, +Auto-generated RPC secret (inter-node clustering auth; loopback-only on a single +node, so it is never consumed externally). `lookup` reuses the previously stored +value on `helm upgrade` to avoid a needless Secret diff — this is churn +avoidance, not a correctness requirement: the node's identity and cluster layout +live in the metadata volume, independent of this secret, so regenerating it does +not lose data or orphan the layout. The admin_token, by contrast, is a required, operator-provided value (see admin.token) so it is known up front. */}} {{- define "garage.rpcSecret" -}} From 11a45972d09bd650e7483b5725d9721b61f8cc3b Mon Sep 17 00:00:00 2001 From: Simon Planinschek Date: Fri, 10 Jul 2026 07:20:02 +0200 Subject: [PATCH 04/12] align helpers --- garage/templates/_helpers.tpl | 18 +++++++++++++++++- garage/templates/httproute.yaml | 2 +- garage/templates/ingress.yaml | 2 +- garage/values.reference.yaml | 2 ++ garage/values.yaml | 2 ++ 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/garage/templates/_helpers.tpl b/garage/templates/_helpers.tpl index c0b13ba..be4ed0e 100644 --- a/garage/templates/_helpers.tpl +++ b/garage/templates/_helpers.tpl @@ -41,12 +41,28 @@ Name of the ServiceAccount to use. */}} {{- define "garage.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} -{{- default (include "garage.fullname" .) .Values.serviceAccount.name }} +{{- $baseName := .Values.serviceAccount.name | default (printf "sa-%s" (include "garage.fullname" .)) -}} +{{- $baseName | trunc 63 | trimSuffix "-" }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} +{{/* +Names for the ingress / HTTPRoute objects (prefixed for readability, overridable +via .Values..name). These name the routing object only — the backend still +targets the Service (garage.fullname). +*/}} +{{- define "garage.ingressName" -}} +{{- $baseName := .Values.ingress.name | default (printf "ingress-%s" (include "garage.fullname" .)) -}} +{{- $baseName | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "garage.httpRouteName" -}} +{{- $baseName := .Values.httpRoute.name | default (printf "httproute-%s" (include "garage.fullname" .)) -}} +{{- $baseName | trunc 63 | trimSuffix "-" }} +{{- end }} + {{/* Auto-generated RPC secret (inter-node clustering auth; loopback-only on a single node, so it is never consumed externally). `lookup` reuses the previously stored diff --git a/garage/templates/httproute.yaml b/garage/templates/httproute.yaml index 82be1fc..b82b28f 100644 --- a/garage/templates/httproute.yaml +++ b/garage/templates/httproute.yaml @@ -9,7 +9,7 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: - name: {{ $fullName }} + name: {{ include "garage.httpRouteName" . }} labels: {{- include "garage.labels" . | nindent 4 }} {{- with .Values.httpRoute.labels }} diff --git a/garage/templates/ingress.yaml b/garage/templates/ingress.yaml index 4b08932..7a77d86 100644 --- a/garage/templates/ingress.yaml +++ b/garage/templates/ingress.yaml @@ -4,7 +4,7 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: {{ $fullName }} + name: {{ include "garage.ingressName" . }} labels: {{- include "garage.labels" . | nindent 4 }} {{- with .Values.ingress.labels }} diff --git a/garage/values.reference.yaml b/garage/values.reference.yaml index e8723a8..6fe82ea 100644 --- a/garage/values.reference.yaml +++ b/garage/values.reference.yaml @@ -94,6 +94,8 @@ service: ingress: # -- Enable the Ingress. enabled: false + # -- Name of the Ingress object. Defaults to ingress- when empty. + name: "" # -- Ingress class name. className: "" # -- Ingress annotations. diff --git a/garage/values.yaml b/garage/values.yaml index 85be388..32f7be6 100644 --- a/garage/values.yaml +++ b/garage/values.yaml @@ -38,6 +38,7 @@ service: ingress: enabled: false + name: "" className: "" annotations: {} labels: {} @@ -50,6 +51,7 @@ ingress: httpRoute: enabled: false + name: "" parentRefs: [] hostnames: [] annotations: {} From e2fccabd45746365c742b00a45c127f7bd35109b Mon Sep 17 00:00:00 2001 From: Simon Planinschek Date: Fri, 10 Jul 2026 07:20:15 +0200 Subject: [PATCH 05/12] update helm version --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43d54bd..d7b6c09 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: - name: Set up Helm uses: azure/setup-helm@v4 with: - version: 3.17.1 + version: 3.21.3 - name: Set version of Chart.yaml file run: yq -i '.version = "${{ github.event.inputs.version }}"' ${{ env.CHART_NAME }}/Chart.yaml shell: bash From 6bed931d016bc0d4ed81c209c103221024625ae8 Mon Sep 17 00:00:00 2001 From: Simon Planinschek Date: Fri, 10 Jul 2026 07:33:31 +0200 Subject: [PATCH 06/12] fix missing ref --- garage/values.reference.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/garage/values.reference.yaml b/garage/values.reference.yaml index 6fe82ea..2ed5825 100644 --- a/garage/values.reference.yaml +++ b/garage/values.reference.yaml @@ -122,6 +122,8 @@ ingress: httpRoute: # -- Enable the HTTPRoute. enabled: false + # -- Name of the HTTPRoute object. Defaults to httproute- when empty. + name: "" # -- Gateway(s) this route attaches to. Required when httpRoute.enabled=true. parentRefs: [] # - name: my-gateway From 2a1d059332d0f13222796a7c072c705a3da09846 Mon Sep 17 00:00:00 2001 From: Simon Planinschek Date: Thu, 16 Jul 2026 13:07:32 +0200 Subject: [PATCH 07/12] remove flag to disable admin api --- garage/templates/secret.yaml | 9 +-------- garage/templates/service.yaml | 4 +--- garage/templates/servicemonitor.yaml | 2 +- garage/templates/statefulset.yaml | 2 -- garage/values.reference.yaml | 9 ++++----- garage/values.yaml | 1 - 6 files changed, 7 insertions(+), 20 deletions(-) diff --git a/garage/templates/secret.yaml b/garage/templates/secret.yaml index 5fb152f..17b032b 100644 --- a/garage/templates/secret.yaml +++ b/garage/templates/secret.yaml @@ -1,8 +1,5 @@ {{- $rpcSecret := include "garage.rpcSecret" . -}} -{{- $adminToken := "" -}} -{{- if .Values.admin.enabled -}} -{{- $adminToken = required "admin.token is required when admin.enabled=true" .Values.admin.token -}} -{{- end -}} +{{- $adminToken := required "admin.token is required" .Values.admin.token -}} apiVersion: v1 kind: Secret metadata: @@ -12,9 +9,7 @@ metadata: type: Opaque stringData: rpc_secret: {{ $rpcSecret | quote }} - {{- if .Values.admin.enabled }} admin_token: {{ $adminToken | quote }} - {{- end }} {{- if .Values.bootstrap.enabled }} default_access_key: {{ required "bootstrap.accessKey is required when bootstrap.enabled=true (e.g. GK + 16 hex chars)" .Values.bootstrap.accessKey | quote }} default_secret_key: {{ required "bootstrap.secretKey is required when bootstrap.enabled=true" .Values.bootstrap.secretKey | quote }} @@ -49,9 +44,7 @@ stringData: api_bind_addr = "[::]:{{ .Values.service.s3.port }}" root_domain = {{ .Values.s3.rootDomain | quote }} - {{- if .Values.admin.enabled }} [admin] api_bind_addr = "[::]:{{ .Values.admin.port }}" admin_token = {{ $adminToken | quote }} - {{- end }} diff --git a/garage/templates/service.yaml b/garage/templates/service.yaml index fd7b68d..03c070e 100644 --- a/garage/templates/service.yaml +++ b/garage/templates/service.yaml @@ -19,12 +19,10 @@ spec: - name: s3 port: {{ .Values.service.s3.port }} targetPort: s3 - {{- if .Values.admin.enabled }} - name: admin port: {{ .Values.admin.port }} targetPort: admin - {{- end }} -{{- if and .Values.admin.enabled .Values.monitoring.metrics.enabled }} +{{- if .Values.monitoring.metrics.enabled }} --- apiVersion: v1 kind: Service diff --git a/garage/templates/servicemonitor.yaml b/garage/templates/servicemonitor.yaml index bd09249..0879449 100644 --- a/garage/templates/servicemonitor.yaml +++ b/garage/templates/servicemonitor.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.admin.enabled .Values.monitoring.metrics.enabled .Values.monitoring.metrics.serviceMonitor.enabled }} +{{- if and .Values.monitoring.metrics.enabled .Values.monitoring.metrics.serviceMonitor.enabled }} apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: diff --git a/garage/templates/statefulset.yaml b/garage/templates/statefulset.yaml index bee84c8..dcd1239 100644 --- a/garage/templates/statefulset.yaml +++ b/garage/templates/statefulset.yaml @@ -83,10 +83,8 @@ spec: containerPort: {{ .Values.service.s3.port }} - name: rpc containerPort: 3901 - {{- if .Values.admin.enabled }} - name: admin containerPort: {{ .Values.admin.port }} - {{- end }} volumeMounts: - name: config mountPath: /etc/garage.toml diff --git a/garage/values.reference.yaml b/garage/values.reference.yaml index 2ed5825..36f7d22 100644 --- a/garage/values.reference.yaml +++ b/garage/values.reference.yaml @@ -50,13 +50,12 @@ s3: rootDomain: ".s3.garage" # -- Admin API: bucket/key management, health checks, and Prometheus metrics. +# Always enabled — the /health probes and monitoring below depend on it. # Kept ClusterIP-only; never exposed via ingress. admin: - # -- Enable the admin API. Required for the /health probes and monitoring below. - enabled: true # -- Port the admin API listens on inside the container. port: 3903 - # -- Bearer token protecting the admin API. Required when admin.enabled=true. + # -- Bearer token protecting the admin API. Required. # Set your own (any sufficiently random string) so it is known up front for # managing buckets/keys via the admin API. Prefer --set / Terraform over # committing it to git. @@ -203,7 +202,7 @@ securityContext: readOnlyRootFilesystem: true # -- Liveness/readiness probes. Default to the admin API's /health endpoint -# (200 only when the node can serve requests). NOTE: requires admin.enabled=true. +# (200 only when the node can serve requests), served by the admin API. # If you disable the admin API, switch these to a tcpSocket check on the `s3` port. probes: liveness: @@ -238,7 +237,7 @@ serviceAccount: # -- Name of the ServiceAccount (generated from the fullname template if empty). name: "" -# -- Prometheus monitoring (requires admin.enabled=true). +# -- Prometheus monitoring (scrapes the admin API port). monitoring: metrics: # -- Expose the admin API's Prometheus metrics on a separate headless Service. diff --git a/garage/values.yaml b/garage/values.yaml index 32f7be6..ec7f5ef 100644 --- a/garage/values.yaml +++ b/garage/values.yaml @@ -20,7 +20,6 @@ s3: rootDomain: ".s3.garage" admin: - enabled: true port: 3903 token: "" From 35fa35fdc96b2959e6ce1aad87d90d128945b6b9 Mon Sep 17 00:00:00 2001 From: Simon Planinschek Date: Thu, 16 Jul 2026 13:12:37 +0200 Subject: [PATCH 08/12] add github mirror to Chart.yaml --- garage/Chart.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/garage/Chart.yaml b/garage/Chart.yaml index ebf79b3..2a2f027 100644 --- a/garage/Chart.yaml +++ b/garage/Chart.yaml @@ -11,3 +11,4 @@ keywords: home: https://garagehq.deuxfleurs.fr sources: - https://git.deuxfleurs.fr/Deuxfleurs/garage + - https://github.com/deuxfleurs-org/garage From ab7ae91929b54753c71310ba889e8c174ee9aac8 Mon Sep 17 00:00:00 2001 From: Simon Planinschek Date: Thu, 16 Jul 2026 13:13:53 +0200 Subject: [PATCH 09/12] apply suggestions from code review Co-authored-by: Thomas Sapelza --- .github/workflows/release.yml | 4 ++-- garage/Chart.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d7b6c09..fcf62b7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,12 +17,12 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 15 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 with: token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} - uses: aboutbits/github-actions-base/git-setup@v2 - name: Set up Helm - uses: azure/setup-helm@v4 + uses: azure/setup-helm@v5 with: version: 3.21.3 - name: Set version of Chart.yaml file diff --git a/garage/Chart.yaml b/garage/Chart.yaml index ebf79b3..a4aeb7a 100644 --- a/garage/Chart.yaml +++ b/garage/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: garage description: AboutBits single-node Garage (S3-compatible object storage) Helm chart type: application -version: 0.1.0 +version: "0.1.0" appVersion: "v2.3.0" keywords: - garage From 87ab5f8a95a747f58c262628a7689c3f728315f7 Mon Sep 17 00:00:00 2001 From: Simon Planinschek Date: Thu, 16 Jul 2026 13:24:13 +0200 Subject: [PATCH 10/12] update to helm v4 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fcf62b7..a981f74 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: - name: Set up Helm uses: azure/setup-helm@v5 with: - version: 3.21.3 + version: 4.2.3 - name: Set version of Chart.yaml file run: yq -i '.version = "${{ github.event.inputs.version }}"' ${{ env.CHART_NAME }}/Chart.yaml shell: bash From ac3bd6cd28767246d0cb305d82f4135865cbba7e Mon Sep 17 00:00:00 2001 From: Simon Planinschek Date: Thu, 16 Jul 2026 13:27:30 +0200 Subject: [PATCH 11/12] add lint ci --- .github/workflows/lint.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..1b529cb --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,23 @@ +name: Lint Chart + +on: + pull_request: + +env: + CHART_NAME: garage + +jobs: + lint: + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - uses: actions/checkout@v5 + - name: Set up Helm + uses: azure/setup-helm@v5 + with: + version: 4.2.3 + # admin.token is a required value; a dummy is passed so all templates + # render during linting instead of being skipped with a warning. + - name: Lint Helm chart + run: helm lint ${{ env.CHART_NAME }} --strict --set admin.token=ci-lint-only + shell: bash From 255b546ca5a33ab3b834cc2dcbf6e66a94cc5060 Mon Sep 17 00:00:00 2001 From: Simon Planinschek Date: Thu, 16 Jul 2026 13:36:51 +0200 Subject: [PATCH 12/12] update path type from Prefix to ImplementationSpecific --- garage/values.reference.yaml | 2 +- garage/values.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/garage/values.reference.yaml b/garage/values.reference.yaml index 36f7d22..3c79bbc 100644 --- a/garage/values.reference.yaml +++ b/garage/values.reference.yaml @@ -107,7 +107,7 @@ ingress: - host: s3.example.com paths: - path: / - pathType: Prefix + pathType: ImplementationSpecific # -- TLS configuration. tls: [] # - secretName: garage-tls diff --git a/garage/values.yaml b/garage/values.yaml index ec7f5ef..64b3ec7 100644 --- a/garage/values.yaml +++ b/garage/values.yaml @@ -45,7 +45,7 @@ ingress: - host: s3.example.com paths: - path: / - pathType: Prefix + pathType: ImplementationSpecific tls: [] httpRoute: