Add Docker file to your Payload code

Self hosted PayloadCMS and PostgreSQL website on Docker

1 min read

Published Jun 17 2025, updated Aug 17 2026


10
0
0
0

CaddyDockerGitHub ActionsJavascriptNextJSPayloadCMSPortainerTailscaleUbuntuUFW

Now we need to add a docker file to the root of your Payload website code.


First we need to set the standalone flag in the next.config.mjs file at the route of the project:

/next.config.mjs
const nextConfig = {  output: 'standalone',}

If you have other config already then merge appropriately.


Then you need to add a new script line in the package.json file runs both database migrate and then build, call it ci as thats what we will reference in the docker file when building:

/package.json
"scripts": {    ...    "ci": "payload migrate && pnpm build",    ...  },


Next add a Dockerfile to the route of the project:

/Dockerfile
FROM node:22.12.0-alpine AS base# Install dependencies only when neededFROM 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-compatWORKDIR /appENV COREPACK_DEFAULT_TO_LATEST=0# Install dependencies based on the preferred package managerCOPY 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# Rebuild the source code only when neededFROM base AS builderWORKDIR /app# Database and payload secret are required to build the application.# They are passed as build arguments to ensure that the application can connect to the database during the build process.# This is in an early stage so doesn't persist in the final image.ARG DATABASE_URIARG PAYLOAD_SECRETENV DATABASE_URI=$DATABASE_URIENV PAYLOAD_SECRET=$PAYLOAD_SECRETCOPY --from=deps /app/node_modules ./node_modulesCOPY . .ENV COREPACK_DEFAULT_TO_LATEST=0RUN \  if [ -f yarn.lock ]; then yarn run ci; \  elif [ -f package-lock.json ]; then npm run ci; \  elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run ci; \  else echo "Lockfile not found." && exit 1; \  fi# Production image, copy all the files and run nextFROM base AS runnerWORKDIR /appENV NODE_ENV productionRUN addgroup --system --gid 1001 nodejsRUN adduser --system --uid 1001 nextjs# Set the correct permission for prerender cacheRUN mkdir .nextRUN chown nextjs:nodejs .next# Automatically leverage output traces to reduce image size# https://nextjs.org/docs/advanced-features/output-file-tracingCOPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/staticCOPY --from=builder --chown=nextjs:nodejs /app/public ./publicUSER nextjsEXPOSE 3000ENV PORT 3000# server.js is created by next build from the standalone output# https://nextjs.org/docs/pages/api-reference/next-config-js/outputCMD HOSTNAME="0.0.0.0" node server.js

This is a multi-stage build file that will create a small final image size, and will also run the data base migrations as part of the build process.

In order to build the project in the docker file, the payload secret and database connection string env values are both required, so we will pass them in from GitHub actions as build arguments, however they will not be present in the final image and will be set in Docker/Portainer with the other env values when deploying the service.

© 2025 SimpleSteps.guide
AboutFAQPoliciesContact
Self hosted PayloadCMS and PostgreSQL website on Docker | Add Docker file to your Payload code | SimpleSteps.guide