On a server or a platform
Any platform that runs a Dockerfile or a container image works. The steps are the same everywhere; only the names of the settings differ.
1. Build the image #
Either let the platform build from the repository's Dockerfile, or build in CI and push the image to a registry the platform pulls from. Building needs about 4 GB of memory, so a small server is better off pulling a ready image.
2. Get a database #
A managed MongoDB or PostgreSQL service, or a database container on the same server. Keep it private to the site: only the container needs to reach it. See The database.
3. Set the environment #
At least:
DATABASE_URL=mongodb://…
PAYLOAD_SECRET=… # openssl rand -hex 32, kept the same from now on
NEXT_PUBLIC_SERVER_URL=https://www.example.com
Then the optional services you use: SMTP for email, ANTHROPIC_API_KEY for AI translation, and so on (Environment variables). Use the platform's secret settings for secrets.
4. Mount the uploads volume #
Give the container a persistent volume at /app/uploads. Without one, every uploaded image and file is lost when the container is replaced. The image creates one folder per upload collection inside it.
A platform without persistent disks needs the uploads in a bucket instead. The kit puts them on Vercel Blob when BLOB_READ_WRITE_TOKEN is set (see On Vercel); another storage plugin, such as Payload's S3 adapter, can take its place in src/plugins/storage.ts. Every upload collection must be listed in it, the media library and translation-files alike.
5. Route traffic and HTTPS #
The container listens on port 3000 (or PORT). Point the platform's domain or your reverse proxy at it and let that handle HTTPS. The site address in NEXT_PUBLIC_SERVER_URL must match the address visitors use.
6. Health check #
Use GET /api/health: 200 while the server and the database are up, 503 when the database does not answer. The image has a HEALTHCHECK with it built in.
7. Background jobs #
Nothing to set up for one container: the server runs scheduled publishing and the other background work itself, once a minute. Running more than one container, or a platform that stops idle containers? Read Background jobs.
8. First start #
Open /admin on the new site. On an empty database it asks for the first user, who becomes the admin.
Search engines are welcome by default. On a staging copy, set Site settings → Behaviour → Search engines to "Keep out", which changes robots.txt; the demo seed does that itself.
Updating #
Deploy a new image with the same environment and the same volume. Logins survive as long as PAYLOAD_SECRET stays the same. On PostgreSQL, the image applies new migrations when it starts.
Back up both the database and the uploads volume: the database names the files, the volume holds them.
Platform notes #
- Coolify: a "Dockerfile" resource (or a "Docker image" one, from your registry). Add the variables under Environment Variables, a persistent storage mounted at
/app/uploads, and/api/healthas the health check path. - Railway: a service from the repository or from an image. Add the variables, and a volume mounted at
/app/uploads. - A VPS with Docker Compose: take the
appservice fromdocker-compose.ymlinto the server's compose file, with a managed database or a database service beside it, and a reverse proxy (Caddy, Traefik, nginx) in front for HTTPS. - Without Docker:
pnpm install,pnpm buildandpnpm startwith Node 22, under a process manager. Uploads go touploads/in the working directory, so keep that folder across releases.