Self-hosting Payload CMS for Under €5: My Setup with Coolify and Hetzner

Payload CMS is compelling across the board – I explained that in detail in my last article. But the best CMS solution is useless if hosting costs a fortune or you're completely at the mercy of cloud providers.
Payload Cloud charges $20 per month. Vercel gets expensive quickly with larger projects. Both are solid options, but not everyone wants or can afford that. Plus, there's this: self-hosting gives you full control. Over data, location, backups – everything.
I've been hosting my Payload projects on Hetzner with Coolify for under €5 per month for months now. The setup runs stable, is fast, and costs a fraction of the cloud alternatives.
Note: This blog and the entire website run on a Hetzner CX22 – practical proof that this setup works reliably in real-world scenarios.
This isn't a one-click setup. There's a learning curve, but once you've got that behind you, it's much easier than expected. Anyone who's tackled Payload CMS should be able to handle self-hosting too. Here's the complete guide – with all the hurdles I encountered.
The Combo: Hetzner, Coolify, Resend
Hetzner is unbeatable in terms of price. You won't get a VPS with 2 GB RAM for under €5 anywhere else. Plus there's an important point: I can decide for myself where my data lives. Especially in the current political climate with Trump and the unclear data protection situation in the US, that's a real advantage. European servers, European law, no surprises.
Coolify is basically Vercel for your own server. Docker deployments, automatic SSL certificates, Git integration. The best part: it's open source and completely free. I used to do everything manually with Docker Compose and nginx. It works, but it's tedious to maintain. Coolify takes most of the work off my hands.
For emails, I use Resend. 3,000 emails per month free, then affordable pricing, and the API is simple. Another advantage: Resend has servers in Ireland, so even the emails stay in Europe.

The current prices at Hetzner (June 2025)
Setting up the Server at Hetzner
The smallest VPS is sufficient: CPX11 for under €5. 2 vCPU AMD, 2 GB RAM, 40 GB NVMe SSD. Adequate for smaller Payload projects; you can scale up if needed.
Choose Ubuntu 22.04 as the operating system. Set up SSH key instead of password login. If you don't have an SSH key:
ssh-keygen -t ed25519 -C "[email protected]"
Installing Coolify
The installation is straightforward:
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
The script installs Docker, sets up Coolify, and starts all services. After a few minutes, the interface is accessible at http://server-ip:8000. On first access, create an admin account and optionally enable 2FA.
Setting up Domain and SSL
In Coolify under "Settings" > "Configuration", enter your domain. Important: the DNS records must already be set correctly – A record pointing to the server IP.
Coolify automatically generates Let's Encrypt certificates. This usually works without problems. If not, it's usually due to incorrectly configured DNS entries.

The current dashboard of Coolify.
Deploying Payload Project
The big advantage of Coolify: you don't need your own Dockerfile. Coolify uses Nixpacks, which automatically detects what kind of project it is and creates the appropriate build configuration. This works out-of-the-box with Node.js projects.
If Nixpacks causes problems with Payload projects (which occasionally happens), you can switch to your own Dockerfile. Here's a proven Dockerfile for Payload CMS that works well.
# Base image
FROM node:23-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install latest corepack to fix signature issues
RUN npm install -g corepack@latest && corepack enable
# Copy lockfiles and configs
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
# Copy source code
COPY . .
# Install dependencies based on the lockfile
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
# Build stage
FROM base AS builder
WORKDIR /app
# Copy dependencies from previous stage
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Install pnpm globally for Payload operations
RUN npm install -g pnpm --unsafe-perm
# Run database migrations
RUN pnpm payload migrate:status || echo "No pending migrations found."
RUN pnpm payload migrate || echo "No migrations to apply."
# Build the application
RUN \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then pnpm generate:importmap && pnpm run build; \
else echo "Lockfile not found." && exit 1; \
fi
# Run postbuild script (optional)
RUN pnpm run postbuild || echo "Postbuild script failed."
# Production image
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
# Create user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy public assets
COPY --from=builder /app/public ./public
# Prepare .next directory and set permissions
RUN mkdir .next && chown nextjs:nodejs .next
# Copy standalone app and static files with correct ownership
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
# Start server (created by `next build` in standalone mode)
CMD ["node", "server.js"]
Create a new project in Coolify and connect it to your Git repository. GitHub, GitLab, or self-hosted Git servers all work. Auto-deploy on Git push is available.
In Coolify, you also need to create a PostgreSQL database. This is done via "Resources" > "Databases" > "PostgreSQL". Coolify automatically generates secure credentials that you can then use in your application. The first build takes a bit longer, after that it goes much faster.
DNS Configuration for Website Availability
The most beautiful Payload installation is useless if it disappears into digital nowhere. DNS must be set up before SSL configuration in Coolify – otherwise Let's Encrypt fails at domain validation. Standard approach: Create an A-record with your domain provider, set name to @ for the main domain or coolify for a subdomain, value is your Hetzner server IP. For subdomains like coolify.yourdomain.com, simply create additional A-records pointing to the same destination.
Anyone already using Cloudflare can skip A-records entirely and go with Cloudflare Tunnels. Your server IP stays hidden, DDoS protection included, and it even works without a public IP. Coolify has a ready-made Cloudflared template – just create the service, enter your tunnel token, done. In Cloudflare, create public hostnames for each desired subdomain: coolify.yourdomain.com points to localhost:8000, app.yourdomain.com to localhost:80. Important note: Use http in Coolify's domain settings, Cloudflare handles HTTPS automatically. Set SSL/TLS to Full in Cloudflare, otherwise you'll get redirect loops between Cloudflare and Coolify.
Persistent Storage Setup
Before deploying, there's one crucial step: setting up persistent storage. Without this, you'll lose all uploaded files (images, documents, etc.) with every new deployment. In your application settings, go to the "Storage" tab and add a persistent volume.
For the destination path, use /app/media or /app/uploads (wherever Payload stores your uploads). The base directory inside the container is /app, so if you need to store files under a storage directory, you need to define the full path starting with /app/. You can either use a Docker volume (Coolify manages it) or a bind mount (specify a host path like /data/your-app/media).
This ensures your media files survive deployments and container restarts.
The first build takes a bit longer, after that it goes much faster.
Important environment variables:
- DATABASE_URL: postgres://user:password@postgres:5432/payload (you get the credentials after creating the database in Coolify)
- PAYLOAD_SECRET: Long, random string (at least 32 characters)
- PAYLOAD_PUBLIC_SERVER_URL: https://your-domain.com
- RESEND_API_KEY: Your Resend API key
CI/CD with GitHub
Coolify offers seamless GitHub integration. You can either use the GitHub App or set up a Deploy Key. With the GitHub App, you get automatic deployments on every push and even preview deployments for pull requests.
For private repositories, you need a GitHub token or Deploy Key. Coolify shows you exactly which SSH key to add in the GitHub settings in the interface. Once set up, Coolify automatically deploys on every Git push.
An important note about the build process: with larger Payload projects, it can happen that the build process overwhelms the small CPX11 server. The site remains accessible, but the CI pipeline fails. This problem can be easily solved by upgrading to the CPX21 (around €8/month) – with 4 GB RAM and 3 vCPUs you'll have enough resources for smooth builds.
Performance and Scaling
The 2GB server easily handles several hundred concurrent users. If requirements increase, you can easily scale up at Hetzner – without reinstallation or complicated migration. In the Hetzner Cloud Console, you can change the server size with just a few clicks. The server is briefly stopped, gets more resources, and then continues running. This usually takes only a few minutes.
Coolify provides basic monitoring. For more detailed monitoring, Uptime Kuma can be installed directly in Coolify.
Emails with Resend
Create an account at Resend and generate an API key. Add it to the Payload config:
import { resendAdapter } from '@payloadcms/email-resend'
export default buildConfig({
email: resendAdapter({
defaultFromAddress: '[email protected]',
defaultFromName: 'Project Name',
apiKey: process.env.RESEND_API_KEY,
}),
})
Don't forget to verify your domain at Resend, otherwise emails will end up in spam.
Analytics with Umami
For those who want to track website statistics without using Google Analytics, Umami offers a perfect alternative. This open-source tool is privacy-friendly, requires no cookies, and is fully GDPR-compliant.
Umami can be installed in Coolify with just one click: simply create a new resource and select Umami from the service list. Coolify automatically sets up everything, including the required PostgreSQL database.
After startup, log in with admin/umami, add your website, embed the tracking code – done. The data stays on your own server and nobody else has access to it. This is a clear advantage over external analytics services, especially in times of stricter data protection regulations.
Conclusion
For technically savvy users, this setup is a good alternative to expensive cloud solutions. You save over $15 per month and retain full control over the infrastructure. Additionally, you can choose between several European locations – Nuremberg, Falkenstein, or Helsinki – depending on your needs.
For beginners or users without Docker experience, Payload Cloud is the better choice. The few extra euros are well invested.
But for those who already have experience with Docker and are interested in infrastructure management, Hetzner + Coolify + Resend provides a solid, cost-effective solution for under €5.