Self-Hosting Guide

TeachRepo is MIT-licensed. Fork the template, wire up Supabase + Stripe, and deploy anywhere Node runs β€” Vercel, Railway, Fly.io, a plain VPS. No platform fees. No lock-in.

πŸ’‘Time to deploy: ~20 minutes for a fresh Vercel + Supabase setup using the one-click template. The GitHub Actions workflow then handles every subsequent deploy on push.

Prerequisites

1

Fork and clone the template

The full source is in the openclaw-workspace monorepo. The web app lives at startup-91-lean-launch-plan-repo-to-course-git-native-paywall/apps/web/.

# Fork on GitHub first, then:
git clone https://github.com/YOUR_ORG/YOUR_FORK.git
cd YOUR_FORK/startup-91-lean-launch-plan-repo-to-course-git-native-paywall/apps/web

npm install
ℹ️The monorepo root contains startup-*/ directories. The teachrepo web app is entirely self-contained in apps/web/ with its own package.json.
2

Set up Supabase

Create a new project at supabase.com. Then apply the schema:

# In the Supabase SQL editor, run both files in order:
# 1. supabase/schema.sql   β€” tables, indexes, enums
# 2. supabase/rls.sql      β€” row-level security policies

Grab your credentials from Project Settings β†’ API:

  • NEXT_PUBLIC_SUPABASE_URL β€” Project URL (e.g. https://xxxx.supabase.co)
  • NEXT_PUBLIC_SUPABASE_ANON_KEY β€” public anon key
  • SUPABASE_SERVICE_ROLE_KEY β€” secret service role key (never expose client-side)
⚠️SUPABASE_SERVICE_ROLE_KEY bypasses RLS. Keep it server-only. Never put it in NEXT_PUBLIC_* env vars.

Enable email auth

Authentication β†’ Email β†’ Enable email confirmations. For development you can disable email confirmation to skip the verify step.

Storage bucket (optional)

Create a public bucket named course-assets for cover images and attachments. The app falls back gracefully if the bucket is absent.

3

Set up Stripe

Log in to dashboard.stripe.com. In test mode, collect your keys:

  • STRIPE_SECRET_KEY β€” starts with sk_test_
  • NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY β€” starts with pk_test_
  • STRIPE_WEBHOOK_SECRET β€” generated when you create a webhook endpoint

Create webhook endpoint

In Stripe Dashboard β†’ Developers β†’ Webhooks β†’ Add endpoint:

# Endpoint URL:
https://YOUR_DOMAIN/api/webhooks/stripe

# Events to listen for:
checkout.session.completed
invoice.payment_succeeded
customer.subscription.updated
customer.subscription.deleted

Copy the Signing secret (starts with whsec_) into STRIPE_WEBHOOK_SECRET.

Course paywall pricing

Course prices are set per-course in the dashboard β€” no hard-coded Stripe price IDs needed. The import pipeline creates a Stripe Price on first publish.

πŸ’‘For local webhook testing, use the Stripe CLI:
stripe listen --forward-to localhost:3000/api/webhooks/stripe
4

Configure environment variables

Copy the example env file and fill in your values:

cp .env.example .env.local

Minimum required variables:

# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhb...
SUPABASE_SERVICE_ROLE_KEY=eyJhb...

# Stripe
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

# App
NEXT_PUBLIC_APP_URL=http://localhost:3000   # or https://yourdomain.com in prod

# GitHub (for repo-based imports)
GITHUB_TOKEN=ghp_...   # GitHub PAT with repo read scope
ℹ️Optional variables (AI quiz generation, analytics, PostHog, etc.) are documented in .env.example. The app boots without them β€” features degrade gracefully.
5

Run locally

npm run dev
# β†’ http://localhost:3000

# Type-check:
npm run type-check

# Build for production:
npm run build
npm start

The first time you visit /auth/signup, create your founder account. The first signup auto-receives admin privileges if FOUNDER_EMAIL is set in env.

6

Deploy to Vercel

Option A β€” Vercel CLI (quickest)

# From apps/web/
npx vercel --prod

# Follow prompts: link to your Vercel project, select framework = Next.js
# Set env vars when prompted, or add them in the Vercel dashboard

Option B β€” Vercel dashboard import

  1. New Project β†’ Import Git Repository β†’ select your fork
  2. Set Root Directory to startup-91-lean-launch-plan-repo-to-course-git-native-paywall/apps/web
  3. Framework preset: Next.js
  4. Add all env vars (see Step 4) in the Environment Variables section
  5. Deploy

Add your custom domain

vercel domains add yourdomain.com
# Then set the A record / CNAME in your DNS provider per Vercel's instructions

Update NEXT_PUBLIC_APP_URL to your custom domain and redeploy.

7

Automate with GitHub Actions

Add this workflow to your fork for automatic deploy-on-push:

# .github/workflows/deploy.yml
name: Deploy to Vercel

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: startup-91-lean-launch-plan-repo-to-course-git-native-paywall/apps/web

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
          cache-dependency-path: |
            startup-91-lean-launch-plan-repo-to-course-git-native-paywall/apps/web/package-lock.json

      - run: npm ci

      - name: Type-check
        run: npm run type-check

      - name: Install Vercel CLI
        run: npm install -g vercel@latest

      - name: Pull Vercel env
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}

      - name: Build
        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}

      - name: Deploy
        if: github.ref == 'refs/heads/main'
        run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}

GitHub Secrets to set

Secret nameWhere to get it
VERCEL_TOKENVercel β†’ Account Settings β†’ Tokens
VERCEL_ORG_IDvercel.com/account β†’ General β†’ Your ID, or .vercel/project.json
VERCEL_PROJECT_ID.vercel/project.json after first vercel link
SUPABASE_SERVICE_ROLE_KEYSupabase β†’ Project Settings β†’ API
STRIPE_SECRET_KEYStripe Dashboard β†’ Developers β†’ API keys
STRIPE_WEBHOOK_SECRETStripe Dashboard β†’ Developers β†’ Webhooks

Go to your GitHub repo β†’ Settings β†’ Secrets and variables β†’ Actions β†’ New repository secret.

8

(Optional) Run the TeachRepo CLI

The CLI lets you push course updates directly from your local repo:

npm install -g @teachrepo/cli

# Authenticate (generates an API token for your account)
teachrepo login

# In your course repo:
teachrepo push --url https://yourdomain.com

# Push a specific branch as a version
teachrepo push --branch v1.2 --label "v1.2"

See the full CLI Reference for all commands.

9

(Optional) Deploy on Railway or Fly.io

TeachRepo is a standard Next.js app. Any platform that runs Node 18+ works.

Railway

# railway.toml (place in apps/web/)
[build]
builder = "nixpacks"
buildCommand = "npm run build"

[deploy]
startCommand = "npm start"
healthcheckPath = "/api/health"
restartPolicyType = "on_failure"

Fly.io

# From apps/web/:
fly launch --name teachrepo-app --region iad
fly secrets set NEXT_PUBLIC_SUPABASE_URL=... STRIPE_SECRET_KEY=... # etc.
fly deploy

Docker (VPS / bare metal)

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
ℹ️Add output: 'standalone' to next.config.js to enable the minimal Docker image (Next.js docs: output: standalone).

Self-hosted vs. Hosted SaaS β€” differences

AspectSelf-hostedHosted (Creator plan)
CostInfra costs only (Vercel/Supabase free tiers = $0/mo)$29/mo managed
Course limitUnlimited (you own the DB)β†’ Unlimited (plan unlocked)
Lesson limitUnlimited→ Unlimited
Custom domainYes β€” configure in VercelYes β€” one-click in dashboard
Stripe accountYour ownYour own (Connect)
Platform fee0%0% direct, 10% marketplace
Marketplace listingNo (self-hosted not indexed)Yes
UpdatesManual β€” merge upstream PRsAutomatic
Devops burdenYou manage Supabase, Vercel, webhooksManaged for you
Data ownershipFull β€” your Supabase projectFull β€” export anytime
AI quiz generationUnlimited (bring your own key)Unlimited (included)
SupportGitHub Issues / communityPriority email support

Keeping your fork up to date

Add the upstream remote and merge new releases:

git remote add upstream https://github.com/ErlisK/openclaw-workspace.git

# Check for new commits:
git fetch upstream

# Merge latest:
git merge upstream/main

# Or cherry-pick specific commits:
git cherry-pick <commit-sha>
⚠️Check the release notes before merging β€” database schema migrations need to be applied manually to your Supabase project via the SQL editor.

Troubleshooting

❌ Stripe webhook returns 400 "No signatures found"

βœ… Make sure STRIPE_WEBHOOK_SECRET matches the signing secret for the endpoint in your Stripe dashboard. For local dev, use the Stripe CLI to forward events.

❌ Supabase RLS blocking all reads

βœ… Check that you applied supabase/rls.sql. If testing, temporarily disable RLS on a table via the Supabase dashboard to isolate the issue.

❌ Build fails with "SUPABASE_SERVICE_ROLE_KEY not found"

βœ… Add the secret to Vercel env vars (Dashboard β†’ Project β†’ Settings β†’ Environment Variables) or to your .env.local file.

❌ GitHub import returns 404

βœ… Make sure GITHUB_TOKEN has repo read scope and the repo is not private (or the token has access to private repos).

❌ Vercel deploy fails with "Build Error"

βœ… Run npm run build locally first. The most common cause is a missing env var β€” check the build log for "undefined" values.

Need help?