Files
bmp-website-2026/docs/vps-deployment.md
2026-07-13 12:19:45 +02:00

4.2 KiB

VPS deployment

The live VPS deployment runs this Payload/Next app with Docker Compose and Caddy.

Current default target:

https://srv1.bayerischer-mittelstandspreis.de

The app is intentionally split into committed code and uncommitted runtime state.

Server Layout

/var/www/bmp-website/
  current/          # synced application code
  shared/
    .env            # production secrets, never committed
    content.db      # live SQLite database, never committed
    media/          # live Payload uploads, never committed
  backups/          # server-side DB backups

The Docker Compose file lives in the repo at:

deploy/vps/docker-compose.yml

It starts:

  • bmp_website: the Next/Payload app
  • bmp_caddy: Caddy reverse proxy on ports 80 and 443

Caddy reuses the certificate volumes from the former n8n deployment:

n8n-docker_caddy_data
n8n-docker_caddy_config

The old n8n containers and volumes may still exist on the server, but are stopped after the BMP deployment takes over public traffic.

What Belongs In Git

Commit:

  • application code
  • Payload schema/config changes
  • Dockerfile
  • .dockerignore
  • deploy/vps/docker-compose.yml
  • deployment scripts

Do not commit:

  • .env
  • content.db
  • public/media/
  • production backups
  • any secrets

Deploying Code

Normal deploys should be code-only. The live production database and uploads on the VPS are authoritative and must not be overwritten by routine deploys.

From a clean, committed worktree:

pnpm deploy:vps

The deploy script:

  1. refuses to deploy a dirty worktree
  2. rsyncs code to /var/www/bmp-website/current
  3. excludes runtime state, dependency caches, generated files, and local agent tooling
  4. writes the deployed Git revision to .deploy-revision
  5. builds the Docker image on the VPS
  6. recreates only the bmp_website app container
  7. verifies / and /admin

The required workflow is:

commit locally -> deploy that commit -> verify production

Configuration

The scripts default to the current VPS:

SSH_USER=syntaxbullet
VPS_HOST=srv1.bayerischer-mittelstandspreis.de
VPS_PORT=22
SSH_KEY=~/.ssh/bmp-vps-deploy
APP_ROOT=/var/www/bmp-website
APP_DOMAIN=srv1.bayerischer-mittelstandspreis.de

Override any value inline when needed:

APP_DOMAIN=www.example.com pnpm deploy:vps

If the Caddy container also needs to be recreated:

DEPLOY_CADDY=1 pnpm deploy:vps

Payload Migrations

Routine deploys do not run migrations. For schema-changing commits, create and commit the Payload migration files, then deploy with migrations enabled:

RUN_MIGRATIONS=1 pnpm deploy:vps

That mode:

  1. syncs the committed code
  2. builds the new Docker image
  3. stops the app container
  4. copies shared/content.db to backups/content-*-pre-migrate.db
  5. runs pnpm payload migrate in a one-off app container
  6. starts the app container again

To run migrations against the already deployed code/image without syncing code:

pnpm vps:migrate

Run migrations only when the commit includes migration files under src/migrations/. For plain frontend/content-rendering changes, use the normal pnpm deploy:vps path.

Status And Logs

pnpm vps:status

This prints:

  • deployed revision
  • running containers
  • old n8n container status
  • shared DB/media sizes
  • disk usage
  • recent app and Caddy logs

Backing Up SQLite

Create a server-side SQLite backup before schema-sensitive deploys:

pnpm vps:backup-db

This briefly stops the app container, copies /var/www/bmp-website/shared/content.db into /var/www/bmp-website/backups/, then starts the app container again. Caddy remains running.

Rollback To Old n8n

The old n8n compose project was intentionally not deleted. If you need to replace the BMP site with the previous n8n service:

CONFIRM=replace-bmp-with-n8n pnpm vps:rollback:n8n

This stops the BMP Caddy/app containers and starts /root/n8n-docker.

Production State Caution

After launch, CMS edits and uploads happen on the VPS. Do not sync local content.db or public/media/ over production unless you are intentionally restoring or replacing production content and have a current backup.