Best Self-Hosted Headless CMS Compared
What Is a Headless CMS?
A headless CMS separates content management from content presentation. You get an admin panel for creating and organizing content, and an API (REST, GraphQL, or both) for delivering that content to any frontend — a static site, a mobile app, a kiosk, whatever. There is no built-in theme or template engine rendering pages for visitors. The CMS is purely a content backend.
This matters for self-hosters because a headless CMS gives you full control over your data, your API, and your content workflow without locking you into a vendor’s frontend framework or a SaaS platform’s pricing tiers. You own the database. You own the API. You deploy it wherever you want.
If you need a traditional CMS with built-in page rendering, look at Ghost or WordPress instead. This guide covers the API-first, headless options.
Why Self-Host a Headless CMS?
Cost. Cloud-hosted headless CMS platforms (Contentful, Sanity, Hygraph) charge $300-$1,500+/month for team plans. Self-hosting costs you the price of a VPS — often under $10/month.
Data ownership. Your content lives in your database, not in a vendor’s proprietary storage. Export is trivial. Migration is a database dump.
No API rate limits. Cloud CMS platforms throttle API calls on lower tiers. Self-hosted, your API serves as fast as your hardware allows.
Customization. Extend the admin UI, add custom fields, write hooks, modify the API schema — without waiting for a vendor to ship a feature.
The Contenders
Five self-hosted headless CMS platforms worth evaluating:
- Directus — wraps any SQL database with an instant REST and GraphQL API plus a polished admin UI
- Strapi — the most popular open-source headless CMS by GitHub stars, Node.js-based
- Payload CMS — TypeScript-native, built on Next.js since v3, code-first schema
- KeystoneJS — Prisma-based, GraphQL-native, aimed at developers who want full schema control
- Wagtail — Django-based, Python-native, originally a traditional CMS with strong headless API support
Feature Comparison
| Feature | Directus | Strapi | Payload CMS | KeystoneJS | Wagtail |
|---|---|---|---|---|---|
| Language | Node.js (TypeScript) | Node.js (TypeScript) | TypeScript (Next.js) | TypeScript (Prisma) | Python (Django) |
| Database | PostgreSQL, MySQL, MariaDB, SQLite, MS SQL, CockroachDB | PostgreSQL, MySQL, MariaDB, SQLite | PostgreSQL, MongoDB | PostgreSQL, MySQL, SQLite (via Prisma) | PostgreSQL (recommended), SQLite, MySQL |
| API type | REST + GraphQL | REST + GraphQL | REST + GraphQL + Local API | GraphQL (primary), REST (limited) | REST (Wagtail API) + GraphQL (plugin) |
| Admin UI | Excellent — no-code, drag-and-drop, role-based | Good — content-type builder, plugin marketplace | Good — lives inside Next.js app | Minimal — functional but basic | Excellent — StreamField, page tree, rich editing |
| Content modeling | No-code via admin UI or schema migration | No-code via Content-Type Builder or code | Code-first (TypeScript config files) | Code-first (TypeScript schema) | Code-first (Python models) |
| Media management | Built-in DAM with transformations | Built-in media library | Built-in upload collections | Basic file handling | Built-in image/document management |
| Authentication | Built-in with SSO, LDAP, OAuth, SAML | Built-in with providers plugin | Built-in with access control | Session-based, customizable | Django auth + SSO packages |
| License | BSL 1.1 (converts to GPL after 3 years) | MIT (community) / proprietary (enterprise) | MIT | MIT | BSD-3-Clause |
| Official Docker image | Yes (directus/directus) | No (build your own) | No (build your own) | No (build your own) | No (build your own Dockerfile) |
| Docker complexity | Low — single image, one command | Medium — must scaffold project first, then Dockerize | Medium — Next.js build step required | Medium-High — build step + Prisma migrations | High — Django scaffold + collectstatic + Gunicorn |
| RAM usage (idle) | ~150-250 MB | ~200-400 MB | ~200-350 MB | ~150-300 MB | ~250-500 MB (with Gunicorn workers) |
| GitHub stars | ~29k | ~66k | ~32k | ~9k | ~18k |
Directus — Best Overall
Directus takes a fundamentally different approach from the others. Instead of generating a database schema from your content model, it introspects an existing SQL database and wraps it with a REST API, a GraphQL API, and an admin UI. You can point it at an empty database and build your schema through the admin panel, or connect it to an existing database and get an instant API.
Strengths:
- The admin UI is the most polished of the five — non-technical editors can manage content without training
- Both REST and GraphQL out of the box, no plugins needed
- Database-agnostic — works with PostgreSQL, MySQL, MariaDB, SQLite, MS SQL, and CockroachDB
- SQLite mode means zero external dependencies for small projects
- Official Docker image makes deployment trivial
- Built-in file storage with image transformations
- Granular role-based access control with field-level permissions
- Flows (automation engine) built into the platform
- Real-time updates via WebSockets
Weaknesses:
- BSL 1.1 license is not truly open source until it converts to GPL after 3 years — you cannot offer Directus as a hosted service to third parties
- The “database introspection” model can be confusing if you expect a code-first schema approach
- Extensions require understanding Directus’s module system
Best for: Teams where non-developers need to manage content, projects that need both REST and GraphQL, anyone who wants the simplest Docker deployment.
Quick Docker Compose Setup (Directus)
This is a production-ready Directus stack with PostgreSQL:
services:
database:
image: postgres:16-alpine
restart: unless-stopped
volumes:
- directus_db:/var/lib/postgresql/data
environment:
POSTGRES_USER: "directus"
POSTGRES_PASSWORD: "change-this-strong-password" # CHANGE THIS
POSTGRES_DB: "directus"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U directus"]
interval: 10s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
directus:
image: directus/directus:11.16.0
restart: unless-stopped
ports:
- "8055:8055"
volumes:
- directus_uploads:/directus/uploads
- directus_extensions:/directus/extensions
depends_on:
database:
condition: service_healthy
cache:
condition: service_healthy
environment:
SECRET: "replace-with-a-long-random-string" # CHANGE THIS — used for signing tokens
DB_CLIENT: "pg"
DB_HOST: "database"
DB_PORT: "5432"
DB_DATABASE: "directus"
DB_USER: "directus"
DB_PASSWORD: "change-this-strong-password" # Must match POSTGRES_PASSWORD
CACHE_ENABLED: "true"
CACHE_AUTO_PURGE: "true"
CACHE_STORE: "redis"
REDIS: "redis://cache:6379"
ADMIN_EMAIL: "[email protected]" # CHANGE THIS — first admin login
ADMIN_PASSWORD: "change-this-admin-password" # CHANGE THIS — first admin password
PUBLIC_URL: "https://cms.yourdomain.com" # CHANGE THIS — your public URL
WEBSOCKETS_ENABLED: "true"
volumes:
directus_db:
directus_uploads:
directus_extensions:
Start it:
docker compose up -d
Access the admin panel at http://your-server:8055. Log in with the ADMIN_EMAIL and ADMIN_PASSWORD you set. The initial admin account is only created on first startup — change the password immediately if using default values.
For a simpler setup without PostgreSQL or Redis (suitable for small projects or testing), Directus also supports SQLite with no external dependencies. See the full Directus setup guide for both configurations.
Strapi — Most Popular, Largest Ecosystem
Strapi is the most widely adopted open-source headless CMS. It has the largest plugin marketplace, the most tutorials, and the biggest community. Content types can be defined through the admin UI (Content-Type Builder) or in code.
Strengths:
- Largest community and plugin ecosystem (66k+ GitHub stars)
- Content-Type Builder lets non-developers define schemas in the admin UI
- MIT-licensed community edition
- Strong documentation
- Internationalization (i18n) built in
- Plugin marketplace for extending functionality
Weaknesses:
- No official Docker image — you must scaffold a project with
npx create-strapi@latest, then write your own Dockerfile - Content-Type Builder is disabled in production by default (schema changes require a rebuild)
- Higher memory footprint than Directus
- v4 to v5 migration was disruptive — some plugins broke
- Enterprise features (SSO, audit logs, review workflows) require a paid license
Best for: Teams that want the largest ecosystem and community support, projects where plugin availability matters.
Strapi runs on port 1337 by default and requires you to build a project-specific Docker image. See the full Strapi setup guide for a complete Docker deployment walkthrough.
Payload CMS — Best for Developers
Payload CMS v3 is built on Next.js and is entirely TypeScript-native. The admin panel lives inside your Next.js application — there is no separate CMS server. Schema is defined in TypeScript config files, giving you full type safety from database to frontend.
Strengths:
- TypeScript-first — full type safety across the entire stack
- Admin UI is a Next.js route, so you can customize it with React components
- Local API means no HTTP overhead when the CMS and frontend share a process
- Access control is code-defined with fine granularity
- MIT license with no feature gating
- Excellent developer experience if you already know Next.js
Weaknesses:
- No official Docker image — you must build your own from the Next.js project
- Requires Next.js knowledge — not approachable for non-developers
- Code-first only — no visual schema builder in the admin UI
- Relatively young (v3 is a major rewrite), so some rough edges remain
- Deployment is more complex than Directus because you are deploying a full Next.js app
Best for: TypeScript developers building custom applications where the CMS is deeply integrated with the frontend.
Payload runs on port 3000 by default (Next.js default). See the full Payload CMS guide for Docker deployment.
KeystoneJS — GraphQL-Native
KeystoneJS uses Prisma as its ORM and generates a GraphQL API from your TypeScript schema definitions. It is the most developer-oriented option — minimal admin UI, maximum schema control.
Strengths:
- Prisma ORM means strong database tooling and migration support
- GraphQL-native with auto-generated queries and mutations
- Virtual fields, hooks, and access control at the schema level
- Flexible — use it as a headless CMS, a backend framework, or both
- MIT license
Weaknesses:
- Admin UI is functional but basic compared to Directus or Strapi
- No official Docker image — you build and deploy a Node.js app
- Smaller community than Strapi or Directus (9k stars)
- Limited REST support — GraphQL is the primary API
- Development pace has slowed (last major release was May 2025)
Best for: Developers who want GraphQL-first and prefer defining everything in code with Prisma.
See the full KeystoneJS guide for Docker setup.
Wagtail — Best Python/Django Option
Wagtail is a Django CMS that started as a traditional page-oriented CMS but has strong headless capabilities through its built-in API and optional GraphQL support. If your team is Python-native, Wagtail is the only serious contender in this list.
Strengths:
- StreamField is one of the best content modeling tools in any CMS — flexible, nested, block-based
- Mature page tree and workflow system (drafts, moderation, scheduling)
- Excellent admin UI for editors — rich previews, image focal points, accessibility checker
- Large Django ecosystem for authentication, permissions, and extensions
- BSD license
Weaknesses:
- No official Docker image — you must scaffold a project with
wagtail start, write a Dockerfile, and configure Gunicorn - Highest deployment complexity of the five — requires Python environment, collectstatic, Gunicorn workers, PostgreSQL, and Redis
- REST API requires explicit configuration per model (not automatic like Directus)
- GraphQL requires the
wagtail-grappleorstrawberry-djangoplugin - Higher idle RAM due to multiple Gunicorn worker processes
- Not JavaScript-native, which limits frontend developer adoption
Best for: Python/Django teams, projects with complex editorial workflows, content-heavy sites where the page tree model is valuable.
See the full Wagtail guide for Docker deployment.
Decision Matrix
| Use Case | Choose |
|---|---|
| Simplest self-hosted deployment | Directus — official Docker image, SQLite option, one command |
| Non-technical editors managing content | Directus — best admin UI for non-developers |
| Largest plugin ecosystem | Strapi — most plugins, most community resources |
| TypeScript/Next.js project | Payload CMS — native integration, type safety |
| GraphQL-first backend | KeystoneJS — GraphQL is the primary API |
| Python/Django stack | Wagtail — only serious Python option |
| Need both REST and GraphQL | Directus — both out of the box, no plugins |
| Smallest resource footprint | Directus with SQLite — no external database needed |
| Complex editorial workflows | Wagtail — drafts, moderation, scheduling, page tree |
| Maximum flexibility, no vendor lock-in | Strapi or Payload — MIT license, code-first |
Recommendation
Directus is the best choice for most self-hosters. It offers the lowest deployment friction (official Docker image), the most polished admin interface, both REST and GraphQL APIs without plugins, and supports everything from SQLite for small projects to PostgreSQL for production workloads. The fact that non-developers can manage content without touching code is a significant practical advantage.
Choose Strapi if you need the largest ecosystem and plugin marketplace. Choose Payload if you are building a TypeScript/Next.js application and want deep CMS integration. Choose Wagtail if your stack is Python. Skip KeystoneJS unless you specifically need a GraphQL-native schema built on Prisma — its smaller community and slower development pace make it harder to recommend in 2026.
The BSL license on Directus is the one asterisk — if you plan to offer CMS hosting as a commercial service, Strapi (MIT) or Payload (MIT) give you more licensing freedom. For internal use or powering your own sites, the BSL restriction does not apply.
Common Mistakes
Choosing based on GitHub stars alone. Strapi has the most stars, but stars measure popularity, not fitness for your use case. Directus with 29k stars has a better admin UI and simpler deployment than Strapi with 66k.
Ignoring deployment complexity. Directus is the only one with an official Docker image. The other four require you to scaffold a project, write a Dockerfile, and manage a build step. Factor this into your decision.
Using SQLite in production for high-traffic sites. SQLite works well for small sites and development. For concurrent writes or multi-instance deployments, use PostgreSQL. Directus and Strapi both support both — start with SQLite, migrate to PostgreSQL when you need to.
Forgetting to set strong secrets. Every CMS in this list requires a secret key for signing tokens. Generate a random 64+ character string — do not use defaults in production. Use openssl rand -hex 32 to generate one.
Skipping Redis for caching. Directus and Strapi both benefit significantly from Redis caching in production. The Docker Compose example above includes Redis for good reason — API response times drop substantially.
Next Steps
Pick the CMS that fits your stack and deploy it:
- How to Self-Host Directus — full Docker setup with both SQLite and PostgreSQL options
- How to Self-Host Strapi — project scaffolding, custom Dockerfile, and production deployment
- How to Self-Host Payload CMS — Next.js-based deployment with Docker
- How to Self-Host KeystoneJS — Prisma + GraphQL setup
- How to Self-Host Wagtail — Django deployment with Gunicorn and Docker
Once your CMS is running, put it behind a reverse proxy for SSL:
- Reverse Proxy Setup — Nginx Proxy Manager, Traefik, or Caddy
Related
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments