← All articles
CONTENT MANAGEMENT Self-Hosting Strapi: A Headless CMS for Your Fronten... 2026-02-09 · strapi · headless-cms · api

Self-Hosting Strapi: A Headless CMS for Your Frontend Stack

Content Management 2026-02-09 strapi headless-cms api content-management development

You're building a website with Next.js, Astro, or Nuxt. You need a CMS so non-developers can manage content, but WordPress is a monolith tied to PHP, and you don't want to pay $300/month for Contentful. You need a headless CMS — something that manages content through an API while your frontend handles the presentation.

Strapi is the most popular open-source headless CMS. It gives you an admin panel for content editors, a flexible content modeling system, and REST + GraphQL APIs for your frontend. Self-hosting it means no per-seat fees, no API call limits, and full control over your data.

What Strapi Does

The key concept: you define content types (like "Blog Post" with fields for title, body, author, featured image), and Strapi automatically generates the admin interface and API endpoints. No code required for basic setups.

Content Editors Admin panel Strapi Content type builder Admin panel REST & GraphQL APIs Media library manage PostgreSQL Content storage Next.js / Astro Mobile App Static Site Gen REST / GraphQL Media Storage Local / S3 / Cloudinary

Docker Deployment

# docker-compose.yml
services:
  strapi:
    image: strapi/strapi:latest
    ports:
      - "1337:1337"
    volumes:
      - strapi_data:/srv/app
    environment:
      DATABASE_CLIENT: postgres
      DATABASE_HOST: db
      DATABASE_PORT: 5432
      DATABASE_NAME: strapi
      DATABASE_USERNAME: strapi
      DATABASE_PASSWORD: strapipass
      DATABASE_SSL: "false"
      APP_KEYS: "key1,key2,key3,key4"
      API_TOKEN_SALT: "your-api-token-salt"
      ADMIN_JWT_SECRET: "your-admin-jwt-secret"
      JWT_SECRET: "your-jwt-secret"
      TRANSFER_TOKEN_SALT: "your-transfer-token-salt"
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:16
    volumes:
      - strapi_db:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: strapi
      POSTGRES_USER: strapi
      POSTGRES_PASSWORD: strapipass
    restart: unless-stopped

volumes:
  strapi_data:
  strapi_db:
docker compose up -d

Strapi is now available at http://your-server:1337/admin. The first visit prompts you to create an admin account.

Important: Generate real random strings for APP_KEYS, API_TOKEN_SALT, ADMIN_JWT_SECRET, and JWT_SECRET. Use openssl rand -base64 32 to generate each one.

Building Content Types

The content type builder is Strapi's core feature. Access it at Content-Type Builder in the admin panel.

Example: Blog post content type

  1. Click Create new collection type
  2. Name it "Article"
  3. Add fields:
    • title (Text, required)
    • slug (UID, attached to title)
    • body (Rich text)
    • excerpt (Text, short)
    • featuredImage (Media, single)
    • category (Relation, many-to-one with Categories)
    • publishedDate (Date)

Strapi generates the database tables and API endpoints automatically. You now have:

API Access and Permissions

By default, Strapi's API endpoints are locked down. You need to configure permissions:

  1. Go to Settings > Users & Permissions > Roles
  2. Click Public
  3. Under your content type, enable find and findOne
  4. Click Save

Now unauthenticated requests can read your content:

curl 'http://localhost:1337/api/articles?populate=*'

The populate=* parameter includes related content (like the featured image and category).

API tokens for authenticated access

For write operations or private content, create an API token:

  1. Go to Settings > API Tokens
  2. Click Create new API Token
  3. Set permissions (full access or custom)
  4. Copy the generated token
curl 'http://localhost:1337/api/articles' \
  -X POST \
  -H 'Authorization: Bearer your-api-token' \
  -H 'Content-Type: application/json' \
  -d '{"data": {"title": "My First Post", "body": "Hello world"}}'

GraphQL Support

Install the GraphQL plugin (it's built-in, just needs enabling):

# Inside the Strapi container
npm run strapi install graphql

Then query your content with GraphQL:

query {
  articles {
    data {
      attributes {
        title
        body
        publishedDate
        category {
          data {
            attributes {
              name
            }
          }
        }
      }
    }
  }
}

Access the GraphQL playground at http://localhost:1337/graphql.

Media Handling

By default, Strapi stores media files locally. For production, you'll want external storage:

S3-compatible storage

Install the AWS S3 provider:

npm install @strapi/provider-upload-aws-s3

Configure in config/plugins.js:

module.exports = {
  upload: {
    config: {
      provider: 'aws-s3',
      providerOptions: {
        s3Options: {
          credentials: {
            accessKeyId: process.env.AWS_ACCESS_KEY_ID,
            secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
          },
          region: process.env.AWS_REGION,
          params: {
            Bucket: process.env.AWS_BUCKET,
          },
        },
      },
    },
  },
};

This works with any S3-compatible storage: AWS S3, Backblaze B2, MinIO, or Cloudflare R2.

Strapi vs Other Headless CMS Options

Feature Strapi Directus Ghost WordPress (headless)
Content modeling Visual builder Database-driven Fixed blog schema Plugins
API output REST + GraphQL REST + GraphQL Content API REST
Admin UI Modern Modern Excellent (writing) Dated
Self-hosted Yes (free) Yes (free) Yes (free) Yes (free)
Media handling Built-in Built-in Built-in Built-in
Roles/permissions Granular Granular Basic Plugin-dependent
Use case Any content Any content Blogs/newsletters Blogs/general
Extensibility Plugins + code Extensions Themes + API Massive ecosystem

When to choose Strapi

When to choose something else

Production Tips

Reverse proxy configuration

Put Strapi behind Caddy or Nginx:

cms.yourdomain.com {
    reverse_proxy strapi:1337
}

Environment-specific configuration

Strapi reads from environment variables for production settings. Key ones:

NODE_ENV=production
HOST=0.0.0.0
PORT=1337
URL=https://cms.yourdomain.com

Backup strategy

Back up two things:

  1. PostgreSQL databasepg_dump on a schedule
  2. Upload directory — Media files (if using local storage)
# Database backup
docker exec strapi-db-1 pg_dump -U strapi strapi > strapi-backup.sql

The Bottom Line

Strapi is the go-to self-hosted headless CMS for teams that need flexible content modeling and a clean admin interface. It sits between "just use a database" and "pay for a managed CMS service." If you're building a content-driven site with a modern frontend framework and want non-developers to manage the content, Strapi gives you the admin panel, the API, and the content modeling tools without monthly fees or vendor lock-in.