Overview
What deploying a Crudy app looks like end-to-end.
A production Crudy deployment is three containers (backend, frontend, postgres), one private container (postgrest), a license file, and a small set of environment variables. The framework ships Dockerfiles and a compose file as the canonical reference.
What you ship
A deployment artifact is the union of:
- A built backend image (Node 24 Alpine,
node dist/server.js). - A built frontend image (Next.js standalone output,
node server.js). - A managed PostgreSQL with the
pgmorbacextension installed. - A PostgREST instance reachable only by the backend.
- A
license.licand matchinglicense-public.pemissued by Crudy. - Environment variables for database, JWT secret, license file paths, and any module-required secrets.
Build
Inside the app folder:
docker build -f backend/Dockerfile -t my-app-backend .
docker build -f frontend/Dockerfile -t my-app-frontend ./frontendThe backend image runs tsc then drops production-only dependencies into a final stage; the frontend image leverages Next.js' standalone output (set by createNextConfig, which the scaffolded next.config.ts calls).
Run
The simplest path is the compose file generated by crudy create:
docker compose up -dIt starts Postgres, PostgREST, the backend, and the frontend on a private bridge network. The backend exposes port 3000, the frontend exposes port 3001. PostgREST has no published port.
Required environment
At minimum the backend needs:
DB_HOST,DB_USER,DB_PASSWORD,DB_NAMEfor PostgreSQL.POSTGREST_URLpointing at the internal PostgREST.JWT_SECRET(>= 32 chars, high entropy).LICENSE_FILEandLICENSE_PUBLIC_KEY_FILE(orLICENSE_PUBLIC_KEY).ADMIN_EMAIL(defaults toadmin@localhost).- Any
requiredSecretsdeclared by your modules.
See Environment Variables for the full reference.
License placement
Mount license.lic and license-public.pem into the backend container and set LICENSE_FILE and LICENSE_PUBLIC_KEY_FILE to their in-container paths. The compose file generated by the CLI does this at /app/license.lic and /app/license-public.pem.
The backend verifies the license cryptographically at boot and enforces its expiry at runtime via a per-request guard and a self-terminate timer. If the file is missing, expired, or its audience does not match app.config.json, the process exits with a non-zero status. See Licensing for the full lifecycle.
Migrations
Migrations are not run automatically by the production image. Apply them from a one-shot container or a CI step:
docker run --rm \
--network <your-network> \
-e DB_HOST=postgres -e DB_USER=app -e DB_PASSWORD=... -e DB_NAME=app \
my-app-backend node dist/scripts/migrate.js(Adapt to wherever your migration script lives in your build.)
In dev, the generated app Makefile ships make dev-reset (drop volumes) and make dev-up (restart). Migrations re-run automatically at backend boot against the fresh database.
Health checks
- Backend:
GET /healthreturns{"status":"ok","db":"connected"}when the database is reachable. - Frontend:
GET /returns the home page. - Postgres:
pg_isready -U <db-user>.
The generated app Makefile has a make health target that runs all three at once against the local stack.
What is intentionally not in the framework
The framework does not ship:
- A managed control plane.
- A hosted license server.
- Automatic schema migrations on boot for production images.
- Per-customer secret rotation.
These are deployment concerns; pick the tooling your organization already uses.