YYayaw

Deployment Environment Setup

How to collect, scope, and verify the environment variables required for deployment.

This page is the deployment operator guide for environment variables. Use it when creating a new Vercel project, restoring a production environment, checking that preview deployments have the same provider wiring as production, or preparing the Docker self-host runtime.

Rules

  • Never commit secrets to .env, .env.production, or any tracked file.
  • Store Vercel production and preview secrets in Vercel project environment variables.
  • Store self-host production secrets in the orchestrator secret store. For the compose baseline, use an untracked .env.self-host file copied from .env.self-host.example.
  • Store local secrets in .env.local or in a pulled .env.local file created by vercel env pull.
  • Keep NEXT_PUBLIC_* values non-secret because they are exposed to the browser bundle.
  • Prefer admin/runtime settings for operational non-secrets when the dashboard supports them. For example, GitHub code-access repository settings are managed from /dashboard/admin/billing-settings; environment values are only fallbacks.

Vercel Workflow

Link the local checkout to the right Vercel project before pulling or pushing environment variables:

vercel link --yes --project <project-name-or-id> --scope <team-or-user>

Add secrets from the Vercel dashboard or CLI. Scope production-only values to Production, preview values to Preview, and local development values to Development:

echo "<secret-value>" | vercel env add VARIABLE_NAME production
echo "<secret-value>" | vercel env add VARIABLE_NAME preview
echo "<secret-value>" | vercel env add VARIABLE_NAME development

After adding or changing variables, redeploy the affected Vercel environment. Existing deployments do not automatically receive updated environment values.

Pull a local copy for development:

vercel env pull .env.local --environment=development --yes

Pull production values only for an explicit audit on a trusted machine:

vercel env pull .env.production.local --environment=production --yes

Check that every key listed in .env.example is present locally:

while IFS='=' read -r key _; do
  [[ -z "$key" || "$key" == \#* ]] && continue
  grep -q "^${key}=" .env.local || echo "Missing in .env.local: $key"
done < .env.example

Self-Hosted Docker Workflow

The portable baseline uses Docker Compose with Postgres, MinIO/S3, the Next.js standalone app server, a Page AI worker, and Caddy:

cp .env.self-host.example .env.self-host
docker compose --env-file .env.self-host -f docker-compose.self-host.yml --profile setup run --rm migrate
docker compose --env-file .env.self-host -f docker-compose.self-host.yml up --build

Docker builds use NEXT_BUILD_WORKERS=1 by default for stable Next.js static generation on small hosts. On larger machines you can raise it for faster builds, for example NEXT_BUILD_WORKERS=2 docker compose --env-file .env.self-host -f docker-compose.self-host.yml up --build.

Self-hosted Docker builds also default NEXT_STATIC_PAGE_GENERATION_TIMEOUT to 180 seconds. This gives slower hosts enough time to prerender large docs pages without tripping Next.js' default 60 second static generation timeout. Lower it on faster CI builders or raise it only if the build logs show legitimate static pages timing out.

Self-hosted Docker builds render Fumadocs pages dynamically instead of pre-generating every docs page during image builds. This keeps small Coolify or Docker hosts from spending most of their deployment budget on documentation pages while still serving docs normally at runtime.

The Compose files tag the app and worker build targets with YAYAW_APP_IMAGE and YAYAW_WORKER_IMAGE. Keep the same worker image tag for the migrate and worker services so Docker or Coolify can reuse the worker target instead of building it twice.

The Yayaw-managed Coolify deployment uses prebuilt images instead of building on the production VM. GitHub Actions builds the app and worker images on GitHub-hosted ubuntu-24.04-arm runners for the Mac mini VM's linux/arm64 platform, pushes the images to GHCR, makes the Coolify server pull them through the private self-hosted runner, updates YAYAW_APP_IMAGE and YAYAW_WORKER_IMAGE, and then asks Coolify to run the compose deployment. This keeps the automatic production release on main while moving the expensive Docker build away from the Mac mini VM and avoiding GitHub Actions artifact storage quota. Coolify must store those image tag variables as build-time and runtime values because Docker Compose interpolates image: entries while preparing the deployment.

The setup profile runs drizzle-kit push schema sync plus the seed script. Re-run it before starting a freshly provisioned self-host database and after schema changes. It is not a versioned Drizzle migration runner.

Do not commit .env.self-host. For production, move the same values to the target host's secret manager or compose environment. Generate BETTER_AUTH_SECRET before the first Docker build, for example with openssl rand -hex 32; the Dockerfile fails fast when it is empty. The --env-file flag makes the same public values available to Docker build args. BETTER_AUTH_SECRET is also passed to next build so Better Auth route evaluation never falls back to a development secret during image builds. Rebuild the image when NEXT_PUBLIC_* values change because Next.js exposes them in the browser bundle at build time.

Coolify CI Environments

Self-hosted Coolify deployments are driven from GitHub Actions:

  • Pull requests deploy to a shared development Coolify application after the CI checks pass.
  • Pushes to main deploy to the production Coolify application after the CI checks pass.

Configure two GitHub environments with separate Coolify application UUIDs:

GitHub environmentRequired secret or variablePurpose
developmentCOOLIFY_URLCoolify API origin.
developmentCOOLIFY_API_TOKENToken allowed to update and deploy the development app.
developmentCOOLIFY_APPLICATION_UUIDShared development app UUID.
developmentDEVELOPMENT_BASIC_AUTH_USERNAMEHTTP Basic username for the protected development app.
developmentDEVELOPMENT_BASIC_AUTH_PASSWORDHTTP Basic password for the protected development app.
developmentDEVELOPMENT_URL variableOptional smoke-test URL. Defaults to https://dev.yayaw.app; use an internal runner-reachable URL when the public DNS is not routed yet.
productionCOOLIFY_URLCoolify API origin.
productionCOOLIFY_API_TOKENToken allowed to deploy the production app.
productionCOOLIFY_APPLICATION_UUIDProduction app UUID.
development, productionBETTER_AUTH_SECRETBuild-time secret required by the Next.js Docker image build.
development, productionDATABASE_URLBuild-time database URL for server route evaluation.
development, productionNEXT_PUBLIC_* build secretsPublic client bundle values baked into the Docker image.
development, productionNEXT_BUILD_WORKERS variableBuild worker count. Defaults to 2 for GitHub-hosted builds.
development, productionNEXT_STATIC_PAGE_GENERATION_TIMEOUT variableStatic generation timeout. Defaults to 180.
development, productionCOOLIFY_SERVER_SSH_HOST variableOptional host override for loading images onto the Coolify VM. Defaults to 127.0.0.1 from the Mac mini runner.
development, productionCOOLIFY_SERVER_SSH_PORT variableOptional SSH port override. Defaults to 2222.
development, productionCOOLIFY_SERVER_SSH_USER variableOptional SSH user override. Defaults to yannis.
development, productionCOOLIFY_SERVER_SSH_KEY_PATH variable or COOLIFY_SERVER_SSH_KEY secretOptional SSH key override. The Mac mini runner defaults to /Users/yannis/.ssh/coolify_vm_ed25519.
development, productionCOOLIFY_SERVER_KNOWN_HOSTS_PATH variableOptional known-hosts override. Defaults to /Users/yannis/.ssh/known_hosts_coolify.

The GitHub workflows also need repository package permissions: the image-build jobs use packages: write to publish ghcr.io/<owner>/yayaw-app:<sha> and ghcr.io/<owner>/yayaw-worker:<sha>, while the Coolify deploy jobs use packages: read to pull those images from the self-hosted runner.

The shared development app is intentionally mutable: each pull request updates the app's git_branch in Coolify and deploys that branch without forcing a rebuild, so BuildKit can reuse layers across development and production deployments. GitHub Actions serializes development deploys through a single development-coolify-deploy concurrency group, so the shared environment stays predictable while new runs wait for the current deployment to finish.

Keep the development origin private. Prefer a Tailscale-only hostname or enable HTTP Basic authentication at the Coolify app level while still returning X-Robots-Tag: noindex, nofollow, noarchive. The smoke test runs from the self-hosted runner with the development Basic Auth secrets, so the runner must be able to reach the protected development URL.

Production Baseline

These values are required before a production deployment can serve the app correctly:

VariableWhere to get itValue guidance
NODE_ENVVercel runtimeUsually set by the platform. Do not override unless debugging a non-Vercel runtime.
NEXT_PUBLIC_BASE_URLVercel DomainsSet to the canonical public origin, for example https://yayaw.app.
BETTER_AUTH_SECRETSecret managerSet a stable long random value for Better Auth cookies and tokens.
DATABASE_URLPostgres providerUse the production pooled connection string when the provider exposes one.
BETTER_AUTH_TRUSTED_ORIGINSDeployment domainsAdd extra preview/local origins as a comma-separated list. The canonical host and www variant are derived from NEXT_PUBLIC_BASE_URL.
CADDY_HOST_PORTCoolify application envHost port for the compose Caddy container. Production defaults to 3080; set a different value such as 3081 for the shared development app.
MINIO_API_HOST_PORTCoolify application envHost port for MinIO's S3-compatible API. Production defaults to 9000; set a different value such as 9100 for the shared development app.
MINIO_CONSOLE_HOST_PORTCoolify application envHost port for the MinIO console. Production defaults to 9001; set a different value such as 9101 for the shared development app.
VERCEL_URLVercel runtimeSet automatically by Vercel. Keep it out of local .env unless reproducing platform behavior.

Optional database pool knobs:

VariableWhen to set it
DATABASE_POOL_MAXRaise only when the database provider can support more per-runtime connections.
DATABASE_IDLE_TIMEOUT_SECONDSTune idle lifecycle for non-serverless deployments.
DATABASE_MAX_LIFETIME_SECONDSTune connection rotation for long-lived runtimes.
DATABASE_CONNECT_TIMEOUT_SECONDSTune startup behavior for slow private networks.
DATABASE_STATEMENT_TIMEOUT_SECONDSAdd a server-side statement timeout for each database connection.
DATABASE_PREPARE_STATEMENTSKeep false for pooler/serverless production connections unless the provider supports prepared statements.
CMS_DASHBOARD_FULL_METRICSKeep false unless the database has been tuned for the full CMS publication and activity aggregates.

Database

DATABASE_URL is required by the application runtime, setup scripts, seeds, and Drizzle operations.

For the Coolify self-host deployment, schema sync and seeds run inside the compose migrate service before the app and worker start. Keep the production DATABASE_URL in Coolify or the orchestrator secret store so the setup step can resolve internal Docker service names such as postgres.

For hosted providers that do not run the self-host compose stack, execute the chosen Drizzle schema operation from a runner that can reach the production database before promoting the new app version.

For hosted Postgres providers:

  1. Create the production database.
  2. Open the provider connection string panel.
  3. Copy the pooled connection string when available.
  4. Store it as DATABASE_URL in Vercel Production and Preview, or in the self-host secret store.
  5. Use a separate development database for Vercel Development, local .env.local, or .env.self-host.

If the provider exposes both direct and pooled URLs, prefer the pooled URL for Vercel functions. The app already defaults to a conservative production pool of three connections per runtime to avoid exhausting serverless database limits.

Canonical Host and Auth

Set:

NEXT_PUBLIC_BASE_URL=https://yayaw.app
BETTER_AUTH_TRUSTED_ORIGINS=https://*.yayaw.app,https://*.vercel.app

NEXT_PUBLIC_BASE_URL drives canonical URLs, Better Auth base URL, OAuth metadata, sitemap/robots links, and generated absolute asset URLs.

Do not set NEXT_PUBLIC_SITE_URL; the application does not read it. Do not set BETTER_AUTH_URL for new deployments; Better Auth receives its base URL from NEXT_PUBLIC_BASE_URL.

The branch-backed preview domain is https://preview.yayaw.app and tracks the durable preview Git branch.

Retired .eu hosts may stay attached only as Vercel 308 redirects to their .app replacements, such as yayaw.eu and www.yayaw.eu to yayaw.app. They must not serve dashboard/auth traffic or appear in BETTER_AUTH_TRUSTED_ORIGINS.

Stripe Billing

Stripe has three required secrets when billing is enabled:

VariableWhere to get itNotes
STRIPE_SECRET_KEYStripe Dashboard, Developers, API keysUse sk_live_... in production and sk_test_... in staging/local test environments.
STRIPE_WEBHOOK_SECRETStripe Dashboard, Developers, Webhooks, subscription endpoint signing secretUsed by the Better Auth Stripe webhook endpoint.
STRIPE_ONE_TIME_WEBHOOK_SECRETStripe Dashboard, Developers, Webhooks, one-time endpoint signing secretUsed by the custom one-time checkout webhook endpoint.

Create two Stripe webhook endpoints for production:

https://yayaw.app/api/auth/stripe/webhook
https://yayaw.app/api/billing/stripe/webhook

Use the signing secret from the first endpoint for STRIPE_WEBHOOK_SECRET and the signing secret from the second endpoint for STRIPE_ONE_TIME_WEBHOOK_SECRET. Do not reuse one endpoint secret for the other endpoint.

The subscription endpoint should receive subscription checkout and billing lifecycle events. The one-time endpoint only needs checkout.session.completed for lifetime purchases.

Billing product prices are managed from /dashboard/admin/billing-products or through the control plane MCP. Stripe price IDs are stored in the database after catalog sync; they are not deployment secrets.

These values are optional, non-secret URLs shown on /dashboard/organization/code-access after a paid purchase unlocks code access:

VariableExampleWhen to use it
BILLING_CODE_ACCESS_REPOSITORY_URLhttps://github.com/Yayaw-eu/yayawLink to the private repository after GitHub access is granted.
BILLING_CODE_ACCESS_DOWNLOAD_URLhttps://github.com/Yayaw-eu/yayaw/releasesLink to release artifacts or archive downloads.
BILLING_CODE_ACCESS_DOCUMENTATION_URLhttps://docs.yayaw.appLink to setup or usage documentation.
BILLING_CODE_ACCESS_SUPPORT_URLmailto:support@yayaw.appLink to support for access problems.

Leave a value empty when that deliverable requires manual provisioning.

GitHub Code Access

Production repository access should use a GitHub App, not a personal access token. The app lets Yayaw invite paid customers to the configured repository with read-only pull access without storing a human user's GitHub credentials.

Create the GitHub App

Create the app from the account that owns the private repository:

GitHub organization -> Settings -> Developer settings -> GitHub Apps -> New GitHub App

Recommended app values:

FieldValue
GitHub App nameYayaw Code Access
Homepage URLThe production app URL, for example https://yayaw.app
WebhookDisabled unless a future feature needs GitHub callbacks
Where can this GitHub App be installedOnly on this account

Repository permissions:

PermissionAccess
AdministrationRead and write
MetadataRead-only, automatically granted by GitHub

The Administration: Read and write permission is required because Yayaw calls the GitHub collaborator API to invite the requested username to the repository. The app does not need Contents permissions for the current flow because the customer receives normal repository access through their own GitHub account.

Install the GitHub App

After creating the app:

  1. Generate a private key from the app settings and download the .pem file.
  2. Install the app on the owner account or organization.
  3. Select only the repository that paid customers should access.
  4. Copy the App ID from the app settings.
  5. Copy the installation ID from the installation URL.

The installation URL usually looks like this for an organization install:

https://github.com/organizations/Yayaw-eu/settings/installations/12345678

The final numeric segment is the GitHub App installation ID.

Store the GitHub App secret

Store the private key in the target environment. For Vercel:

vercel env add BILLING_CODE_ACCESS_GITHUB_APP_PRIVATE_KEY production
vercel env add BILLING_CODE_ACCESS_GITHUB_APP_PRIVATE_KEY preview

The Vercel dashboard is the safest place to paste a multiline PEM value for the hosted provider. For a local .env.local or self-host secret file, either paste the multiline key in quotes or escape newlines on one line:

printf 'BILLING_CODE_ACCESS_GITHUB_APP_PRIVATE_KEY="%s"\n' "$(perl -0pe 's/\n/\\n/g' yayaw-code-access.pem)" >> .env.local

The runtime accepts escaped \n sequences and converts them back to real newlines before signing the GitHub App JWT. If a deployment platform flattens the PEM into one line with spaces between the header, body, and footer, Yayaw normalizes it back into PEM format at runtime.

Configure non-secret GitHub values

Prefer the admin UI for non-secret values:

/dashboard/admin/billing-settings

Set:

Admin settingValue
EnabledOn
RepositoryYayaw-eu/yayaw or the exact target repository
GitHub App IDThe numeric App ID from the GitHub App settings
GitHub App installation IDThe numeric installation ID from the installation URL

Environment fallbacks are available for deployments that need boot-time config:

BILLING_CODE_ACCESS_GITHUB_REPOSITORY=Yayaw-eu/yayaw
BILLING_CODE_ACCESS_GITHUB_APP_ID=123456
BILLING_CODE_ACCESS_GITHUB_APP_INSTALLATION_ID=12345678
BILLING_CODE_ACCESS_GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"

Local or staging token fallback

Use BILLING_CODE_ACCESS_GITHUB_TOKEN only for local or staging environments where a GitHub App is not available:

BILLING_CODE_ACCESS_GITHUB_REPOSITORY=Yayaw-eu/yayaw
BILLING_CODE_ACCESS_GITHUB_TOKEN=github_pat_...

The token owner must be allowed to add collaborators to the repository. For a fine-grained token, grant access only to the target repository and include the repository administration permission needed to invite collaborators. Do not use the token fallback for production unless there is an incident workaround and a rotation plan.

Email

RESEND_API_KEY enables invitation, reset, magic-link, and billing transactional emails.

To retrieve it:

  1. Verify the sending domain in Resend for production email.
  2. Create a server-side API key in Resend.
  3. Store it as RESEND_API_KEY in the target deployment environment.
  4. Send a test email from the transactional email admin surface after deploy.

Set EMAIL_SENDER to a verified sender on the Resend domain, for example team@yayaw.app. Set EMAIL_SUPPORT to the support address shown inside email templates, and EMAIL_USERNAME to the sender display name. These variables are bootstrap defaults and fallbacks; after the first admin user can sign in, Admin > Site Settings > Email takes priority at runtime.

Missing RESEND_API_KEY does not break billing webhook processing, but it does skip transactional emails.

OAuth Providers

These values are optional and only required when the matching provider is enabled:

ProviderVariablesWhere to get them
GitHub OAuthGITHUB_CLIENT_ID, GITHUB_CLIENT_SECRETGitHub OAuth App settings. This is separate from the GitHub App used for code access.
Google OAuthGOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRETGoogle Cloud Console OAuth client.
Facebook OAuthFACEBOOK_CLIENT_ID, FACEBOOK_CLIENT_SECRETMeta developer app settings.

Use the production origin from NEXT_PUBLIC_BASE_URL when configuring OAuth redirect URIs.

Object Storage

Media storage is optional until upload, thumbnail, logo, or generated-image features are used. Use STORAGE_PROVIDER=supabase for the hosted Supabase path or STORAGE_PROVIDER=s3 for MinIO, S3, R2, or another S3-compatible provider. When the provider is unset, Supabase is selected if Supabase storage env is present; otherwise S3 is selected if the S3 env set is complete.

Common variables:

VariableWhere to get it
STORAGE_PROVIDERSet to supabase or s3; leave empty for auto-detection.
STORAGE_MEDIA_BUCKETBucket name for media assets. Defaults to media.
STORAGE_PUBLIC_BASE_URLPublic object origin for S3-compatible storage, serving /<bucket>/<key>. Use the app or CDN origin only when those bucket prefixes are routed to the object store.

For same-origin or CDN-fronted S3 storage, proxy every public bucket prefix to the object store before the app fallback. The built-in public buckets are /media/* for uploaded media and /organization-logos/* for organization logos. If STORAGE_PUBLIC_BASE_URL points directly to a dedicated object-store origin, that origin must serve the same /<bucket>/<key> paths.

During setup, bun run seed uploads the default global site-variable assets to the configured media bucket under global/site-variables/* and writes those public URLs into the site-variables/main global-data entry. Existing custom site-variable asset URLs are preserved when the seed is re-run.

Supabase storage variables:

VariableWhere to get it
NEXT_PUBLIC_SUPABASE_URLSupabase project API settings.
NEXT_PUBLIC_SUPABASE_ANON_KEYSupabase project API settings. Public browser key, not a secret.
SUPABASE_SERVICE_ROLE_KEYSupabase project API settings. Secret server-side key.

S3-compatible storage variables:

VariableWhere to get it
S3_ENDPOINTProvider endpoint, for example http://minio:9000.
S3_REGIONProvider region. Use us-east-1 for MinIO unless configured otherwise.
S3_ACCESS_KEY_IDServer-side storage credential.
S3_SECRET_ACCESS_KEYServer-side storage credential.
S3_FORCE_PATH_STYLESet true for MinIO and most local S3-compatible endpoints.

Never expose SUPABASE_SERVICE_ROLE_KEY, S3_ACCESS_KEY_ID, or S3_SECRET_ACCESS_KEY to the browser or a NEXT_PUBLIC_* variable.

Public Domains

Organization public domains use PUBLIC_DOMAIN_PROVIDER.

VariablePurpose
PUBLIC_DOMAIN_PROVIDERvercel for Vercel project-domain management or manual-dns for self-hosted DNS verification.
APP_MANAGED_HOSTSComma-separated app-owned hosts that customers cannot claim.
RESERVED_PUBLIC_DOMAIN_SUFFIXESAdditional suffixes that customers cannot claim. .vercel.app is always reserved.
PUBLIC_DOMAIN_CNAME_TARGETManual DNS CNAME target shown to operators.
PUBLIC_DOMAIN_IPV4_TARGETSComma-separated manual DNS A-record targets.
PUBLIC_DOMAIN_TXT_PREFIXTXT verification prefix, default _yayaw.
VERCEL_TOKENRequired only for the Vercel provider.
VERCEL_PROJECT_IDRequired only for the Vercel provider.
VERCEL_TEAM_IDOptional Vercel team scope.

manual-dns generates a TXT ownership challenge and verifies public DNS. It does not automate DNS-provider changes or TLS certificate issuance.

OpenAI

AI generation features require:

OPENAI_API_KEY=

Create the key from the OpenAI platform API key page and store it server-side in the target deployment environment. The key enables component AI fallback, page AI generation, and image generation when the corresponding feature flags and runtime settings are on.

Optional AI runtime toggles:

VariableDefaultPurpose
OPENAI_COMPONENTS_AI_FALLBACKtrueEnables AI fallback for component inference and classification.
OPENAI_IMAGE_GENERATION_ENABLEDtrueEnables page-builder image generation when an API key exists.
OPENAI_IMAGE_MODELgpt-image-1.5Image generation model.
PAGE_AI_QUEUE_DRIVERAutoUse direct locally, vercel-queue on Vercel, or db-worker for a long-lived worker. Production auto-detects Vercel and otherwise defaults to db-worker.
PAGE_AI_DEEP_REFINEMENTfalseInternal quality toggle, not exposed in admin settings.
PAGE_AI_WORKER_POLL_MS1500Poll interval for the database worker driver.
PAGE_AI_WORKER_IDEmptyOptional identifier for a long-lived worker.

Analytics

Choose one analytics provider and one capture mode:

NEXT_PUBLIC_ANALYTICS_PROVIDER=umami
NEXT_PUBLIC_ANALYTICS_CAPTURE_MODE=hybrid

Supported providers are posthog, umami, and none. Capture mode can be hybrid, server, or client. hybrid is the default and records reliable server-side billing/auth events while keeping browser product analytics. server does not render browser analytics scripts; it sends CMS page views and conversion events from the app server instead. client keeps browser-only capture.

Server-side conversion events include checkout_started, purchase_completed, subscription_started, subscription_updated, subscription_cancel_scheduled, subscription_canceled, and payment_failed. Billing events include revenue, value, currency, plan, product_key, and Stripe IDs for reconciliation. Stripe test-mode billing events are still tracked with billing_mode=test and is_test_data=true, but they do not count as conversion, revenue, or value unless the Track Stripe Test Billing Analytics admin setting is enabled. The original test amount is kept in test_amount_cents and test_revenue for debugging.

PostHog

Client capture and feature flags use public PostHog values:

NEXT_PUBLIC_POSTHOG_KEY=
NEXT_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com
NEXT_PUBLIC_POSTHOG_ENABLE_LOCAL=false

Server-side dashboard analytics use private PostHog values:

POSTHOG_PERSONAL_API_KEY=
POSTHOG_PROJECT_ID=
POSTHOG_API_HOST=https://eu.posthog.com
POSTHOG_ORG_ID_PROPERTY=organization_id
POSTHOG_FLAG_LOOKUP_TIMEOUT_MS=

Retrieve the public project key and project ID from the PostHog project settings. Create a personal API key with enough access for the Query API and store it as POSTHOG_PERSONAL_API_KEY. Keep POSTHOG_ORG_ID_PROPERTY=organization_id unless the analytics event property changes in the product. Set POSTHOG_FLAG_LOOKUP_TIMEOUT_MS only when server-side PostHog flag lookups need a timeout other than the default 1500 milliseconds.

Umami

Client capture uses public Umami values:

NEXT_PUBLIC_UMAMI_HOST_URL=https://analytics.example.com
NEXT_PUBLIC_UMAMI_SCRIPT_URL=
NEXT_PUBLIC_UMAMI_WEBSITE_ID=
NEXT_PUBLIC_UMAMI_DOMAINS=
NEXT_PUBLIC_UMAMI_AUTO_TRACK=true

Server-side dashboard reads and server capture use private Umami values:

UMAMI_API_URL=https://analytics.example.com
UMAMI_WEBSITE_ID=
UMAMI_API_TOKEN=
UMAMI_API_KEY=
UMAMI_USERNAME=
UMAMI_PASSWORD=
UMAMI_CMS_EVENT_PAGE_SIZE=1000

Use either UMAMI_API_TOKEN (or the legacy alias UMAMI_API_KEY) or UMAMI_USERNAME plus UMAMI_PASSWORD for dashboard data reads. Server event capture uses Umami /api/send, so it only needs UMAMI_API_URL and UMAMI_WEBSITE_ID.

Control Plane MCP

Production MCP does not require an environment variable inside the app runtime. Clients connect to /api/mcp with a Better Auth API key or OAuth access token.

Local stdio testing can use:

YAYAW_MCP_API_KEY=
YAYAW_MCP_LOCAL_USER_ID=

Create, inspect, and revoke MCP keys with:

bun run mcp:key

Store trusted client keys outside the repository and pass them to the client as YAYAW_MCP_API_KEY.

Maintenance Mode

Maintenance mode is optional:

MAINTENANCE_MODE=false
MAINTENANCE_MODE_END_DATE=

Set MAINTENANCE_MODE=true only when the app should serve the maintenance surface. Use MAINTENANCE_MODE_END_DATE for operator-facing status context.

Deployment Metadata

Vercel deployments get runtime metadata from Vercel automatically. Self-hosted deployments can provide static metadata:

VariablePurpose
DEPLOYMENT_PROVIDERvercel, static, or local; empty auto-detects.
DEPLOYMENT_URLPublic deployment URL outside Vercel.
DEPLOYMENT_ENVEnvironment label, such as production or preview.
DEPLOYMENT_GIT_COMMIT_SHACommit SHA for dashboard/control-plane status.
DEPLOYMENT_GIT_COMMIT_REFCommit ref or branch name.

Deployment Checklist

Before promoting a deployment:

  1. The target environment has every required variable from .env.example or .env.self-host.example.
  2. Preview has either production-equivalent provider values or explicit staging provider values.
  3. NEXT_PUBLIC_BASE_URL matches the public HTTPS origin for the environment.
  4. Stripe has two webhook endpoints and each endpoint secret is mapped to the matching environment variable.
  5. GitHub code access has either admin settings plus BILLING_CODE_ACCESS_GITHUB_APP_PRIVATE_KEY, or explicit environment fallbacks for repository, App ID, installation ID, and private key.
  6. Resend, PostHog, Supabase/S3, OpenAI, and OAuth variables are present only when those features are enabled.
  7. Coolify or the orchestrator has the runtime DATABASE_URL used by the compose migrate service, and GitHub environments have the build-time DATABASE_URL value needed by next build.
  8. A fresh deployment or container restart was triggered after the last environment variable change.
  9. Run the local quality gates before merging deployment config changes:
bun run check
bun run docs:generate
bun run docs:check-links
bun run docs:check-translations
bun run docs:llm:check
bunx tsc --noEmit
bun run build

Provider References