Deployment

Guidely is a standard Next.js application plus a PostgreSQL database and an optional worker.

Container

docker build -f apps/web/Dockerfile -t guidely-web .
docker run -p 3000:3000 \
  -e DATABASE_URL=postgresql://user:pass@host:5432/guidely?schema=public \
  -e SESSION_SECRET=$(openssl rand -base64 48) \
  -e APP_URL=https://guidely.example.com \
  -e NEXT_PUBLIC_GUIDELY_API_URL=https://guidely.example.com \
  guidely-web

The image's entrypoint runs prisma migrate deploy and then the server; run the worker from the same image with the worker command and seed demo data with seed. DATABASE_URL can also be composed from DB_HOST/DB_PORT/DB_NAME/DB_USER/DB_PASSWORD.

docker compose --profile full up runs Postgres + web locally from the compose file.

For AWS (ECS Fargate + RDS + public ALB + GitHub organization login) use the CDK app in infra/, documented in aws-deployment.md.

Run migrations before each release: pnpm db:deploy (or npx prisma migrate deploy with the schema in the image at apps/web/prisma).

Run the worker as a second process/container with the same image and node_modules: pnpm worker (it is a plain Node process using pg-boss; one replica is enough, more are safe).

Platforms

  • Vercel / Netlify (dashboard + API): set env vars, build command pnpm build:packages && pnpm --filter @guidelytech/web build, output directory apps/web/.next. Run the worker elsewhere (Railway/Fly/ECS cron) since serverless has no long-lived process.
  • Fly.io / Railway / Render / ECS / Kubernetes: use the Dockerfile; mount nothing; add a Postgres and (optionally) Redis.
  • Put the SDK bundle (apps/web/public/sdk/guidely.js) on a CDN and set NEXT_PUBLIC_GUIDELY_CDN_URL for faster customer loads.

Operational checklist

  • Health: GET /api/health returns { status, db: { ok, latencyMs }, metrics } (503 when the DB is down).
  • Logs: JSON lines on stdout; set LOG_LEVEL.
  • Errors: wire captureException in apps/web/src/server/logger.ts to Sentry/Datadog.
  • Metrics: incr() counters are exposed on the health endpoint; forward them to StatsD/Prometheus from there.
  • Secrets: never in the image; inject at runtime.
  • CORS: configure allowed origins per environment in Settings → Environments; leave empty only for development.
  • Backups: use managed Postgres PITR or pg_dump --format=custom guidely > backup.dump daily; restore with pg_restore --clean --if-exists -d guidely backup.dump. Immutable GuideVersion rows make restoring a single guide possible by re-pointing GuideEnvironmentState.
  • Rollback a release: deploy the previous image; migrations are additive (never drop columns in the same release that stops writing them).
  • Scaling: the web tier is stateless (sessions in Postgres, caches in Redis when configured). Event ingestion writes in batches with createMany … skipDuplicates; at roughly >50M events/month move EventIngestService behind a queue and reporting onto rollups/columnar storage (see architecture doc).