Docker
The project includes a production-ready multi-stage Dockerfile and a docker-compose.yml.
Build the Image
docker build -t buildpad-daas:latest .Run with Docker Compose
Copy .env.local.example to .env and fill in your Supabase credentials, then:
docker-compose up -dThe app is available at http://localhost:3000.
Environment Variables
Pass environment variables via a .env file or directly:
docker run -d \
-p 3000:3000 \
-e NEXT_PUBLIC_SUPABASE_URL=https://xyz.supabase.co \
-e NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key \
-e SUPABASE_SERVICE_ROLE_KEY=your-service-role-key \
buildpad-daas:latestIn Docker/Kubernetes environments where Supabase runs as a sidecar or internal service, use SUPABASE_INTERNAL_URL so the server connects directly without going through the public load balancer:
docker run -d \
-p 3000:3000 \
-e NEXT_PUBLIC_SUPABASE_URL=https://xyz.supabase.co \
-e SUPABASE_INTERNAL_URL=http://supabase-kong:8000 \
-e NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key \
-e SUPABASE_SERVICE_ROLE_KEY=your-service-role-key \
buildpad-daas:latestThe browser still uses NEXT_PUBLIC_SUPABASE_URL. All server-side code — including cookie-based session requests — uses SUPABASE_INTERNAL_URL. Cookie names are automatically kept in sync with NEXT_PUBLIC_SUPABASE_URL so browser sessions work correctly.
Startup Entrypoint
The docker-entrypoint.sh script runs database migrations automatically on container start before starting the cluster launcher (cluster.js). This ensures the schema is always up to date without manual intervention.
Migrations are idempotent — running them on a database that’s already up to date is safe.
Multi-Process Cluster
The image starts node cluster.js (not node server.js). The launcher forks one worker per available core; workers share port 3000 and the primary distributes connections between them. Set WEB_CONCURRENCY to cap the worker count.
Each worker is a full Next.js server (~400 MB resident under load), so size container memory at roughly workers × 400–500 MB. If the container’s memory limit is below cores × 400 MB, set WEB_CONCURRENCY explicitly — otherwise the default forks more workers than the limit can hold and the container OOM-loops on startup (e.g., a 1 GB limit → WEB_CONCURRENCY=2).
Crashed workers restart automatically, and the cron scheduler runs on a single leader worker with failover if that worker dies.
Known limitation: the in-memory log buffer behind the Logs page is per worker, so /logs shows only one worker’s output.
Health Check
The Docker Compose configuration includes a health check that polls /api/health every 30 seconds.
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/api/health', ...)"]
interval: 30s
timeout: 3s
retries: 3Building the Docs Site
The documentation site is built into the image automatically (Dockerfile stage docs-builder) and served by the app at /docs. The separate nginx docs service in docker-compose.yml (profile standalone-docs, port 3001) is only needed to serve the docs as a standalone site — see AWS Deployment.