Everything needed to build and run this on Dokploy at linkdr.serfaty.site, plus the two things that turned out to be broken the moment it left a laptop. The build did not work in a container at all. `lib/auth.ts` throws when AUTH_SECRET or NEXT_PUBLIC_APP_URL is missing — correct at boot, wrong during `next build`, which imports every route module with NODE_ENV=production and none of the runtime secrets. The only way past it was baking a session key into an image layer, which is worse than the problem the guard exists to prevent. Both checks now skip NEXT_PHASE=phase-production-build and still fire on a real boot. Corepack in node:22.12-alpine ships expired npm registry signing keys and dies before it can download pnpm, so the image installs corepack first and prepares the pinned version explicitly. The image is the standalone trace, which needs outputFileTracingRoot at the REPO root: pnpm hoists to a root .pnpm store and tracing from apps/web silently omits every workspace package. 427MB, runs as non-root, and its healthcheck talks to Postgres — a container that cannot reach its database must never enter rotation, because a deploy that goes green and then 500s does not roll back. DEMO_LOGIN is a login bypass under NODE_ENV=production and there is no honest way to describe it otherwise. It is a separate variable from ALLOW_DEV_LOGIN so that copying a dev .env into a real environment cannot enable it by accident, it still only affects the one seeded number, and it prints a boot warning every single start so it cannot be forgotten. That deployment holds nothing but fixtures. It comes out before the platform sees a real signup. Also: /api/health, and next/image hosts corrected to the Spaces bucket rather than the R2 one this stopped using. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
123 lines
4.7 KiB
Bash
123 lines
4.7 KiB
Bash
# ---- Core ----
|
|
NODE_ENV=development
|
|
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
|
|
|
# ---- Database (Postgres 16 + PostGIS) ----
|
|
DATABASE_URL=postgresql://linkder:linkder@localhost:5442/linkder
|
|
# Managed Postgres only. Verifies the server's IDENTITY, not merely that the
|
|
# link is encrypted — sslmode=require alone leaves you open to anything that can
|
|
# answer for the hostname. Takes a path to the provider's .crt, or the PEM
|
|
# inline for a platform whose secrets are environment variables.
|
|
DATABASE_CA_CERT=
|
|
|
|
# ---- Redis (pub/sub for SSE chat + BullMQ queues) ----
|
|
REDIS_URL=redis://localhost:6389
|
|
|
|
# ---- Auth.js v5 ----
|
|
# generate with: openssl rand -base64 32
|
|
AUTH_SECRET=
|
|
AUTH_URL=http://localhost:3000
|
|
# Social sign-in. Each provider is optional and independent — leave a pair
|
|
# blank and phone OTP still works. The buttons render either way and tell the
|
|
# user when a provider is not set up, so the screen never changes shape between
|
|
# environments. Set BOTH values of a pair or neither: a half-set pair is treated
|
|
# as unset (see lib/auth.ts).
|
|
#
|
|
# Authorised redirect URI: {NEXT_PUBLIC_APP_URL}/api/auth/callback/google
|
|
AUTH_GOOGLE_ID=
|
|
AUTH_GOOGLE_SECRET=
|
|
|
|
# Microsoft Entra ID (Azure AD). Register an app at
|
|
# https://entra.microsoft.com > App registrations, add a Web platform with
|
|
# redirect URI {NEXT_PUBLIC_APP_URL}/api/auth/callback/microsoft, then create a
|
|
# client secret under Certificates & secrets.
|
|
#
|
|
# TENANT_ID decides WHO may sign in and defaults to `common`:
|
|
# common work, school and personal Microsoft accounts
|
|
# organizations work and school only
|
|
# consumers personal only
|
|
# <tenant guid> one organisation only
|
|
# For a consumer marketplace `common` is almost always what you want — set the
|
|
# app registration's supported account types to match, or sign-in fails at
|
|
# Microsoft's end with AADSTS50194 no matter what is set here.
|
|
AUTH_MICROSOFT_ID=
|
|
AUTH_MICROSOFT_SECRET=
|
|
AUTH_MICROSOFT_TENANT_ID=common
|
|
|
|
# GitHub. Create an OAuth app at
|
|
# https://github.com/settings/developers > New OAuth App, with
|
|
# Authorization callback URL {NEXT_PUBLIC_APP_URL}/api/auth/callback/github.
|
|
#
|
|
# GitHub only returns a primary email if the OAuth app requests `user:email`
|
|
# AND the account has a verified one; a user whose email is private signs up
|
|
# with no address, so never assume `users.email` is reachable mail — gate
|
|
# outbound on isSyntheticEmail() from @linkdr/shared, same as phone signups.
|
|
AUTH_GITHUB_ID=
|
|
AUTH_GITHUB_SECRET=
|
|
|
|
# ---- Geocoding (Mapbox) ----
|
|
# Turns a typed address into the coordinates the deck matches on. Without it,
|
|
# every job and every pro base falls back to the city centre and is stored with
|
|
# location_precision='city' — honest, but unmatched: ST_Distance measures a
|
|
# constant and ST_DWithin passes everyone.
|
|
#
|
|
# The token MUST be entitled for PERMANENT geocoding. We store the coordinates
|
|
# indefinitely because they are the matching primitive, and Mapbox's temporary
|
|
# endpoint forbids persistence — every request sets permanent=true, so a token
|
|
# without that entitlement returns 401/403 rather than silently working.
|
|
MAPBOX_TOKEN=
|
|
# ISO 3166-1 alpha-2. Bounds results to one country: "Carrer de Sants" matches
|
|
# in several places and the wrong continent is a worse answer than none.
|
|
MAPBOX_COUNTRY=mx
|
|
|
|
# ---- Phone OTP (Twilio Verify) ----
|
|
TWILIO_ACCOUNT_SID=
|
|
TWILIO_AUTH_TOKEN=
|
|
TWILIO_VERIFY_SERVICE_SID=
|
|
|
|
# ---- Stripe Connect ----
|
|
STRIPE_SECRET_KEY=
|
|
STRIPE_WEBHOOK_SECRET=
|
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
|
|
# Platform commission in basis points (1500 = 15%)
|
|
PLATFORM_FEE_BPS=1500
|
|
|
|
# ---- Didit (ID verification) ----
|
|
DIDIT_API_KEY=
|
|
DIDIT_WORKFLOW_ID=
|
|
DIDIT_WEBHOOK_SECRET=
|
|
|
|
# ---- Resend (transactional email) ----
|
|
RESEND_API_KEY=
|
|
EMAIL_FROM=noreply@linkdr.app
|
|
|
|
# ---- Launch market (city-scoped MVP) ----
|
|
NEXT_PUBLIC_CITY_NAME=Ciudad de México
|
|
NEXT_PUBLIC_CITY_LAT=19.4326
|
|
NEXT_PUBLIC_CITY_LNG=-99.1332
|
|
TWILIO_FROM_NUMBER=
|
|
|
|
# Dev-only fixed login (+52 55 0000 0000 / code 000000). Ignored entirely when
|
|
# NODE_ENV=production, so this cannot leak a bypass into a real deployment.
|
|
ALLOW_DEV_LOGIN=false
|
|
|
|
# The SAME fixed login, deliberately allowed in a production build, for the
|
|
# client-demo deployment only. Separate from ALLOW_DEV_LOGIN so that copying a
|
|
# developer's .env into a real environment cannot switch it on by accident.
|
|
#
|
|
# Only +52 55 0000 0000 is affected; every other number still goes through
|
|
# Twilio. Prints a loud warning on every boot. MUST be unset before this
|
|
# platform accepts a real signup — see server/dev-login.ts.
|
|
DEMO_LOGIN=false
|
|
|
|
# Bugsink (Sentry-compatible error tracking). Write-only ingest key, safe in the
|
|
# client bundle. Leave blank to disable reporting entirely.
|
|
NEXT_PUBLIC_SENTRY_DSN=
|
|
|
|
# ---- Object storage (DigitalOcean Spaces) ----
|
|
SPACES_REGION=nyc3
|
|
SPACES_BUCKET=
|
|
SPACES_KEY=
|
|
SPACES_SECRET=
|
|
SPACES_CDN_URL=
|