website Documentation

Kubernetes website and documentation repo:

5,330 stars HTML HTML 1 file ยท ~14,880 tokens
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy website on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name website -p 1313:1313 --restart always kubernetes/website:latest

1. Project Overview & Quickstart (rivet-dev/website)

File: README.md

Rivet Website

The source for rivet.dev โ€” marketing pages, docs, and blog.

Each product's documentation is authored in that product's own repository and
vendored in here. See CLAUDE.md for the layout and
HIDDEN.md for what is deliberately unpublished.

Develop

bash
pnpm install
pnpm assemble   # link each product's docs bundle
pnpm dev

License

Apache 2.0


File: cloud/docs/content/docs/compute.mdx


title: "Rivet Compute"

Rivet Compute runs your backend for you. You supply a container image; Rivet builds it, pushes it to its own registry, provisions a managed pool, and gives you a URL.

Using an AI coding agent? Open **Connect** on the [Rivet dashboard](https://dashboard.rivet.dev), select **Rivet Cloud**, and paste the one-shot prompt into your agent to have it connect for you.

Requirements

  • A Rivet Cloud account and project
  • Docker running locally
  • Your app. If you do not have one yet, start from the Quickstart.

Steps

At your project root:

dockerfile @nocheck
FROM node:24-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "src/server.js"]

Your server must listen on the port in RIVET_PORT, which defaults to 3000.

  1. Open the dashboard and go to your project.
  2. Click Connect, then Rivet Cloud.
  3. Copy RIVET_CLOUD_TOKEN.

This is a cloud_api_* management token, not a pk_* publishable key. See Tokens.

bash
npx @rivetkit/cli deploy --token cloud_api_xxxxx

The token is written to ~/.rivet/credentials, so later deploys can omit the flag. The CLI resolves your project from the token, builds and pushes the image, upserts the managed pool, and prints the deployment URL once the pool is ready.

The dashboard shows live status:

Status Meaning
Initializing Starting the runtime environment
Deploying Pulling and launching your container
Binding Connecting the runner to the network
Ready Live, and accepting actor connections

If you are an agent polling the API rather than watching the dashboard, use the managed-pool endpoint on the Cloud API.

RIVET_CLOUD_TOKEN is a cloud_api_* token scoped to cloud-api.rivet.dev. Send it as Authorization: Bearer .... A pk_* publishable key is scoped to the Engine API at api.rivet.dev and will 401 here.

Substitute $CLOUD_API_URL (usually https://cloud-api.rivet.dev), $PROJECT, $ORG, $CLOUD_NAMESPACE, and $CLOUD_TOKEN. Poll every 5 seconds until status is ready. Stop and investigate if status is error.

bash
curl -s "$CLOUD_API_URL/projects/$PROJECT/namespaces/$CLOUD_NAMESPACE/managed-pools/default?org=$ORG" -H "Authorization: Bearer $CLOUD_TOKEN"

Once local deploys work, install the GitHub Actions workflow:

bash
npx @rivetkit/cli setup-ci

This writes .github/workflows/rivet-deploy.yml. Add the token as a repository secret:

bash
gh secret set RIVET_CLOUD_TOKEN

The workflow deploys production on push to your default branch and creates an isolated namespace per pull request. See Preview Deployments.

Logs

rivet logs prints the last 100 lines from the default pool in the production namespace, oldest first, then exits.

bash
npx @rivetkit/cli logs

Token resolution matches deploy: the --token flag, then RIVET_CLOUD_TOKEN, then ~/.rivet/credentials.

Add --follow (-f) to stream instead of fetching history:

bash
npx @rivetkit/cli logs --follow

Each line is <timestamp> [<SEVERITY>] <region> <message>:

text
2026-06-16T18:26:51.160Z [INFO] eu-central-1 server listening on port 3000
2026-06-17T11:24:20.425Z [ERROR] us-east-1 failed to connect to upstream

More examples:

bash
# Last 200 lines from a specific namespace
npx @rivetkit/cli logs --namespace production -n 200

# A specific compute pool
npx @rivetkit/cli logs --pool my-pool

# Live tail, filtered
npx @rivetkit/cli logs --follow --contains error

# JSON, piped to jq
npx @rivetkit/cli logs -n 50 --json | jq .

Troubleshooting

If the status stays in Initializing for more than a few minutes:

  • Check that RIVET_CLOUD_TOKEN is set correctly as a repository secret.
  • Check the GitHub Actions run logs for a failed step.

The container is failing to start or exiting immediately. Test the image locally with docker run first. Common causes:

  • The server file never calls registry.start().
  • A runtime crash on startup.
  • The server is not listening on RIVET_PORT (default 3000).

Pricing

You are billed for the compute your deployment uses while it is running.


File: cloud/docs/content/docs/index.mdx


title: "Rivet Cloud"

Rivet Cloud runs the control plane for you. You keep writing the same code and deploy it one of two ways:

  • Bring your own compute. Run workers on your own infrastructure and point them at Rivet Cloud. Every platform in the Self-Host worker guides works this way.
  • Rivet Compute. Hand Rivet a container and it runs your backend as well. See Rivet Compute.

Nothing here is required to use Rivet. If you would rather run the control plane yourself, see Self-Host.

Getting started

  1. Create a project at dashboard.rivet.dev.
  2. Open Connect and pick how you want to deploy.
  3. Copy the endpoint values, or the cloud token if you are deploying with Rivet Compute.

Tokens

Rivet Cloud issues three kinds of token, and mixing them up is the most common source of 401s.

Token Prefix Scope Used for
Secret sk_ Engine API at api.rivet.dev RIVET_ENDPOINT, server-side only
Publishable pk_ Engine API at api.rivet.dev RIVET_PUBLIC_ENDPOINT, safe for browsers
Cloud API cloud_api_ Cloud API at cloud-api.rivet.dev rivet deploy, CI, managed-pool APIs

A pk_ token will fail against the Cloud API, and a cloud_api_ token will fail against the Engine API. They are not interchangeable.

In this section


File: cloud/docs/content/docs/preview-deployments.mdx


title: "Preview Deployments"

A preview deployment gives each pull request its own Rivet namespace, so branch work never touches production state. The namespace is created when the PR opens and destroyed when it closes.

Which setup you use depends on where your workers run.

With Rivet Compute

setup-ci writes a workflow that already does this:

bash
npx @rivetkit/cli setup-ci
gh secret set RIVET_CLOUD_TOKEN

The generated .github/workflows/rivet-deploy.yml deploys production on push to your default branch, creates a namespace per pull request, posts the preview link as a comment, and cleans the namespace up on close. See Rivet Compute.

With Vercel

Use the preview-namespace-action. It creates the namespace and writes RIVET_ENDPOINT and RIVET_PUBLIC_ENDPOINT into the Vercel preview environment, so the deployment picks them up with no manual configuration.

yaml .github/workflows/rivet-preview.yml
name: Rivet Preview

on:
  pull_request:
    types: [opened, synchronize, reopened]
  push:
    branches: [main]

concurrency:
  group: rivet-preview-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  rivet-preview:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: rivet-dev/preview-namespace-action@v1
        with:
          platform: vercel
          rivet-token: ${{ secrets.RIVET_CLOUD_TOKEN }}
          vercel-token: ${{ secrets.VERCEL_TOKEN }}

The action also configures the Vercel Deployment Protection bypass header, which is otherwise a common source of 401s from Rivet into preview deployments. See Vercel.

On other platforms

Any platform works, as long as the pull-request build gets endpoint values for a fresh namespace instead of production's. Create the namespace with the Cloud API using a cloud_api_* token, inject the resulting endpoints as build-time environment variables, and delete the namespace when the pull request closes.

Never point a preview build at your production namespace. Previews create and destroy actors freely, and that state is shared with whatever else uses the namespace.

Next steps


File: packages/icons/README.md

Rivet Icons

This package is licensed exclusively for use in Rivet products and services. Using this package in any other product, project, or commercial application is strictly prohibited and may constitute a violation of Font Awesome's Terms of Service and intellectual property rights.

This package includes icons from Font Awesome Pro, which are licensed to Rivet for use in Rivet products only. If you wish to use Font Awesome Pro icons in your own projects, you must obtain your own license from Font Awesome.

Overview

Rivet Icons is an icon library built on Font Awesome that provides pre-generated SVG icons for use in Rivet products. All icons (including Pro icons) are committed to the repository, making this package work out-of-the-box with no Font Awesome token required for end users.

Installation

bash
pnpm add @rivet-gg/icons

Usage

tsx
import { Icon, faCheckCircle } from "@rivet-gg/icons";

Contributing

Prerequisites

For maintainers only: Obtain a Font Awesome Pro license and set the FONTAWESOME_PACKAGE_TOKEN environment variable to your Font Awesome Pro token. This is only required when vendoring new icons.

Adding new icons

  1. Ensure you have a FONTAWESOME_PACKAGE_TOKEN environment variable set
  2. Modify scripts/generate-manifest.js to include new icons
  3. Run pnpm manifest to generate a new manifest.json file
    • If you're getting an error about missing packages, run pnpm install in the src folder first
  4. Run pnpm vendor to generate icon files:
    • src/index.gen.js
    • src/index.gen.ts
    • dist/index.js
  5. Commit all changes including the generated files

Troubleshooting

Icons not showing up

All icons (including Font Awesome Pro icons) are pre-generated and committed to this repository. If icons aren't showing:

  1. Make sure you have the peer dependencies installed:

    • @fortawesome/fontawesome-svg-core
    • @fortawesome/free-solid-svg-icons
    • @fortawesome/free-brands-svg-icons
    • @fortawesome/react-fontawesome
    • react and react-dom
  2. Check that you're importing icons correctly:

    tsx
    import { Icon, faCheckCircle } from "@rivet-gg/icons";
  3. If the problem persists, contact us on Discord

For Maintainers: `Could not find package @fortawesome/pro-solid-svg-icons`

This error occurs when running the vendor script without access to Font Awesome Pro packages. Make sure:

  1. FONTAWESOME_PACKAGE_TOKEN environment variable is set
  2. Run pnpm install in the src folder to install FA Pro packages
  3. Run pnpm vendor again

File: src/content/self-host/control-plane/aws-ecs.mdx


title: "AWS ECS"

Requirements

  • An AWS account with ECS and Fargate available
  • An EFS file system, or an RDS PostgreSQL instance, for persistence
  • An Application Load Balancer in front of the service
  • AWS Secrets Manager, or SSM Parameter Store, for the admin token
A published Terraform module for the control plane does not exist yet. The [AWS ECS template](https://github.com/rivet-dev/template-aws-ecs) covers workers, not the control plane. The steps below are the generic container requirements mapped onto ECS.

Steps

Fargate task storage is ephemeral, so the data directory must live outside the task.

  • Single node: create an EFS file system and mount it into the task at /data.
  • Multi-node: create an RDS PostgreSQL instance instead and skip the volume. Use the direct endpoint, not RDS Proxy, which is not supported. See Storage.
bash
aws secretsmanager create-secret \
  --name rivet/admin-token \
  --secret-string "$(openssl rand -hex 32)"

Reference it from the task definition's secrets block rather than putting it in environment.

Use a pinned image tag, expose both ports, and mount the volume.

json
{
  "family": "rivet-engine",
  "requiresCompatibilities": ["FARGATE"],
  "networkMode": "awsvpc",
  "cpu": "1024",
  "memory": "2048",
  "containerDefinitions": [
    {
      "name": "rivet-engine",
      "image": "rivetdev/engine:2.3.3",
      "portMappings": [
        { "containerPort": 6420 },
        { "containerPort": 6421 }
      ],
      "environment": [
        { "name": "RIVET__FILE_SYSTEM__PATH", "value": "/data" }
      ],
      "secrets": [
        {
          "name": "RIVET__AUTH__ADMIN_TOKEN",
          "valueFrom": "arn:aws:secretsmanager:REGION:ACCOUNT:secret:rivet/admin-token"
        }
      ],
      "mountPoints": [
        { "sourceVolume": "rivet-data", "containerPath": "/data" }
      ]
    }
  ],
  "volumes": [
    {
      "name": "rivet-data",
      "efsVolumeConfiguration": { "fileSystemId": "fs-XXXXXXXX" }
    }
  ]
}

1 CPU and 2 GB of RAM is the recommended minimum per instance.

The ALB default is 60 seconds, which severs long-lived WebSockets and causes reconnect storms.

bash
aws elbv2 modify-load-balancer-attributes \
  --load-balancer-arn <arn> \
  --attributes Key=idle_timeout.timeout_seconds,Value=3600

Point the target group at container port 6420 and terminate TLS on the listener. See Ports.

Set the target group health check path to /health on port 6421, with a five second timeout.

bash
aws ecs describe-services --cluster <cluster> --services rivet-engine \
  --query 'services[0].deployments[0].rolloutState'

Expect COMPLETED. Then open /ui on the load balancer hostname and log in with the admin token.

A runner config declares a pool that workers connect to. Without one the control plane refuses every worker, so create it before deploying any:

bash
curl -X PUT "http://<load-balancer-host>/runner-configs/default?namespace=default" \
  -H "Authorization: Bearer $RIVET_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"datacenters":{"default":{"normal":{}}}}'

Use normal for long-lived workers. See Runner configs.

Scaling past one task

EFS-backed file system storage is single-node: RocksDB cannot be shared between tasks. Before raising the desired count above one, move to RDS PostgreSQL and add NATS for pub/sub. See Storage.

Once on RDS, ECS Service Auto Scaling can drive the desired count. Use target tracking on average CPU at 60%, matching the Kubernetes manifests, and keep a minimum of two tasks so the service survives losing one. Leave the desired count at one for as long as the task is on EFS.

Next steps

  • Production checklist
  • Deploy workers on AWS ECS
  • Configuration reference

File: src/content/self-host/control-plane/backups.mdx


title: "Backup & Restore"

Everything the control plane persists lives in its storage backend, so backing up the control plane means backing up that backend. The engine itself is stateless between restarts and needs no separate backup.

What you must be able to restore:

  • The storage backend. Actor state, namespaces, and runner configuration.
  • Your configuration. The config file or environment variables, including the admin token. Losing the admin token locks you out of the dashboard and the API.

By backend

Use your provider's automated backups, or pg_dump on a schedule. Point-in-time recovery is worth enabling if your provider offers it.

bash
pg_dump "postgresql://user:password@host:5432/rivet" --format=custom --file=rivet-$(date +%F).dump

Restore into an empty database, then start the control plane against it:

bash
pg_restore --dbname="postgresql://user:password@host:5432/rivet" --clean --if-exists rivet-2026-01-01.dump

Managed providers generally do this better than you will. Prefer their automated backups plus a standby replica over a cron job.

Stop the control plane before copying the data directory. RocksDB is not safe to copy while a process holds it open, and a copy taken from a running node can restore into a corrupt state.

bash
# Stop the control plane first
tar czf rivet-data-$(date +%F).tar.gz -C /var/lib/rivet data

Restore by extracting into the same path and starting the control plane again. If you cannot tolerate the downtime this implies, that is a signal to move to PostgreSQL.

FoundationDB has its own backup agent and continuous backup support. Backup topology is covered during enterprise onboarding. Contact enterprise support.

Test the restore

A backup you have never restored is not a backup. Restore into a scratch environment on a schedule and confirm:

  1. The control plane starts and reports healthy on /health.
  2. The dashboard loads and your namespaces are present.
  3. A worker connects and appears under Runners.
  4. An actor created before the backup still resolves and reads its state.

Next steps

  • Storage backends
  • Upgrades
  • Production checklist

File: src/content/self-host/control-plane/configuration.mdx


title: "Configuration"

import EngineConfigSchema from "@/components/docs/EngineConfigSchemaClient.astro";

The control plane is the Rivet Engine. It reads configuration from files, from environment variables, or from both.

Configuration sources

The engine accepts JSON, JSON5, JSONC, YAML, and YML, plus environment variables.

Environment variables

Prefix a setting with RIVET__ and separate each level of nesting with __. Names are case-insensitive. To set postgres.url:

bash
RIVET__POSTGRES__URL="postgresql://user:password@host:5432/database"

Configuration files

Files are discovered automatically in a platform-specific directory:

Platform Path
Linux /etc/rivet/config.json
macOS /Library/Application Support/rivet/config.json
Windows C:\ProgramData\rivet\config.json

Multiple files in the same directory are merged. /etc/rivet/config.json and /etc/rivet/database.json are loaded together.

Override the search path with --config:

bash
# Load from a specific file
rivet-engine --config /path/to/config.json

# Load from a directory
rivet-engine --config /etc/rivet

# Load multiple paths, merged in order
rivet-engine --config /etc/rivet/base.json --config /etc/rivet/override.json

Reference

Envoy load balancing

pegboard.envoy_load_balancer selects how actors are assigned to envoys. The hash strategy uses a hash ring:

json
{
	"pegboard": {
		"envoy_load_balancer": {
			"hash": {
				"virtual_nodes": 8,
				"samples": 2,
				"max_scan": 16,
				"use_snapshot_read": true
			}
		}
	}
}

Use samples: 1 for a uniform random pick that skips slot reads, or samples: 2 or higher for power-of-K choices over envoy slot counts. Treat virtual_nodes as an operational invariant once envoys have registered: changing it reshuffles the ring.

Telemetry

The engine exports traces and metrics over OpenTelemetry.

Variable Purpose Default
RIVET_OTEL_ENABLED Set to 1 to enable export off
RIVET_OTEL_GRPC_ENDPOINT Collector endpoint http://localhost:4317
RIVET_OTEL_SAMPLER_RATIO Trace sampling ratio 0.001

Next steps

  • Storage backends
  • Ports
  • Production checklist

File: src/content/self-host/control-plane/custom.mdx


title: "Custom Platform"

The control plane is a single container image, rivetdev/engine. Any platform that can run it with a persistent volume and a stable address can host it.

Requirements

Your platform must provide:

  • A container runtime, or a Linux host you can run the binary on.
  • A persistent volume. The control plane is stateful. Storage that resets on redeploy will lose actor state.
  • A stable network identity, so workers keep reaching the same place.
  • Long-lived connections. Platforms that cap request duration or idle time below an hour will sever WebSockets. This rules out serverless platforms entirely.
  • SIGTERM plus a grace period, so the control plane can drain and flush state before it is killed.

Steps

bash
openssl rand -hex 32

Store it wherever your platform keeps secrets. Without it, the API is unauthenticated.

Pin the tag. Never run latest in production.

bash
docker run -d \
  --restart unless-stopped \
  -p 6420:6420 \
  -p 6421:6421 \
  -v rivet-data:/data \
  -e RIVET__FILE_SYSTEM__PATH="/data" \
  -e RIVET__AUTH__ADMIN_TOKEN="<your-token>" \
  rivetdev/engine:2.3.3

For multi-node, drop the file system variable, set RIVET__POSTGRES__URL instead, and add NATS for pub/sub. Do not replicate or autoscale a node that is still on the file system backend: RocksDB is single-node. See Storage.

6420 carries API, dashboard, and WebSocket traffic and should sit behind TLS. 6421 serves health checks and should stay internal. See Ports.

Set idle, read, and send timeouts to at least 3600 seconds on every proxy in the path. The 30 to 60 second defaults sever live WebSockets and cause reconnect storms.

bash
curl -i http://<host>:6421/health

Expect 200. The dashboard is at /ui on port 6420.

A runner config declares a pool that workers connect to. Without one the control plane refuses every worker, so create it before deploying any:

bash
curl -X PUT "http://<host>:6420/runner-configs/default?namespace=default" \
  -H "Authorization: Bearer $RIVET_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"datacenters":{"default":{"normal":{}}}}'

Use normal for long-lived workers. See Runner configs.

Point a worker at the control plane and confirm it appears under Runners:

bash
RIVET_ENDPOINT="https://default:<admin-token>@your-control-plane.example.com"

Troubleshooting

Workers connect, then drop every 30 to 60 seconds. The load balancer idle timeout is still at its default.

State disappears after a deploy. The volume is not persistent, or the data path does not point at it.

The dashboard rejects your token. RIVET__AUTH__ADMIN_TOKEN is not reaching the container. Confirm the variable name, including the double underscores.

Next steps

  • Configuration reference
  • Production checklist
  • Deploy your workers

File: src/content/self-host/control-plane/docker-compose.mdx


title: "Docker Compose"

Requirements

  • Docker with the Compose plugin
  • A host with a persistent volume

Steps

This is a single-node deployment on the file system backend. The named volume is what makes the data survive a restart.

yaml
services:
  rivet-engine:
    image: rivetdev/engine:latest
    ports:
      - "6420:6420"
    volumes:
      - rivet-data:/data
    environment:
      RIVET__FILE_SYSTEM__PATH: "/data"
      RIVET__AUTH__ADMIN_TOKEN: "${RIVET_ADMIN_TOKEN}"
    restart: unless-stopped

volumes:
  rivet-data:
bash
echo "RIVET_ADMIN_TOKEN=$(openssl rand -hex 32)" >> .env

Without a token the API is unauthenticated. Keep .env out of version control.

bash
docker compose up -d
bash
curl -i http://localhost:6420/health

Expect 200. The dashboard is at http://localhost:6420/ui; log in with the admin token.

A runner config declares a pool that workers connect to. Without one the control plane refuses every worker, so create it before adding your app:

bash
curl -X PUT "http://localhost:6420/runner-configs/default?namespace=default" \
  -H "Authorization: Bearer $RIVET_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"datacenters":{"default":{"normal":{}}}}'

Use normal for the long-lived workers this file runs. See Runner configs.

Add your app to the same Compose file. Inside the Compose network it reaches the control plane at rivet-engine:6420.

yaml
my-app:
    build: .
    environment:
      RIVET_ENDPOINT: "http://default:${RIVET_ADMIN_TOKEN}@rivet-engine:6420"
    depends_on:
      - rivet-engine
    restart: unless-stopped
bash
docker compose up -d

Confirm the worker appears under Runners in the dashboard.

Using PostgreSQL

Swap the file system backend for PostgreSQL when you outgrow one node.

yaml
services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_DB: rivet
      POSTGRES_USER: rivet
      POSTGRES_PASSWORD: rivet_password
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U rivet -d rivet"]
      interval: 5s
      timeout: 5s
      retries: 12
    restart: unless-stopped

  rivet-engine:
    image: rivetdev/engine:latest
    ports:
      - "6420:6420"
    environment:
      RIVET__POSTGRES__URL: postgresql://rivet:rivet_password@postgres:5432/rivet
      RIVET__AUTH__ADMIN_TOKEN: "${RIVET_ADMIN_TOKEN}"
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

volumes:
  postgres-data:

This file still runs a single engine container, so it is not yet a multi-node deployment. It is the prerequisite for one: the file system backend is single-node RocksDB and cannot be shared, while PostgreSQL can. Multi-node deployments also need NATS. See Storage.

Using a config file

Mount JSON instead of setting environment variables:

yaml
volumes:
      - ./rivet-config.json:/etc/rivet/config.json:ro
      - rivet-data:/data

See Configuration for the full schema.

Next steps

  • Production checklist
  • Deploy your workers
  • Configuration reference

2. Official Technical Reference & Guides (rivet-dev/website)

File: README.md

Rivet Website

The source for rivet.dev โ€” marketing pages, docs, and blog.

Each product's documentation is authored in that product's own repository and
vendored in here. See CLAUDE.md for the layout and
HIDDEN.md for what is deliberately unpublished.

Develop

bash
pnpm install
pnpm assemble   # link each product's docs bundle
pnpm dev

License

Apache 2.0


File: cloud/docs/content/docs/compute.mdx


title: "Rivet Compute"

Rivet Compute runs your backend for you. You supply a container image; Rivet builds it, pushes it to its own registry, provisions a managed pool, and gives you a URL.

Using an AI coding agent? Open **Connect** on the [Rivet dashboard](https://dashboard.rivet.dev), select **Rivet Cloud**, and paste the one-shot prompt into your agent to have it connect for you.

Requirements

  • A Rivet Cloud account and project
  • Docker running locally
  • Your app. If you do not have one yet, start from the Quickstart.

Steps

At your project root:

dockerfile @nocheck
FROM node:24-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "src/server.js"]

Your server must listen on the port in RIVET_PORT, which defaults to 3000.

  1. Open the dashboard and go to your project.
  2. Click Connect, then Rivet Cloud.
  3. Copy RIVET_CLOUD_TOKEN.

This is a cloud_api_* management token, not a pk_* publishable key. See Tokens.

bash
npx @rivetkit/cli deploy --token cloud_api_xxxxx

The token is written to ~/.rivet/credentials, so later deploys can omit the flag. The CLI resolves your project from the token, builds and pushes the image, upserts the managed pool, and prints the deployment URL once the pool is ready.

The dashboard shows live status:

Status Meaning
Initializing Starting the runtime environment
Deploying Pulling and launching your container
Binding Connecting the runner to the network
Ready Live, and accepting actor connections

If you are an agent polling the API rather than watching the dashboard, use the managed-pool endpoint on the Cloud API.

RIVET_CLOUD_TOKEN is a cloud_api_* token scoped to cloud-api.rivet.dev. Send it as Authorization: Bearer .... A pk_* publishable key is scoped to the Engine API at api.rivet.dev and will 401 here.

Substitute $CLOUD_API_URL (usually https://cloud-api.rivet.dev), $PROJECT, $ORG, $CLOUD_NAMESPACE, and $CLOUD_TOKEN. Poll every 5 seconds until status is ready. Stop and investigate if status is error.

bash
curl -s "$CLOUD_API_URL/projects/$PROJECT/namespaces/$CLOUD_NAMESPACE/managed-pools/default?org=$ORG" -H "Authorization: Bearer $CLOUD_TOKEN"

Once local deploys work, install the GitHub Actions workflow:

bash
npx @rivetkit/cli setup-ci

This writes .github/workflows/rivet-deploy.yml. Add the token as a repository secret:

bash
gh secret set RIVET_CLOUD_TOKEN

The workflow deploys production on push to your default branch and creates an isolated namespace per pull request. See Preview Deployments.

Logs

rivet logs prints the last 100 lines from the default pool in the production namespace, oldest first, then exits.

bash
npx @rivetkit/cli logs

Token resolution matches deploy: the --token flag, then RIVET_CLOUD_TOKEN, then ~/.rivet/credentials.

Add --follow (-f) to stream instead of fetching history:

bash
npx @rivetkit/cli logs --follow

Each line is <timestamp> [<SEVERITY>] <region> <message>:

text
2026-06-16T18:26:51.160Z [INFO] eu-central-1 server listening on port 3000
2026-06-17T11:24:20.425Z [ERROR] us-east-1 failed to connect to upstream

More examples:

bash
# Last 200 lines from a specific namespace
npx @rivetkit/cli logs --namespace production -n 200

# A specific compute pool
npx @rivetkit/cli logs --pool my-pool

# Live tail, filtered
npx @rivetkit/cli logs --follow --contains error

# JSON, piped to jq
npx @rivetkit/cli logs -n 50 --json | jq .

Troubleshooting

If the status stays in Initializing for more than a few minutes:

  • Check that RIVET_CLOUD_TOKEN is set correctly as a repository secret.
  • Check the GitHub Actions run logs for a failed step.

The container is failing to start or exiting immediately. Test the image locally with docker run first. Common causes:

  • The server file never calls registry.start().
  • A runtime crash on startup.
  • The server is not listening on RIVET_PORT (default 3000).

Pricing

You are billed for the compute your deployment uses while it is running.


File: cloud/docs/content/docs/index.mdx


title: "Rivet Cloud"

Rivet Cloud runs the control plane for you. You keep writing the same code and deploy it one of two ways:

  • Bring your own compute. Run workers on your own infrastructure and point them at Rivet Cloud. Every platform in the Self-Host worker guides works this way.
  • Rivet Compute. Hand Rivet a container and it runs your backend as well. See Rivet Compute.

Nothing here is required to use Rivet. If you would rather run the control plane yourself, see Self-Host.

Getting started

  1. Create a project at dashboard.rivet.dev.
  2. Open Connect and pick how you want to deploy.
  3. Copy the endpoint values, or the cloud token if you are deploying with Rivet Compute.

Tokens

Rivet Cloud issues three kinds of token, and mixing them up is the most common source of 401s.

Token Prefix Scope Used for
Secret sk_ Engine API at api.rivet.dev RIVET_ENDPOINT, server-side only
Publishable pk_ Engine API at api.rivet.dev RIVET_PUBLIC_ENDPOINT, safe for browsers
Cloud API cloud_api_ Cloud API at cloud-api.rivet.dev rivet deploy, CI, managed-pool APIs

A pk_ token will fail against the Cloud API, and a cloud_api_ token will fail against the Engine API. They are not interchangeable.

In this section


File: cloud/docs/content/docs/preview-deployments.mdx


title: "Preview Deployments"

A preview deployment gives each pull request its own Rivet namespace, so branch work never touches production state. The namespace is created when the PR opens and destroyed when it closes.

Which setup you use depends on where your workers run.

With Rivet Compute

setup-ci writes a workflow that already does this:

bash
npx @rivetkit/cli setup-ci
gh secret set RIVET_CLOUD_TOKEN

The generated .github/workflows/rivet-deploy.yml deploys production on push to your default branch, creates a namespace per pull request, posts the preview link as a comment, and cleans the namespace up on close. See Rivet Compute.

With Vercel

Use the preview-namespace-action. It creates the namespace and writes RIVET_ENDPOINT and RIVET_PUBLIC_ENDPOINT into the Vercel preview environment, so the deployment picks them up with no manual configuration.

yaml .github/workflows/rivet-preview.yml
name: Rivet Preview

on:
  pull_request:
    types: [opened, synchronize, reopened]
  push:
    branches: [main]

concurrency:
  group: rivet-preview-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  rivet-preview:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: rivet-dev/preview-namespace-action@v1
        with:
          platform: vercel
          rivet-token: ${{ secrets.RIVET_CLOUD_TOKEN }}
          vercel-token: ${{ secrets.VERCEL_TOKEN }}

The action also configures the Vercel Deployment Protection bypass header, which is otherwise a common source of 401s from Rivet into preview deployments. See Vercel.

On other platforms

Any platform works, as long as the pull-request build gets endpoint values for a fresh namespace instead of production's. Create the namespace with the Cloud API using a cloud_api_* token, inject the resulting endpoints as build-time environment variables, and delete the namespace when the pull request closes.

Never point a preview build at your production namespace. Previews create and destroy actors freely, and that state is shared with whatever else uses the namespace.

Next steps


File: packages/icons/README.md

Rivet Icons

This package is licensed exclusively for use in Rivet products and services. Using this package in any other product, project, or commercial application is strictly prohibited and may constitute a violation of Font Awesome's Terms of Service and intellectual property rights.

This package includes icons from Font Awesome Pro, which are licensed to Rivet for use in Rivet products only. If you wish to use Font Awesome Pro icons in your own projects, you must obtain your own license from Font Awesome.

Overview

Rivet Icons is an icon library built on Font Awesome that provides pre-generated SVG icons for use in Rivet products. All icons (including Pro icons) are committed to the repository, making this package work out-of-the-box with no Font Awesome token required for end users.

Installation

bash
pnpm add @rivet-gg/icons

Usage

tsx
import { Icon, faCheckCircle } from "@rivet-gg/icons";

Contributing

Prerequisites

For maintainers only: Obtain a Font Awesome Pro license and set the FONTAWESOME_PACKAGE_TOKEN environment variable to your Font Awesome Pro token. This is only required when vendoring new icons.

Adding new icons

  1. Ensure you have a FONTAWESOME_PACKAGE_TOKEN environment variable set
  2. Modify scripts/generate-manifest.js to include new icons
  3. Run pnpm manifest to generate a new manifest.json file
    • If you're getting an error about missing packages, run pnpm install in the src folder first
  4. Run pnpm vendor to generate icon files:
    • src/index.gen.js
    • src/index.gen.ts
    • dist/index.js
  5. Commit all changes including the generated files

Troubleshooting

Icons not showing up

All icons (including Font Awesome Pro icons) are pre-generated and committed to this repository. If icons aren't showing:

  1. Make sure you have the peer dependencies installed:

    • @fortawesome/fontawesome-svg-core
    • @fortawesome/free-solid-svg-icons
    • @fortawesome/free-brands-svg-icons
    • @fortawesome/react-fontawesome
    • react and react-dom
  2. Check that you're importing icons correctly:

    tsx
    import { Icon, faCheckCircle } from "@rivet-gg/icons";
  3. If the problem persists, contact us on Discord

For Maintainers: `Could not find package @fortawesome/pro-solid-svg-icons`

This error occurs when running the vendor script without access to Font Awesome Pro packages. Make sure:

  1. FONTAWESOME_PACKAGE_TOKEN environment variable is set
  2. Run pnpm install in the src folder to install FA Pro packages
  3. Run pnpm vendor again

File: src/content/self-host/control-plane/aws-ecs.mdx


title: "AWS ECS"

Requirements

  • An AWS account with ECS and Fargate available
  • An EFS file system, or an RDS PostgreSQL instance, for persistence
  • An Application Load Balancer in front of the service
  • AWS Secrets Manager, or SSM Parameter Store, for the admin token
A published Terraform module for the control plane does not exist yet. The [AWS ECS template](https://github.com/rivet-dev/template-aws-ecs) covers workers, not the control plane. The steps below are the generic container requirements mapped onto ECS.

Steps

Fargate task storage is ephemeral, so the data directory must live outside the task.

  • Single node: create an EFS file system and mount it into the task at /data.
  • Multi-node: create an RDS PostgreSQL instance instead and skip the volume. Use the direct endpoint, not RDS Proxy, which is not supported. See Storage.
bash
aws secretsmanager create-secret \
  --name rivet/admin-token \
  --secret-string "$(openssl rand -hex 32)"

Reference it from the task definition's secrets block rather than putting it in environment.

Use a pinned image tag, expose both ports, and mount the volume.

json
{
  "family": "rivet-engine",
  "requiresCompatibilities": ["FARGATE"],
  "networkMode": "awsvpc",
  "cpu": "1024",
  "memory": "2048",
  "containerDefinitions": [
    {
      "name": "rivet-engine",
      "image": "rivetdev/engine:2.3.3",
      "portMappings": [
        { "containerPort": 6420 },
        { "containerPort": 6421 }
      ],
      "environment": [
        { "name": "RIVET__FILE_SYSTEM__PATH", "value": "/data" }
      ],
      "secrets": [
        {
          "name": "RIVET__AUTH__ADMIN_TOKEN",
          "valueFrom": "arn:aws:secretsmanager:REGION:ACCOUNT:secret:rivet/admin-token"
        }
      ],
      "mountPoints": [
        { "sourceVolume": "rivet-data", "containerPath": "/data" }
      ]
    }
  ],
  "volumes": [
    {
      "name": "rivet-data",
      "efsVolumeConfiguration": { "fileSystemId": "fs-XXXXXXXX" }
    }
  ]
}

1 CPU and 2 GB of RAM is the recommended minimum per instance.

The ALB default is 60 seconds, which severs long-lived WebSockets and causes reconnect storms.

bash
aws elbv2 modify-load-balancer-attributes \
  --load-balancer-arn <arn> \
  --attributes Key=idle_timeout.timeout_seconds,Value=3600

Point the target group at container port 6420 and terminate TLS on the listener. See Ports.

Set the target group health check path to /health on port 6421, with a five second timeout.

bash
aws ecs describe-services --cluster <cluster> --services rivet-engine \
  --query 'services[0].deployments[0].rolloutState'

Expect COMPLETED. Then open /ui on the load balancer hostname and log in with the admin token.

A runner config declares a pool that workers connect to. Without one the control plane refuses every worker, so create it before deploying any:

bash
curl -X PUT "http://<load-balancer-host>/runner-configs/default?namespace=default" \
  -H "Authorization: Bearer $RIVET_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"datacenters":{"default":{"normal":{}}}}'

Use normal for long-lived workers. See Runner configs.

Scaling past one task

EFS-backed file system storage is single-node: RocksDB cannot be shared between tasks. Before raising the desired count above one, move to RDS PostgreSQL and add NATS for pub/sub. See Storage.

Once on RDS, ECS Service Auto Scaling can drive the desired count. Use target tracking on average CPU at 60%, matching the Kubernetes manifests, and keep a minimum of two tasks so the service survives losing one. Leave the desired count at one for as long as the task is on EFS.

Next steps

  • Production checklist
  • Deploy workers on AWS ECS
  • Configuration reference

File: src/content/self-host/control-plane/backups.mdx


title: "Backup & Restore"

Everything the control plane persists lives in its storage backend, so backing up the control plane means backing up that backend. The engine itself is stateless between restarts and needs no separate backup.

What you must be able to restore:

  • The storage backend. Actor state, namespaces, and runner configuration.
  • Your configuration. The config file or environment variables, including the admin token. Losing the admin token locks you out of the dashboard and the API.

By backend

Use your provider's automated backups, or pg_dump on a schedule. Point-in-time recovery is worth enabling if your provider offers it.

bash
pg_dump "postgresql://user:password@host:5432/rivet" --format=custom --file=rivet-$(date +%F).dump

Restore into an empty database, then start the control plane against it:

bash
pg_restore --dbname="postgresql://user:password@host:5432/rivet" --clean --if-exists rivet-2026-01-01.dump

Managed providers generally do this better than you will. Prefer their automated backups plus a standby replica over a cron job.

Stop the control plane before copying the data directory. RocksDB is not safe to copy while a process holds it open, and a copy taken from a running node can restore into a corrupt state.

bash
# Stop the control plane first
tar czf rivet-data-$(date +%F).tar.gz -C /var/lib/rivet data

Restore by extracting into the same path and starting the control plane again. If you cannot tolerate the downtime this implies, that is a signal to move to PostgreSQL.

FoundationDB has its own backup agent and continuous backup support. Backup topology is covered during enterprise onboarding. Contact enterprise support.

Test the restore

A backup you have never restored is not a backup. Restore into a scratch environment on a schedule and confirm:

  1. The control plane starts and reports healthy on /health.
  2. The dashboard loads and your namespaces are present.
  3. A worker connects and appears under Runners.
  4. An actor created before the backup still resolves and reads its state.

Next steps

  • Storage backends
  • Upgrades
  • Production checklist

File: src/content/self-host/control-plane/configuration.mdx


title: "Configuration"

import EngineConfigSchema from "@/components/docs/EngineConfigSchemaClient.astro";

The control plane is the Rivet Engine. It reads configuration from files, from environment variables, or from both.

Configuration sources

The engine accepts JSON, JSON5, JSONC, YAML, and YML, plus environment variables.

Environment variables

Prefix a setting with RIVET__ and separate each level of nesting with __. Names are case-insensitive. To set postgres.url:

bash
RIVET__POSTGRES__URL="postgresql://user:password@host:5432/database"

Configuration files

Files are discovered automatically in a platform-specific directory:

Platform Path
Linux /etc/rivet/config.json
macOS /Library/Application Support/rivet/config.json
Windows C:\ProgramData\rivet\config.json

Multiple files in the same directory are merged. /etc/rivet/config.json and /etc/rivet/database.json are loaded together.

Override the search path with --config:

bash
# Load from a specific file
rivet-engine --config /path/to/config.json

# Load from a directory
rivet-engine --config /etc/rivet

# Load multiple paths, merged in order
rivet-engine --config /etc/rivet/base.json --config /etc/rivet/override.json

Reference

Envoy load balancing

pegboard.envoy_load_balancer selects how actors are assigned to envoys. The hash strategy uses a hash ring:

json
{
	"pegboard": {
		"envoy_load_balancer": {
			"hash": {
				"virtual_nodes": 8,
				"samples": 2,
				"max_scan": 16,
				"use_snapshot_read": true
			}
		}
	}
}

Use samples: 1 for a uniform random pick that skips slot reads, or samples: 2 or higher for power-of-K choices over envoy slot counts. Treat virtual_nodes as an operational invariant once envoys have registered: changing it reshuffles the ring.

Telemetry

The engine exports traces and metrics over OpenTelemetry.

Variable Purpose Default
RIVET_OTEL_ENABLED Set to 1 to enable export off
RIVET_OTEL_GRPC_ENDPOINT Collector endpoint http://localhost:4317
RIVET_OTEL_SAMPLER_RATIO Trace sampling ratio 0.001

Next steps

  • Storage backends
  • Ports
  • Production checklist

File: src/content/self-host/control-plane/custom.mdx


title: "Custom Platform"

The control plane is a single container image, rivetdev/engine. Any platform that can run it with a persistent volume and a stable address can host it.

Requirements

Your platform must provide:

  • A container runtime, or a Linux host you can run the binary on.
  • A persistent volume. The control plane is stateful. Storage that resets on redeploy will lose actor state.
  • A stable network identity, so workers keep reaching the same place.
  • Long-lived connections. Platforms that cap request duration or idle time below an hour will sever WebSockets. This rules out serverless platforms entirely.
  • SIGTERM plus a grace period, so the control plane can drain and flush state before it is killed.

Steps

bash
openssl rand -hex 32

Store it wherever your platform keeps secrets. Without it, the API is unauthenticated.

Pin the tag. Never run latest in production.

bash
docker run -d \
  --restart unless-stopped \
  -p 6420:6420 \
  -p 6421:6421 \
  -v rivet-data:/data \
  -e RIVET__FILE_SYSTEM__PATH="/data" \
  -e RIVET__AUTH__ADMIN_TOKEN="<your-token>" \
  rivetdev/engine:2.3.3

For multi-node, drop the file system variable, set RIVET__POSTGRES__URL instead, and add NATS for pub/sub. Do not replicate or autoscale a node that is still on the file system backend: RocksDB is single-node. See Storage.

6420 carries API, dashboard, and WebSocket traffic and should sit behind TLS. 6421 serves health checks and should stay internal. See Ports.

Set idle, read, and send timeouts to at least 3600 seconds on every proxy in the path. The 30 to 60 second defaults sever live WebSockets and cause reconnect storms.

bash
curl -i http://<host>:6421/health

Expect 200. The dashboard is at /ui on port 6420.

A runner config declares a pool that workers connect to. Without one the control plane refuses every worker, so create it before deploying any:

bash
curl -X PUT "http://<host>:6420/runner-configs/default?namespace=default" \
  -H "Authorization: Bearer $RIVET_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"datacenters":{"default":{"normal":{}}}}'

Use normal for long-lived workers. See Runner configs.

Point a worker at the control plane and confirm it appears under Runners:

bash
RIVET_ENDPOINT="https://default:<admin-token>@your-control-plane.example.com"

Troubleshooting

Workers connect, then drop every 30 to 60 seconds. The load balancer idle timeout is still at its default.

State disappears after a deploy. The volume is not persistent, or the data path does not point at it.

The dashboard rejects your token. RIVET__AUTH__ADMIN_TOKEN is not reaching the container. Confirm the variable name, including the double underscores.

Next steps

  • Configuration reference
  • Production checklist
  • Deploy your workers

File: src/content/self-host/control-plane/docker-compose.mdx


title: "Docker Compose"

Requirements

  • Docker with the Compose plugin
  • A host with a persistent volume

Steps

This is a single-node deployment on the file system backend. The named volume is what makes the data survive a restart.

yaml
services:
  rivet-engine:
    image: rivetdev/engine:latest
    ports:
      - "6420:6420"
    volumes:
      - rivet-data:/data
    environment:
      RIVET__FILE_SYSTEM__PATH: "/data"
      RIVET__AUTH__ADMIN_TOKEN: "${RIVET_ADMIN_TOKEN}"
    restart: unless-stopped

volumes:
  rivet-data:
bash
echo "RIVET_ADMIN_TOKEN=$(openssl rand -hex 32)" >> .env

Without a token the API is unauthenticated. Keep .env out of version control.

bash
docker compose up -d
bash
curl -i http://localhost:6420/health

Expect 200. The dashboard is at http://localhost:6420/ui; log in with the admin token.

A runner config declares a pool that workers connect to. Without one the control plane refuses every worker, so create it before adding your app:

bash
curl -X PUT "http://localhost:6420/runner-configs/default?namespace=default" \
  -H "Authorization: Bearer $RIVET_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"datacenters":{"default":{"normal":{}}}}'

Use normal for the long-lived workers this file runs. See Runner configs.

Add your app to the same Compose file. Inside the Compose network it reaches the control plane at rivet-engine:6420.

yaml
my-app:
    build: .
    environment:
      RIVET_ENDPOINT: "http://default:${RIVET_ADMIN_TOKEN}@rivet-engine:6420"
    depends_on:
      - rivet-engine
    restart: unless-stopped
bash
docker compose up -d

Confirm the worker appears under Runners in the dashboard.

Using PostgreSQL

Swap the file system backend for PostgreSQL when you outgrow one node.

yaml
services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_DB: rivet
      POSTGRES_USER: rivet
      POSTGRES_PASSWORD: rivet_password
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U rivet -d rivet"]
      interval: 5s
      timeout: 5s
      retries: 12
    restart: unless-stopped

  rivet-engine:
    image: rivetdev/engine:latest
    ports:
      - "6420:6420"
    environment:
      RIVET__POSTGRES__URL: postgresql://rivet:rivet_password@postgres:5432/rivet
      RIVET__AUTH__ADMIN_TOKEN: "${RIVET_ADMIN_TOKEN}"
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

volumes:
  postgres-data:

This file still runs a single engine container, so it is not yet a multi-node deployment. It is the prerequisite for one: the file system backend is single-node RocksDB and cannot be shared, while PostgreSQL can. Multi-node deployments also need NATS. See Storage.

Using a config file

Mount JSON instead of setting environment variables:

yaml
volumes:
      - ./rivet-config.json:/etc/rivet/config.json:ro
      - rivet-data:/data

See Configuration for the full schema.

Next steps

  • Production checklist
  • Deploy your workers
  • Configuration reference