Overview
Kubernetes deployment provides a production-ready, scalable infrastructure for Skyvern. This guide covers deploying Skyvern on Kubernetes with proper configuration for high availability and scalability.
Security Notice : It is not recommended to deploy Skyvern directly to the Internet without authentication. Use network policies, ingress controllers with authentication, or deploy in a private network.
Prerequisites
Kubernetes cluster (1.20+)
kubectl configured and connected to your cluster
Persistent storage provisioner (for PostgreSQL and artifacts)
(Optional) Ingress controller (nginx, traefik, etc.)
(Optional) TLS certificates for HTTPS
Architecture
The Kubernetes deployment consists of:
Namespace : skyvern (isolated environment)
PostgreSQL : Stateful database with persistent storage
Skyvern Backend : Deployment for the API and automation engine
Skyvern Frontend : Deployment for the web UI
Services : ClusterIP or LoadBalancer for network access
Ingress (Optional): For domain-based routing with TLS
Quick Deployment
Using the Deploy Script
# Clone the repository
git clone https://github.com/skyvern-ai/skyvern.git
cd skyvern/kubernetes-deployment
# Make deploy script executable
chmod +x k8s-deploy.sh
# Deploy all resources
./k8s-deploy.sh
The script will:
Create the skyvern namespace
Apply PostgreSQL configuration (secrets, storage, deployment, service)
Deploy backend and frontend services
(Optional) Apply ingress configuration
Step-by-Step Manual Deployment
1. Create Namespace
# namespace.yaml
apiVersion : v1
kind : Namespace
metadata :
name : skyvern
kubectl apply -f namespace.yaml
Before deploying , generate your .env file using skyvern init llm, then copy the values to the secrets files.
Backend Secrets
# backend/backend-secrets.yaml
apiVersion : v1
kind : Secret
metadata :
name : skyvern-backend-env
namespace : skyvern
type : Opaque
stringData :
ENV : local
# LLM Configuration
ENABLE_OPENAI : "true"
OPENAI_API_KEY : "your-openai-key"
LLM_KEY : OPENAI_GPT4_1
# Database
DATABASE_STRING : postgresql+psycopg://skyvern:skyvern@postgres/skyvern
# Browser
BROWSER_TYPE : chromium-headless
MAX_STEPS_PER_RUN : "50"
# API
PORT : "8000"
LOG_LEVEL : INFO
# Skyvern API Key (get from UI settings after first deploy)
SKYVERN_API_KEY : ""
PostgreSQL Secrets
# postgres/postgres-secrets.yaml
apiVersion : v1
kind : Secret
metadata :
name : postgres-secret
namespace : skyvern
type : Opaque
stringData :
POSTGRES_USER : skyvern
POSTGRES_PASSWORD : skyvern
POSTGRES_DB : skyvern
kubectl apply -f backend/backend-secrets.yaml
kubectl apply -f postgres/postgres-secrets.yaml
3. Deploy PostgreSQL
Persistent Storage
# postgres/postgres-storage.yaml
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : postgres-pvc
namespace : skyvern
spec :
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 10Gi
PostgreSQL Deployment
# postgres/postgres-deployment.yaml
apiVersion : apps/v1
kind : Deployment
metadata :
name : postgres
namespace : skyvern
spec :
replicas : 1
selector :
matchLabels :
app : postgres
template :
metadata :
labels :
app : postgres
spec :
containers :
- name : postgres
image : postgres:14-alpine
envFrom :
- secretRef :
name : postgres-secret
env :
- name : PGDATA
value : /var/lib/postgresql/data/pgdata
ports :
- containerPort : 5432
volumeMounts :
- name : postgres-storage
mountPath : /var/lib/postgresql/data
readinessProbe :
exec :
command : [ "pg_isready" , "-U" , "skyvern" ]
initialDelaySeconds : 5
periodSeconds : 5
volumes :
- name : postgres-storage
persistentVolumeClaim :
claimName : postgres-pvc
PostgreSQL Service
# postgres/postgres-service.yaml
apiVersion : v1
kind : Service
metadata :
name : postgres
namespace : skyvern
spec :
selector :
app : postgres
ports :
- port : 5432
targetPort : 5432
type : ClusterIP
kubectl apply -f postgres/
4. Deploy Skyvern Backend
# backend/backend-deployment.yaml
apiVersion : apps/v1
kind : Deployment
metadata :
name : skyvern-backend
namespace : skyvern
spec :
replicas : 1
selector :
matchLabels :
app : skyvern-backend
template :
metadata :
labels :
app : skyvern-backend
spec :
containers :
- name : skyvern-backend
image : public.ecr.aws/skyvern/skyvern:latest
ports :
- containerPort : 8000
- containerPort : 9222
envFrom :
- secretRef :
name : skyvern-backend-env
volumeMounts :
- name : artifacts
mountPath : /data/artifacts
- name : videos
mountPath : /data/videos
- name : har
mountPath : /data/har
- name : log
mountPath : /data/log
- name : streamlit
mountPath : /app/.streamlit
readinessProbe :
exec :
command : [ "test" , "-f" , "/app/.streamlit/secrets.toml" ]
initialDelaySeconds : 5
periodSeconds : 5
timeoutSeconds : 5
failureThreshold : 5
volumes :
- name : artifacts
hostPath :
path : /data/artifacts
type : DirectoryOrCreate
- name : videos
hostPath :
path : /data/videos
type : DirectoryOrCreate
- name : har
hostPath :
path : /data/har
type : DirectoryOrCreate
- name : log
hostPath :
path : /data/log
type : DirectoryOrCreate
- name : streamlit
hostPath :
path : /app/.streamlit
type : DirectoryOrCreate
# backend/backend-service.yaml
apiVersion : v1
kind : Service
metadata :
name : skyvern-backend
namespace : skyvern
spec :
selector :
app : skyvern-backend
ports :
- name : api
port : 8000
targetPort : 8000
- name : cdp
port : 9222
targetPort : 9222
type : ClusterIP # or LoadBalancer for direct access
kubectl apply -f backend/
5. Deploy Skyvern Frontend
# frontend/frontend-deployment.yaml
apiVersion : apps/v1
kind : Deployment
metadata :
name : skyvern-frontend
namespace : skyvern
spec :
replicas : 1
selector :
matchLabels :
app : skyvern-frontend
template :
metadata :
labels :
app : skyvern-frontend
spec :
containers :
- name : skyvern-frontend
image : public.ecr.aws/skyvern/skyvern-ui:latest
ports :
- containerPort : 8080
- containerPort : 9090
envFrom :
- secretRef :
name : skyvern-frontend-env
volumeMounts :
- name : artifacts
mountPath : /data/artifacts
- name : videos
mountPath : /data/videos
- name : streamlit
mountPath : /app/.streamlit
volumes :
- name : artifacts
hostPath :
path : /data/artifacts
type : DirectoryOrCreate
- name : videos
hostPath :
path : /data/videos
type : DirectoryOrCreate
- name : streamlit
hostPath :
path : /app/.streamlit
type : DirectoryOrCreate
# frontend/frontend-service.yaml
apiVersion : v1
kind : Service
metadata :
name : skyvern-frontend
namespace : skyvern
spec :
selector :
app : skyvern-frontend
ports :
- name : ui
port : 8080
targetPort : 8080
- name : artifact-api
port : 9090
targetPort : 9090
type : LoadBalancer # or ClusterIP with Ingress
kubectl apply -f frontend/
Network Access Options
Option 1: LoadBalancer (Simple)
Set service type to LoadBalancer for direct external access:
spec :
type : LoadBalancer
ports :
- port : 8080
targetPort : 8080
Get external IP:
kubectl get svc -n skyvern skyvern-frontend
Option 2: Ingress (Production)
For domain-based routing with TLS:
# ingress.yaml
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : skyvern-ingress
namespace : skyvern
annotations :
nginx.ingress.kubernetes.io/rewrite-target : /
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec :
# tls:
# - hosts:
# - skyvern.example.com
# secretName: skyvern-tls
rules :
- host : skyvern.example.com
http :
paths :
- path : /api
pathType : Prefix
backend :
service :
name : skyvern-backend
port :
number : 8000
- path : /
pathType : Prefix
backend :
service :
name : skyvern-frontend
port :
number : 8080
For TLS, uncomment the tls section and update frontend secrets:
stringData :
VITE_WSS_BASE_URL : wss://skyvern.example.com/api/v1
VITE_API_BASE_URL : https://skyvern.example.com/api/v1
Storage Considerations
HostPath (Development)
The default configuration uses hostPath volumes:
volumes :
- name : artifacts
hostPath :
path : /data/artifacts
type : DirectoryOrCreate
Pros : Simple, no additional setup
Cons : Not suitable for multi-node clusters, data tied to specific nodes
PersistentVolumeClaim (Production)
For production, use PVCs with a storage class:
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : skyvern-artifacts-pvc
namespace : skyvern
spec :
accessModes :
- ReadWriteMany # Multiple pods can access
storageClassName : nfs-client # Your storage class
resources :
requests :
storage : 100Gi
Update deployment:
volumes :
- name : artifacts
persistentVolumeClaim :
claimName : skyvern-artifacts-pvc
Scaling Considerations
Horizontal Scaling
# Scale backend replicas
kubectl scale deployment skyvern-backend -n skyvern --replicas=3
# Scale frontend replicas
kubectl scale deployment skyvern-frontend -n skyvern --replicas=2
Important : When scaling, ensure:
Shared storage (PVC with ReadWriteMany)
Load balancer distributes traffic evenly
Database can handle increased connections
Resource Limits
Define resource requests and limits:
resources :
requests :
memory : "4Gi"
cpu : "2000m"
limits :
memory : "8Gi"
cpu : "4000m"
Configuring the Skyvern API Key
Deploy without SKYVERN_API_KEY set
Access the UI and navigate to Settings
Copy the API key
Update the backend secret:
kubectl edit secret skyvern-backend-env -n skyvern
Add:
stringData :
SKYVERN_API_KEY : "your-copied-api-key"
Restart backend pods:
kubectl delete pod -n skyvern -l app=skyvern-backend
Monitoring and Management
Check Deployment Status
# All resources in namespace
kubectl get all -n skyvern
# Pod status
kubectl get pods -n skyvern
# Service endpoints
kubectl get svc -n skyvern
View Logs
# Backend logs
kubectl logs -n skyvern -l app=skyvern-backend -f
# Frontend logs
kubectl logs -n skyvern -l app=skyvern-frontend -f
# PostgreSQL logs
kubectl logs -n skyvern -l app=postgres -f
Access Pod Shell
kubectl exec -it -n skyvern deployment/skyvern-backend -- /bin/bash
Cleanup
Remove from Specific Nodes
# Delete data directories on nodes
rm -rf /data/ /app/
Delete Entire Deployment
# Delete namespace (removes all resources)
kubectl delete namespace skyvern
# Or delete specific resources
kubectl delete -f kubernetes-deployment/
Troubleshooting
Pods Not Starting
# Check pod events
kubectl describe pod -n skyvern < pod-nam e >
# Check resource availability
kubectl top nodes
kubectl top pods -n skyvern
Database Connection Issues
# Test PostgreSQL connectivity
kubectl run -it --rm --restart=Never postgres-client \
--image=postgres:14-alpine -n skyvern \
-- psql -h postgres -U skyvern -d skyvern
Persistent Volume Issues
# Check PVC status
kubectl get pvc -n skyvern
# Describe PVC for events
kubectl describe pvc postgres-pvc -n skyvern
Ingress Not Working
# Check ingress status
kubectl describe ingress skyvern-ingress -n skyvern
# Verify ingress controller
kubectl get pods -n ingress-nginx
Production Recommendations
Use PersistentVolumeClaims instead of hostPath for all data volumes
Enable TLS for ingress with cert-manager
Set resource limits on all deployments
Configure network policies to restrict pod-to-pod communication
Use external PostgreSQL (e.g., AWS RDS, Google Cloud SQL) for better reliability
Implement backup strategy for database and artifact storage
Enable monitoring with Prometheus and Grafana
Configure pod disruption budgets for high availability
Next Steps
LLM Configuration Configure LLM providers
Storage Configuration Set up S3 or Azure Blob Storage
Environment Variables Complete configuration reference