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

FeatureDirectusStrapiPayload CMSKeystoneJSWagtail
LanguageNode.js (TypeScript)Node.js (TypeScript)TypeScript (Next.js)TypeScript (Prisma)Python (Django)
DatabasePostgreSQL, MySQL, MariaDB, SQLite, MS SQL, CockroachDBPostgreSQL, MySQL, MariaDB, SQLitePostgreSQL, MongoDBPostgreSQL, MySQL, SQLite (via Prisma)PostgreSQL (recommended), SQLite, MySQL
API typeREST + GraphQLREST + GraphQLREST + GraphQL + Local APIGraphQL (primary), REST (limited)REST (Wagtail API) + GraphQL (plugin)
Admin UIExcellent — no-code, drag-and-drop, role-basedGood — content-type builder, plugin marketplaceGood — lives inside Next.js appMinimal — functional but basicExcellent — StreamField, page tree, rich editing
Content modelingNo-code via admin UI or schema migrationNo-code via Content-Type Builder or codeCode-first (TypeScript config files)Code-first (TypeScript schema)Code-first (Python models)
Media managementBuilt-in DAM with transformationsBuilt-in media libraryBuilt-in upload collectionsBasic file handlingBuilt-in image/document management
AuthenticationBuilt-in with SSO, LDAP, OAuth, SAMLBuilt-in with providers pluginBuilt-in with access controlSession-based, customizableDjango auth + SSO packages
LicenseBSL 1.1 (converts to GPL after 3 years)MIT (community) / proprietary (enterprise)MITMITBSD-3-Clause
Official Docker imageYes (directus/directus)No (build your own)No (build your own)No (build your own)No (build your own Dockerfile)
Docker complexityLow — single image, one commandMedium — must scaffold project first, then DockerizeMedium — Next.js build step requiredMedium-High — build step + Prisma migrationsHigh — 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 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-grapple or strawberry-django plugin
  • 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 CaseChoose
Simplest self-hosted deploymentDirectus — official Docker image, SQLite option, one command
Non-technical editors managing contentDirectus — best admin UI for non-developers
Largest plugin ecosystemStrapi — most plugins, most community resources
TypeScript/Next.js projectPayload CMS — native integration, type safety
GraphQL-first backendKeystoneJS — GraphQL is the primary API
Python/Django stackWagtail — only serious Python option
Need both REST and GraphQLDirectus — both out of the box, no plugins
Smallest resource footprintDirectus with SQLite — no external database needed
Complex editorial workflowsWagtail — drafts, moderation, scheduling, page tree
Maximum flexibility, no vendor lock-inStrapi 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:

Once your CMS is running, put it behind a reverse proxy for SSL:

Comments