Last updated on 7th March 2026

Deploy Payload CMS with DeployHQ

Prerequisites:

  • Node.js 18 or later
  • MongoDB or PostgreSQL
  • SSH access to your server
  • A DeployHQ account

What is Payload CMS?

Payload is a headless CMS built with TypeScript and Node.js. It's code-first: you define collections, fields, and access control in TypeScript config files — no GUI-based schema building. Payload auto-generates an admin panel, REST API, and GraphQL API from your configuration.

Payload 3.0 is built on Next.js, combining the CMS backend and frontend into a single application.

Step 1: Create a Payload Project

npx create-payload-app@latest my-cms
cd my-cms
npm run dev

The admin panel runs at http://localhost:3000/admin.

Step 2: Project Structure

my-cms/
├── src/
│   ├── app/                    # Next.js app directory
│   │   ├── (frontend)/         # Public-facing pages
│   │   └── (payload)/          # Admin panel routes
│   ├── collections/            # Content type definitions
│   │   ├── Posts.ts
│   │   ├── Pages.ts
│   │   ├── Media.ts
│   │   └── Users.ts
│   ├── globals/                # Singleton content types
│   │   ├── Header.ts
│   │   └── Footer.ts
│   ├── blocks/                 # Reusable content blocks
│   └── payload.config.ts       # Main Payload configuration
├── public/                     # Static assets
├── media/                      # Uploaded files (if local storage)
├── next.config.mjs
├── tsconfig.json
├── package.json
└── .env                        # Environment variables

Step 3: Configuration

// src/payload.config.ts
import { buildConfig } from 'payload'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
// or: import { postgresAdapter } from '@payloadcms/db-postgres'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import { s3Storage } from '@payloadcms/storage-s3'

import { Posts } from './collections/Posts'
import { Pages } from './collections/Pages'
import { Media } from './collections/Media'
import { Users } from './collections/Users'

export default buildConfig({
  admin: {
    user: Users.slug,
  },
  collections: [Posts, Pages, Media, Users],
  editor: lexicalEditor(),
  db: mongooseAdapter({
    url: process.env.DATABASE_URI!,
  }),
  plugins: [
    s3Storage({
      collections: { media: true },
      bucket: process.env.S3_BUCKET!,
      config: {
        region: process.env.S3_REGION!,
        credentials: {
          accessKeyId: process.env.S3_ACCESS_KEY!,
          secretAccessKey: process.env.S3_SECRET_KEY!,
        },
      },
    }),
  ],
})

Collection Definition

// src/collections/Posts.ts
import type { CollectionConfig } from 'payload'

export const Posts: CollectionConfig = {
  slug: 'posts',
  admin: {
    useAsTitle: 'title',
  },
  access: {
    read: () => true,
    create: ({ req: { user } }) => !!user,
    update: ({ req: { user } }) => !!user,
    delete: ({ req: { user } }) => !!user,
  },
  fields: [
    { name: 'title', type: 'text', required: true },
    { name: 'slug', type: 'text', unique: true },
    { name: 'content', type: 'richText' },
    { name: 'publishedDate', type: 'date' },
    { name: 'status', type: 'select', options: ['draft', 'published'] },
    {
      name: 'author',
      type: 'relationship',
      relationTo: 'users',
    },
    {
      name: 'featuredImage',
      type: 'upload',
      relationTo: 'media',
    },
  ],
}

Step 4: Core Features

  • Auto-generated Admin UI — CRUD interface for all collections
  • REST APIGET /api/posts, POST /api/posts, etc.
  • GraphQL API — Full GraphQL endpoint with auto-generated schema
  • Authentication — Built-in user authentication with role-based access
  • File Uploads — Local storage, S3, or other cloud storage
  • Hooks — Before/after change, validate, and access control hooks
  • Localization — Built-in i18n for content fields
  • Versions & Drafts — Content versioning and draft/publish workflow

Step 5: Build for Production

npm run build

Payload 3.0 builds as a Next.js application. Output goes to .next/.

Step 6: Deploy with DeployHQ

Build Command

cd %path% && npm ci && npm run build

PM2 Configuration

Create ecosystem.config.cjs:

module.exports = {
  apps: [{
    name: 'payload-cms',
    script: 'node_modules/.bin/next',
    args: 'start',
    instances: 'max',
    exec_mode: 'cluster',
    env_production: {
      NODE_ENV: 'production',
      PORT: 3000,
    },
    max_memory_restart: '1G',
  }]
}

Post-Deployment SSH Command

cd /var/www/payload-cms && npm ci --omit=dev && pm2 reload ecosystem.config.cjs --env production

Environment Variables

Set on the server in .env (never commit to Git):

DATABASE_URI=mongodb://localhost:27017/payload-cms
PAYLOAD_SECRET=a-long-random-string
NEXT_PUBLIC_SERVER_URL=https://cms.example.com

# S3 storage (if using)
S3_BUCKET=my-uploads
S3_REGION=eu-west-1
S3_ACCESS_KEY=AKIA...
S3_SECRET_KEY=...

Nginx Reverse Proxy

server {
    listen 443 ssl http2;
    server_name cms.example.com;

    client_max_body_size 100M;  # For file uploads

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Media Storage

For production, use S3 or similar cloud storage for uploads. If using local storage, configure media/ as a shared path in DeployHQ's zero-downtime deployment settings.

Step 7: Troubleshooting

Build Fails with Memory Error

Payload + Next.js builds can be memory-intensive: bash NODE_OPTIONS="--max-old-space-size=4096" npm run build

Database Connection Refused

Ensure MongoDB/PostgreSQL is running and the DATABASE_URI is correct. For MongoDB, check authentication: bash mongosh --eval "db.runCommand({ connectionStatus: 1 })"

Admin Panel Returns 404

Ensure the Next.js server is running and Nginx is proxying correctly. Check PM2 status: bash pm2 status payload-cms pm2 logs payload-cms

Migrations (PostgreSQL)

Payload with PostgreSQL uses Drizzle ORM for migrations: bash npx payload migrate

Run this post-deployment if schema changes exist.

Conclusion

Payload CMS combines the flexibility of a headless CMS with the power of Next.js — all defined in TypeScript. DeployHQ handles the build and deployment pipeline, including PM2 process management for zero-downtime updates.

Sign up for DeployHQ — free for one project.

Questions? Contact support@deployhq.com or on Twitter/X.