Deployment
Nick has one entrypoint, nick, which runs any combination of three modules:
| Module | What it does |
|---|---|
migrate | Runs the knex migrations of core and of every package of the profiles (in dependency order), installs profiles that are not installed yet and runs the upgrade steps of installed profiles. Exits non-zero on any error. |
api | Serves the REST API on port (PORT), on every interface or only on host (BIND_HOST), for example 127.0.0.1 behind a reverse proxy on the same machine. |
worker | Runs queued jobs and schedules periodic jobs. |
nick # all modules: migrate, then api and worker
nick --modules=migrate # migrate and exit (0 on success, 1 on failure)
nick --modules=api # API only
nick --modules=worker # worker only
nick --modules=api,worker # API and worker in one process
NICK_MODULES=worker nick # the same with an environment variable
nick --modules=migrate --check-schema # also compare the schema with src/migrations/schema.snapshot.txt
In a checkout, run it with pnpm nick --modules=.... pnpm start runs the API and the worker (with nodemon, for development); pnpm migrate and pnpm seed still work as before.
migrate always runs first. When it fails, the other modules do not start and the process exits with code 1. Only one process migrates at a time (a Postgres advisory lock), so several replicas started with migrate wait for each other instead of running the same migrations twice.
Docker image
The Dockerfile builds a production image: production dependencies only (no nodemon), running as the unprivileged node user, with nick as its entrypoint and a health check on @health.
docker build -t nick .
# Migrate (exits non-zero on errors), then start the API and a worker
docker run --rm -e DB_HOST=db -e DB_PASSWORD=... -e SECRET=... nick --modules=migrate
docker run -d -p 8080:8080 -e NICK_MODULES=api -e DB_HOST=db -e DB_PASSWORD=... -e SECRET=... nick
docker run -d -e NICK_MODULES=worker -e DB_HOST=db -e DB_PASSWORD=... -e SECRET=... nick
- Configuration: environment variables (
DB_HOST,DB_PORT,DB_USER,DB_PASSWORD,DB_NAME,SECRET,PORT,BIND_HOST,ALLOWED_ORIGINS,API_RATE_LIMIT,JOBS_*,S3_*, …) or aconfig.tsmounted at/app/config.ts. LeaveBIND_HOSTunset in a container, Nick has to listen on the container’s interface to be reachable through the published port. The image runs withNODE_ENV=production, soSECRETandDB_PASSWORDare required.PROFILESlists the profiles to install, comma separated (for examplePROFILES=@plone/nick:core,@plone/nick:default); without it and without aconfig.ts, the@plone/nick:defaultprofile is installed: the demo site, with anadminuser whose password isadmin. SetPROFILESto your own profiles for a real site, or change the password right after the first start. Profiles of your own can be mounted under/app/src/profiles/<name>and listed inconfig.ts, or added in an image builtFROMthis one. Add-ons from the Nick repository can be built in with--build-arg ADDONS="packages/nick-example-addon"; projects with add-ons from npm build an image of their own project. - Modules: pass
--modules=...as the command, or setNICK_MODULES. SetNICK_MODULESwhen you split the modules: the health check passes without HTTP for processes that do not run the API. - Blobs: the default
filestorage writes to/app/var/blobstorageinside the container. Mount a volume there, or usedbor S3-compatible storage when you run more than one API replica. - TypeScript: Nick runs from its TypeScript sources with the
tsxloader (node --import tsx). The image compiles the sources once while it is built, so processes start from a warm cache (about 2.5 s to a listening API).
Small sites: one process
Run nick (all modules) as the only process. It migrates on start, serves the API and runs the jobs.
Larger sites: split the modules
- Run
nick --modules=migrateonce per deployment, before the new version starts (for example as a Kubernetes Job or an init container). Stop the deployment if it fails. - Run as many
nick --modules=apireplicas behind a load balancer as you need. The API keeps no state in the process that other replicas need: tokens, locks, jobs and job aborts are in the database. SetRATE_LIMIT_STORE=postgresso rate limits count across replicas (Limits);@healthreports on the replica that answers (Health). Store blobs indbor S3, not on the local disk. - Run one or more
nick --modules=workerreplicas. Workers share the queue safely; a periodic job runs once per time slot however many workers run.
The processes stop gracefully on SIGTERM: the API stops accepting connections and the worker finishes the job it is running.
Upgrades
migrate upgrades installed profiles step by step (upgrades/<version>/ of each profile). Every step runs in its own transaction, which also stores the step’s number as the profile version. When a step fails, its changes are rolled back, the profile stays at the previous step, migrate exits with code 1, and the next run starts again at the step that failed. Upgrade steps must therefore finish within one transaction; split large data changes into several steps or into jobs.
pnpm seed:upgrade runs the same upgrade steps without the knex migrations.
CI bootstraps the version before each push and then migrates it to the pushed version twice, so a broken or non-repeatable migration or upgrade step fails the build.