Skip to main content
Running Workloads

Versions & Upgrades

Roll out new code to your nodes and upgrade Rivet Actor state without downtime.

Rolling out a new version

Deploy new code by rolling your nodes like any other service. When a node stops, its Rivet Actors sleep and save their state, then wake on a running node on their next request. During the rollout, old and new nodes both run Rivet Actors, so keep each deploy compatible with the state the previous one wrote. See Upgrading Actor State.

Setting the version

Set RIVET_NODE_VERSION to a number that increases on every deploy. Within a node pool, new and waking Rivet Actors are only placed on nodes running the newest version that has a connected node, so as soon as the first node of a deploy connects, the previous deploy stops receiving new actors. Actors already running on older nodes keep running until those nodes stop. A version whose nodes have all disconnected is ignored, so a failed deploy does not block placement.

Without RIVET_NODE_VERSION, every node reports version 1 and placement spreads across all connected nodes in the pool. The version of each node is shown in the node listing API as version.

Drain on version upgrade

Placement alone leaves Rivet Actors that never sleep on the old nodes until those nodes stop. Set drain_on_version_upgrade on the pool to move them as well: once a node of a newer version is connected, actors on older nodes are stopped the same way a node shutdown stops them, and they wake on the newest version on their next request. Eviction starts after actor_eviction_delay seconds and is paced by actor_eviction_period and actor_eviction_rate, so a large pool is not rescheduled all at once. See Pool settings.

The older nodes stay connected and keep receiving heartbeats, but no new actors are placed on them. Rivet never exits a node process; stopping or replacing the old nodes is up to whatever runs them. If every node of the newer version disconnects, the drain stops and the older nodes serve again. A pool’s settings are read when a node connects, so changing them applies to nodes that connect afterwards.

Upgrading Actor State

When you deploy a new version, existing actors may need to handle schema changes in their persisted data.

Drizzle (recommended)

Use Drizzle for typed schemas with generated migrations. Drizzle generates versioned .sql migration files from your TypeScript schema and applies them in order automatically. This is the recommended approach when your schema evolves frequently.

Raw SQL

For actors using raw SQLite, use migrations to apply numbered SQL steps in order. Give each schema its own migration table. RivetKit runs the returned onMigrate hook inside a SQLite savepoint, so the migration ladder is atomic:

import { actor, setup } from "rivetkit";
import { db } from "rivetkit/db";
import { migrations } from "rivetkit/unstable/migrations";

const migrateTodos = migrations({
	tableName: "todos_schema_version",
	migrations: [
		{
			version: 1,
			sql: `
				CREATE TABLE todos (
					id INTEGER PRIMARY KEY AUTOINCREMENT,
					title TEXT NOT NULL
				)
			`,
		},
		{
			version: 2,
			sql: `
				ALTER TABLE todos
				ADD COLUMN completed INTEGER NOT NULL DEFAULT 0
			`,
		},
	],
});

const todoList = actor({
	db: db({ onMigrate: migrateTodos }),
	actions: {
		addTodo: async (c, title: string) => {
			await c.db.execute("INSERT INTO todos (title) VALUES (?)", title);
		},
	},
});

const registry = setup({ use: { todoList } });
registry.start();

Stopping a node

When a node receives SIGTERM, it puts each of its Rivet Actors to sleep before exiting. Each Rivet Actor gets up to 15 seconds to save its state. If a node disappears without stopping, the control plane reschedules its Rivet Actors after 15 seconds.