Run a server
Start the Resonate server with persistent storage.
This page will help you install and run the Resonate Server.
First install the Server on your machine.
Install with Homebrew#
You can install the latest Resonate Server version with Homebrew via:
brew install resonatehq/tap/resonateAll available releases and associated release artifacts are available on Github.
Once installed, you can start the server with an in-memory store by running:
resonate devOr, you can run the server with the default SQLite store by running:
resonate serveYou will see log output like the following:
INFO resonate: Resonate Server starting port=8001
INFO resonate: Using SQLite backend path="resonate.db"
INFO resonate: Auth disabled — all requests acceptedThe HTTP API is on port 8001 and the Prometheus metrics endpoint is on port 9090.
Both default ports can be changed.
Run with Docker#
The Resonate Server is published as an official image on Docker Hub at resonatehqio/resonate.
Tags track the server release versions (e.g. v0.9.7) plus a rolling latest.
docker run --rm -p 8001:8001 -p 9090:9090 resonatehqio/resonate:v0.9.7resonate serve is the default command. Override only when you need to change behavior:
docker run --rm -p 8001:8001 -p 9090:9090 \
-e RESONATE_STORAGE__TYPE=postgres \
-e RESONATE_STORAGE__POSTGRES__URL="postgres://<USER>:<PASSWORD>@host:5432/resonate" \
resonatehqio/resonate:v0.9.7The image exposes both 8001 (HTTP API) and 9090 (Prometheus metrics).
Configuration#
The server is configured by layering, in this order — each layer overrides the previous:
- Built-in defaults
resonate.toml(optional, looked up in the working directory)- Environment variables prefixed with
RESONATE_using__to nest - CLI flags passed to
resonate serve
The recommended approach for any non-trivial deployment is a resonate.toml file checked into your infra repo, with secrets injected via environment variables.
resonate.toml#
level = "info"
[server]
host = "localhost"
port = 8001
[storage]
type = "sqlite"
[storage.sqlite]
path = "resonate.db"
[tasks]
lease_timeout = 15000
retry_timeout = 30000For Postgres, swap the [storage] section:
[storage]
type = "postgres"
[storage.postgres]
url = "postgres://<USER>:<PASSWORD>@host:5432/resonate"
pool_size = 20For MySQL, the equivalent block is:
[storage]
type = "mysql"
[storage.mysql]
url = "mysql://<USER>:<PASSWORD>@host:3306/resonate"
pool_size = 20To enable JWT auth, add an [auth] section:
[auth]
publickey = "/etc/resonate/auth/public.pem"Environment variables#
Every config field is also reachable as an environment variable. The convention is RESONATE_<SECTION>__<FIELD>, with __ separating nested keys:
RESONATE_LEVEL=info
RESONATE_SERVER__PORT=8001
RESONATE_STORAGE__TYPE=postgres
RESONATE_STORAGE__POSTGRES__URL=postgres://<USER>:<PASSWORD>@host:5432/resonate
RESONATE_AUTH__PUBLICKEY=/etc/resonate/auth/public.pemThis is the form used in the official Docker Compose file and is the right choice for container platforms that prefer env-var injection.
CLI flags#
For the full client + server CLI surface (subcommands, scenarios, worked examples), see CLI reference. The table below covers the operator flags most commonly needed when running resonate serve.
Most settings can also be passed as CLI flags — useful for ad-hoc overrides:
resonate serve \
--storage-type postgres \
--storage-postgres-url "postgres://<USER>:<PASSWORD>@localhost:5432/resonate" \
--storage-postgres-pool-size 20Run resonate serve --help for the full list. Common flags:
| Flag | Default | Description |
|---|---|---|
--server-host | localhost | HTTP server host |
--server-port | 8001 | HTTP server port |
--server-bind | 0.0.0.0 | Bind address |
--server-url | http://{host}:{port} | Externally-reachable URL (required for distributed deployments where workers call back to the server) |
--level | info | Log level: debug, info, warn, error |
--storage-type | sqlite | Storage backend: sqlite, postgres, or mysql |
--storage-postgres-url | — | Postgres connection string |
--storage-postgres-pool-size | 10 | Postgres connection pool size |
--storage-mysql-url | — | MySQL connection string |
--storage-mysql-pool-size | 10 | MySQL connection pool size |
--auth-publickey | — | Path to JWT public key (PEM format) for authentication |
--server-cors-allow-origin | — | Allowed CORS origin (repeatable; use * for permissive). See Security › CORS |
--tasks-lease-timeout | 15000 (ms) | Task lease timeout |
--tasks-retry-timeout | 30000 (ms) | Suspend/wake retry interval. Lower values (e.g. 500) significantly reduce latency for chained or recursive workflows |
--observability-metrics-port | 9090 | Prometheus metrics port (0 to disable) |
The default --tasks-retry-timeout of 30 seconds adds significant latency to chained workflows (sagas, recursive fan-out).
For most deployments, set this to 500 (milliseconds) to keep suspend/wake cycles fast.
Transport flags#
v0.9.5 added explicit enable/disable flags for every transport so operators can shape which delivery paths are live per deployment. See Message transports for the per-transport behaviour.
| Flag | Default | Description |
|---|---|---|
--transports-http-push-enabled | true | Enable the http:// / https:// push transport |
--transports-http-poll-enabled | true | Enable the SSE poll transport used by long-polling SDK clients |
--transports-gcps-enabled | false | Enable the gcps:// GCP Pub/Sub transport |
--transports-gcps-project | — | Default GCP project ID for the Pub/Sub transport |
--transports-bash-exec-enabled | false | Enable the bash:/// local-script transport (see Bash exec) |
Outbound HTTP push authentication flags#
v0.9.5 added outbound authentication for HTTP push deliveries so workers behind authenticated endpoints (for example GCP Cloud Run with --no-allow-unauthenticated) can verify the server's identity. See Outbound HTTP push authentication for the full configuration guide.
| Flag | Default | Description |
|---|---|---|
--transports-http-push-auth-mode | none | Outbound auth mode: none, bearer, or gcp |
--transports-http-push-auth-token | — | Static bearer token (used only when mode = bearer) |
--transports-http-push-auth-aud | delivery target URL | Fixed OIDC audience (used only when mode = gcp; defaults to the per-delivery target URL) |
--transports-http-push-auth-header | Authorization | Name of the request header that carries the token |
Run with PostgreSQL#
For production deployments, use PostgreSQL as the storage backend.
With a resonate.toml (recommended):
[storage]
type = "postgres"
[storage.postgres]
url = "postgres://<USER>:<PASSWORD>@localhost:5432/resonate"
pool_size = 20Or with CLI flags:
resonate serve \
--storage-type postgres \
--storage-postgres-url "postgres://<USER>:<PASSWORD>@localhost:5432/resonate" \
--storage-postgres-pool-size 20Migrations are applied automatically on first startup.
See the Availability guide for high-availability PostgreSQL configuration.
Run with MySQL#
MySQL is a third storage option alongside SQLite and PostgreSQL. Use it when your operational tooling already centers on MySQL.
With a resonate.toml (recommended):
[storage]
type = "mysql"
[storage.mysql]
url = "mysql://<USER>:<PASSWORD>@localhost:3306/resonate"
pool_size = 20Or with CLI flags:
resonate serve \
--storage-type mysql \
--storage-mysql-url "mysql://<USER>:<PASSWORD>@localhost:3306/resonate" \
--storage-mysql-pool-size 20Or with environment variables:
RESONATE_STORAGE__TYPE=mysql
RESONATE_STORAGE__MYSQL__URL=mysql://<USER>:<PASSWORD>@localhost:3306/resonate
RESONATE_STORAGE__MYSQL__POOL_SIZE=20Migrations are applied automatically on first startup. MySQL 8.0 or later is required (the schema relies on generated columns and JSON path expressions).
A minimal Docker Compose stack that boots the server against a MySQL service:
services:
resonate-server:
image: resonatehqio/resonate:v0.9.7
ports:
- "8001:8001"
- "9090:9090"
environment:
RESONATE_SERVER__BIND: "0.0.0.0"
RESONATE_STORAGE__TYPE: mysql
RESONATE_STORAGE__MYSQL__URL: mysql://resonate:resonate@mysql:3306/resonate
RESONATE_STORAGE__MYSQL__POOL_SIZE: 20
depends_on:
mysql:
condition: service_healthy
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: resonate
MYSQL_USER: resonate
MYSQL_PASSWORD: resonate
MYSQL_DATABASE: resonate
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "resonate", "-presonate"]
interval: 5s
timeout: 5s
retries: 10The MySQL schema stores promise IDs, task IDs, listener addresses, and a few derived tag values (resonate:target, resonate:origin, resonate:branch) as VARCHAR(255).
Inputs that exceed 255 characters fail with a 400 InvalidInput response. SQLite and Postgres do not enforce this bound, so applications written against those backends should verify ID lengths before switching to MySQL.
Deploy to GCP Cloud Run#
Cloud Run can pull the official image directly:
gcloud run deploy resonate-server \
--image=resonatehqio/resonate:v0.9.7 \
--region=<your-region> \
--port=8001 \
--allow-unauthenticated \
--max-instances=1Set the --server-url flag in the container args (or RESONATE_SERVER__URL env var) to your service URL after the first deploy so workers can call back correctly. For Postgres, set RESONATE_STORAGE__TYPE=postgres and RESONATE_STORAGE__POSTGRES__URL.
For a worked end-to-end example, see Cloud Run workers.
Run on Kubernetes#
The recommended Kubernetes deployment is a single-replica Deployment that pulls the official image:
---
apiVersion: v1
kind: Service
metadata:
name: resonate
spec:
selector:
app: resonate
ports:
- port: 8001
name: api
- port: 9090
name: metrics
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: resonate
spec:
replicas: 1
selector:
matchLabels:
app: resonate
template:
metadata:
labels:
app: resonate
spec:
containers:
- name: resonate
image: resonatehqio/resonate:v0.9.7
env:
- name: RESONATE_SERVER__BIND
value: "0.0.0.0"
- name: RESONATE_STORAGE__TYPE
value: "postgres"
- name: RESONATE_STORAGE__POSTGRES__URL
valueFrom:
secretKeyRef:
name: resonate-secrets
key: database-url
- name: RESONATE_TASKS__RETRY_TIMEOUT
value: "500"
ports:
- name: api
containerPort: 8001
- name: metrics
containerPort: 9090
livenessProbe:
httpGet:
path: /health
port: 8001
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8001
initialDelaySeconds: 5
periodSeconds: 5Create a file named resonate-k8s.yaml and apply the manifest:
kubectl apply -f resonate-k8s.yamlTo remove Resonate from your cluster:
kubectl delete -f resonate-k8s.yamlHealth and readiness#
The server exposes two HTTP endpoints for liveness and readiness checks:
| Endpoint | Returns | Use for |
|---|---|---|
GET /health | 200 OK always (as long as the process is up) | Liveness probes |
GET /ready | 200 OK if storage is reachable, 503 otherwise | Readiness probes |
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8001/health
# 200Build from source#
If you don't have Homebrew, you can build the server from source. The server is written in Rust:
git clone https://github.com/resonatehq/resonate
cd resonate
cargo build --release
./target/release/resonate serveDefaults#
The most commonly referenced server defaults:
- HTTP API port —
8001(--server-port) - Bind address —
0.0.0.0(--server-bind); host advertised aslocalhost - Storage backend —
sqliteatresonate.db --tasks-lease-timeout—15_000 ms--tasks-retry-timeout—30_000 ms(lower to500 msfor chained / recursive workflows)- Prometheus metrics port —
9090(0disables) - Log level —
info - Postgres / MySQL pool size —
10 --transports-http-push-enabled/--transports-http-poll-enabled—true--transports-gcps-enabled/--transports-bash-exec-enabled—false
These mirror the operator surface above; the canonical cross-component reference (including SDK init, ctx.run, retry policy, and env-var defaults) is the Defaults reference.
Ports#
The server binds two ports by default:
| Port | Purpose |
|---|---|
:8001 | HTTP API (all SDK traffic, SSE polling, and task dispatch) |
:9090 | Prometheus metrics |
On platforms like Railway, Render, or Fly.io that auto-detect a single primary port, explicitly route to 8001.
Without this, the platform may pick the metrics port (9090) and route all traffic there, resulting in 502 errors.