Getting Started with Pouta
Pouta is a serverless, edge-native, framework-agnostic Git-backed CMS running entirely on Cloudflare Pages/Workers and D1 SQLite. Follow this comprehensive setup guide to boot your own instance.
Register a GitHub App
Pouta CMS integrates as a GitHub App to secure individual repository scopes, list repository installations, and commit files using dynamic, short-lived installation access tokens.
Go to your GitHub profile settings: Settings > Developer Settings > GitHub Apps > New GitHub App.
Configure the primary Application settings:
- Homepage URL:
https://your-pouta-domain.com(Marketing site) - Callback URL:
https://app.your-pouta-domain.com/api/auth/callback(Active app auth API endpoint) - Setup URL:
https://app.your-pouta-domain.com(Redirects after installation)
Grant dynamic Repository Permissions:
- Contents:
Read & Write(To load configurations and commit Markdown files) - Metadata:
Read-Only(Required by GitHub to list active repositories)
Generate secure credentials and encode keys:
Create an OAuth Client Secret and record the Client ID. Then, generate a Private Key (.pem). Since Cloudflare secrets do not handle raw multiline PEM formatting cleanly, encode the key in Base64:
base64 -i your-app-private-key.pem | pbcopy Configure Cloudflare Variables
Sensitive credentials, client secrets, and base64-encoded private keys must never be committed to repository files. Pouta loads these keys dynamically at the Edge.
A. Local Development (.dev.vars)
Create a Git-ignored file named .dev.vars in the project root:
GITHUB_APP_ID="1002345"
GITHUB_CLIENT_ID="Iv1.xxxxxxxxx"
GITHUB_CLIENT_SECRET="xxxxxxxxxxxxxxx"
GITHUB_APP_PRIVATE_KEY_B64="base64_pem..."
SESSION_SECRET="32_char_minimum_secret" B. Production Dashboard Bindings
Go to Cloudflare Dashboard > Pages/Workers Project > Settings > Environment Variables and define:
GITHUB_APP_ID,GITHUB_CLIENT_IDGITHUB_CLIENT_SECRET(Mark as Secret)GITHUB_APP_PRIVATE_KEY_B64(Mark as Secret)SESSION_SECRET(Stateless Session Encryption Key)- D1 Database Binding: Bind variables under Settings > Functions. Bind the name
DBdirectly to your production SQLite D1 database.
Create Website Configuration
Pouta is completely schema-agnostic. Drop a pouta.config.json at the root of your target website repository. Pouta reads this schema dynamically to draw sidebars, fields, and paths.
{
"contentTypes": [
{
"type": "blog",
"label": "Blog Posts",
"writePath": "src/content/blog/{slug}.md",
"fields": [
{ "name": "featured_image", "label": "Cover Image", "type": "image" },
{ "name": "description", "label": "Meta Description", "type": "textarea" },
{ "name": "pinned", "label": "Pinned Post", "type": "boolean" }
]
}
]
} image (draws visual asset selector), textarea (ideal for SEO meta blocks), and boolean (draws visual toggle switches). If no fields are needed, declare "fields": [].
Boot Server & Run Migrations
Pouta uses a lightweight local SQLite engine using Cloudflare's Wrangler. Boot migrations on your local sandbox and start drafting.
npx wrangler d1 execute pouta-d1-db --local --file=db/schema.sql npm run dev
Visit http://localhost:4321 to sign in with GitHub, grant installations to your repository workspace, and begin drafting content. Every autosave updates D1 SQLite at the edge, and clicking "Publish" commits structured Markdown directly to your Git branch!
Production SaaS Domain Architecture
For production SaaS implementations, we strongly recommend separating your public-facing marketing assets and documentation resources from the high-security admin workspace:
Hosts your public-facing marketing landing page, product pricing details, terms, and search-optimized developer documentation.
Dedicated workspace hosting the interactive Pouta admin dashboard panel, edge API routes, auth callback links, and session cookie validation gates.
This design pattern scopes your HTTP-only, cryptographically sealed session cookie strictly to your secure app. subdomain, isolating it from generic marketing scripts and assets.