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.5) plus a rolling latest.
docker run --rm -p 8001:8001 -p 9090:9090 resonatehqio/resonate:v0.9.5resonate 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:pass@host:5432/resonate" \
resonatehqio/resonate:v0.9.5The 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:pass@host:5432/resonate"
pool_size = 20For MySQL, the equivalent block is:
[storage]
type = "mysql"
[storage.mysql]
url = "mysql://user:pass@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:pass@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#
Most settings can also be passed as CLI flags — useful for ad-hoc overrides:
resonate serve \
--storage-type postgres \
--storage-postgres-url "postgres://user:pass@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:pass@localhost:5432/resonate"
pool_size = 20Or with CLI flags:
resonate serve \
--storage-type postgres \
--storage-postgres-url "postgres://user:pass@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:pass@localhost:3306/resonate"
pool_size = 20Or with CLI flags:
resonate serve \
--storage-type mysql \
--storage-mysql-url "mysql://user:pass@localhost:3306/resonate" \
--storage-mysql-pool-size 20Or with environment variables:
RESONATE_STORAGE__TYPE=mysql
RESONATE_STORAGE__MYSQL__URL=mysql://user:pass@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.5
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.5 \
--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.5
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 servePorts#
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.