obsidian/wiki/payloadcms/production-deployment.md
2026-05-15 16:29:19 +01:00

6.2 KiB

title aliases tags sources created updated
Production Deployment
payload-deployment
payload-production
payloadcms
deployment
docker
production
database
file-storage
raw/production__deployment.md
2026-05-15 2026-05-15

Overview

Payload runs fully inside Next.js — deploy anywhere Next.js runs (Vercel, Netlify, DigitalOcean, AWS, Cloudflare Workers, SST, self-hosted). Every project also needs:

  • A database (Postgres or MongoDB)
  • File storage (persistent or cloud)
  • An email provider
  • Optionally a CDN

Basics

Build via the build npm script (wraps next build). Start via next start. This is not next dev.

npm run build   # or pnpm build
npm run start   # next start

Security Checklist

  • Secret key — must be long, complex, impossible to brute-force. Never commit to git.
  • Access Control — audit every collection before going live. By default all CRUD requires a logged-in user. Public registration loosens this — tighten explicitly.
  • Secure cookies — enable when using SSL (production always should).
  • Anti-abuse — built-in: login lockout after N failures, GraphQL complexity limits, max depth. See wiki/payloadcms/preventing-abuse.

Database

Set DATABASE_URL env var. Supported:

Engine Notes
Postgres Preferred; full feature support
MongoDB Mongoose adapter
AWS DocumentDB Configure connectOptions in mongooseAdapter
Azure Cosmos DB Use compatibilityOptions.cosmosdb preset (see below)

Cosmos DB Compatibility Preset

import { mongooseAdapter, compatibilityOptions } from '@payloadcms/db-mongodb'

export default buildConfig({
  db: mongooseAdapter({
    url: process.env.DATABASE_URL,
    ...compatibilityOptions.cosmosdb,
    indexSortableFields: true,
  }),
})
Option Value Purpose
bulkOperationsSingleTransaction true One-at-a-time bulk ops (Cosmos limits)
transactionOptions false Multi-doc transactions unsupported
useJoinAggregations false Multiple finds instead of subqueries
usePipelineInSortLookup false Disable $lookup pipeline in sort

File Storage

Ephemeral vs Persistent Filesystems

Ephemeral (uploads lost on restart) Persistent
Heroku DigitalOcean Droplets
DigitalOcean Apps Amazon EC2
GoDaddy

If using uploads on ephemeral hosts → must use a cloud storage plugin.

Official Cloud Storage Adapters

  • @payloadcms/storage-s3 — AWS S3
  • @payloadcms/storage-azure — Azure Blob Storage
  • @payloadcms/storage-gcs — Google Cloud Storage
  • @payloadcms/storage-vercel-blob — Vercel Blob Storage
  • @payloadcms/storage-uploadthing — Uploadthing

See wiki/payloadcms/upload for adapter config details.

Docker

next.config.js

const nextConfig = {
  output: 'standalone',
}

Multi-Stage Dockerfile

FROM node:24-alpine AS base

FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm ci; \
  elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
  else echo "Lockfile not found." && exit 1; \
  fi

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN \
  if [ -f yarn.lock ]; then yarn run build; \
  elif [ -f package-lock.json ]; then npm run build; \
  elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
  else echo "Lockfile not found." && exit 1; \
  fi

FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
RUN mkdir .next && chown nextjs:nodejs .next
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD HOSTNAME="0.0.0.0" node server.js

Required env vars at runtime: PAYLOAD_SECRET, PAYLOAD_CONFIG_PATH, DATABASE_URL.

Docker Compose (Development)

version: '3'
services:
  payload:
    image: node:24-alpine
    ports:
      - '3000:3000'
    volumes:
      - .:/home/node/app
      - node_modules:/home/node/app/node_modules
    working_dir: /home/node/app/
    command: sh -c "corepack enable && corepack prepare pnpm@latest --activate && pnpm install && pnpm dev"
    depends_on:
      - mongo
    env_file:
      - .env

  mongo:
    image: mongo:latest
    ports:
      - '27017:27017'
    command: --storageEngine=wiredTiger
    volumes:
      - data:/data/db
    logging:
      driver: none

volumes:
  data:
  node_modules:

For Postgres: uncomment the postgres service and use DATABASE_URL=postgres://....

Key Takeaways

  • Payload deploys anywhere Next.js does — the build process is next build.
  • Always use a long, random secret key in production; double-check access control before launch.
  • Ephemeral filesystems (Heroku, DigitalOcean Apps) lose uploads on restart — use S3/GCS/Azure instead.
  • Use output: 'standalone' in next.config.js for Docker builds.
  • The multi-stage Dockerfile works across npm/yarn/pnpm automatically via lockfile detection.
  • Azure Cosmos DB needs compatibilityOptions.cosmosdb spread into mongooseAdapter for reliable operation.
  • For Docker Compose dev: database hostname in DATABASE_URL must match the service name (e.g. mongo).

Sources