Resonate server deployment
Agent skills for building with Resonate — durable execution for long-running, crash-safe workflows.
npx -y skills add resonatehq/resonate-skills --skill resonate-server-deploymentAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Deploy and configure the Resonate server on Linux systems with systemd. Covers installation, public URL configuration, JWT authentication, and troubleshooting.
The file declares its own license as Apache-2.0. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
12.9 KB, as published. Nobody here has run it
Resonate Server Deployment
Overview
Deploy the Resonate server on Linux systems using systemd for process management. This skill covers installation, configuration of public URLs, JWT authentication setup, and common troubleshooting scenarios.
Deploying on GCP instead? See resonate-server-deployment-cloud-run for the Cloud Run + Cloud SQL variant.
Prerequisites
- Linux system with systemd
- Root/sudo access
- curl installed
- (Optional) openssl for generating JWT keys
- (Optional) jwt-cli for generating tokens
Quick Start
Basic Deployment (No Auth)
# Download and run
curl -L -o deploy-resonate.sh https://example.com/deploy-resonate.sh
chmod +x deploy-resonate.sh
sudo ./deploy-resonate.sh
Deployment with Public URL and JWT Auth
# Generate RSA key pair first
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem
# Deploy with configuration (setting the public key enables JWT auth)
sudo RESONATE_SERVER__URL=https://resonate.example.com \
RESONATE_AUTH__PUBLICKEY=/path/to/public_key.pem \
./deploy-resonate.sh
Configuration Options
| Environment Variable | Default | Description |
|---|---|---|
RESONATE_VERSION | v0.9.8 | Server version the install script downloads |
RESONATE_SERVER__PORT | 8001 | HTTP API port |
RESONATE_SERVER__URL | (none) | Public URL for the server (e.g., https://resonate.example.com) |
RESONATE_AUTH__PUBLICKEY | (none) | Path to JWT public key file; setting it enables JWT auth (there is no separate enable flag) |
Server Flags Reference
The Resonate server binary accepts these flags:
resonate serve [flags]
Flags:
--server-url string Public URL for the server (included in response headers)
--server-port int HTTP API port (default 8001)
--auth-publickey string Path to JWT public key for authentication
--storage-type string Storage backend: sqlite or postgres (default sqlite)
--storage-postgres-url string PostgreSQL connection URL
All flags are also settable via RESONATE_-prefixed environment variables with __ for nesting, e.g. RESONATE_SERVER__URL, RESONATE_AUTH__PUBLICKEY, RESONATE_STORAGE__POSTGRES__URL.
Architecture
Internet
│
▼
┌────────────────┐
│ Nginx/Caddy │ (SSL termination)
│ Port 443 │
└────────────────┘
│
▼
┌────────────────┐
│ Resonate Server│ (systemd service)
│ Port 8001 │
└────────────────┘
│
┌────────┴────────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ Worker 1 │ │ Worker 2 │
└──────────┘ └──────────┘
Systemd Service Configuration
Basic Service File
# /etc/systemd/system/resonate.service
[Unit]
Description=Resonate Server
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/var/lib/resonate
ExecStart=/usr/local/bin/resonate serve
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=resonate
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Service File with Public URL and Auth
# /etc/systemd/system/resonate.service
[Unit]
Description=Resonate Server
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/var/lib/resonate
ExecStart=/usr/local/bin/resonate serve \
--server-url https://resonate.example.com \
--auth-publickey /etc/resonate/public_key.pem
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=resonate
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
JWT Authentication Setup
1. Generate RSA Key Pair
# Generate private key (keep secret!)
openssl genrsa -out private_key.pem 2048
# Extract public key (deploy with server)
openssl rsa -in private_key.pem -pubout -out public_key.pem
2. Install JWT CLI (for generating tokens)
# macOS
brew install mike-engel/jwt-cli/jwt-cli
# Linux (download binary)
curl -L -o jwt https://github.com/mike-engel/jwt-cli/releases/latest/download/jwt-linux
chmod +x jwt
sudo mv jwt /usr/local/bin/
3. Generate Client Tokens
IMPORTANT: An empty payload {} will DENY all access. You must include a prefix claim.
# Unrestricted access (empty prefix = all promises)
jwt encode --secret @private_key.pem -A RS256 '{"prefix":""}'
# Admin access (alternative way to get full access)
jwt encode --secret @private_key.pem -A RS256 '{"role":"admin"}'
# Restricted to prefix (only access promises starting with "my-app")
jwt encode --secret @private_key.pem -A RS256 '{"prefix":"my-app"}'
# Unrestricted with expiration
jwt encode --secret @private_key.pem -A RS256 --exp='+30 days' '{"prefix":""}'
Prefix claim behavior:
| Payload | Access |
|---|---|
{} | DENIED (no prefix claim = unauthorized) |
{"prefix": ""} | ALL promises (empty string = unrestricted) |
{"prefix": "my-app"} | Only promises starting with my-app |
{"role": "admin"} | ALL promises (admin role) |
4. Configure Server
# Copy public key to server
scp public_key.pem root@server:/etc/resonate/
# Update service (public key present → JWT auth on)
sudo RESONATE_AUTH__PUBLICKEY=/etc/resonate/public_key.pem \
./deploy-resonate.sh --update-service
5. Configure Clients
Pass the server URL and a JWT token to the client. TypeScript shown — every SDK takes a URL + token the same way; see the per-SDK skill (resonate-basic-ephemeral-world-usage-{typescript,python,rust,go}) for client init in your language. The RESONATE_TOKEN env var and the Authorization: Bearer <jwt> header are identical across all SDKs.
// SDK client
const resonate = new Resonate({
url: "https://resonate.example.com",
token: process.env.RESONATE_TOKEN // JWT token
});
// Direct HTTP calls
const headers = {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
};
Why API URL Matters
The --server-url flag tells the Resonate server its public address. This is critical when:
- Workers poll for tasks: The server returns URLs that workers use for callbacks
- Clients connect from different networks: The server advertises its address
- Behind a reverse proxy: Server needs to know the external URL, not localhost
Without --server-url:
- Server returns
http://localhost:8001in responses - External workers can't reach callback URLs
- Polling may fail with connection errors
With --server-url:
- Server returns
https://resonate.example.comin responses - Workers and clients use the correct public URL
Directory Structure
/usr/local/bin/
└── resonate # Server binary
/etc/resonate/
├── public_key.pem # JWT public key (if auth enabled)
└── resonate.yml # Optional config file
/var/lib/resonate/
└── resonate.db # SQLite database (default)
/etc/systemd/system/
└── resonate.service # Systemd service file
Common Operations
View Logs
# Follow logs
journalctl -u resonate -f
# Last 100 lines
journalctl -u resonate -n 100
# Logs since boot
journalctl -u resonate -b
# Logs from specific time
journalctl -u resonate --since "2024-01-28 10:00:00"
Service Management
# Status
systemctl status resonate
# Start/Stop/Restart
systemctl start resonate
systemctl stop resonate
systemctl restart resonate
# Enable/Disable on boot
systemctl enable resonate
systemctl disable resonate
# Reload service file after changes
systemctl daemon-reload
Check Configuration
# View current service configuration
systemctl cat resonate
# Check what command is running
ps aux | grep resonate
# Test server is responding
curl http://localhost:8001/promises
Troubleshooting
401 Unauthorized Errors
Symptoms:
- Clients get 401 errors
- "missing authorization header" in logs
- Workers can't poll for tasks
Causes & Fixes:
-
Auth enabled on server but client has no token — add a token to the client (TypeScript shown; every SDK takes a token the same way — see the per-SDK skills)
// Add token to client const resonate = new Resonate({ url: "https://resonate.example.com", token: process.env.RESONATE_TOKEN }); -
Token is expired
# Generate new token (a payload with no prefix/role claim is DENIED — include one) jwt encode --secret @private_key.pem -A RS256 --exp='+30 days' '{"prefix":""}' -
Wrong public key on server
# Verify key matches openssl rsa -in private_key.pem -pubout | diff - public_key.pem -
Token signed with wrong private key
# Decode and verify token jwt decode $TOKEN
Server Not Responding
Symptoms:
curl http://localhost:8001hangs or refuses connection- Service shows as active but port not open
Causes & Fixes:
-
Service crashed on startup
journalctl -u resonate -n 50 # Look for error messages -
Port already in use
netstat -tlnp | grep 8001 # Kill conflicting process or change port -
Firewall blocking port
# Check UFW (Ubuntu) ufw status # Allow internal access only (recommended) # Don't expose 8001 directly - use reverse proxy
Workers Can't Connect
Symptoms:
- Workers start but never receive tasks
- "connection refused" errors
Causes & Fixes:
-
Wrong RESONATE_URL in worker
# Should be public URL if server is remote RESONATE_URL=https://resonate.example.com # NOT http://localhost:8001 (unless worker is on same machine) -
Missing --server-url on server
# Server doesn't know its public URL # Update service with --server-url flag -
SSL/TLS issues
# Test with curl curl -v https://resonate.example.com/promises # Check certificate is valid
Database Issues
Symptoms:
- "database is locked" errors
- Data not persisting across restarts
Causes & Fixes:
-
Multiple processes accessing SQLite
# Only one server should access the DB ps aux | grep resonate # Kill duplicates -
Wrong working directory
# Check where DB is being created find / -name "resonate.db" 2>/dev/null # Ensure WorkingDirectory is set in service file -
Disk full
df -h # Clean up or expand disk
Production Checklist
- Server binary installed and versioned
- Systemd service file created and enabled
-
--server-urlset to public URL - JWT authentication enabled (if needed)
- Public key deployed to
/etc/resonate/ - Reverse proxy configured (Nginx/Caddy)
- SSL certificate installed
- Firewall configured (only 443 exposed)
- Logs rotating (journald handles this)
- Monitoring configured (port 9090 metrics)
- Backup strategy for database
Reverse Proxy Configuration
Nginx
server {
listen 443 ssl;
server_name resonate.example.com;
ssl_certificate /etc/letsencrypt/live/resonate.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/resonate.example.com/privkey.pem;
location / {
proxy_pass http://localhost:8001;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# For long-polling
proxy_read_timeout 120s;
proxy_send_timeout 120s;
}
}
Caddy
resonate.example.com {
reverse_proxy localhost:8001
}
Summary
Key deployment steps:
- Install binary from GitHub releases
- Create systemd service with appropriate flags
- Configure
--server-urlfor public accessibility - Enable JWT auth with
--auth-publickeyif needed - Set up reverse proxy with SSL
- Configure clients with correct URL and token
Critical flags:
--server-url: Server's public URL (required for external access)--auth-publickey: JWT public key path (required for auth)