Skip to content

Self-host with Docker

View markdown

docker-compose.yml in the repository starts two services: PostgreSQL 16 (db) and the application (app, port 3000). Migrations run automatically every time the app starts.

Three named volumes keep data across docker compose down:

VolumeContents
postgres_dataThe database
media_dataUploaded images and files (/app/media) for sites without Cloudflare R2
app_dataApplication data, including the generated PAYLOAD_SECRET

docker compose down -v removes all three.

Set them in the environment or in a .env file next to docker-compose.yml. Running locally needs none of them.

VariableDefaultMeaning
PAYLOAD_PUBLIC_SERVER_URLhttp://localhost:3000Public address of the CMS as browsers see it; also the prefix of media URLs
PAYLOAD_SECRETempty (generated once)Session signing secret. Set your own on a server, at least 32 characters
POSTGRES_PASSWORDkdigiDatabase password
APP_PORT / DB_PORT3000 / 5432Host ports. The database listens on 127.0.0.1 only
SEED_DEMOemptytrue loads the demo site (safe to re-run)
SKIP_MIGRATIONSemptytrue skips the automatic migration step at container start

The full list, including AI SEO, comment reCAPTCHA, OpenTelemetry and the WordPress importer, is in the repository's .env.example and in the configuration reference.

  1. Set PAYLOAD_PUBLIC_SERVER_URL to the real https:// address of the CMS and PAYLOAD_SECRET to a long random string (openssl rand -hex 32).
  2. Change POSTGRES_PASSWORD from the default.
  3. Put a TLS reverse proxy (Traefik, Caddy, nginx) in front of port 3000.
  4. Keep the volumes so updating the image does not lose data.

If you use Coolify, read Deploy on Coolify.

Running the app outside Docker (development)

Section titled “Running the app outside Docker (development)”

Start only the database with Compose and run Next.js on your machine:

Terminal window
docker compose up db
cp .env.example .env
pnpm install
pnpm migrate
pnpm dev

GET /api/health returns 200 while the process is serving HTTP. It does not query the database, so do not treat it as proof that the schema is ready.