This content originally appeared on DEV Community and was authored by Lukas Mauser
Payload is an open source backend framework and it is mainly used as a content management system.
You can use Docker to run your own instance of Payload on Sliplane, however, when I tried using the Dockerfile that gets created using the pnpx create-payload-app
it did not work for me right away and I had to apply a few tweaks and settings in order to get payload running.
Here’s how you can run Payload with Docker:
Prerequisites
NodeJs and Docker should be installed on your system. For the demo I use pnpm as a package manager so make sure to install it as well or tweak the installation instructions to use your package manager of choice.
In my case I used:
- Node version: v22.12.0
- pnpm version: 9.13.2
Create a new Payload App
Open a new terminal in the parent folder where your Payload project should be created. Run the install wizard with:
# npx
npx create-payload-app
# or pnpx
pnpx create-payload-app
You will be guided through a series of short questions, I’ll use:
- “Payload Demo” as my project name
- setup a blank project
- go with MongoDB as my database and use the default connection setting to begin with
- and pnpm as the package manager
To install payload into an existing project, please follow the installation instructions on the official payload documentation.
After the wizard has finished, we can see that the project includes a Dockerfile out of the box. However, when I tried to use this Dockerfile, I ran into several issues that prevented it from working properly in my environment.
Issues I Encountered
When trying to run the generated Dockerfile, I encountered a few problems:
- Unpinned pnpm version – The Dockerfile didn’t specify a specific pnpm version, which caused my Docker builds to fail
- Missing standalone mode – My builds also failed because Next.js wasn’t configured for standalone output
- Public folder issues – The Dockerfile tried to copy a public folder that didn’t exist in my setup in the beginning
-
Database connection issues – I missed the
authSource
connection parameter in the MongoDB connection URI - File upload permissions – Media uploads failed due to incorrect folder permissions
Let me show you how I fixed these issues one by one.
My Fixed Dockerfile
Here’s the corrected Dockerfile that resolved the issues I encountered:
# To use this Dockerfile, you have to set `output: 'standalone'` in your next.config.mjs file.
# From https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile
FROM node:22.12.0-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
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 && corepack prepare pnpm@9.13.2 --activate && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1
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 && corepack prepare pnpm@9.13.2 --activate && pnpm run build; \
else echo "Lockfile not found." && exit 1; \
fi
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Remove this line if you do not have this folder
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
RUN mkdir -p media && chown -R nextjs:nodejs media
USER nextjs
EXPOSE 3000
ENV PORT 3000
# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD HOSTNAME="0.0.0.0" node server.js
Building and Running
Here’s how you can build and run your Payload CMS application:
# Build the Docker image
docker build -t payload-cms .
# Run the container
docker run -p 3000:3000 \
-e DATABASE_URI=mongodb://your-mongo-host:27017/payload?authSource=admin \
-e PAYLOAD_SECRET=your-secret-key \
payload-cms
Note: If you want to persist uploaded files, you can mount a volume to
/app/media
, however in a more production-ready setup, you might want to use an object storage solution
Pinning the pnpm Version
To fix the pnpm version issue, I pinned the specific version in the Dockerfile in the deps and builder stages:
RUN corepack enable pnpm && corepack prepare pnpm@9.13.2 --activate
This ensures the same pnpm version is used consistently across all environments.
Configuring Next.js for Standalone Output
You’ll also need to update your next.config.mjs
file to enable standalone output mode:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
// ... other configuration options
}
export default nextConfig
This tells Next.js to create a standalone version of your application that includes all the necessary dependencies.
Setting up the Public Folder
If your project doesn’t have a public folder yet, create one:
mkdir public
echo "" > public/.gitkeep
This helps ensure the COPY command in the Dockerfile doesn’t fail.
Database Configuration
I used MongoDB in a container, and had to include the authSource=admin
parameter in the connection URI in order to connect:
DATABASE_URI=mongodb://username:password@localhost:27017/payload?authSource=admin
File Upload Permissions
I attached the media folder to the Docker container using a volume mount. This allows you to persist uploaded files outside the container.
For uploads to work correctly, I had to ensure the media folder had the right permissions, which I set in the Dockerfile with:
RUN mkdir -p media && chown -R nextjs:nodejs media
This creates the media directory and sets the proper ownership so the nextjs user can write uploaded files to it.
Object Storage for Media Files
At some point, you might want to configure object storage for media files instead of storing them in a volume:
// payload.config.ts
import { s3Adapter } from '@payloadcms/plugin-cloud-storage/s3'
export default buildConfig({
plugins: [
cloudStorage({
collections: {
media: {
adapter: s3Adapter({
config: {
endpoint: process.env.S3_ENDPOINT,
region: process.env.S3_REGION,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
},
},
bucket: process.env.S3_BUCKET,
}),
},
},
}),
],
// ... rest of your config
})
Deploy to Sliplane
You can deploy your Payload CMS instance to Sliplane by following a few sim
- Create a new project and give it a name of your choice
- Next we will deploy a MongoDB database
- Navigate into the project and click “Deploy Service” choose a server and select the MongoDB preset. You can make the service “private” since we don’t want to expose it to the public internet
- Deploy the database, alternatively, you can tweak the connection settings like user, password and database name
- Next we will deploy Payload CMS and connect it to the MongoDB instance we just created
- In the project, click “Deploy Service” again, choose the same server, where your database is running on and select “Repository” as the deployment method
- In the repository URL field, look for your Payload CMS repository. If it does not show up in the list, make sure you granted Sliplane access to the repo using the “Configure Repository Access” button
- Add a volume, give it a name of your choice and mount it to
/app/media
- Add the
PAYLOAD_SECRET
andDATABASE_URI
environment variables.PAYLOAD_SECRET
is an arbitrary password. The database URI should look like this:mongodb://username:password@mongodb:27017/payload?authSource=admin
– You can find all connection settings like user, password, database and internal host name in the MongoDB service settings - Click “Deploy” and wait for the deployment to finish
Conclusion
Running Payload CMS in Docker requires a few configuration tweaks beyond the default setup.
The key fixes include pinning the pnpm version, enabling Next.js standalone mode, setting proper file permissions, and optionally configuring object storage for production use.
This content originally appeared on DEV Community and was authored by Lukas Mauser