Self-Hosting Guide
Contentrain Studio supports self-hosting of the AGPL core. This guide covers everything you need to deploy and operate Studio in your own environment; managed Pro/Enterprise operation may also be available separately.
Core Principles
- The application is stateless -- no local writable state beyond normal container runtime behavior
- Git repositories remain external and authoritative -- Studio never stores content in its database
- Auth/data are supplied through provider-backed integrations (swappable)
- Enterprise features degrade safely when
ee/functionality or external services are absent
Prerequisites
Required
| Component | Purpose |
|---|---|
| Public URL | A reachable HTTPS endpoint for the application |
| Supabase-compatible deployment | Auth (GitHub OAuth, Google OAuth, Magic Link) + PostgreSQL with RLS |
| GitHub App | Repository access, installation lifecycle, commit operations |
NUXT_SESSION_SECRET | Minimum 32 characters, used for AES-256 cookie encryption |
Optional
| Component | Purpose | When Needed |
|---|---|---|
| Anthropic API key | Operator-managed AI agent | If users should not need BYOA keys |
| Resend API key | Email notifications (invites, reminders) | For team invite workflows |
| Stripe keys | Billing / subscription management | For plan-gated deployments |
| Cloudflare R2 | Object storage for CDN and media | For CDN delivery and media library |
| Redis | Distributed rate limiting | For multi-instance deployments |
Deployment Shapes
Recommended options:
- Single container behind a reverse proxy (simplest)
- Railway / Render / Fly.io Docker deployment
- VM or bare-metal Docker deployment
- Kubernetes with one web deployment plus external backing services
Docker Deployment
Build the Image
docker build -t contentrain-studio .The Docker image contains:
- Built Nuxt/Nitro server output
- Generated
.contentrainassets for UI strings gitbinary for repository operations- Non-root
studiouser (security best practice)
Run the Container
docker run \
--name contentrain-studio \
--env-file .env \
-p 3000:3000 \
contentrain-studioThe container listens on port 3000.
Docker Compose Example
version: '3.8'
services:
studio:
image: contentrain-studio
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
env_file:
- .env
environment:
- NODE_ENV=production
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
# If using self-hosted Supabase:
# supabase:
# image: supabase/postgres:latest
# ...Health Check
The application exposes a health endpoint:
curl http://localhost:3000/api/health
# Returns 200 OKSelf-Host Modes
Minimal Core Mode
Use this when you want the AGPL core without premium operational surfaces.
Configure:
- Supabase (auth + database)
- GitHub App
- No Stripe, no R2, no Redis (for dev/single instance)
This gives you:
- Authenticated application
- Workspace and project management
- Repository connection and content operations
- AI chat (with an operator-managed key or BYOA)
- All agent tools and content engine
Operational Mode
Add delivery and automation surfaces on top of core:
Additionally configure:
- Redis (distributed rate limiting)
- Resend (email notifications)
- Cloudflare R2 (CDN and media delivery)
- Stripe (if billing is required)
External Service Setup
Supabase
- Create a Supabase project (or self-host Supabase)
- Apply all database migrations before first production traffic
- Configure environment variables:
NUXT_SUPABASE_URL=https://your-project.supabase.co
NUXT_SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
NUXT_SUPABASE_ANON_KEY=your-anon-key- Configure OAuth providers in Supabase Dashboard:
- GitHub OAuth: for workspace owners (needs repo access)
- Google OAuth: for invited team members
- Email/Magic Link: for passwordless auth
WARNING
The service role key has admin access to your database. Keep it private and never expose it to the client.
GitHub App
- Create a GitHub App at
https://github.com/settings/apps/new - Configure permissions:
- Repository contents: Read & Write
- Pull requests: Read & Write
- Metadata: Read-only
- Set the callback URL to
{SITE_URL}/auth/callback - Set the webhook URL to
{SITE_URL}/api/webhooks/github - Generate a private key (
.pemfile) - Base64-encode the private key:
base64 -i your-app.private-key.pem | tr -d '\n'- Configure environment variables:
NUXT_GITHUB_APP_ID=your-app-id
NUXT_GITHUB_CLIENT_ID=your-client-id
NUXT_GITHUB_CLIENT_SECRET=your-client-secret
NUXT_GITHUB_PRIVATE_KEY=base64-encoded-pem-content
NUXT_GITHUB_WEBHOOK_SECRET=your-hmac-webhook-secret
NUXT_PUBLIC_GITHUB_APP_SLUG=your-app-slugEmail (Resend)
NUXT_RESEND_API_KEY=re_your-resend-api-key
NUXT_EMAIL_SENDER_ADDRESS=[email protected]
NUXT_EMAIL_SENDER_NAME=Contentrain StudioIf using local Supabase auth with SMTP, also set:
RESEND_API_KEY=re_your-resend-api-keyBilling (Stripe)
When billing is enabled:
NUXT_STRIPE_SECRET_KEY=sk_live_your-stripe-secret-key
NUXT_STRIPE_WEBHOOK_SECRET=whsec_your-webhook-signing-secret
NUXT_STRIPE_STARTER_PRICE_ID=price_starter-monthly-id
NUXT_STRIPE_PRO_PRICE_ID=price_pro-monthly-id
NUXT_PUBLIC_BILLING_ENABLED=trueWhen billing is not enabled, leave all Stripe keys unset. Studio will operate in no-billing mode where all workspaces get starter-level access to core features.
CDN / Object Storage (Cloudflare R2)
NUXT_CDN_R2_ACCOUNT_ID=your-cloudflare-account-id
NUXT_CDN_R2_ACCESS_KEY_ID=your-r2-access-key-id
NUXT_CDN_R2_SECRET_ACCESS_KEY=your-r2-secret-access-key
NUXT_CDN_R2_BUCKET=contentrain-cdnRedis
For multi-instance production deployments:
REDIS_URL=redis://localhost:6379
# Use REDIS_CA_CERT for custom CA trust with rediss://Without Redis, rate limiting degrades to in-memory and is only suitable for single-instance setups.
Production Checklist
Base
- [ ] Set
NODE_ENV=production - [ ] Set
NUXT_PUBLIC_SITE_URLto your public application URL - [ ] Set a strong
NUXT_SESSION_SECRET(minimum 32 characters) - [ ] Run behind TLS (reverse proxy or platform load balancer)
Database / Auth
- [ ] Provide all three Supabase variables (
URL,SERVICE_ROLE_KEY,ANON_KEY) - [ ] Apply all database migrations before first production traffic
- [ ] Configure OAuth callback URLs correctly
GitHub
- [ ] Create and configure GitHub App
- [ ] Provide all five GitHub variables
- [ ] Test webhook delivery to
/api/webhooks/github
Reverse Proxy
Your reverse proxy should:
- Forward HTTPS traffic to port
3000 - Preserve
Hostand standard forwarding headers (X-Forwarded-For,X-Forwarded-Proto) - Set a sane request body limit for media uploads (50MB+)
- Not cache authenticated application routes
- Support Server-Sent Events (SSE) for chat streaming
Secret Rotation
For session/encryption secret rotation:
- Set the new secret in
NUXT_SESSION_SECRET - Keep the old value in
NUXT_SESSION_SECRET_PREVIOUS - Deploy the new version
- Allow existing sessions to refresh (token auto-refresh handles this)
- Remove
NUXT_SESSION_SECRET_PREVIOUSafter the migration window
Backup Strategy
Studio itself does not require local writable state. Back up these external components:
| Component | Strategy |
|---|---|
| Database | Supabase automated backups or pg_dump |
| Object Storage | R2 bucket versioning / cross-region replication |
| Git Repositories | Already distributed (GitHub) |
| Deployment Secrets | Secure vault (AWS Secrets Manager, Vault, etc.) |
Do not treat the Studio container filesystem as durable state.
Post-Deploy Smoke Checks
After first deploy, verify:
/api/healthreturns200- Login page loads
- OAuth callback URLs are correct
- A workspace can be created and listed
- GitHub App installation flow completes
- A repository can be scanned and connected
- At least one chat / content flow works end-to-end
- Email, CDN, media, and billing surfaces work (if configured)
Release Validation
Before cutting a release image:
pnpm release:checkThis runs lint, typecheck, and build verification.
Related Pages
- Environment Variables -- complete variable reference
- Architecture -- system design and provider pattern
- Enterprise Edition -- what requires
ee/