Self-Hosting
Deploy your own instance of RelayAPI on Cloudflare Workers with step-by-step instructions.
Overview
RelayAPI runs entirely on Cloudflare's developer platform. This guide walks you through setting up every resource from scratch and deploying both the API and dashboard.
Prerequisites
- Node.js v20+ or Bun v1.0+
- Git
- A PostgreSQL 15+ database (any provider — Neon, Supabase, Railway, self-hosted, etc.)
1. Create a Cloudflare Account
- Go to dash.cloudflare.com and sign up for a free account
- After signing in, navigate to Workers & Pages in the sidebar to confirm your account is activated
2. Install Wrangler
Wrangler is Cloudflare's CLI for managing Workers and resources.
npm install -g wranglerAuthenticate with your Cloudflare account:
wrangler loginThis opens a browser window. Authorize Wrangler and return to your terminal. Verify it works:
wrangler whoami3. Clone the Repository
git clone https://github.com/relayapi-dev/relayapi.git
cd relayapi
bun install4. Create Cloudflare Resources
KV Namespace
KV is used to cache API keys (24-hour TTL) for fast authentication.
wrangler kv namespace create KVThis outputs a namespace ID. Save it — you'll need it for the wrangler config.
# Example output:
# 🌀 Creating namespace with title "relayapi-KV"
# ✅ Success! Add the following to your wrangler configuration:
# [[kv_namespaces]]
# binding = "KV"
# id = "abc123..."R2 Buckets
RelayAPI uses three R2 buckets for file storage:
# Media uploads (images, videos attached to posts)
wrangler r2 bucket create relayapi-media
# User avatars
wrangler r2 bucket create relayapi-avatars
# Public assets
wrangler r2 bucket create relayapi-public-assetsQueues
RelayAPI uses queues for async job processing. Create each one:
# Core publishing queue
wrangler queues create relayapi-publish
# Email notifications
wrangler queues create relayapi-email
# Dead letter queue for failed emails
wrangler queues create relayapi-email-dlq
# OAuth token refresh
wrangler queues create relayapi-refresh
# Inbox sync (comments, messages)
wrangler queues create relayapi-inbox
# Background tools (validation, processing)
wrangler queues create relayapi-tools
# Ad campaign sync
wrangler queues create relayapi-ads
# Account data sync
wrangler queues create relayapi-sync
# Media cleanup
wrangler queues create relayapi-media-cleanupHyperdrive
Hyperdrive provides connection pooling for your PostgreSQL database. You need a running PostgreSQL instance first.
wrangler hyperdrive create relayapi-db \
--connection-string="postgresql://user:password@host:5432/relayapi"Save the Hyperdrive ID from the output.
Replace the connection string with your actual PostgreSQL credentials. The database must be accessible from Cloudflare's network — use a cloud-hosted provider like Neon, Supabase, or Railway for the easiest setup.
5. Update Wrangler Configuration
Open apps/api/wrangler.jsonc and replace the resource IDs with your own:
{
"kv_namespaces": [
{
"binding": "KV",
"id": "<your-kv-namespace-id>"
}
],
"r2_buckets": [
{
"binding": "MEDIA_BUCKET",
"bucket_name": "relayapi-media"
}
],
"hyperdrive": [
{
"binding": "HYPERDRIVE",
"id": "<your-hyperdrive-id>"
}
]
}Do the same for apps/app/wrangler.jsonc:
{
"kv_namespaces": [
{
"binding": "KV",
"id": "<your-kv-namespace-id>"
}
],
"r2_buckets": [
{
"binding": "AVATARS_BUCKET",
"bucket_name": "relayapi-avatars"
},
{
"binding": "PUBLIC_ASSETS",
"bucket_name": "relayapi-public-assets"
}
],
"hyperdrive": [
{
"binding": "HYPERDRIVE",
"id": "<your-hyperdrive-id>"
}
]
}6. Set Up Secrets
Secrets are environment variables that are encrypted and not visible in your config files. Set them for the API worker:
# Database connection (used by Drizzle for migrations, not Hyperdrive)
wrangler secret put DATABASE_URL --name relayapi
# Encryption key for sensitive fields (generate a random 32-byte hex string)
wrangler secret put ENCRYPTION_KEY --name relayapi
# Better Auth secret (generate a random string)
wrangler secret put BETTER_AUTH_SECRET --name relayapi
# Google OAuth (for dashboard login)
wrangler secret put GOOGLE_CLIENT_ID --name relayapi
wrangler secret put GOOGLE_CLIENT_SECRET --name relayapiFor each social media platform you want to support, set the corresponding OAuth credentials:
# Example: Twitter/X
wrangler secret put TWITTER_CLIENT_ID --name relayapi
wrangler secret put TWITTER_CLIENT_SECRET --name relayapi
# Example: Facebook/Instagram/Threads
wrangler secret put FACEBOOK_APP_ID --name relayapi
wrangler secret put FACEBOOK_APP_SECRET --name relayapi
# Repeat for each platform you want to enableYou only need to configure OAuth credentials for platforms you plan to support. Platforms without credentials will simply be unavailable for account connections.
7. Set Up the Database
Generate and apply the Drizzle ORM migrations:
# Generate migration files from the schema
bun run db:generate
# Apply migrations to your PostgreSQL database
bun run db:migrateThis creates all required tables in both the auth schema (managed by Better Auth) and the public schema (business data).
8. Local Development
For local development, you need to tell Wrangler how to reach your database without Hyperdrive.
Set the local connection string as an environment variable:
export CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="postgresql://user:password@localhost:5432/relayapi"Then start the dev servers:
# API server (localhost:8789)
bun run dev:api
# Dashboard (localhost:4321)
bun run dev:app
# Docs site
bun run dev:docsIf your database is remote, set up an SSH tunnel to forward a local port to the remote PostgreSQL port, then point the connection string at localhost.
9. Deploy to Cloudflare
Deploy each app using Wrangler:
# Deploy the API
cd apps/api
wrangler deploy
# Deploy the dashboard
cd ../app
wrangler deployAfter deployment, Wrangler outputs the URL for each worker (e.g., https://relayapi.<your-subdomain>.workers.dev).
Custom Domains
To use a custom domain, go to your worker in the Cloudflare dashboard:
- Navigate to Workers & Pages > your worker > Settings > Domains & Routes
- Click Add and enter your domain (e.g.,
api.yourdomain.com) - Cloudflare automatically provisions SSL
10. CI/CD (Optional)
The repository includes GitHub Actions workflows that deploy each app independently on push to main when relevant paths change. To enable:
- In your GitHub repository, go to Settings > Secrets and variables > Actions
- Add the following secret:
CLOUDFLARE_API_TOKEN— create an API token in the Cloudflare dashboard with Workers edit permissions
Pushes to main will automatically deploy changed apps.
Architecture Overview
Once deployed, your self-hosted RelayAPI instance consists of:
| Component | Cloudflare Service | Purpose |
|---|---|---|
| API | Worker (relayapi) | REST API handling all requests |
| Dashboard | Worker (relayapi-app) | Astro SSR web application |
| Database | Hyperdrive + external PostgreSQL | Data storage with connection pooling |
| Media | R2 (relayapi-media) | Uploaded images and videos |
| Avatars | R2 (relayapi-avatars) | User profile pictures |
| Assets | R2 (relayapi-public-assets) | Public static files |
| Cache | KV | API key cache for fast auth |
| Jobs | Queues (9 queues) | Async publishing, email, sync |
| AI | Workers AI | Content generation features |
| Realtime | Durable Object (RealtimeDO) | WebSocket connections |
Troubleshooting
"Hyperdrive connection failed"
Ensure your PostgreSQL database is accessible from Cloudflare's network. Most home/office databases are behind firewalls. Use a cloud-hosted database provider instead.
"KV namespace not found"
Double-check that the KV namespace ID in your wrangler.jsonc matches the one from wrangler kv namespace create. You can list existing namespaces with:
wrangler kv namespace list"Queue not found"
Verify queues were created in the same Cloudflare account:
wrangler queues listMigrations fail
Make sure DATABASE_URL is set and points to your PostgreSQL instance. For local development, the connection string should use the local port (e.g., localhost:5432 or your SSH tunnel port).
Found something wrong? Help us improve this page.