> ## Documentation Index
> Fetch the complete documentation index at: https://skyvern.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Kubernetes Deployment

> Deploy Skyvern on Kubernetes for production environments with horizontal scaling and high availability. Includes manifests for the API server, frontend, PostgreSQL, and ingress configuration.

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 theme={null}
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 theme={null}
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 theme={null}
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_GPT5_5"

  # 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: "10"

  # Server
  PORT: "8000"
  LOG_LEVEL: "INFO"
```

For other LLM providers, see [LLM Configuration](/developers/self-hosted/llm-configuration).

### 3. Configure frontend secrets

Edit `frontend/frontend-secrets.yaml`:

```yaml frontend/frontend-secrets.yaml theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
kubectl exec -n skyvern deployment/skyvern-backend -- cat /app/.skyvern/credentials.toml
```

Copy the `cred` value and update `frontend/frontend-secrets.yaml`:

```yaml theme={null}
VITE_SKYVERN_API_KEY: "eyJhbGciOiJIUzI1..."
```

Reapply the frontend secrets and restart:

```bash theme={null}
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 theme={null}
volumes:
  - name: artifacts
    persistentVolumeClaim:
      claimName: skyvern-artifacts-pvc
  - name: videos
    persistentVolumeClaim:
      claimName: skyvern-videos-pvc
```

Create the PVCs:

```yaml skyvern-storage.yaml theme={null}
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](/developers/self-hosted/storage).

***

## Scaling

### Horizontal scaling

To run multiple backend instances, increase the replica count:

```yaml backend/backend-deployment.yaml theme={null}
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 theme={null}
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 theme={null}
spec:
  tls:
    - hosts:
        - skyvern.example.com
      secretName: skyvern-tls-secret
```

Create the TLS secret:

```bash theme={null}
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 theme={null}
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 theme={null}
DATABASE_STRING: "postgresql+psycopg://user:password@your-db-host:5432/skyvern"
```

***

## Troubleshooting

### Pods stuck in Pending

Check for resource constraints:

```bash theme={null}
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 theme={null}
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 theme={null}
kubectl get ingress -n skyvern
kubectl describe ingress skyvern-ingress -n skyvern
```

***

## Cleanup

To remove the entire deployment:

```bash theme={null}
kubectl delete namespace skyvern
```

This removes all resources in the `skyvern` namespace.

To clean up host storage (if using hostPath):

```bash theme={null}
rm -rf /data/artifacts /data/videos /data/har /data/log /app/.skyvern
```

***

## Next steps

<CardGroup cols={2}>
  <Card title="Storage Configuration" icon="hard-drive" href="/developers/self-hosted/storage">
    Configure S3 or Azure Blob for artifact storage
  </Card>

  <Card title="LLM Configuration" icon="microchip" href="/developers/self-hosted/llm-configuration">
    Configure additional LLM providers
  </Card>
</CardGroup>
