---
title: "Production Deployment"
aliases: [payload-deployment, payload-production]
tags: [payloadcms, deployment, docker, production, database, file-storage]
sources: [raw/production__deployment.md]
created: 2026-05-15
updated: 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`.

```bash
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|Preventing API 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

```ts
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|Upload & Media]] for adapter config details.

## Docker

### next.config.js

```js
const nextConfig = {
  output: 'standalone',
}
```

### Multi-Stage Dockerfile

```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)

```yml
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`).

## Related

- [[wiki/payloadcms/building-without-db-connection|Building Without a DB Connection]] — Docker build failures when SSG calls Local API
- [[wiki/payloadcms/upload|Upload & Media]] — storage adapter config details
- [[wiki/payloadcms/performance-overview|Performance Optimization]] — DB proximity, indexes, caching
- [[wiki/payloadcms/environment-vars|Environment Variables]] — `.env` setup for Next.js and standalone
- [[wiki/payloadcms/authentication-overview|Authentication — Overview]] — secure cookie settings

## Sources

- `raw/production__deployment.md` — https://payloadcms.com/docs/production/deployment
