Table of Contents
- Run Payload locally
- Create your first content type in Payload
- Initialize the Git repository for version control
- Configure Docker
- Configure the VPS
- Configure DNS
- Create an SSL certificate to enable HTTPS
Run Payload locally
1. Prerequisites: Install the basic tools
- Install Homebrew (the package manager for macOS): https://brew.sh/
- Install Node.js and npm via Homebrew:
1
brew install node- Check the installed versions:
1
2
node -v
npm -v2. Install & launch MongoDB via Brew
- Install MongoDB Community Edition:
(Example: MongoDB 8.0 — make sure to adapt the command according to the desired version)
1
2
brew tap mongodb/brew
brew install [email protected]- Start MongoDB with Brew Services:
1
brew services start [email protected]- Check that MongoDB is running:
1
brew services listMongoDB will be available at: mongodb://localhost:27017 (by default).Official documentation:
3. Create the Next.js project
In your working directory, run:
1
npx create-payload-appAnswer the questions:
- Project name
- Database choice (MongoDB)
- Template choice (for example,
blankif you want a minimal project)
4. Install a code editor
You can use Visual Studio Code, for example:
5. Run the project locally
- Open the project folder with VSCode.
- Open the terminal (shortcut: Cmd + J).
- Install dependencies:Installe les dépendances :
1
npm i- Start the project:
1
npm run dev- In your browser, go to: :
http://localhost:3000
6. First steps in the Payload interface
- Click on: “Go to Admin Panel”
- Create your admin account.
Create your first content type in Payload
1. Create the Todo collection in the backend
Goal: Create a “Todo” content type (a simple task list) that can be displayed on your website and managed directly from Payload.
- In VSCode, locate the folder:
src/collections. - Create a file:
Todo.ts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import type { CollectionConfig } from 'payload'
export const Todo: CollectionConfig = {
slug: 'todo',
admin: {
useAsTitle: 'Task',
},
fields: [
{
name: 'Task',
type: 'text',
required: true,
},
],
}- In the
payload.config.tsfile (insidesrc), add the Todo collectionTodo:
1
2
3
4
5
//before:
collections: [Users, Media],
//after:
collections: [Users, Media, Todo],- Remember to import
Todoat the top of the filepayload.config.ts:
1
import { Todo } from './collections/Todo'Once the project is restarted, you will see your new Todo collection appear in the admin panel.
2. Display the Todo list on the frontend
- Open the main website page :
src/app/(frontend)/page.tsxand modify its content to retrieve and display tasks:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { getPayload } from 'payload'
import React from 'react'
import config from '@/payload.config'
import './styles.css'
export const dynamic = 'force-dynamic'
export default async function HomePage() {
const payload = await getPayload({ config });
const Todos = await payload.find({
collection: 'todo',
})
return (
<div>
{
Todos.docs?.map((todo) => (
<div key={todo.id}>
<h2>{todo.task}</h2>
</div>
))
}
</div>
)
}Now every task created in Payload will appear on the homepage.
Initialize the Git repository for version control
1. Create a GitHub account
2. Create an SSH key
1
ssh-keygen -t rsa -b 4096 -C "[email protected]"- When prompted to specify a file path for the key, press
EnterEnter to accept the default location (~/.ssh/id_rsa). - Then choose a secure password for the SSH key or press
Enterto skip it (optional but recommended).
3. Start the SSH agent
Run the SSH agent to manage your key:
1
eval "$(ssh-agent -s)"4. Add the SSH key to the agent
Add the private key to the agent so it can be used automatically:
1
ssh-add ~/.ssh/id_rsa5. Copy the public SSH key
1
cat ~/.ssh/id_rsa.pubSelect and copy the displayed content.
6. Add the SSH key to GitHub (or GitLab, etc.)
- In GitHub: : Settings > SSH and GPG keys > New SSH key.
- Paste your public key, give it a clear name (for example: “VPS” or “MacBook”), then confirm.
7. Test the connection
1
ssh -T [email protected]A welcome message should appear.
8. Create a repository
- Create a repository : https://github.com/new
- Choose the repository name, visibility (private or public), etc.
- Once created, follow the instructions to link your project:
1
2
3
git remote add origin [email protected]:beease/payload-test.git
git branch -M main
git push -u origin main- Commit your changes:
1
2
3
git add .
git commit -am "todo"
git pushCongratulations, your changes are now online. Other developers with access to the project can update their version with: git pullDocker Configuration
1. Configure Docker Compose
- At the root of the project, you will find the
docker-compose.ymlfile. It is used to manage Docker containers. The default Payload template already includes a configuration. We will replace it with the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
services:
payload:
build:
context: .
volumes:
- ./media:/app/media
ports:
- '3000:3000'
depends_on:
- mongo
mongo:
container_name: mongo
image: mongo:latest
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: username
MONGO_INITDB_ROOT_PASSWORD: password
ports:
- "27017:27017"
volumes:
- mongodb_data:/data/db
nginx:
container_name: webserver
restart: always
image: nginx:latest
ports:
- '80:80'
volumes:
- ./nginx.conf:/etc/nginx/conf.d/nginx.conf:ro
depends_on:
- payload
volumes:
mongodb_data:2. Docker image configuration
- Replace the content of the
Dockerfilefile located at the root of the project:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
FROM node:22-alpine AS builder
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build:compile
FROM node:22-alpine AS runner
WORKDIR /app
COPY . .
COPY --from=builder /app/package-lock.json ./
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
RUN npm ci --omit=dev
EXPOSE 3000
ENV PORT 3000
CMD ["sh", "-c", "npm run payload migrate && npm run build:generate && node server.js"](Here, I am using Node 22 as an example. Adapt it if necessary.)
3. Configure the Next.js project for production
- In the
next.config.mjsfile, enable standalone mode:
1
2
3
4
5
6
7
8
import { withPayload } from '@payloadcms/next/withPayload'
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone",
}
export default withPayload(nextConfig, { devBundleServerPackages: false })- In the
package.jsonfile at the root of the project, add these script lines:
1
2
3
4
5
6
"scripts": {
...
"build:compile": "next build --experimental-build-mode compile",
"build:generate": "next build --experimental-build-mode generate",
...
},4. Web server configuration
- Still at the root of the project, create an
nginx.conffile:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
upstream payload {
server payload:3000;
}
server {
listen 80;
listen [::]:80;
server_name [domain] www.[domain];
server_tokens off;
location / {
proxy_pass http://payload;
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-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}- Once everything is configured, commit and push your changes to your repository.
1
2
3
git add .
git commit -am "setup docker & nginx"
git pushConfigure the VPS
On the VPS, Docker will install everything you need inside the containers. You only need to install Git and Docker on the host machine.
1. Install Git
1
2
3
sudo apt update
sudo apt install git -y
git --version2. Install Docker
Follow the official Docker documentation:
1
2
3
4
5
6
7
8
9
10
11
12
13
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update- Install the Docker packages:
1
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin- Test the installation:
1
sudo docker run hello-world3. Create a Git SSH key on your VPSur ton VPS
Follow the same process as on your local machine (see section C), so you can clone your repository via SSH.
4. Clone the Git project
- On GitHub, retrieve the SSH clone URL:

- On your VPS, navigate to the folder where you want to clone the project:
1
git clone [email protected]:MonCompte/mon-projet.git5. Create the .env file
1
2
cd mon-projet
nano .env- Write the environment variables:
1
2
DATABASE_URI=mongodb://username:password@mongo:27017/payload?authSource=admin
PAYLOAD_SECRET=[Clef secrète payload] (In this example, mongo corresponds to the Mongo container name defined in docker-compose.yml.)
To save and exit Nano :Ctrl + OthenCtrl + X(orCmd + S,Cmd + Xon Mac if you are connected through iTerm, etc.).
6. Start the Docker containers
- Create the Docker images:
1
sudo docker compose build- Start the Docker containers:
1
sudo docker compose upYour website is almost online! The only remaining step is configuring DNS to link your domain name to the VPS public IP address.
Configure DNS
In your domain name DNS settings, add an A record pointing to your VPS public IP address.
For example:
- Type : A
- Value: 123.456.789.000 (your VPS IP address)
You can then access your admin interface at: http://your-domain.com/adminCreate an SSL certificate to enable HTTPS
1. Basic Nginx configuration for HTTP
- In your
docker-compose.ymlfile, add (or update) the configuration for Nginx and Certbot:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
services:
nginx:
container_name: webserver
restart: always
image: nginx:latest
ports:
- '80:80'
- '443:443'
volumes:
- ./nginx.conf:/etc/nginx/conf.d/nginx.conf:ro
- ./certbot/www:/var/www/certbot/:ro
- ./certbot/conf/:/etc/nginx/ssl/:ro
depends_on:
- payload
entrypoint: "/bin/sh -c 'while :; do sleep 6h; nginx -s reload; done & nginx -g \"daemon off;\"'"
certbot:
restart: always
image: certbot/certbot:latest
volumes:
- ./certbot/www/:/var/www/certbot/:rw
- ./certbot/conf/:/etc/letsencrypt/:rw
#entrypoint: "/bin/sh -c 'while :; do certbot renew; sleep 12h; done'"- Configure
nginx.conf(initial version for the HTTP phase):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 80;
listen [::]:80;
server_name [domain].fr www.[domain].fr;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://[domain]$request_uri;
}
}- Commit and push your changes.
- On your VPS, run
git pulland restart Docker Compose:
1
2
git pull
sudo docker compose down && sudo docker compose up2. Generate the certificate with Certbot
- First, test in
dry-runmode: (Replace the domain name with yours)
1
2
3
sudo docker compose run --rm certbot certonly \
--webroot --webroot-path /var/www/certbot/ \
--dry-run -d mon-domaine.frThe message : The dry run was successful should appear.
- If everything works correctly, create the certificates:
1
2
3
sudo docker compose run --rm certbot certonly \
--webroot --webroot-path /var/www/certbot/ \
-d mon-domaine.fr3. Update the Nginx configuration for HTTPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
upstream payload {
server payload:3000;
}
# Redirection automatique HTTP -> HTTPS
server {
listen 80;
listen [::]:80;
server_name [domain].fr www.[domain].fr;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://[domain]$request_uri;
}
}
# Serveur HTTPS
server {
listen 443 default_server ssl http2;
listen [::]:443 ssl http2;
server_name [domain].fr www.[domain].fr;
ssl_certificate /etc/nginx/ssl/live/[domain.fr]/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/live/[domain.fr]/privkey.pem;
location / {
proxy_pass http://payload;
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-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}- In
docker-compose.yml, uncomment this line to enable automatic certificate renewal:
1
2
3
4
5
6
7
8
certbot:
restart: always
image: certbot/certbot:latest
volumes:
- ./certbot/www/:/var/www/certbot/:rw
- ./certbot/conf/:/etc/letsencrypt/:rw
==> entrypoint: "/bin/sh -c 'while :; do certbot renew; sleep 12h; done'"
- Commit and push your changes.
- On your VPS, run
git pulland restart Docker Compose:
1
2
git pull
sudo docker compose down && sudo docker compose upCongratulations: your Payload application is now accessible via HTTPS at : https://[your-domain]/adminConclusion
You now have a complete environment to:
- Develop locally with Payload, MongoDB, and Next.js.
- Version your changes using Git and GitHub.
- Deploy to a VPS with Docker + Nginx + Certbot to obtain a secure HTTPS website.
Good luck! 🐝



