docker.recipes

Payload CMS Headless

intermediate

Payload CMS headless with MongoDB.

Overview

Payload CMS is a modern, TypeScript-first headless content management system that prioritizes developer experience with a code-first approach. Built on Node.js, Payload generates admin interfaces automatically from configuration files and provides powerful APIs for content delivery, making it an ideal choice for developers who want full control over their CMS architecture without sacrificing usability for content editors. This deployment creates a production-ready Payload CMS environment with three specialized services: the main Payload application running on Node.js 20 Alpine, a dedicated MongoDB 6 database instance for content storage, and an NGINX reverse proxy for efficient request handling and static file serving. The configuration mounts the Payload application code directly into the container, enabling rapid development cycles while maintaining persistent data storage for both media files and database content. This stack is perfect for development teams building headless websites, mobile applications, or multi-channel digital experiences who need a CMS that can scale with their TypeScript codebase. The combination of Payload's developer-friendly architecture with MongoDB's flexible document storage and NGINX's high-performance web serving creates an efficient foundation for content-driven applications that require both technical flexibility and editorial ease of use.

Key Features

  • TypeScript-first CMS with automatic admin UI generation from configuration files
  • MongoDB document-based storage with flexible schema evolution for content types
  • Built-in media management with persistent volume storage for uploaded assets
  • NGINX reverse proxy configuration for optimized static file delivery and caching
  • Hot-reload development environment with direct code mounting for rapid iteration
  • RESTful and GraphQL APIs automatically generated from Payload field configurations
  • Environment-based configuration supporting multiple deployment stages
  • Containerized architecture enabling consistent behavior across development environments

Common Use Cases

  • 1Headless e-commerce platforms requiring flexible product catalog management with TypeScript integration
  • 2Multi-site content management for agencies serving multiple clients from a single CMS instance
  • 3Mobile app backends needing structured content delivery with real-time API access
  • 4Developer portfolio sites and blogs where technical control over CMS architecture is essential
  • 5Startup MVPs requiring rapid content modeling iteration without database migration complexity
  • 6Enterprise marketing websites needing both developer flexibility and content editor accessibility
  • 7Documentation platforms and knowledge bases with complex content relationships and categorization

Prerequisites

  • Docker and Docker Compose installed with at least 3GB available RAM for optimal MongoDB performance
  • Node.js 20 project structure with Payload CMS already initialized in the ./app directory
  • PAYLOAD_SECRET environment variable configured with a secure random string for session management
  • Basic TypeScript knowledge for customizing Payload configuration and field schemas
  • NGINX configuration file prepared at ./nginx.conf with appropriate proxy settings for port 3000
  • Port availability: 3000 for Payload CMS and 80 for NGINX (customizable via environment variables)

For development & testing. Review security settings, change default credentials, and test thoroughly before production use. See Terms

docker-compose.yml

docker-compose.yml
1services:
2 payload:
3 image: node:20-alpine
4 container_name: payload-cms
5 restart: unless-stopped
6 working_dir: /app
7 command: npm start
8 ports:
9 - "${PAYLOAD_PORT:-3000}:3000"
10 environment:
11 - MONGODB_URI=mongodb://payload-mongo:27017/payload
12 - PAYLOAD_SECRET=${PAYLOAD_SECRET}
13 volumes:
14 - ./app:/app
15 - payload_media:/app/media
16 depends_on:
17 - payload-mongo
18
19 payload-mongo:
20 image: mongo:6
21 container_name: payload-mongo
22 restart: unless-stopped
23 volumes:
24 - mongo_data:/data/db
25
26 nginx:
27 image: nginx:alpine
28 container_name: payload-nginx
29 restart: unless-stopped
30 ports:
31 - "${NGINX_PORT:-80}:80"
32 volumes:
33 - ./nginx.conf:/etc/nginx/nginx.conf:ro
34
35volumes:
36 payload_media:
37 mongo_data:

.env Template

.env
1# Payload CMS
2PAYLOAD_PORT=3000
3PAYLOAD_SECRET=your_payload_secret
4NGINX_PORT=80

Usage Notes

  1. 1Payload at http://localhost:3000
  2. 2Admin at /admin
  3. 3Create payload project first
  4. 4TypeScript-first CMS

Individual Services(3 services)

Copy individual services to mix and match with your existing compose files.

payload
payload:
  image: node:20-alpine
  container_name: payload-cms
  restart: unless-stopped
  working_dir: /app
  command: npm start
  ports:
    - ${PAYLOAD_PORT:-3000}:3000
  environment:
    - MONGODB_URI=mongodb://payload-mongo:27017/payload
    - PAYLOAD_SECRET=${PAYLOAD_SECRET}
  volumes:
    - ./app:/app
    - payload_media:/app/media
  depends_on:
    - payload-mongo
payload-mongo
payload-mongo:
  image: mongo:6
  container_name: payload-mongo
  restart: unless-stopped
  volumes:
    - mongo_data:/data/db
nginx
nginx:
  image: nginx:alpine
  container_name: payload-nginx
  restart: unless-stopped
  ports:
    - ${NGINX_PORT:-80}:80
  volumes:
    - ./nginx.conf:/etc/nginx/nginx.conf:ro

Quick Start

terminal
1# 1. Create the compose file
2cat > docker-compose.yml << 'EOF'
3services:
4 payload:
5 image: node:20-alpine
6 container_name: payload-cms
7 restart: unless-stopped
8 working_dir: /app
9 command: npm start
10 ports:
11 - "${PAYLOAD_PORT:-3000}:3000"
12 environment:
13 - MONGODB_URI=mongodb://payload-mongo:27017/payload
14 - PAYLOAD_SECRET=${PAYLOAD_SECRET}
15 volumes:
16 - ./app:/app
17 - payload_media:/app/media
18 depends_on:
19 - payload-mongo
20
21 payload-mongo:
22 image: mongo:6
23 container_name: payload-mongo
24 restart: unless-stopped
25 volumes:
26 - mongo_data:/data/db
27
28 nginx:
29 image: nginx:alpine
30 container_name: payload-nginx
31 restart: unless-stopped
32 ports:
33 - "${NGINX_PORT:-80}:80"
34 volumes:
35 - ./nginx.conf:/etc/nginx/nginx.conf:ro
36
37volumes:
38 payload_media:
39 mongo_data:
40EOF
41
42# 2. Create the .env file
43cat > .env << 'EOF'
44# Payload CMS
45PAYLOAD_PORT=3000
46PAYLOAD_SECRET=your_payload_secret
47NGINX_PORT=80
48EOF
49
50# 3. Start the services
51docker compose up -d
52
53# 4. View logs
54docker compose logs -f

One-Liner

Run this command to download and set up the recipe in one step:

terminal
1curl -fsSL https://docker.recipes/api/recipes/payload-cms-stack/run | bash

Troubleshooting

  • MongoDB connection refused error: Verify payload-mongo container is running and accessible on port 27017 within Docker network
  • Payload fails to start with 'PAYLOAD_SECRET required': Set PAYLOAD_SECRET environment variable with minimum 32-character random string
  • NGINX 502 Bad Gateway errors: Check that Payload container is healthy on port 3000 and NGINX upstream configuration matches service name
  • File permission errors on media uploads: Ensure payload_media volume has correct write permissions for Node.js user in Alpine container
  • TypeScript compilation errors on startup: Verify all dependencies are installed in ./app directory and tsconfig.json is properly configured
  • Admin interface not accessible: Confirm Payload is configured with proper server URL and admin settings in payload.config.ts

Community Notes

Loading...
Loading notes...

Download Recipe Kit

Get all files in a ready-to-deploy package

Includes docker-compose.yml, .env template, README, and license

Ad Space