What You’ll Learn
- Deploy Sablier on Kubernetes with Traefik integration
- Configure automatic scale-to-zero for infrequently used services
- Group related services to scale together as a unit
- Set up on-demand service wake-up with waiting pages
Prerequisites
- Kubernetes cluster with Traefik ingress controller
- kubectl configured
- Basic understanding of Kubernetes deployments
Overview
Sablier scales your services to zero replicas when idle and automatically wakes them up when accessed. Perfect for development environments, admin dashboards, and internal tools that aren’t used 24/7.
When someone accesses a scaled-down service, they see a waiting page while Sablier brings it back online in seconds.
Key concept: Services are organized into groups. All deployments in the same group scale up and down together. For example, if your application has a frontend, backend, and database, they can all be in one group - accessing any one will wake up all three.
Step 1: Enable Sablier Plugin in Traefik
Add the Sablier plugin to your Traefik Helm values:
# traefik-values.yaml
experimental:
plugins:
sablier:
moduleName: "github.com/sablierapp/sablier"
version: "v1.10.1"
Update Traefik:
helm upgrade traefik traefik/traefik -n traefik -f traefik-values.yaml
Step 2: Deploy Sablier Server
Create namespace and RBAC:
# sablier/rbac.yaml
apiVersion: v1
kind: Namespace
metadata:
name: sablier
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: sablier
namespace: sablier
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: sablier
rules:
- apiGroups:
- apps
- ""
resources:
- deployments
- statefulsets
verbs:
- get # Retrieve info about specific dep
- list # Events
- watch # Events
- apiGroups:
- apps
- ""
resources:
- deployments/scale
- statefulsets/scale
verbs:
- patch # Scale up and down
- update # Scale up and down
- get # Retrieve info about specific dep
- list # Events
- watch # Events
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: sablier
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: sablier
subjects:
- kind: ServiceAccount
name: sablier
namespace: sablier
Deploy Sablier:
# sablier/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: sablier
namespace: sablier
spec:
replicas: 1
selector:
matchLabels:
app: sablier
template:
metadata:
labels:
app: sablier
spec:
serviceAccountName: sablier
containers:
- name: sablier
image: sablierapp/sablier:1.10.1
args:
- start
- --provider.name=kubernetes
- --logging.level=info
ports:
- containerPort: 10000
---
apiVersion: v1
kind: Service
metadata:
name: sablier
namespace: sablier
spec:
selector:
app: sablier
ports:
- port: 10000
targetPort: 10000
Apply and verify:
kubectl apply -f sablier/rbac.yaml
kubectl apply -f sablier/deployment.yaml
kubectl -n sablier get pods
Step 3: Understanding Groups and Middlewares
Important: You need one Traefik middleware per group. Each middleware defines:
- Which group it manages
- Session duration for that group
- Waiting page theme and settings
For example:
sablier-whoamimiddleware → manageswhoami-groupsablier-dev-appmiddleware → managesdev-app-group(frontend + backend + postgres)sablier-staging-appmiddleware → managesstaging-app-group
All deployments with the same sablier.group label will scale together when any of them is accessed.
Step 4: Create Middleware for Simple Application
Let’s start with a simple single-deployment example:
# traefik/sablier-whoami-middleware.yaml
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: sablier-whoami
namespace: traefik
spec:
plugin:
sablier:
sablierUrl: http://sablier.sablier.svc.cluster.local:10000
sessionDuration: 1m
group: whoami-group
dynamic:
displayName: "Starting Whoami"
theme: "ghost"
showDetails: "true"
refreshFrequency: "5s"
Apply:
kubectl apply -f traefik/sablier-whoami-middleware.yaml
Deploy the application:
# apps/whoami/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: sablier-whoami
namespace: sablier
labels:
app: sablier-whoami
sablier.enable: "true"
sablier.group: "whoami-group"
spec:
replicas: 1
selector:
matchLabels:
app: sablier-whoami
template:
metadata:
labels:
app: sablier-whoami
spec:
containers:
- name: sablier-whoami
image: acouvreur/whoami:v1.10.2
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: sablier-whoami
namespace: sablier
spec:
selector:
app: sablier-whoami
ports:
- port: 80
targetPort: 80
⚠️ Important: The
sablier.enableandsablier.grouplabels must be onmetadata.labels(Deployment level), not onspec.template.metadata.labels(Pod level). Sablier scans Deployment metadata, not pod labels.
Create IngressRoute using the middleware:
# apps/whoami/ingressroute.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: sablier-whoami
namespace: sablier
spec:
entryPoints:
- websecure
routes:
- match: Host(`whoami.yourdomain.com`)
kind: Rule
priority: 10
middlewares:
- name: sablier-whoami
namespace: traefik
services:
- name: sablier-whoami
port: 80
Apply:
kubectl apply -f apps/whoami/
Step 5: Multi-Service Application Example
Now let’s create a complete application with frontend, backend, and database that scale together:
Create the middleware for this application:
# traefik/sablier-myapp-middleware.yaml
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: sablier-myapp
namespace: traefik
spec:
plugin:
sablier:
sablierUrl: http://sablier.sablier.svc.cluster.local:10000
sessionDuration: 5m
group: myapp-dev
dynamic:
displayName: "Starting Development App"
theme: "hacker-terminal"
showDetails: "true"
refreshFrequency: "3s"
Deploy all three services with the same group:
# apps/myapp/deployments.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-frontend
namespace: myapp
labels:
app: myapp-frontend
sablier.enable: "true"
sablier.group: "myapp-dev"
spec:
replicas: 1
selector:
matchLabels:
app: myapp-frontend
template:
metadata:
labels:
app: myapp-frontend
spec:
containers:
- name: frontend
image: your-frontend:latest
ports:
- containerPort: 3000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-backend
namespace: myapp
labels:
app: myapp-backend
sablier.enable: "true"
sablier.group: "myapp-dev"
spec:
replicas: 1
selector:
matchLabels:
app: myapp-backend
template:
metadata:
labels:
app: myapp-backend
spec:
containers:
- name: backend
image: your-backend:latest
ports:
- containerPort: 8080
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: myapp-postgres
namespace: myapp
labels:
app: myapp-postgres
sablier.enable: "true"
sablier.group: "myapp-dev"
spec:
serviceName: myapp-postgres
replicas: 1
selector:
matchLabels:
app: myapp-postgres
template:
metadata:
labels:
app: myapp-postgres
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
value: changeme
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
Create services:
# apps/myapp/services.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-frontend
namespace: myapp
spec:
selector:
app: myapp-frontend
ports:
- port: 3000
---
apiVersion: v1
kind: Service
metadata:
name: myapp-backend
namespace: myapp
spec:
selector:
app: myapp-backend
ports:
- port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: myapp-postgres
namespace: myapp
spec:
selector:
app: myapp-postgres
ports:
- port: 5432
Create IngressRoute (only for the frontend):
# apps/myapp/ingressroute.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: myapp
namespace: myapp
spec:
entryPoints:
- websecure
routes:
- match: Host(`myapp.yourdomain.com`)
kind: Rule
priority: 10
middlewares:
- name: sablier-myapp
namespace: traefik
services:
- name: myapp-frontend
port: 3000
tls:
secretName: myapp-tls
Now when someone accesses myapp.yourdomain.com:
- Sablier detects all three deployments in the
myapp-devgroup - Scales all three (frontend, backend, postgres) to 1 replica
- Shows waiting page until all are ready
- Forwards traffic to frontend
Step 6: Test It
Check Sablier logs to see group discovery:
kubectl -n sablier logs -l app=sablier -f
Access the application:
curl https://myapp.yourdomain.com
Watch all deployments in the group scale together:
kubectl -n myapp get deployments,statefulsets -w
After 5 minutes of inactivity, all three will scale back to 0.
Configuration Options
Session Duration
Adjust per-group based on usage patterns:
spec:
plugin:
sablier:
sessionDuration: 30s # Quick demos
# sessionDuration: 5m # Development work
# sessionDuration: 30m # Extended sessions
Themes
Available themes: ghost, hacker-terminal, matrix, shuffle
dynamic:
displayName: "Starting Your App"
theme: "matrix"
showDetails: "true"
refreshFrequency: "3s"
Blocking Strategy
For APIs, use blocking mode instead of showing a waiting page:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: sablier-api
namespace: traefik
spec:
plugin:
sablier:
sablierUrl: http://sablier.sablier.svc.cluster.local:10000
sessionDuration: 10m
group: api-group
blocking:
timeout: 60s
The HTTP request will wait (up to 60s) until all services in the group are ready.
Multiple Environments Example
Create separate groups and middlewares for different environments:
# Dev environment
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: sablier-myapp-dev
namespace: traefik
spec:
plugin:
sablier:
group: myapp-dev
sessionDuration: 5m
# ... rest of config
---
# Staging environment
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: sablier-myapp-staging
namespace: traefik
spec:
plugin:
sablier:
group: myapp-staging
sessionDuration: 10m
# ... rest of config
Tag deployments accordingly:
# Dev deployments
metadata:
labels:
sablier.enable: "true"
sablier.group: "myapp-dev"
---
# Staging deployments
metadata:
labels:
sablier.enable: "true"
sablier.group: "myapp-staging"
Resources
Series Navigation:
- Previous: Zero to GitOps Episode 18
Questions? Drop a comment below!