v0.1.1 Prebuilt Docker images

The self-hosted CMS for multilingual blogs

Write posts in a structured editor, publish directly or through review, and deliver each translation through built-in reading pages, a REST API or webhooks. Open source, one container, no other services.

  • Apache 2.0 license
  • Prebuilt image for amd64 and arm64
  • Embedded SQLite database
  • Any BCP 47 code for your content languages
  • 6 panel languages English, Turkish, French, German, Japanese and Simplified Chinese
  • OWASP ASVS 5.0 reviewed at levels 1 and 2
  • One container with an embedded SQLite database
Writing

A focused editor with a safety net

Structured content instead of raw HTML, saved while you type and kept in a history you can always go back to.

  • Block editor

    Headings, nested and task lists, quotes, tables, highlighted code, math, images from the media library and YouTube or Vimeo embeds.

  • Autosave and revisions

    The working draft saves itself a few seconds after you stop typing. Every save goes into a revision history you can restore.

  • Private previews

    Open unpublished versions and pending submissions the way readers will see them, without making them public.

  • Media library

    Uploads are re-encoded to WebP in several sizes with their metadata removed, and every image keeps alternative text per language.

  • Categories and tags

    Categories carry a name and a slug in every language, and tags are entered per translation.

  • Search and sharing fields

    Each translation has its own meta title, meta description and sharing image, and its reading time is calculated on save.

Multilingual

Every translation is a first-class page

A post holds one translation per content language. Each has its own address, fields and publishing state, so every language can move at its own pace.

  • Add content languages in the panel with any BCP 47 code, such as de, pt-BR or zh-Hans.
  • Publish the English version while the German one is still in review.
  • Reading pages link translations with hreflang, including an x-default entry for your default language.
  • API clients can fall back to the default language when a translation is missing.
  • The panel speaks English, Turkish, French, German, Japanese and Simplified Chinese, whatever your content languages are.
Translations 4 languages
en English/blog/en/designing-for-readers Published
tr Türkçe/blog/tr/okurlar-icin-tasarlamak Published
de Deutsch/blog/de/fuer-leser-gestalten Pending review
pt-BR Português (Brasil)/blog/pt-BR/design-para-leitores Draft
<head> of /blog/en/designing-for-readers
<link rel="canonical" href="https://cms.example.com/blog/en/designing-for-readers">
<link rel="alternate" hreflang="en" href="https://cms.example.com/blog/en/designing-for-readers">
<link rel="alternate" hreflang="tr" href="https://cms.example.com/blog/tr/okurlar-icin-tasarlamak">
<link rel="alternate" hreflang="x-default" href="https://cms.example.com/blog/en/designing-for-readers">
Only published translations are linked. Drafts and pending changes stay private.
Editorial workflow

Publish directly or go through review

Roles decide who may publish. Everyone else submits their work, and what readers see stays unchanged until a change is approved.

  1. Draft

    Write

    Authors write in the editor while the working draft saves itself.

  2. Pending review

    Submit

    Authors without direct publishing send their changes to the review queue.

  3. Approved

    Review

    An admin or the founder approves, or rejects with a note that the author sees.

  4. Published

    Publish

    The translation goes live right away or at its scheduled time, and webhooks fire.

The founder, admins and authors allowed to publish directly skip the queue and publish or schedule right away.

  • Founder

    Exactly one per installation. Everything an admin can do, plus system settings, managing admins and changing roles.

  • Admin

    Manages authors, reviews their submissions, moderates posts, and looks after languages, categories, API keys, webhooks and CORS.

  • Author

    Writes their own posts and manages their own images. Publishing needs approval unless the author may publish directly.

Nobody edits another person's post, not even the founder. Staff can hide a post with a reason that its owner sees, and an append-only audit log records sign-ins, account changes, publishing, moderation and configuration.

Delivery

Use the built-in blog or go headless

Readers can get posts straight from Servitor, your own front end can pull them through the API, or both at once. Turn the reading pages off to run headless.

  • Reading pages

    Server-rendered pages under /blog without JavaScript, with canonical and hreflang links, Open Graph, JSON-LD, RSS feeds and sitemaps.

  • REST API

    Read-only JSON with API keys limited to languages and categories, full-text search, per-key rate limits, ETags and an OpenAPI 3.1 description.

  • Webhooks

    Signed with HMAC-SHA256 and sent from a persistent queue with retries, so a static site can rebuild whenever published content changes.

REST API request
curl -H "Authorization: Bearer $SERVITOR_API_KEY" \
  "https://cms.example.com/api/v1/posts?lang=tr&per_page=10"
Response
{
  "data": [
    {
      "id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
      "published_at": "2026-09-24T09:00:00.000Z",
      "translations": [
        {
          "language": "tr",
          "slug": "okurlar-icin-tasarlamak",
          "title": "Okurlar için tasarlamak",
          "reading_time_minutes": 4,
          "content_html": "<p>…</p>"
        }
      ]
    }
  ],
  "meta": { "page": 1, "per_page": 10, "total": 42, "total_pages": 5 }
}
Webhook delivery
POST /hooks/servitor HTTP/1.1
Host: build.example.com
Content-Type: application/json
User-Agent: Servitor-Webhooks/1.0
X-Servitor-Event: post.published
X-Servitor-Delivery: 0f6c9a55-7a4e-4f21-9f8d-2d0f6f3b9b1a
X-Servitor-Signature: t=1790251200,v1=5d2a9c…

{
  "event": "post.published",
  "delivery_id": "0f6c9a55-7a4e-4f21-9f8d-2d0f6f3b9b1a",
  "timestamp": "2026-09-24T12:00:00.000Z",
  "post_id": "8b1d1c52-5d0e-4e0f-9c1b-3f7a2b6e4d10",
  "languages": ["en", "tr"],
  "slugs": {
    "en": "designing-for-readers",
    "tr": "okurlar-icin-tasarlamak"
  }
}
Security

Secure by default, reviewed in the open

Servitor is checked against every level 1 and 2 requirement of OWASP ASVS 5.0, and the protections below are on from the first start.

  • No public sign-up

    Accounts come only from the founder seed and invitations, and sign-in answers never reveal whether an account exists.

  • Two-factor authentication

    TOTP with single-use backup codes, and a setting that requires it for the founder and admins.

  • Careful password handling

    12 to 128 characters, checked against bundled lists of common passwords and hashed with scrypt.

  • One permission module

    Every page, action and endpoint is checked on the server, and a test covers every cell of the permission matrix.

  • Sanitized content

    Editor JSON is validated against an allowlist and rendered to sanitized HTML on the server before it is stored.

  • Strict browser policies

    A nonce-based Content Security Policy on every response, and reading pages that ship without any script.

  • Scoped, hashed API keys

    Stored only as SHA-256 hashes, limited to languages and categories, and refused when sent in a query string.

  • SSRF-safe webhooks

    Targets are resolved and checked before every delivery, so webhooks cannot reach into your internal network.

  • Hardened container

    An unprivileged user, a read-only root filesystem and no capabilities. Every image carries a software bill of materials and a signed build attestation.

Self-hosting

Up and running with Docker Compose

All you need is a server with Docker and Docker Compose. Every release is published as a signed, prebuilt image for amd64 and arm64 on the GitHub Container Registry.

  1. Get the Compose file

    Clone the repository and create your configuration from the example file.

    Terminal
    git clone https://github.com/justhasanuknow/servitor-cms.git
    cd servitor-cms
    cp .env.example .env
  2. Set the secret and the founder

    Set ORIGIN to your public address, generate the secret with openssl rand -hex 32 and fill in the founder account. For a quick local test, use ORIGIN=http://localhost:3000 and leave ADDRESS_HEADER and XFF_DEPTH empty.

    .env
    SERVITOR_VERSION=0.1
    ORIGIN=https://cms.example.com
    BETTER_AUTH_SECRET=<output of openssl rand -hex 32>
    FOUNDER_EMAIL=you@example.com
    FOUNDER_NAME=Your Name
    FOUNDER_PASSWORD=<12 to 128 characters>
  3. Start the container

    Compose pulls the image in the version set by SERVITOR_VERSION, where 0.1 follows the newest 0.1.x release, and starts Servitor. Open /panel/login, sign in as the founder and choose a new password when the panel asks for it.

    Terminal
    docker compose up -d
  • Automatic migrations

    The database schema is updated on start, before the server accepts requests.

  • Health check

    A /healthz endpoint reports whether the database is reachable, for Docker and your monitoring.

  • Backup and restore

    One command writes a consistent snapshot of the database and uploads, and restores check an archive before replacing anything.

  • Behind your proxy

    Documented setups for Caddy, Nginx and Coolify, with TLS and real client addresses for rate limiting.

Servitor runs as a single instance: rate limits, the publishing scheduler and the webhook worker live inside the application process, so never start more than one container on the same data.

Open source

Apache 2.0, built on proven tools

Read the code, run it on your own infrastructure and adapt it to your needs. Bug reports, fixes and translations of the panel are welcome.

  • SvelteKit
  • Svelte 5
  • TypeScript
  • Tailwind CSS
  • shadcn-svelte
  • Tiptap
  • Better Auth
  • Drizzle ORM
  • SQLite
  • Paraglide
  • sharp
  • Vitest
  • Playwright
  • Node.js 24
FAQ

Frequently asked questions

Is Servitor CMS free to use?

Yes. Servitor CMS is open source under the Apache License 2.0. You can use it commercially, change it and run it on your own servers, as long as derivative works keep the license and the NOTICE file.

What do I need to run it?

A server with Docker and Docker Compose. Servitor runs as one container with an embedded SQLite database and needs no other services. In production, put it behind a reverse proxy such as Caddy, Nginx or Coolify for TLS.

Can I use Servitor as a headless CMS?

Yes. Turn off the public reading site in the settings and use the read-only REST API and webhooks with any front end, such as a static site generator or a mobile app.

Which languages can I write in?

Any language you can describe with a BCP 47 code, such as en, de, pt-BR or zh-Hans. Every post can have one translation per content language. The panel itself is available in English, Turkish, French, German, Japanese and Simplified Chinese.

Can I run several instances behind a load balancer?

No. Rate limits, the publishing scheduler and the webhook worker live inside the application process, so exactly one container runs per data directory.

How do backups work?

A command inside the container writes a timestamped archive with a consistent database snapshot and all uploads. Restoring checks the archive before it replaces anything and keeps the previous data aside.

How do I update Servitor?

Take a backup, check out the tag of the new release so that docker-compose.yml and .env.example match it, set SERVITOR_VERSION and run docker compose pull and docker compose up -d. Migrations run automatically on start, and with a minor line such as 0.1, docker compose pull alone picks up the newest patch release.

Can I verify the Docker image?

Yes. Every published image carries a signed attestation of the workflow and commit it was built from. The GitHub CLI checks it with gh attestation verify oci://ghcr.io/justhasanuknow/servitor-cms:0.1 --owner justhasanuknow.

Is Servitor ready for production?

Version 0.1.1 is the current release. Servitor follows semantic versioning: before 1.0.0, a minor version may change the configuration, the API or the data in incompatible ways, and its release notes say so.

Where is the documentation?

The documentation lives in the docs folder of the repository, and every installation serves it at /docs. The panel links to it under Help, Documentation.

Start publishing in the languages your readers speak

Install Servitor CMS with Docker Compose, invite your team and publish your first translation.