Skip to content

Scale-to-Zero with Sablier

This guide explains how to enable automatic scale-to-zero for applications in the homelab. Apps will automatically shut down after a period of inactivity and start back up when someone visits them.

How It Works

  1. Idle State: App runs with 0 replicas (no resources used)
  2. User Visits: Sablier shows a friendly "Starting Up" page
  3. Auto Scale-Up: Sablier scales the deployment to 1 replica
  4. Ready: Once the pod is healthy, traffic flows to the app
  5. Auto Scale-Down: After 30 minutes of no activity, scales back to 0

Components

  • Sablier Server (sablier namespace): Manages scaling and sessions
  • Traefik Plugin: Shows the loading page and triggers scale-up
  • Middleware: Configured per-app to connect Traefik to Sablier

Quick Setup for a New App

Step 1: Add Labels to Your Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: myapp
  labels:
    # Enable Sablier for this deployment
    sablier.enable: "true"
    sablier.group: "myapp"  # Must match middleware group
spec:
  # Start scaled to 0
  replicas: 0
  # ... rest of deployment spec
  template:
    spec:
      containers:
        - name: myapp
          # IMPORTANT: Add a readiness probe so Sablier knows when app is ready
          readinessProbe:
            httpGet:
              path: /
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5

Step 2: Create a Sablier Middleware

Create middleware.yaml in your app's folder:

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: myapp-sablier
  namespace: myapp
spec:
  plugin:
    sablier:
      sablierUrl: http://sablier-sablier.sablier.svc.cluster.local:10000
      group: myapp  # Must match deployment label sablier.group
      sessionDuration: 30m  # Time before auto scale-down
      dynamic:
        displayName: "My App - Starting Up"  # Shown on loading page
        showDetails: true
        theme: ghost  # Options: ghost, shuffle, hacker-terminal, matrix
        refreshFrequency: 3s

Step 3: Add Middleware to Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp
  namespace: myapp
  annotations:
    traefik.ingress.kubernetes.io/router.middlewares: myapp-myapp-sablier@kubernetescrd
spec:
  # ... ingress rules

Middleware reference format: <namespace>-<middleware-name>@kubernetescrd

Step 4: Update Kustomization

Add the middleware to your kustomization.yaml:

resources:
  - namespace.yaml
  - deployment.yaml
  - service.yaml
  - ingress.yaml
  - middleware.yaml  # Add this

Step 5: Disable Gatus Checks (Optional)

If you have Gatus monitoring, disable checks for scale-to-zero apps:

  • Internal checks: Will fail when scaled down (bypasses Sablier)
  • External checks: Will trigger scale-up every check interval

Comment out both in infrastructure/base/gatus/release.yaml.

Theme Options

Theme Description Best For
ghost Clean, minimal white design Family/general users
shuffle Animated card shuffle Fun, playful apps
hacker-terminal Green terminal style Technical users
matrix Matrix rain effect Fun, technical

Configuration Options

Option Description Default
sablierUrl Sablier API endpoint Required
group Group name matching deployment label Required
sessionDuration Time before auto scale-down 30m
dynamic.displayName Title shown on loading page Middleware name
dynamic.showDetails Show scaling progress details true
dynamic.theme Loading page theme hacker-terminal
dynamic.refreshFrequency How often page checks status 5s

GitOps Considerations

Since Flux manages deployments:

  1. Keep replicas: 0 in your deployment.yaml - Sablier handles scaling
  2. Flux won't fight Sablier because Sablier only scales up temporarily
  3. On next Flux reconciliation, it will scale back to 0 (which is fine)

Troubleshooting

App won't scale up

  1. Check Sablier logs: kubectl logs -n sablier deployment/sablier-sablier
  2. Verify deployment has correct labels: sablier.enable: "true" and sablier.group
  3. Check middleware group matches deployment label

Loading page shows but app never becomes ready

  1. Check pod logs: kubectl logs -n <namespace> deployment/<name>
  2. Verify readiness probe is configured and passing
  3. Check if pod is stuck in CrashLoopBackOff

App immediately scales back down

  1. Check for health checks hitting the endpoint (Gatus, uptime monitors)
  2. Session duration might be too short

404 when scaled to 0

  1. Verify Traefik has allowEmptyServices: true in config
  2. Check ingress has correct middleware annotation

Example: BentoPDF

See apps/base/bento/ for a complete working example:

  • deployment.yaml - Has Sablier labels and replicas: 0
  • middleware.yaml - Sablier plugin configuration
  • ingress.yaml - References the middleware

Infrastructure

The Sablier infrastructure is in infrastructure/base/sablier/:

  • Helm release deploys Sablier server
  • Traefik config in infrastructure/base/controllers/traefik/traefik-config.yaml enables the plugin
experimental:
  plugins:
    sablier:
      moduleName: github.com/sablierapp/sablier-traefik-plugin
      version: v1.1.0