442 lines
10 KiB
Plaintext
442 lines
10 KiB
Plaintext
---
|
|
title: Kubernetes Deployment
|
|
subtitle: Deploy Skyvern at scale with Kubernetes manifests
|
|
slug: self-hosted/kubernetes
|
|
---
|
|
|
|
This guide walks through deploying Skyvern on Kubernetes for production environments. Use this when you need horizontal scaling, high availability, or integration with existing Kubernetes infrastructure.
|
|
|
|
<Warning>
|
|
Do not expose this deployment to the public internet without adding authentication at the ingress layer.
|
|
</Warning>
|
|
|
|
## Prerequisites
|
|
|
|
- A running Kubernetes cluster (1.19+)
|
|
- `kubectl` configured to access your cluster
|
|
- An ingress controller (the manifests use Traefik, but any controller works)
|
|
- An LLM API key (OpenAI, Anthropic, Azure, etc.)
|
|
|
|
## Architecture overview
|
|
|
|
The Kubernetes deployment creates three services:
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
subgraph Kubernetes Cluster
|
|
Ingress[Ingress] --> FE[Frontend<br/>:8080]
|
|
Ingress --> BE[Backend<br/>:8000]
|
|
Ingress --> Art[Artifacts<br/>:9090]
|
|
BE --> DB[(PostgreSQL<br/>:5432)]
|
|
BE --> Browser[Browser<br/>embedded]
|
|
FE --> Art
|
|
end
|
|
|
|
BE --> LLM[LLM Provider]
|
|
```
|
|
|
|
| Component | Service | Purpose |
|
|
|-----------|---------|---------|
|
|
| Backend | `skyvern-backend` | API server + embedded browser |
|
|
| Frontend | `skyvern-frontend` | Web UI + artifact server |
|
|
| PostgreSQL | `postgres` | Database for tasks, workflows, credentials |
|
|
|
|
---
|
|
|
|
## Quick start
|
|
|
|
### 1. Clone the repository
|
|
|
|
```bash
|
|
git clone https://github.com/Skyvern-AI/skyvern.git
|
|
cd skyvern/kubernetes-deployment
|
|
```
|
|
|
|
### 2. Configure backend secrets
|
|
|
|
Edit `backend/backend-secrets.yaml` with your LLM provider credentials:
|
|
|
|
```yaml backend/backend-secrets.yaml
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: skyvern-backend-env
|
|
namespace: skyvern
|
|
type: Opaque
|
|
stringData:
|
|
ENV: local
|
|
|
|
# LLM Configuration - set your provider
|
|
ENABLE_OPENAI: "true"
|
|
OPENAI_API_KEY: "sk-your-api-key-here"
|
|
LLM_KEY: "OPENAI_GPT4O"
|
|
|
|
# Database - points to the PostgreSQL service
|
|
DATABASE_STRING: "postgresql+psycopg://skyvern:skyvern@postgres/skyvern"
|
|
|
|
# Browser settings
|
|
BROWSER_TYPE: "chromium-headless"
|
|
BROWSER_ACTION_TIMEOUT_MS: "5000"
|
|
MAX_STEPS_PER_RUN: "50"
|
|
|
|
# Server
|
|
PORT: "8000"
|
|
LOG_LEVEL: "INFO"
|
|
```
|
|
|
|
For other LLM providers, see [LLM Configuration](/self-hosted/llm-configuration).
|
|
|
|
### 3. Configure frontend secrets
|
|
|
|
Edit `frontend/frontend-secrets.yaml`:
|
|
|
|
```yaml frontend/frontend-secrets.yaml
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: skyvern-frontend-env
|
|
namespace: skyvern
|
|
type: Opaque
|
|
stringData:
|
|
VITE_API_BASE_URL: "http://skyvern.example.com/api/v1"
|
|
VITE_WSS_BASE_URL: "ws://skyvern.example.com/api/v1"
|
|
VITE_ARTIFACT_API_BASE_URL: "http://skyvern.example.com/artifacts"
|
|
VITE_SKYVERN_API_KEY: "" # Leave empty for initial deploy
|
|
```
|
|
|
|
Replace `skyvern.example.com` with your actual domain.
|
|
|
|
### 4. Configure ingress
|
|
|
|
Edit `ingress.yaml` with your domain and TLS settings:
|
|
|
|
```yaml ingress.yaml
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: skyvern-ingress
|
|
namespace: skyvern
|
|
annotations:
|
|
# Adjust for your ingress controller
|
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
|
spec:
|
|
ingressClassName: traefik # Change to nginx, kong, etc.
|
|
rules:
|
|
- host: skyvern.example.com # Your domain
|
|
http:
|
|
paths:
|
|
- path: /api
|
|
pathType: Prefix
|
|
backend:
|
|
service:
|
|
name: skyvern-backend
|
|
port:
|
|
number: 8000
|
|
- path: /artifacts
|
|
pathType: Prefix
|
|
backend:
|
|
service:
|
|
name: skyvern-frontend
|
|
port:
|
|
number: 9090
|
|
- path: /
|
|
pathType: Prefix
|
|
backend:
|
|
service:
|
|
name: skyvern-frontend
|
|
port:
|
|
number: 8080
|
|
```
|
|
|
|
### 5. Deploy
|
|
|
|
Run the deployment script:
|
|
|
|
```bash
|
|
chmod +x k8s-deploy.sh
|
|
./k8s-deploy.sh
|
|
```
|
|
|
|
This applies manifests in order:
|
|
1. Namespace
|
|
2. PostgreSQL (secrets, storage, deployment, service)
|
|
3. Backend (secrets, deployment, service)
|
|
4. Frontend (secrets, deployment, service)
|
|
5. Ingress
|
|
|
|
### 6. Verify deployment
|
|
|
|
Check that all pods are running:
|
|
|
|
```bash
|
|
kubectl get pods -n skyvern
|
|
```
|
|
|
|
Expected output:
|
|
|
|
```
|
|
NAME READY STATUS RESTARTS AGE
|
|
postgres-xxx 1/1 Running 0 2m
|
|
skyvern-backend-xxx 1/1 Running 0 1m
|
|
skyvern-frontend-xxx 1/1 Running 0 30s
|
|
```
|
|
|
|
The backend pod takes 1-2 minutes to become ready as it runs database migrations.
|
|
|
|
### 7. Get your API key
|
|
|
|
Wait for the backend pod to show `1/1` in the `READY` column of `kubectl get pods -n skyvern` before running this command. The API key file is generated during startup and won't exist until the pod is ready.
|
|
|
|
```bash
|
|
kubectl exec -n skyvern deployment/skyvern-backend -- cat /app/.streamlit/secrets.toml
|
|
```
|
|
|
|
Copy the `cred` value and update `frontend/frontend-secrets.yaml`:
|
|
|
|
```yaml
|
|
VITE_SKYVERN_API_KEY: "eyJhbGciOiJIUzI1..."
|
|
```
|
|
|
|
Reapply the frontend secrets and restart:
|
|
|
|
```bash
|
|
kubectl apply -f frontend/frontend-secrets.yaml -n skyvern
|
|
kubectl rollout restart deployment/skyvern-frontend -n skyvern
|
|
```
|
|
|
|
### 8. Access the UI
|
|
|
|
Navigate to your configured domain (e.g., `https://skyvern.example.com`). You should see the Skyvern dashboard.
|
|
|
|
---
|
|
|
|
## Manifest structure
|
|
|
|
```
|
|
kubernetes-deployment/
|
|
├── namespace.yaml # Creates 'skyvern' namespace
|
|
├── k8s-deploy.sh # Deployment script
|
|
├── ingress.yaml # Ingress configuration
|
|
├── backend/
|
|
│ ├── backend-secrets.yaml # Environment variables
|
|
│ ├── backend-deployment.yaml # Pod spec
|
|
│ └── backend-service.yaml # ClusterIP service
|
|
├── frontend/
|
|
│ ├── frontend-secrets.yaml # Environment variables
|
|
│ ├── frontend-deployment.yaml # Pod spec
|
|
│ └── frontend-service.yaml # ClusterIP service
|
|
└── postgres/
|
|
├── postgres-secrets.yaml # Database credentials
|
|
├── postgres-storage.yaml # PersistentVolumeClaim
|
|
├── postgres-deployment.yaml # Pod spec
|
|
└── postgres-service.yaml # ClusterIP service
|
|
```
|
|
|
|
---
|
|
|
|
## Storage configuration
|
|
|
|
By default, the manifests use `hostPath` volumes. This works for single-node clusters but isn't suitable for multi-node production deployments.
|
|
|
|
### Using PersistentVolumeClaims
|
|
|
|
For production, replace `hostPath` with PVCs. Edit `backend/backend-deployment.yaml`:
|
|
|
|
```yaml
|
|
volumes:
|
|
- name: artifacts
|
|
persistentVolumeClaim:
|
|
claimName: skyvern-artifacts-pvc
|
|
- name: videos
|
|
persistentVolumeClaim:
|
|
claimName: skyvern-videos-pvc
|
|
```
|
|
|
|
Create the PVCs:
|
|
|
|
```yaml skyvern-storage.yaml
|
|
apiVersion: v1
|
|
kind: PersistentVolumeClaim
|
|
metadata:
|
|
name: skyvern-artifacts-pvc
|
|
namespace: skyvern
|
|
spec:
|
|
accessModes:
|
|
- ReadWriteOnce
|
|
resources:
|
|
requests:
|
|
storage: 50Gi
|
|
---
|
|
apiVersion: v1
|
|
kind: PersistentVolumeClaim
|
|
metadata:
|
|
name: skyvern-videos-pvc
|
|
namespace: skyvern
|
|
spec:
|
|
accessModes:
|
|
- ReadWriteOnce
|
|
resources:
|
|
requests:
|
|
storage: 100Gi
|
|
```
|
|
|
|
### Using S3 or Azure Blob
|
|
|
|
For cloud storage, configure the backend environment variables instead of mounting volumes. See [Storage Configuration](/self-hosted/storage).
|
|
|
|
---
|
|
|
|
## Scaling
|
|
|
|
### Horizontal scaling
|
|
|
|
To run multiple backend instances, increase the replica count:
|
|
|
|
```yaml backend/backend-deployment.yaml
|
|
spec:
|
|
replicas: 3 # Run 3 backend pods
|
|
```
|
|
|
|
Each pod runs its own browser instance. Tasks are distributed across pods.
|
|
|
|
<Note>
|
|
When scaling horizontally, ensure your storage backend supports concurrent access (S3, Azure Blob, or ReadWriteMany PVCs). Local storage with ReadWriteOnce PVCs won't work across multiple pods.
|
|
</Note>
|
|
|
|
### Resource limits
|
|
|
|
Add resource limits to prevent pods from consuming excessive resources:
|
|
|
|
```yaml
|
|
containers:
|
|
- name: skyvern-backend
|
|
resources:
|
|
requests:
|
|
memory: "2Gi"
|
|
cpu: "500m"
|
|
limits:
|
|
memory: "4Gi"
|
|
cpu: "2000m"
|
|
```
|
|
|
|
Browser instances need significant memory. Start with 2GB minimum per pod.
|
|
|
|
---
|
|
|
|
## TLS configuration
|
|
|
|
To enable HTTPS, uncomment the TLS section in `ingress.yaml`:
|
|
|
|
```yaml
|
|
spec:
|
|
tls:
|
|
- hosts:
|
|
- skyvern.example.com
|
|
secretName: skyvern-tls-secret
|
|
```
|
|
|
|
Create the TLS secret:
|
|
|
|
```bash
|
|
kubectl create secret tls skyvern-tls-secret \
|
|
--cert=path/to/tls.crt \
|
|
--key=path/to/tls.key \
|
|
-n skyvern
|
|
```
|
|
|
|
Or use cert-manager for automatic certificate management.
|
|
|
|
Update frontend secrets to use `https` and `wss`:
|
|
|
|
```yaml
|
|
VITE_API_BASE_URL: "https://skyvern.example.com/api/v1"
|
|
VITE_WSS_BASE_URL: "wss://skyvern.example.com/api/v1"
|
|
```
|
|
|
|
---
|
|
|
|
## Using an external database
|
|
|
|
For production, consider using a managed PostgreSQL service (RDS, Cloud SQL, Azure Database).
|
|
|
|
1. Remove the `postgres/` manifests from the deployment
|
|
2. Update `backend/backend-secrets.yaml`:
|
|
|
|
```yaml
|
|
DATABASE_STRING: "postgresql+psycopg://user:password@your-db-host:5432/skyvern"
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Pods stuck in Pending
|
|
|
|
Check for resource constraints:
|
|
|
|
```bash
|
|
kubectl describe pod -n skyvern <pod-name>
|
|
```
|
|
|
|
Common causes:
|
|
- Insufficient node resources
|
|
- PersistentVolume not available
|
|
- Image pull errors
|
|
|
|
### Backend crashes on startup
|
|
|
|
Check the logs:
|
|
|
|
```bash
|
|
kubectl logs -n skyvern deployment/skyvern-backend
|
|
```
|
|
|
|
Common causes:
|
|
- Invalid LLM API key
|
|
- Database connection failed
|
|
- Missing environment variables
|
|
|
|
### Frontend shows "Unauthorized"
|
|
|
|
The API key in frontend secrets doesn't match the generated key. Re-copy it from the backend pod.
|
|
|
|
### Ingress not routing correctly
|
|
|
|
Verify your ingress controller is running and the ingress resource is configured:
|
|
|
|
```bash
|
|
kubectl get ingress -n skyvern
|
|
kubectl describe ingress skyvern-ingress -n skyvern
|
|
```
|
|
|
|
---
|
|
|
|
## Cleanup
|
|
|
|
To remove the entire deployment:
|
|
|
|
```bash
|
|
kubectl delete namespace skyvern
|
|
```
|
|
|
|
This removes all resources in the `skyvern` namespace.
|
|
|
|
To clean up host storage (if using hostPath):
|
|
|
|
```bash
|
|
rm -rf /data/artifacts /data/videos /data/har /data/log /app/.streamlit
|
|
```
|
|
|
|
---
|
|
|
|
## Next steps
|
|
|
|
<CardGroup cols={2}>
|
|
<Card title="Storage Configuration" icon="hard-drive" href="/self-hosted/storage">
|
|
Configure S3 or Azure Blob for artifact storage
|
|
</Card>
|
|
<Card title="LLM Configuration" icon="microchip" href="/self-hosted/llm-configuration">
|
|
Configure additional LLM providers
|
|
</Card>
|
|
</CardGroup>
|