Self-Hosting

ExploreSelf-hostDocumentationBlogSign In
Documentation
Overview
Getting Started
Getting StartedPlatform IntegrationsAgentPlaybooks in ClaudePortable Bot TeamsHermes Memory ProviderHermes for OrganisationsAgentPlaybooks Tools in HermesCLI & Editor PluginsPython SecretsCross-platform Agent BackupsRelease DistributionTeam CollaborationFederated MCP & OpenAPI ToolsMCP Registry PublishingRoadmap
Core Concepts
PlaybooksSkillsMemoryMCP Integration
Reference
ArchitectureAPI ReferenceManagement APIDeveloper GuideSelf-HostingEnvironment Setup
View on GitHub
DocsSelf-Hosting

Self-Hosting Guide

Deploy AgentPlaybooks on your own infrastructure.

## Current limitations — read this first
>
Last verified: 2026-10-01.
>
1. Start a new project from supabase/schema.sql, not from
supabase/migrations/. The migrations folder is incremental only — it has
no CREATE TABLE for the core tables and never did, so applying it to an
empty database fails on the first statement. supabase/schema.sql is a
snapshot of the whole current schema and is all a new project needs; the
migrations are the forward history from that snapshot onward.
>
Databases initialized from a schema.sql older than October 2026 lack the
memory history objects (memory_entries, memory_history, memory_at,
is_archived), and memory search fails on them. Apply
20260911042912_memory_time_and_history.sql, then
20260913173449_harden_public_functions.sql, if
select to_regclass('public.memory_entries') returns null.
>
2. "Self-hosted" means running your own Supabase, not just your own
Postgres. Every API route queries the Supabase Data API and authentication
is Supabase Auth, so a bare PostgreSQL server is not enough. For an
on-premise deployment, run the
self-hosted Supabase stack
and point NEXT_PUBLIC_SUPABASE_URL at it. The application needs no changes.
>
3. A bare PostgreSQL container will not do. docker-compose.yml used to
ship one; it was removed because the application never queried it, and its
init step fed Supabase migrations calling auth.uid() into a vanilla
postgres:16-alpine, which errors out. Run the full Supabase stack instead.

Prerequisites

  • Node.js 20+
  • A Supabase project (free tier works)
  • Cloudflare account (free tier works)
  • Git

Option 1: Cloudflare Pages (Recommended)

The easiest way to self-host AgentPlaybooks.

Step 1: Fork the Repository

Fork github.com/matebenyovszky/agentplaybooks to your account.

Step 2: Create Supabase Project

  • Go to supabase.com and create a new project
  • Note your project URL and anon key from Settings > API
  • Initialize a new database from supabase/schema.sql. For an existing
deployment, apply only migrations newer than its current schema. The portable-backup API requires 20260922213700_portable_playbook_snapshots.sql before the new CLI is published or used for push:

# Fresh Supabase project: run supabase/schema.sql in the SQL editor.
# Existing project: apply the pending forward migration(s), then deploy the API.

Step 3: Connect to Cloudflare Pages

  • Go to Cloudflare Dashboard
  • Navigate to Workers & Pages
  • Click Create application > Pages > Connect to Git
  • Select your forked repository
  • Configure build settings:

SettingValue

Framework presetNext.js
Build commandnpm run build:worker
Build output directory.open-next

  • Add environment variables:

VariableValue

NEXT_PUBLIC_SUPABASE_URLYour Supabase URL
NEXT_PUBLIC_SUPABASE_ANON_KEYYour Supabase anon key
SUPABASE_SERVICE_ROLE_KEYYour Supabase service role key

  • Deploy!

Step 4: Configure Auth Providers

In Supabase Dashboard > Authentication > Providers:

  • Enable Email/Password
  • (Optional) Configure Google, GitHub, LinkedIn (OIDC) or Microsoft (Azure) OAuth

Set redirect URLs to https://your-domain.pages.dev/*

The login page asks Supabase Auth which providers are enabled (/auth/v1/settings) and shows only those. An instance with only email login shows only the email form; one with only Microsoft shows only the Microsoft button. Turning off email login hides the email form, and turning off sign-ups hides the sign-up link.

Microsoft Entra ID (self-hosted Supabase)

Register a web application in Microsoft Entra ID with the redirect URI https:///auth/v1/callback, create a client secret, then set these on the Supabase Auth (GoTrue) container and restart it:

GOTRUE_EXTERNAL_AZURE_ENABLED=true
GOTRUE_EXTERNAL_AZURE_CLIENT_ID=<application (client) id>
GOTRUE_EXTERNAL_AZURE_SECRET=<client secret value>
GOTRUE_EXTERNAL_AZURE_URL=https://login.microsoftonline.com/<tenant id>
GOTRUE_EXTERNAL_AZURE_REDIRECT_URI=https://<your-supabase-auth-host>/auth/v1/callback

Setting the tenant in GOTRUE_EXTERNAL_AZURE_URL limits sign-in to your organisation. Entra ID requires an HTTPS redirect URI for anything but localhost, so the Supabase Auth endpoint needs a certificate. Add your app's URL to GOTRUE_URI_ALLOW_LIST so the login can return to it.

Option 2: Docker

For running on your own servers.

Microsoft SQL Server

SQL Server is not supported. An experimental Drizzle/SQL Server layer used to live here. It was removed because only two functions ever queried through it, so DB_DIALECT=mssql produced a split-brain deployment — the playbook list read from SQL Server while everything else, including all authorization checks, still read from Supabase — and it started without error.

The schemas, migration and Compose stack remain in git history; the last commit before removal is 08cb203, so git checkout 08cb203 -- src/lib/db drizzle brings them back.

Supporting SQL Server properly means migrating every remaining Supabase Data API call and replacing Supabase Auth, since GoTrue has no SQL Server equivalent. If you need an on-premise database today, run the self-hosted Supabase stack instead — the application works against it unchanged.

docker-compose.yml and Dockerfile

Both live in the repository root; this guide does not duplicate them, so they cannot drift out of date here.

docker-compose.yml runs the application only. It deliberately does not ship a PostgreSQL service: the app talks to the Supabase Data API and Auth, not to a database directly, so a bare Postgres container would not serve it. Point NEXT_PUBLIC_SUPABASE_URL at either a hosted project or your own self-hosted Supabase stack.

Note that NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY are build arguments as well as runtime variables — Next.js bakes them into the client bundle. They must be the values the browser can reach, and changing them requires a rebuild rather than a restart.

Set NEXT_PUBLIC_APP_URL to the instance's own origin (for example https://agents.example.org) before building, for the same reason. Without it the image advertises https://agentplaybooks.ai as the MCP OAuth resource in /.well-known/oauth-protected-resource, and in generated links.

Intranet instance without TLS

The Hermes memory provider and apb memory setup refuse plain HTTP except on localhost, because the playbook key travels with every request. For an internal test instance that has no certificate yet, opt in explicitly per Hermes profile:

apb memory setup <guid> --target=hermes --url=http://agents.intranet:8007 --allow-insecure-http --apply

This records "allow_insecure_http": true in the profile's agentplaybooks/config.json. Put the instance behind HTTPS before production use.

Run

docker compose up -d --build

Option 3: Vercel

While optimized for Cloudflare, the app works on Vercel too.

  • Import your forked repo to Vercel
  • Set environment variables
  • Deploy

Note: Some Cloudflare-specific features won't work on Vercel.

Database Migrations

Initial Setup

Run supabase/schema.sql once on the empty database, in the Supabase SQL editor or with psql:

psql "$DATABASE_URL" -v ON_ERROR_STOP=1 --single-transaction -f supabase/schema.sql

Upgrades

Apply, in filename order, each file in supabase/migrations/ that is newer than the schema the database was built from. Do not run supabase db push against an empty database: the incremental history cannot create the core tables.

Environment Variables

VariableRequiredDescription

NEXT_PUBLIC_SUPABASE_URLYesSupabase project URL
NEXT_PUBLIC_SUPABASE_ANON_KEYYesSupabase anon (public) key
SUPABASE_SERVICE_ROLE_KEYYesSupabase service role key (secret)
NEXT_PUBLIC_APP_URLYes for self-hostingThis instance's own origin. Baked in at build time; unset, OAuth metadata and generated links point at https://agentplaybooks.ai.
SECRETS_ENCRYPTION_KEYYes for the secrets vault64 hexadecimal characters. Rotating it makes existing secrets undecryptable — there is no re-encryption tooling yet.
ALLOWED_ORIGINSNoComma-separated origins allowed to make credentialed cross-origin API calls. Setting it replaces the default list, which is what a self-hosted instance wants — otherwise the project's own domains stay trusted. Unset keeps the previous behaviour.
SECRETS_REQUIRE_ALLOWED_HOSTSNoSet to true to refuse outbound use of any secret that has not declared allowed_hosts. Off by default.

Pinning a secret to specific destinations

use_secret and POST /api/playbooks/:guid/secrets/proxy inject a decrypted credential into a request whose URL the caller supplies. The agent never sees the plaintext, but it does choose the destination — so a caller holding a secrets:read key, including an agent following instructions injected into its context, can name any host.

A secret can therefore pin itself:

curl -X PUT 'https://your-domain.com/api/playbooks/GUID/secrets/GITHUB_TOKEN' \
  -H "Authorization: Bearer apb_your_key" \
  -H "Content-Type: application/json" \
  -d '{"allowed_hosts": ["api.github.com", "*.githubusercontent.com"]}'

Entries are case-insensitive hostnames. A leading *. matches subdomains but not the bare domain, so list both if you need both. An unset or empty list means any destination, which keeps existing secrets working unchanged; set SECRETS_REQUIRE_ALLOWED_HOSTS=true to make pinning mandatory on your instance.

Custom Domain

Cloudflare Pages

  • Go to your Pages project > Custom domains
  • Add your domain
  • Configure DNS (Cloudflare or external)

SSL

Cloudflare Pages automatically provides SSL certificates.

Updating

Cloudflare Pages

Push to your main branch. Cloudflare will automatically rebuild and deploy.

Docker

git pull
docker-compose build
docker-compose up -d

Monitoring

Cloudflare Analytics

Built-in analytics available in Cloudflare Dashboard.

Supabase Dashboard

Monitor database usage, auth events, and API calls.

Backup

Database

# Using Supabase CLI
supabase db dump > backup.sql

# Or use Supabase Dashboard > Database > Backups

Troubleshooting

Build Fails

  • Check Node.js version (need 20+)
  • Clear node_modules and reinstall
  • Check environment variables are set

Auth Not Working

  • Verify Supabase URL and keys
  • Check redirect URLs in Supabase Auth settings
  • Ensure RLS policies are applied
  • A provider button is missing: the login page shows only providers that /auth/v1/settings reports as enabled. If that endpoint cannot be reached, only the email form is shown.

API Returns 500

  • Check Supabase service role key
  • Verify database migrations ran successfully
  • Check Cloudflare Worker logs

Security Checklist

  • [ ] Environment variables are set as secrets (not in code)
  • [ ] SUPABASE_SERVICE_ROLE_KEY is never exposed to client
  • [ ] RLS is enabled on all tables
  • [ ] Auth providers configured with correct redirect URLs
  • [ ] Custom domain uses HTTPS
Edit this page on GitHub
On this page
PrerequisitesOption 1: Cloudflare Pages (Recommended)Step 1: Fork the RepositoryStep 2: Create Supabase ProjectStep 3: Connect to Cloudflare PagesStep 4: Configure Auth ProvidersOption 2: DockerMicrosoft SQL Serverdocker-compose.yml and DockerfileIntranet instance without TLSRunOption 3: VercelDatabase MigrationsInitial SetupUpgradesEnvironment VariablesPinning a secret to specific destinationsCustom DomainCloudflare PagesSSLUpdatingCloudflare PagesDockerMonitoringCloudflare AnalyticsSupabase DashboardBackupDatabaseTroubleshootingBuild FailsAuth Not WorkingAPI Returns 500Security Checklist