feat: serverless 2.0.0 architecture with Authentik auth proxy
Implements a complete serverless development container platform:
## Architecture
- Authentik forward auth for authentication/authorization
- NGINX routing proxy extracts GitHub repo from URL path
- Knative Service auto-scales dev container instances from 0
- Dynamic GitHub repo routing via /github/{owner}/{repo}
## Components
- routing-proxy: NGINX-based service for repo extraction and forwarding
- deployment.yaml: Complete K8s manifests (proxy, Knative, ingress, secrets)
- authentik-config.yaml: Authentik application and provider configs
- serverless scripts: Dynamic repo initialization and startup handling
- Comprehensive documentation and Makefile for ops
## Key Features
- Scale to zero when not in use (cost-effective)
- Per-request isolation (each repo gets own container)
- Built-in file manager for upload/download
- Support for private repos via GitHub tokens
- User attribution via Authentik headers
- WebSocket support for VNC connections
Example usage: https://devcontainer.farh.net/github/microsoft/vscode
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
---
|
||||
# Namespace for serverless components
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: devcontainers
|
||||
labels:
|
||||
app.kubernetes.io/name: devcontainer
|
||||
app.kubernetes.io/component: serverless
|
||||
|
||||
---
|
||||
# Secret for GitHub tokens, VNC passwords, etc.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: devcontainer-serverless-secrets
|
||||
namespace: devcontainers
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Update these values as needed
|
||||
GITHUB_TOKEN: ""
|
||||
VNC_PASSWORD: "changeme"
|
||||
ANTHROPIC_API_KEY: ""
|
||||
GIT_USER_NAME: "DevContainer User"
|
||||
GIT_USER_EMAIL: "devcontainer@example.com"
|
||||
|
||||
---
|
||||
# Routing proxy deployment (handles GitHub repo extraction)
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: devcontainer-routing-proxy
|
||||
namespace: devcontainers
|
||||
labels:
|
||||
app.kubernetes.io/name: devcontainer
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
spec:
|
||||
replicas: 2 # High availability
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: devcontainer
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: devcontainer
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
spec:
|
||||
containers:
|
||||
- name: routing-proxy
|
||||
image: ghcr.io/cpfarhood/devcontainer-routing-proxy:latest
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
env:
|
||||
- name: DEVCONTAINER_SERVICE_URL
|
||||
value: "devcontainer-serverless.devcontainers.svc.cluster.local"
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 5
|
||||
|
||||
---
|
||||
# Service for routing proxy
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: devcontainer-routing-proxy
|
||||
namespace: devcontainers
|
||||
labels:
|
||||
app.kubernetes.io/name: devcontainer
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
name: http
|
||||
selector:
|
||||
app.kubernetes.io/name: devcontainer
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
|
||||
---
|
||||
# Knative Service (auto-scaling devcontainer instances)
|
||||
apiVersion: serving.knative.dev/v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: devcontainer-serverless
|
||||
namespace: devcontainers
|
||||
annotations:
|
||||
# Scale to zero when not in use (saves resources)
|
||||
autoscaling.knative.dev/minScale: "0"
|
||||
autoscaling.knative.dev/maxScale: "10"
|
||||
# Keep instances warm for 5 minutes after last request
|
||||
autoscaling.knative.dev/scale-to-zero-grace-period: "5m"
|
||||
# Target 1 concurrent request per pod (ensures isolation)
|
||||
autoscaling.knative.dev/target: "1"
|
||||
# Custom domain (optional - configure after Authentik setup)
|
||||
# serving.knative.dev/domain: "devcontainer.farh.net"
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
# Container port for VNC web interface
|
||||
autoscaling.knative.dev/targetPort: "5800"
|
||||
# Timeout for cold starts (dev containers need time to initialize)
|
||||
serving.knative.dev/timeoutSeconds: "600" # 10 minutes for repo cloning
|
||||
# Resource allocation per instance
|
||||
autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev"
|
||||
autoscaling.knative.dev/metric: "concurrency"
|
||||
spec:
|
||||
# Give containers more time to start (repo cloning + IDE launch)
|
||||
timeoutSeconds: 600 # 10 minutes
|
||||
containers:
|
||||
- name: devcontainer
|
||||
image: ghcr.io/cpfarhood/devcontainer:latest
|
||||
ports:
|
||||
- containerPort: 5800
|
||||
name: vnc-web
|
||||
env:
|
||||
# Flag to indicate serverless mode
|
||||
- name: SERVERLESS_MODE
|
||||
value: "true"
|
||||
- name: DYNAMIC_GITHUB_ROUTING
|
||||
value: "true"
|
||||
- name: IDE
|
||||
value: "vscode"
|
||||
- name: DISPLAY_WIDTH
|
||||
value: "1920"
|
||||
- name: DISPLAY_HEIGHT
|
||||
value: "1080"
|
||||
- name: SECURE_CONNECTION
|
||||
value: "0"
|
||||
- name: USER_ID
|
||||
value: "1000"
|
||||
- name: GROUP_ID
|
||||
value: "1000"
|
||||
# Enable file manager for easy upload/download
|
||||
- name: WEB_FILE_MANAGER
|
||||
value: "1"
|
||||
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
|
||||
value: "/workspace,/config"
|
||||
# Happy Coder config (ephemeral in serverless mode)
|
||||
- name: HAPPY_HOME_DIR
|
||||
value: "/tmp/.happy"
|
||||
- name: HAPPY_EXPERIMENTAL
|
||||
value: "true"
|
||||
# Use secrets for sensitive data
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: devcontainer-serverless-secrets
|
||||
optional: false
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
volumeMounts:
|
||||
- name: tmp-home
|
||||
mountPath: /config
|
||||
- name: shm
|
||||
mountPath: /dev/shm
|
||||
# Readiness probe - VNC must be ready
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 5800
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 10
|
||||
# Liveness probe - ensure container stays healthy
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 5800
|
||||
initialDelaySeconds: 120
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
volumes:
|
||||
- name: tmp-home
|
||||
emptyDir: {} # Ephemeral - each instance gets fresh home
|
||||
- name: shm
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 2Gi
|
||||
|
||||
---
|
||||
# Ingress for the routing proxy (will be secured by Authentik)
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: devcontainer-serverless-ingress
|
||||
namespace: devcontainers
|
||||
annotations:
|
||||
# Authentik forward auth annotations
|
||||
nginx.ingress.kubernetes.io/auth-url: http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx
|
||||
nginx.ingress.kubernetes.io/auth-signin: https://auth.farh.net/outpost.goauthentik.io/start?rd=$escaped_request_uri
|
||||
nginx.ingress.kubernetes.io/auth-response-headers: X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Name
|
||||
nginx.ingress.kubernetes.io/auth-snippet: |
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
|
||||
# SSL and general settings
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
|
||||
# WebSocket support for VNC
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||
|
||||
# Large file upload support (for file manager)
|
||||
nginx.ingress.kubernetes.io/client-max-body-size: "100m"
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- devcontainer.farh.net
|
||||
secretName: devcontainer-serverless-tls
|
||||
rules:
|
||||
- host: devcontainer.farh.net
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: devcontainer-routing-proxy
|
||||
port:
|
||||
number: 80
|
||||
Reference in New Issue
Block a user