Deploying Payload CMS
Introduction
Payload CMS is a self-hosted, open-source headless CMS and application framework built with TypeScript and React. Unlike traditional CMS platforms, Payload gives developers complete control through code-first configuration while providing editors with a powerful, intuitive admin interface.
Designed for modern web development, Payload combines the flexibility of a headless CMS with the capabilities of an application framework, making it ideal for everything from simple blogs to complex enterprise applications.
Key highlights of Payload CMS:
- Code-First Configuration: Define your schema in TypeScript for type safety and version control
- Powerful Admin UI: Auto-generated admin panel based on your configuration
- GraphQL and REST APIs: Automatic API generation for all collections
- Authentication: Built-in authentication with access control at every level
- File Uploads: Media management with image resizing and cloud storage support
- Localization: First-class support for multilingual content
- Draft System: Save drafts and preview content before publishing
- Versions and Revisions: Track changes and restore previous versions
- Blocks and Rich Text: Flexible content modeling with reusable blocks
- Hooks and Plugins: Extend functionality with lifecycle hooks and plugins
- Self-Hosted: Deploy anywhere with complete data ownership
- Open Source: Licensed under MIT
This guide walks through deploying Payload CMS on Klutch.sh using Docker, configuring the application, and setting up for production use.
Why Deploy Payload CMS on Klutch.sh
Deploying Payload CMS on Klutch.sh provides several advantages:
Simplified Deployment: Klutch.sh automatically detects your Dockerfile and builds Payload without complex orchestration. Push to GitHub, and your CMS deploys automatically.
Persistent Storage: Attach persistent volumes for your database and media uploads. Your content survives container restarts and redeployments.
HTTPS by Default: Klutch.sh provides automatic SSL certificates for secure admin and API access.
GitHub Integration: Connect your Payload configuration repository directly. Updates trigger automatic redeployments.
Environment Variable Management: Securely store database credentials and API keys through Klutch.sh’s environment variable system.
Custom Domains: Use your organization’s domain for a professional CMS experience.
Prerequisites
Before deploying Payload CMS on Klutch.sh, ensure you have:
- A Klutch.sh account
- A GitHub account with a repository for your Payload project
- Basic familiarity with Docker, Node.js, and TypeScript
- A MongoDB database (can be deployed on Klutch.sh or use MongoDB Atlas)
Preparing Your Repository
Create a GitHub repository containing your Payload CMS project.
Repository Structure
payload-project/├── Dockerfile├── package.json├── tsconfig.json├── src/│ ├── payload.config.ts│ ├── server.ts│ └── collections/│ ├── Users.ts│ ├── Pages.ts│ └── Media.ts└── .dockerignoreCreating the Payload Configuration
Create src/payload.config.ts:
import { buildConfig } from 'payload/config';import path from 'path';import Users from './collections/Users';import Pages from './collections/Pages';import Media from './collections/Media';
export default buildConfig({ serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL || 'http://localhost:3000', admin: { user: Users.slug, }, collections: [ Users, Pages, Media, ], upload: { limits: { fileSize: 5000000, // 5MB }, }, typescript: { outputFile: path.resolve(__dirname, 'payload-types.ts'), }, graphQL: { schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'), },});Creating the Dockerfile
Create a Dockerfile in the root of your repository:
FROM node:18-alpine AS builder
WORKDIR /app
# Copy package filesCOPY package*.json ./
# Install dependenciesRUN npm ci
# Copy source codeCOPY . .
# Build the applicationRUN npm run build
# Production stageFROM node:18-alpine
WORKDIR /app
# Copy package filesCOPY package*.json ./
# Install production dependencies onlyRUN npm ci --only=production
# Copy built applicationCOPY --from=builder /app/dist ./distCOPY --from=builder /app/build ./build
# Create media directoryRUN mkdir -p /app/media
# Set environment variablesENV NODE_ENV=productionENV PAYLOAD_CONFIG_PATH=dist/payload.config.js
# Expose the application portEXPOSE 3000
# Start the applicationCMD ["node", "dist/server.js"]Creating the .dockerignore File
Create a .dockerignore file:
.git.github*.mdLICENSE.gitignore*.log.DS_Storenode_modules/.env.env.localdist/build/Environment Variables Reference
| Variable | Required | Description |
|---|---|---|
MONGODB_URI | Yes | MongoDB connection string |
PAYLOAD_SECRET | Yes | Secret key for encryption and signing |
PAYLOAD_PUBLIC_SERVER_URL | Yes | Public URL of your Payload instance |
PAYLOAD_PORT | No | Server port (default: 3000) |
Deploying Payload CMS on Klutch.sh
- Select HTTP as the traffic type
- Set the internal port to 3000
- Detect your Dockerfile automatically
- Build the container image
- Attach the persistent volumes
- Start the Payload container
- Provision an HTTPS certificate
Generate a Payload Secret
Generate a secure secret key:
openssl rand -hex 32Set Up MongoDB
Deploy a MongoDB instance on Klutch.sh or use MongoDB Atlas. Note the connection string.
Push Your Repository to GitHub
Initialize your repository and push to GitHub with your Payload project.
Create a New Project on Klutch.sh
Navigate to the Klutch.sh dashboard and create a new project. Give it a descriptive name like “payload-cms” or “content-backend”.
Create a New App
Within your project, create a new app. Connect your GitHub account if you haven’t already, then select the repository containing your Payload project.
Configure HTTP Traffic
In the deployment settings:
Set Environment Variables
Add the following environment variables:
| Variable | Value |
|---|---|
MONGODB_URI | Your MongoDB connection string |
PAYLOAD_SECRET | Your generated secret key |
PAYLOAD_PUBLIC_SERVER_URL | https://your-app-name.klutch.sh |
Attach Persistent Volumes
Add the following volumes:
| Mount Path | Recommended Size | Purpose |
|---|---|---|
/app/media | 10+ GB | Media uploads and files |
Deploy Your Application
Click Deploy to start the build process. Klutch.sh will:
Access Payload CMS
Once deployment completes, access your Payload admin at https://your-app-name.klutch.sh/admin. Create your first admin user on the initial visit.
Initial Setup
Creating the First User
- Navigate to
https://your-app-name.klutch.sh/admin - Complete the registration form
- Your first user becomes an admin automatically
Defining Collections
Collections are the core building blocks in Payload:
import { CollectionConfig } from 'payload/types';
const Pages: CollectionConfig = { slug: 'pages', admin: { useAsTitle: 'title', }, access: { read: () => true, }, fields: [ { name: 'title', type: 'text', required: true, }, { name: 'content', type: 'richText', }, { name: 'status', type: 'select', options: [ { label: 'Draft', value: 'draft' }, { label: 'Published', value: 'published' }, ], defaultValue: 'draft', }, ],};
export default Pages;Media Collection
import { CollectionConfig } from 'payload/types';
const Media: CollectionConfig = { slug: 'media', upload: { staticDir: 'media', imageSizes: [ { name: 'thumbnail', width: 400, height: 300, position: 'centre' }, { name: 'card', width: 768, height: 1024, position: 'centre' }, ], mimeTypes: ['image/*', 'application/pdf'], }, fields: [ { name: 'alt', type: 'text', }, ],};
export default Media;Using the API
REST API
Payload automatically generates REST endpoints:
# Get all pagescurl "https://your-payload-instance/api/pages"
# Get a specific pagecurl "https://your-payload-instance/api/pages/{id}"
# Create a page (authenticated)curl -X POST "https://your-payload-instance/api/pages" \ -H "Authorization: JWT your-token" \ -H "Content-Type: application/json" \ -d '{"title": "New Page", "content": [...]}'GraphQL API
Access GraphQL at /api/graphql:
query { Pages { docs { id title content status } }}Advanced Features
Access Control
Define granular access control:
access: { read: () => true, // Public read create: ({ req: { user } }) => Boolean(user), // Authenticated only update: ({ req: { user } }) => user?.role === 'admin', delete: ({ req: { user } }) => user?.role === 'admin',}Hooks
Add lifecycle hooks:
hooks: { beforeChange: [ ({ data }) => { data.updatedAt = new Date(); return data; }, ], afterChange: [ async ({ doc }) => { // Trigger webhook, update cache, etc. }, ],}Plugins
Extend Payload with plugins for features like SEO, redirects, and more.
Additional Resources
- Payload CMS Official Website
- Payload Documentation
- Payload GitHub Repository
- Payload Discord Community
- Klutch.sh Persistent Volumes
- Klutch.sh Deployments
Conclusion
Deploying Payload CMS on Klutch.sh gives you a powerful, self-hosted headless CMS with the flexibility of an application framework. The combination of code-first configuration and auto-generated admin UI means you get both developer experience and editor usability.
With TypeScript throughout, automatic API generation, and extensive customization options, Payload provides the foundation for building modern content-driven applications while keeping your data under your control.