Skip to content

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

ComponentPurpose
Public URLA reachable HTTPS endpoint for the application
Supabase-compatible deploymentAuth (GitHub OAuth, Google OAuth, Magic Link) + PostgreSQL with RLS
GitHub AppRepository access, installation lifecycle, commit operations
NUXT_SESSION_SECRETMinimum 32 characters, used for AES-256 cookie encryption

Optional

ComponentPurposeWhen Needed
Anthropic API keyOperator-managed AI agentIf users should not need BYOA keys
Resend API keyEmail notifications (invites, reminders)For team invite workflows
Stripe keysBilling / subscription managementFor plan-gated deployments
Cloudflare R2Object storage for CDN and mediaFor CDN delivery and media library
RedisDistributed rate limitingFor 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

bash
docker build -t contentrain-studio .

The Docker image contains:

  • Built Nuxt/Nitro server output
  • Generated .contentrain assets for UI strings
  • git binary for repository operations
  • Non-root studio user (security best practice)

Run the Container

bash
docker run \
  --name contentrain-studio \
  --env-file .env \
  -p 3000:3000 \
  contentrain-studio

The container listens on port 3000.

Docker Compose Example

yaml
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:

bash
curl http://localhost:3000/api/health
# Returns 200 OK

Self-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

  1. Create a Supabase project (or self-host Supabase)
  2. Apply all database migrations before first production traffic
  3. Configure environment variables:
bash
NUXT_SUPABASE_URL=https://your-project.supabase.co
NUXT_SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
NUXT_SUPABASE_ANON_KEY=your-anon-key
  1. 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

  1. Create a GitHub App at https://github.com/settings/apps/new
  2. Configure permissions:
    • Repository contents: Read & Write
    • Pull requests: Read & Write
    • Metadata: Read-only
  3. Set the callback URL to {SITE_URL}/auth/callback
  4. Set the webhook URL to {SITE_URL}/api/webhooks/github
  5. Generate a private key (.pem file)
  6. Base64-encode the private key:
bash
base64 -i your-app.private-key.pem | tr -d '\n'
  1. Configure environment variables:
bash
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-slug

Email (Resend)

bash
NUXT_RESEND_API_KEY=re_your-resend-api-key
NUXT_EMAIL_SENDER_ADDRESS=[email protected]
NUXT_EMAIL_SENDER_NAME=Contentrain Studio

If using local Supabase auth with SMTP, also set:

bash
RESEND_API_KEY=re_your-resend-api-key

Billing (Stripe)

When billing is enabled:

bash
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=true

When 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)

bash
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-cdn

Redis

For multi-instance production deployments:

bash
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_URL to 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 Host and 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:

  1. Set the new secret in NUXT_SESSION_SECRET
  2. Keep the old value in NUXT_SESSION_SECRET_PREVIOUS
  3. Deploy the new version
  4. Allow existing sessions to refresh (token auto-refresh handles this)
  5. Remove NUXT_SESSION_SECRET_PREVIOUS after the migration window

Backup Strategy

Studio itself does not require local writable state. Back up these external components:

ComponentStrategy
DatabaseSupabase automated backups or pg_dump
Object StorageR2 bucket versioning / cross-region replication
Git RepositoriesAlready distributed (GitHub)
Deployment SecretsSecure vault (AWS Secrets Manager, Vault, etc.)

Do not treat the Studio container filesystem as durable state.

Post-Deploy Smoke Checks

After first deploy, verify:

  1. /api/health returns 200
  2. Login page loads
  3. OAuth callback URLs are correct
  4. A workspace can be created and listed
  5. GitHub App installation flow completes
  6. A repository can be scanned and connected
  7. At least one chat / content flow works end-to-end
  8. Email, CDN, media, and billing surfaces work (if configured)

Release Validation

Before cutting a release image:

bash
pnpm release:check

This runs lint, typecheck, and build verification.

Released under the AGPL-3.0 License.