---
title: "Deploying to Kubernetes"
description: "Deploy your RivetKit app to any Kubernetes cluster."
skill: true
---

## Steps

<Steps>
<Step title="Prerequisites">

- A Kubernetes cluster with `kubectl` access (EKS, GKE, k3s, etc.)
- Container registry credentials (Docker Hub, GHCR, GCR, etc.)
- Your RivetKit app
  - If you don't have one, see the [Quickstart](/docs/actors/quickstart) page or our [Examples](https://github.com/rivet-dev/rivet/tree/main/examples)
- Access to the [Rivet Cloud](https://dashboard.rivet.dev/) or a [self-hosted Rivet Engine](/docs/general/self-hosting)

</Step>
<Step title="Package Your App">

Create a `Dockerfile` in your project root:

```dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV PORT=8080
CMD ["node", "server.js"]
```

</Step>
<Step title="Build and Push the Image">

```bash
docker build -t registry.example.com/your-team/rivetkit-app:latest .
docker push registry.example.com/your-team/rivetkit-app:latest
```

Replace `registry.example.com/your-team` with your registry path. Auth with `docker login` first if needed.

</Step>
<Step title="Set Environment Variables">

After creating your project on the Rivet dashboard, select Kubernetes as your provider. You'll be provided `RIVET_ENDPOINT` and `RIVET_PUBLIC_ENDPOINT` environment variables.

Create a `rivetkit-secrets.yaml` for your environment variables:

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: rivetkit-secrets
type: Opaque
stringData:
  RIVET_ENDPOINT: <your-rivet-endpoint>
  RIVET_PUBLIC_ENDPOINT: <your-rivet-public-endpoint>
```

</Step>
<Step title="Deploy to Kubernetes">

Create a `deployment.yaml`:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rivetkit-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: rivetkit-app
  template:
    metadata:
      labels:
        app: rivetkit-app
    spec:
      # Allow enough time for actors to gracefully stop on SIGTERM.
      # The runner waits up to 30m for actors to finish.
      # Add buffer for runner shutdown overhead after actors stop.
      # See: /docs/actors/versions#graceful-shutdown-sigterm
      terminationGracePeriodSeconds: 2100
      containers:
        - name: rivetkit-app
          image: registry.example.com/your-team/rivetkit-app:latest
          envFrom:
            - secretRef:
                name: rivetkit-secrets
```

Apply both manifests:

```bash
kubectl apply -f rivetkit-secrets.yaml
kubectl apply -f deployment.yaml
```

</Step>
<Step title="Connect to Rivet">

1. Add a `Service` and Ingress to expose your app externally (e.g. `my-app.example.com`)
2. On the Rivet dashboard, paste your domain with the `/api/rivet` path into the connect form (e.g. `https://my-app.example.com/api/rivet`)
3. Click "Done"

<Warning>
Rivet envoys connect to your app over long-lived WebSockets, and your app's clients (browsers, SDKs) do the same. Default Ingress and cloud load balancer idle timeouts (typically 30 to 60 seconds) drop these connections and cause reconnect storms.

Raise the idle / read / send timeout on every Ingress and load balancer in front of your app to at least 1 hour (`3600` seconds). Examples:

- **NGINX Ingress**: `nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"` and `nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"`
- **AWS Load Balancer Controller (ALB)**: `alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=3600`
- **GCE Ingress (GKE)**: set `timeoutSec: 3600` on the `BackendConfig` referenced by the Service.
</Warning>

</Step>
<Step title="Verify">

Check that the pod is running:

```bash
kubectl get pods -l app=rivetkit-app
```

Your app should appear as connected on the Rivet dashboard once the pod is ready.

</Step>
</Steps>
