Skip to main content

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:
  1. Create the skyvern namespace
  2. Apply PostgreSQL configuration (secrets, storage, deployment, service)
  3. Deploy backend and frontend services
  4. (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

2. Configure Secrets

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

  1. Deploy without SKYVERN_API_KEY set
  2. Access the UI and navigate to Settings
  3. Copy the API key
  4. Update the backend secret:
kubectl edit secret skyvern-backend-env -n skyvern
Add:
stringData:
  SKYVERN_API_KEY: "your-copied-api-key"
  1. 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-name>

# 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

  1. Use PersistentVolumeClaims instead of hostPath for all data volumes
  2. Enable TLS for ingress with cert-manager
  3. Set resource limits on all deployments
  4. Configure network policies to restrict pod-to-pod communication
  5. Use external PostgreSQL (e.g., AWS RDS, Google Cloud SQL) for better reliability
  6. Implement backup strategy for database and artifact storage
  7. Enable monitoring with Prometheus and Grafana
  8. 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

Build docs developers (and LLMs) love