Files
garage-ui/helm/garage-ui

Garage UI Helm Chart

A Helm chart for deploying Garage UI, a modern web interface for managing Garage distributed object storage systems.

Version App Version

Table of Contents

Overview

Garage UI provides an intuitive web interface for managing your Garage S3 storage cluster, featuring:

  • Bucket Management - Create, delete, and configure S3 buckets
  • Object Operations - Upload, download, and delete objects through the UI
  • User Access Control - Manage users, access keys, and permissions
  • Cluster Monitoring - View cluster health, status, and statistics
  • Authentication - Support for no auth, basic auth, and OIDC/SSO

Prerequisites

Before installing this chart, ensure you have:

  • Kubernetes 1.19+ or later
  • Helm 3.0+ or later
  • Garage S3 instance running and accessible
  • Garage Admin Token - Required for administrative operations

Required Information

You will need the following information from your Garage installation:

  1. Garage S3 Endpoint - The Garage S3 API endpoint (default port: 3900)
  2. Garage Admin Endpoint - The Garage Admin API endpoint (default port: 3903)
  3. Admin Token - Bearer token for authenticating with the Admin API

To find your admin token, check your Garage server's configuration file.

Quick Start

The fastest way to get started with Garage UI:

Step 1: Create a values file

Create a file named my-values.yaml with your Garage configuration:

config:
  garage:
    endpoint: "http://garage:3900"              # Your Garage S3 endpoint
    admin_endpoint: "http://garage:3903"        # Your Garage Admin endpoint
    admin_token: "YOUR_ADMIN_TOKEN_HERE"        # Your admin token
    region: "garage"                            # S3 region (can be any value)

Step 2: Install the chart

helm install garage-ui ./helm/garage-ui -f my-values.yaml

Step 3: Access the UI

# Forward port to access locally
kubectl port-forward svc/garage-ui 8080:80

# Open in your browser
open http://localhost:8080

That's it! You should now have Garage UI running and accessible.

Installation

Installing from local chart

If you've cloned the repository:

helm install garage-ui ./helm/garage-ui -f my-values.yaml

Installing with inline values

You can also set values directly on the command line:

helm install garage-ui ./helm/garage-ui \
  --set config.garage.endpoint=http://garage:3900 \
  --set config.garage.admin_endpoint=http://garage:3903 \
  --set config.garage.admin_token=your-token-here

Verify installation

Check that the pod is running:

kubectl get pods -l app.kubernetes.io/name=garage-ui

View the logs:

kubectl logs -l app.kubernetes.io/name=garage-ui

Configuration

Configuration Structure

The chart uses a structured config section that maps directly to the application's configuration file. You can override any value using Helm's standard methods.

Minimal Configuration

The absolute minimum configuration requires only the Garage endpoints and admin token:

config:
  garage:
    endpoint: "http://garage:3900"
    admin_endpoint: "http://garage:3903"
    admin_token: "your-admin-token"

Common Configuration Options

Server Settings

config:
  server:
    port: 8080              # Application port
    environment: production # Environment (production/development/staging)

Authentication Configuration

No Authentication (default, suitable for private networks):

config:
  auth:
    admin:
      enabled: false
    oidc:
      enabled: false

Admin Authentication (username/password with JWT):

config:
  auth:
    admin:
      enabled: true
      username: "admin"
      password: "your-secure-password"
    oidc:
      enabled: false

OIDC/SSO (recommended for production):

config:
  auth:
    admin:
      enabled: false
    oidc:
      enabled: true
      provider_name: "Keycloak"
      client_id: "garage-ui"
      client_secret: "your-oidc-secret"
      issuer_url: "https://auth.example.com/realms/master"
      # ... additional OIDC settings

Both Authentication Methods (admin and OIDC simultaneously):

config:
  auth:
    admin:
      enabled: true
      username: "admin"
      password: "your-secure-password"
    oidc:
      enabled: true
      provider_name: "Keycloak"
      client_id: "garage-ui"
      client_secret: "your-oidc-secret"
      issuer_url: "https://auth.example.com/realms/master"
      # ... additional OIDC settings

CORS Configuration

config:
  cors:
    enabled: true
    allowed_origins:
      - "https://garage-ui.example.com"  # Recommended: specific origins
      # - "*"                            # Or: allow all origins (less secure)

Logging

config:
  logging:
    level: info    # debug, info, warn, error
    format: json   # json (recommended for production), text

Kubernetes Resource Configuration

Ingress

Enable external access with Ingress:

ingress:
  enabled: true
  className: nginx
  hosts:
    - host: garage-ui.example.com
      paths:
        - path: /
          pathType: Prefix

With TLS:

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: garage-ui.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: garage-ui-tls
      hosts:
        - garage-ui.example.com

Resource Limits

resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 100m
    memory: 128Mi

Replicas and High Availability

replicaCount: 3

affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
              - key: app.kubernetes.io/name
                operator: In
                values:
                  - garage-ui
          topologyKey: kubernetes.io/hostname

Complete Parameters Reference

For a complete list of all available parameters, see the values.yaml file which includes detailed comments for every configuration option.

Examples

Example 1: Basic Installation (In-Cluster Garage)

For Garage running in the same Kubernetes cluster:

# values-basic.yaml
config:
  garage:
    endpoint: "http://garage.garage-system.svc.cluster.local:3900"
    admin_endpoint: "http://garage.garage-system.svc.cluster.local:3903"
    admin_token: "GgJLd9J...your-token-here"
    region: "garage"

Install:

helm install garage-ui ./helm/garage-ui -f values-basic.yaml

Example 2: External Access with Ingress

# values-ingress.yaml
config:
  garage:
    endpoint: "http://garage:3900"
    admin_endpoint: "http://garage:3903"
    admin_token: "your-admin-token"

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
  hosts:
    - host: garage-ui.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: garage-ui-tls
      hosts:
        - garage-ui.example.com

Install:

helm install garage-ui ./helm/garage-ui -f values-ingress.yaml

Example 3: With Admin Authentication

# values-admin-auth.yaml
config:
  garage:
    endpoint: "http://garage:3900"
    admin_endpoint: "http://garage:3903"
    admin_token: "your-admin-token"

  auth:
    admin:
      enabled: true
      username: "admin"
      password: "super-secret-password-change-me"
    oidc:
      enabled: false

ingress:
  enabled: true
  className: nginx
  hosts:
    - host: garage.local
      paths:
        - path: /
          pathType: Prefix

Install:

helm install garage-ui ./helm/garage-ui -f values-admin-auth.yaml

Example 4: Production Setup with OIDC (Keycloak)

# values-production.yaml
replicaCount: 3

config:
  server:
    environment: production

  garage:
    endpoint: "https://s3.garage.example.com"
    admin_endpoint: "https://admin.garage.example.com"
    admin_token: "your-admin-token"
    region: "us-east-1"

  auth:
    admin:
      enabled: false
    oidc:
      enabled: true
      provider_name: "Keycloak"
      client_id: "garage-ui"
      client_secret: "your-oidc-client-secret"
      scopes:
        - openid
        - email
        - profile
      issuer_url: "https://auth.example.com/realms/production"
      auth_url: "https://auth.example.com/realms/production/protocol/openid-connect/auth"
      token_url: "https://auth.example.com/realms/production/protocol/openid-connect/token"
      userinfo_url: "https://auth.example.com/realms/production/protocol/openid-connect/userinfo"
      cookie_secure: true
      cookie_http_only: true
      cookie_same_site: "lax"

  cors:
    enabled: true
    allowed_origins:
      - "https://garage-ui.example.com"

  logging:
    level: info
    format: json

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "100m"
  hosts:
    - host: garage-ui.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: garage-ui-tls
      hosts:
        - garage-ui.example.com

resources:
  limits:
    cpu: 1000m
    memory: 1Gi
  requests:
    cpu: 200m
    memory: 256Mi

affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
              - key: app.kubernetes.io/name
                operator: In
                values:
                  - garage-ui
          topologyKey: kubernetes.io/hostname

Install:

helm install garage-ui ./helm/garage-ui -f values-production.yaml

Example 5: With Prometheus Monitoring

# values-monitoring.yaml
config:
  garage:
    endpoint: "http://garage:3900"
    admin_endpoint: "http://garage:3903"
    admin_token: "your-admin-token"

serviceMonitor:
  enabled: true
  interval: 30s
  labels:
    prometheus: kube-prometheus

Install:

helm install garage-ui ./helm/garage-ui -f values-monitoring.yaml

For improved security, store the admin token in a Kubernetes secret instead of in values files:

Option A: Use an existing secret

First, create a Kubernetes secret:

kubectl create secret generic garage-admin-token \
  --from-literal=admin-token='your-admin-token-here'

Then configure the chart to use it:

# values-with-secret.yaml
config:
  garage:
    endpoint: "http://garage:3900"
    admin_endpoint: "http://garage:3903"
    # Leave admin_token empty when using existingSecret
    admin_token: ""

    # Reference the existing secret
    existingSecret:
      name: "garage-admin-token"
      key: "admin-token"

Install:

helm install garage-ui ./helm/garage-ui -f values-with-secret.yaml

Option B: Let the chart create the secret

If you provide admin_token in values but don't configure existingSecret, the chart will automatically create a secret for you:

# values-auto-secret.yaml
config:
  garage:
    endpoint: "http://garage:3900"
    admin_endpoint: "http://garage:3903"
    # Chart will create a secret with this value
    admin_token: "your-admin-token"

This approach keeps the token out of the ConfigMap while still allowing you to manage it through Helm values.

Upgrading

Upgrade to a new version

helm upgrade garage-ui ./helm/garage-ui -f my-values.yaml

Upgrade with new values

helm upgrade garage-ui ./helm/garage-ui \
  --set config.logging.level=debug \
  --reuse-values

Check upgrade history

helm history garage-ui

Rollback to previous version

helm rollback garage-ui

Accessing the Application

Method 1: Port Forward (Development)

Quick access for testing:

kubectl port-forward svc/garage-ui 8080:80

Then open: http://localhost:8080

Method 2: Ingress (Production)

If you've enabled Ingress with a domain:

# Access via your configured domain
open https://garage-ui.example.com

Method 3: NodePort (Alternative)

Change service type to NodePort:

service:
  type: NodePort
  port: 80

Find the assigned port:

kubectl get svc garage-ui

Method 4: LoadBalancer (Cloud Environments)

For cloud providers with LoadBalancer support:

service:
  type: LoadBalancer
  port: 80

Get the external IP:

kubectl get svc garage-ui

Monitoring

Prometheus ServiceMonitor

Enable Prometheus metrics scraping (requires Prometheus Operator):

serviceMonitor:
  enabled: true
  interval: 30s
  path: /api/v1/monitoring/metrics
  labels:
    prometheus: kube-prometheus

Metrics Endpoint

The application exposes metrics at:

  • Path: /api/v1/monitoring/metrics
  • Format: Prometheus format (proxies Garage Admin API metrics)

Health Checks

Health endpoint available at:

  • Path: /health
  • Response: {"status": "ok", "version": "0.1.0"}

Used by Kubernetes liveness and readiness probes.

Troubleshooting

Pods Not Starting

Check pod status and logs:

kubectl get pods -l app.kubernetes.io/name=garage-ui
kubectl describe pod -l app.kubernetes.io/name=garage-ui
kubectl logs -l app.kubernetes.io/name=garage-ui

Common issues:

  • Missing admin token: Ensure config.garage.admin_token is set or config.garage.existingSecret.name points to a valid secret
  • Secret not found: If using existingSecret, verify the secret exists: kubectl get secret <secret-name>
  • Unreachable Garage: Verify endpoints are accessible from within the cluster
  • Invalid OIDC config: Check all OIDC URLs and credentials when using auth.oidc.enabled: true

Cannot Access the UI

For Ingress issues:

kubectl get ingress
kubectl describe ingress garage-ui

Check Ingress controller logs:

kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx

Common issues:

  • DNS not configured: Ensure your domain points to the Ingress controller
  • Certificate issues: Check cert-manager logs if using automatic TLS
  • Ingress class mismatch: Verify ingress.className matches your controller

Configuration Not Updating

The deployment includes a ConfigMap checksum annotation that automatically triggers pod restarts when configuration changes. If it's not working:

kubectl rollout restart deployment/garage-ui

Connection to Garage Fails

Test connectivity from within the pod:

kubectl exec -it deployment/garage-ui -- sh

# Inside the pod:
curl http://garage:3900
curl http://garage:3903/health

Authentication Issues

Admin Auth:

  • Verify username/password in config.auth.admin
  • Ensure config.auth.admin.enabled is set to true
  • Check browser developer tools for 401 errors
  • Verify JWT token is being sent in Authorization header

OIDC:

  • Ensure config.auth.oidc.enabled is set to true
  • Verify all OIDC URLs are accessible
  • Check OIDC provider logs
  • Ensure redirect URI is registered: https://your-domain/auth/oidc/callback
  • Verify client ID and secret

View Application Logs

# Follow logs
kubectl logs -f -l app.kubernetes.io/name=garage-ui

# View recent logs
kubectl logs --tail=100 -l app.kubernetes.io/name=garage-ui

# Export logs
kubectl logs -l app.kubernetes.io/name=garage-ui > garage-ui.log

Debug Mode

Enable debug logging:

config:
  logging:
    level: debug

Uninstalling

Remove the release

helm uninstall garage-ui

This removes all Kubernetes components associated with the chart.

Clean up completely

If you want to remove all associated resources:

# Uninstall the release
helm uninstall garage-ui

# Remove any remaining ConfigMaps
kubectl delete configmap -l app.kubernetes.io/name=garage-ui

# Remove any remaining Secrets
kubectl delete secret -l app.kubernetes.io/name=garage-ui

Advanced Configuration

Using Secrets for Sensitive Data

The chart supports using Kubernetes secrets for sensitive data to avoid storing credentials in values files.

Admin Token from Secret

The chart has built-in support for storing the admin token in a Kubernetes secret:

Method 1: Use an existing secret (recommended)

# Create the secret
kubectl create secret generic garage-admin-token \
  --from-literal=admin-token='your-admin-token-here'

Configure in values:

config:
  garage:
    existingSecret:
      name: "garage-admin-token"
      key: "admin-token"

Method 2: Let the chart create the secret

Simply provide the admin_token in values, and the chart will automatically create a secret:

config:
  garage:
    admin_token: "your-admin-token"

The chart will:

  1. Create a secret named <release-name>-admin-token with the token
  2. Remove the token from the ConfigMap
  3. Inject it into the pod via environment variable

This keeps sensitive data out of ConfigMaps while maintaining easy Helm-based management.

JWT Private Key for Session Tokens

The chart automatically manages JWT private keys for signing session tokens. You have three options:

Method 1: Auto-generation (recommended for most cases)

By default, if you don't provide a JWT private key, the chart will automatically generate an Ed25519 private key on first install and persist it in a Kubernetes secret. This key will be reused across upgrades, ensuring session tokens remain valid.

config:
  auth:
    # Leave both empty - chart will auto-generate and persist a key
    jwt_private_key: ""
    jwt_private_key_secret:
      name: ""

The chart will:

  1. Run a pre-install/pre-upgrade Job to generate an Ed25519 key
  2. Store it in a secret named <release-name>-jwt-key
  3. Preserve the secret across chart upgrades (using helm.sh/resource-policy: keep)

Method 2: Provide your own key inline

Generate a key manually and include it in values:

# Generate Ed25519 private key
openssl genpkey -algorithm ED25519 -out jwt-key.pem
config:
  auth:
    jwt_private_key: |
      -----BEGIN PRIVATE KEY-----
      MC4CAQAwBQYDK2VwBCIEI...
      -----END PRIVATE KEY-----
    jwt_private_key_secret:
      name: ""

Method 3: Use an existing Kubernetes secret

Create the secret manually:

# Generate key
openssl genpkey -algorithm ED25519 -out jwt-key.pem

# Create secret
kubectl create secret generic my-jwt-key \
  --from-file=jwt-key.pem=jwt-key.pem

Configure in values:

config:
  auth:
    jwt_private_key: ""
    jwt_private_key_secret:
      name: "my-jwt-key"
      key: "jwt-key.pem"

Important Notes:

  • Ed25519 keys are recommended over RSA for better performance and security
  • The auto-generated key persists across upgrades, so tokens remain valid
  • For multi-replica deployments, all pods share the same key from the secret
  • The secret is marked with helm.sh/resource-policy: keep to prevent deletion during uninstall

OIDC Client Secret

For OIDC credentials, you can similarly use secrets:

Method 1: Let the chart create the secret

config:
  auth:
    oidc:
      enabled: true
      client_secret: "your-oidc-secret"
      # Chart will create a secret automatically

Method 2: Use an existing secret

kubectl create secret generic garage-oidc \
  --from-literal=client-secret='your-oidc-secret'
config:
  auth:
    oidc:
      enabled: true
      client_secret: ""  # Leave empty when using existingSecret
      existingSecret:
        name: "garage-oidc"
        key: "client-secret"

Network Policies

Enable network policies for enhanced security:

networkPolicy:
  enabled: true
  policyTypes:
    - Ingress
    - Egress

This restricts network traffic to/from the pods. Requires a CNI that supports NetworkPolicy (e.g., Calico, Cilium).

Custom Container Images

Use a custom or private registry:

image:
  repository: myregistry.example.com/garage-ui
  tag: "v1.0.0"
  pullPolicy: Always

imagePullSecrets:
  - name: myregistrykey

Version Compatibility

Chart Version App Version Kubernetes Garage
0.1.1 v0.0.6 1.19+ 0.8+

Additional Resources

Support

For help and support:

  1. Documentation: Check this README and the values.yaml file
  2. GitHub Issues: Report bugs or request features at https://github.com/Noooste/garage-ui/issues
  3. Garage Community: Visit https://garagehq.deuxfleurs.fr/ for Garage-specific questions

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Submit a pull request

License

This Helm chart is open source and distributed under the same license as Garage UI.