Backend API server for the Butler Console, providing REST APIs and WebSocket connections for cluster management.
Butler Server is a Go backend that:
- Authenticates users (JWT-based sessions)
- Proxies Kubernetes API requests (avoids CORS issues)
- Watches TenantCluster resources and broadcasts updates via WebSocket
- Provides terminal access to management and tenant clusters
- Serves the Butler Console static files
┌─────────────────────────────────────────────────────────────────┐
│ butler-server │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Auth Layer │ │ API Routes │ │ WebSocket Hub │ │
│ │ (JWT) │ │ (chi) │ │ (cluster watch, terminal)│ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ K8s Client (client-go) ││
│ └─────────────────────────────────────────────────────────────┘│
└───────────────────────────┬─────────────────────────────────────┘
│
▼
Kubernetes API Server
| Method | Path | Description |
|---|---|---|
| POST | /api/auth/login |
Login with username/password |
| POST | /api/auth/logout |
Invalidate session |
| POST | /api/auth/refresh |
Refresh JWT token |
| GET | /api/auth/me |
Get current user |
| Method | Path | Description |
|---|---|---|
| GET | /api/clusters |
List all tenant clusters |
| POST | /api/clusters |
Create a tenant cluster |
| GET | /api/clusters/{ns}/{name} |
Get cluster details |
| DELETE | /api/clusters/{ns}/{name} |
Delete cluster |
| PATCH | /api/clusters/{ns}/{name}/scale |
Scale cluster workers |
| GET | /api/clusters/{ns}/{name}/kubeconfig |
Download kubeconfig |
| GET | /api/clusters/{ns}/{name}/nodes |
Get cluster nodes |
| GET | /api/clusters/{ns}/{name}/addons |
Get addon status |
| GET | /api/clusters/{ns}/{name}/events |
Get cluster events |
# Login and get session cookie
curl -c cookies.txt -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "your-password"}'
# List clusters (using saved cookie)
curl -b cookies.txt http://localhost:8080/api/clusters
# Get cluster details
curl -b cookies.txt http://localhost:8080/api/clusters/butler-tenants/my-cluster
# Download kubeconfig
curl -b cookies.txt http://localhost:8080/api/clusters/butler-tenants/my-cluster/kubeconfig \
-o my-cluster.kubeconfig
# Scale cluster workers
curl -b cookies.txt -X PATCH http://localhost:8080/api/clusters/butler-tenants/my-cluster/scale \
-H "Content-Type: application/json" \
-d '{"replicas": 5}'
# List providers
curl -b cookies.txt http://localhost:8080/api/providers| Method | Path | Description |
|---|---|---|
| GET | /api/providers |
List provider configs |
| GET | /api/providers/{ns}/{name} |
Get provider details |
| POST | /api/providers/{ns}/{name}/validate |
Validate provider connectivity |
| Path | Description |
|---|---|
/ws/clusters |
Real-time cluster status updates |
/ws/terminal/{type}/{ns}/{cluster} |
Terminal session |
Environment variables:
| Variable | Default | Description |
|---|---|---|
BUTLER_JWT_SECRET |
(required) | Secret for JWT signing |
BUTLER_ADMIN_USERNAME |
admin |
Admin username |
BUTLER_ADMIN_PASSWORD |
(required) | Admin password |
BUTLER_TENANT_NAMESPACE |
butler-tenants |
Tenant cluster namespace |
BUTLER_SYSTEM_NAMESPACE |
butler-system |
System namespace |
BUTLER_BASE_URL |
http://localhost:8080 |
Override for the public URL. When left at the default placeholder, request handlers derive the URL from the incoming request instead of advertising localhost. Set to the public URL (https://butler.example.com) only when request-based derivation is not viable. |
BUTLER_FRONTEND_URL |
(unset) | Hard override that beats BUTLER_BASE_URL and request derivation. Use when the frontend runs on a different host than the server. |
BUTLER_TRUST_PROXY_HEADERS |
false |
Honor X-Forwarded-Proto and X-Forwarded-Host when deriving the public URL. Set true only when a trusted ingress or load balancer strips client-supplied values and replaces them; otherwise any client can set these headers and cause the server to return an attacker-controlled URL in responses like the CLI device-flow verification_uri. Most Kubernetes deployments terminate TLS at an ingress controller (nginx-ingress, Traefik, Envoy) that sets X-Forwarded-Proto and X-Forwarded-Host on its own. In that topology the expected setting is true; without it the emitted URL uses http even though the client reached the ingress over https, because the server itself sees plain HTTP. Confirm that the ingress strips the incoming headers and writes its own before flipping the flag. |
- Go 1.24+
- Access to a Kubernetes cluster with Butler CRDs installed
# With kubeconfig
make run
# Or manually
go run ./cmd/server -dev -kubeconfig ~/.kube/config# Binary only
make build
# With console embedded
make build-all
# Docker image
make docker-buildmake testButler Server runs in the management cluster alongside Butler controllers:
apiVersion: apps/v1
kind: Deployment
metadata:
name: butler-server
namespace: butler-system
spec:
replicas: 1
selector:
matchLabels:
app: butler-server
template:
metadata:
labels:
app: butler-server
spec:
serviceAccountName: butler-server
containers:
- name: butler-server
image: ghcr.io/butlerdotdev/butler-server:latest
ports:
- containerPort: 8080
env:
- name: BUTLER_JWT_SECRET
valueFrom:
secretKeyRef:
name: butler-server-config
key: jwt-secret
- name: BUTLER_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: butler-server-config
key: admin-password
livenessProbe:
httpGet:
path: /healthz
port: 8080
readinessProbe:
httpGet:
path: /readyz
port: 8080Apache 2.0
