Files
Dorod-Sky/docs/self-hosted/kubernetes.mdx
2026-02-11 03:39:42 +00:00

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>