The boilerplate is optimized for Docker deployment with Coolify, featuring multi-stage builds, health checks, and proper environment variable handling.

Multi-Stage Docker

Optimized 3-stage build for minimal image size.

Health Endpoint

DB-probing /api/health for external (Coolify/Traefik) liveness monitoring.

Coolify Optimized

docker-compose.yml configured for Coolify deployment.

Non-Root User

Runs as non-root user for enhanced security.

Docker Architecture

The deployment uses a multi-stage Docker build optimized for production. Here's how each stage works:

Stage Purpose What Happens
1. Dependencies Install npm packages Uses Node 25 (Debian slim), runs npm install --prefer-offline for a lockfile-aware install. This layer is cached unless package.json changes.
2. Builder Build the Next.js app Copies source code, injects NEXT_PUBLIC_* variables as build args, runs npm run build to create standalone output.
3. Runner Production image Minimal image with only the built app. Creates non-root user, copies standalone build, exposes port 3777.

Key Features

  • Standalone Output - Next.js creates a self-contained build that doesn't require node_modules at runtime
  • Non-Root User - Application runs as nextjs user for security (not root)
  • Health Endpoint - /api/health is available for the reverse proxy/orchestrator; the Docker/Compose healthcheck is intentionally disabled (Coolify/Traefik handle liveness)
  • Debian Slim Base - Minimal node:25-slim image keeps the runtime small

Docker Compose (Coolify)

The docker-compose.yml file orchestrates the deployment with these key configurations:

Configuration Purpose
Build Args NEXT_PUBLIC_* variables are passed at build time and baked into the image. Changing them requires a rebuild.
Environment Server-only secrets (API keys, webhooks) are injected at runtime. Can be changed without rebuilding.
Resource Limits Default: 2 CPU, 2GB RAM limit with 0.5 CPU, 512MB reservation. Prevents runaway processes.
Health Check Docker healthcheck is disabled (healthcheck: disable: true); Coolify/Traefik perform external monitoring against /api/health.
Restart Policy unless-stopped - Auto-restart on crash, but not if manually stopped.
Network Joins the coolify external network for reverse proxy integration.
Files Location

The Dockerfile and docker-compose.yml are located at the root of the project. Review them for the complete configuration.

Production Go-Live Checklist

Run through this once before pointing real users at the deployment:

  1. Create the production Supabase project; run supabase/schema.sql (and cms-schema.sql) in the SQL editor; enable the pg_cron + pg_net extensions.
  2. Set the Supabase Auth redirect URLs + Site URL to your domain (https://your-domain.com and https://your-domain.com/*/callback), or magic-link / OAuth will break.
  3. Switch Stripe to live keys, create a live webhook endpoint to /api/stripe/webhook, select all required events, and copy the live whsec_ into STRIPE_WEBHOOK_SECRET. Replace all test price IDs in config/pricing.ts with live-mode IDs.
  4. Set every server-only secret in the platform env (not .env.local): rotate JOBS_SECRET_KEY, set LOGS_SALT / REFERRAL_SALT / AFFILIATES_SALT, all provider keys.
  5. Set NEXT_PUBLIC_APP_URL to the real domain and NEXT_PUBLIC_INDEXABLE=true; set NEXT_PUBLIC_PRELAUNCH=false when you go public.
  6. Multi-replica only: configure UPSTASH_REDIS_REST_URL/TOKEN — the in-memory rate-limiter is per-instance and will not coordinate across replicas. Keep TRUST_CLOUDFLARE_IP=false unless direct origin access is blocked or your trusted proxy strips spoofed client-IP headers.
  7. Register the cron jobs (rows in the jobs table) and set the Jobs API URL to the public origin; verify they run (not localhost).
  8. Edit the legal CMS pages (terms, privacy, legal) before launch.
  9. Smoke test: /api/health → a real test checkout → magic-link login → an AI message → confirm credits decremented. Enable LOGS_ENABLED and watch /admin-dashboard/logs.
  10. Enable Supabase automated backups / PITR and confirm a rollback path before your first real customer.
Vercel instead of Docker/Coolify

The app is a standard Next.js standalone build, so it deploys to Vercel without the Dockerfile — set the same env vars in the project settings. Caveat: pg_cron still calls your public /api/jobs/run URL, so jobs work the same; just point the Jobs API URL at the Vercel domain.

Environment Variables

In production, set all server-only secrets at the platform level (Coolify/Vercel project env), never in a committed .env.local: SUPABASE_SECRET_KEY, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET (the live one), JOBS_SECRET_KEY, and the hash salts (LOGS_SALT, REFERRAL_SALT, AFFILIATES_SALT). Build-time NEXT_PUBLIC_* values must be present at build, not just runtime. TRUST_CLOUDFLARE_IP defaults to false; set it to true only when the origin is protected by Cloudflare or a trusted proxy that strips spoofed headers. The full env contract lives in .env.example; the ordered Production Go-Live Checklist is above.

Build-time vs Runtime Variables

NEXT_PUBLIC_* variables are embedded at build time and require a rebuild to change. Server-only variables can be changed at runtime.

Required Variables

These variables must be set for the application to function. Copy .env.example to .env.local and fill in values for: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY, SUPABASE_SECRET_KEY (from your Supabase project settings), STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY (from your Stripe dashboard), LEGAL_COMPANY_NAME, LEGAL_ADDRESS, LEGAL_REGISTRATION_NUMBER for public legal pages, and at least one AI provider key (OPENAI_API_KEY, ANTHROPIC_API_KEY, or GOOGLE_AI_API_KEY).

Email Configuration

Set EMAIL_PROVIDER to brevo, mailjet, or noop. Provide the matching credentials (BREVO_API_KEY for Brevo, or MAILJET_API_KEY_PUBLIC + MAILJET_API_KEY_PRIVATE for Mailjet) plus EMAIL_FROM_NAME and EMAIL_FROM_ADDRESS (the verified sender email). The active provider handles transactional emails (magic links, invitations, deletion notifications, license expiration warnings) and newsletter subscriptions.

Optional Integrations

Optional variables enable additional features: NEXT_PUBLIC_GTM_ID for Google Tag Manager analytics, NEXT_PUBLIC_META_PIXEL_ID for Facebook/Meta pixel tracking, NEXT_PUBLIC_CRISP_WEBSITE_ID for live chat support, UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN for production rate limiting (falls back to in-memory without these), and VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY (server-only) for PWA push notifications. Analytics/chat scripts remain consent-gated even when configured.

Health Check Endpoint

The application includes a health check endpoint at /api/health used by Docker and monitoring tools.

Field Description
status "ok" (HTTP 200) or "degraded" (HTTP 503 when the DB probe fails)
db "ok" / "error" / "timeout"
duration_ms DB probe duration in milliseconds
error Error message (only present when degraded)

The Docker/Compose healthcheck is disabled by design; point your reverse proxy or platform liveness probe at /api/health (it returns 503 when the database probe fails).

Coolify Deployment Steps

Follow these steps to deploy on Coolify:

  1. Create Application - In Coolify, create a new application from your Git repository. Select "Docker Compose" as the build pack.
  2. Configure Build Args - Add all NEXT_PUBLIC_* variables in the Build Args section. These are embedded at build time.
  3. Configure Environment - Add server-only secrets (API keys, webhook secrets) in the Environment section. These can be changed without rebuilding.
  4. Set Domain - Add your domain in Coolify. SSL certificates are automatically provisioned via Let's Encrypt.
  5. Deploy - Click "Deploy" and wait for the build to complete (typically 3-5 minutes for first build, faster for subsequent builds due to caching).
  6. Verify - Check https://your-domain.com/api/health returns a healthy status.

Resource Recommendations

Tier CPU Memory Use Case
Development 1 CPU 1 GB Local testing, staging
Production (Small) 2 CPU 2 GB Low traffic, startup
Production (Medium) 4 CPU 4 GB Medium traffic, growing
Production (Large) 8 CPU 8 GB High traffic, enterprise

Note: Build requires more resources than runtime. Allow 4 GB memory during build process.

Stripe Webhook Setup

Stripe webhooks notify your application when payment events occur (subscriptions, payments, cancellations).

  1. Create Webhook - In the Stripe Dashboard, go to Developers → Webhooks → Add endpoint
  2. Set Endpoint URL - Use https://your-domain.com/api/stripe/webhook
  3. Select Events - Enable these events:
    • Checkout Events:
    • checkout.session.completed - Purchases (subscriptions, credit packs, licenses)
    • checkout.session.async_payment_succeeded - Async payment completed (SEPA, bank transfer)
    • checkout.session.async_payment_failed - Async payment failed
    • checkout.session.expired - Abandoned checkout tracking
    • Subscription Events:
    • customer.subscription.created - New subscriptions
    • customer.subscription.updated - Plan changes, status updates
    • customer.subscription.deleted - Cancellations
    • customer.subscription.paused - Subscription paused
    • customer.subscription.resumed - Subscription resumed
    • customer.subscription.trial_will_end - Trial ending notification (3 days before)
    • Invoice Events:
    • invoice.paid - Successful payments / monthly credit refills
    • invoice.payment_failed - Failed payments (grace period)
    • invoice.payment_action_required - SCA/3D Secure authentication required
    • Charge & Dispute Events:
    • charge.refunded - Refunds (deducts credits, revokes licenses)
    • charge.dispute.created - Chargeback created (requires immediate action)
    • charge.dispute.closed - Dispute resolved (won/lost)
    • Customer Events:
    • customer.created - Link Stripe customer to account
    • customer.updated - Sync customer data changes
    • customer.deleted - Clean up Stripe references
    • Payment Method Events:
    • payment_method.attached - New payment method added
    • payment_method.detached - Payment method removed (churn signal)
  4. Copy Signing Secret - After creation, copy the webhook signing secret (starts with whsec_)
  5. Add to Environment - Set STRIPE_WEBHOOK_SECRET in your environment variables
Local Development

Use the Stripe CLI to forward webhooks locally: stripe listen --forward-to localhost:3777/api/stripe/webhook

Supabase Database Setup

Supabase provides the PostgreSQL database, authentication, and file storage for the application.

  1. Create Project - Create a new project in the Supabase dashboard
  2. Run Schema Migration - Copy the contents of supabase/schema.sql and run it in the SQL Editor
  3. Enable Extensions - Enable these PostgreSQL extensions:
    • uuid-ossp - UUID generation (usually enabled by default)
    • pg_cron - Scheduled job execution (for background jobs)
    • pg_net - HTTP requests from database (for job triggers)
  4. Configure Authentication - In Auth settings:
    • Enable Email provider for magic links
    • Add redirect URLs for your domain
    • Optionally configure OAuth providers (Google, GitHub)
  5. Create Storage Bucket - Create a public bucket named cms-media for CMS uploads (automatically created by npm run init)
  6. Copy API Keys - From Project Settings → API:
    • Project URL → NEXT_PUBLIC_SUPABASE_URL
    • Publishable key → NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY
    • Secret key → SUPABASE_SECRET_KEY