Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Datenbanken 11.09.2026 · 10 min Lesezeit

Strapi mit Docker installieren: Führendes Open-Source Headless CMS selbst hosten

Strapi 5 erzeugt aus Inhaltsmodellen automatisch REST- und GraphQL-Schnittstellen. Diese Anleitung zeigt den Aufbau mit Docker Compose und PostgreSQL, die Pflicht-Secrets und die Absicherung für den Produktivbetrieb.

Neue Version verfügbar: strapi 5.57.0. Diese Anleitung wird überarbeitet. Beschrieben ist strapi 5.56.0.

WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Strapi selbst hosten: Docker Compose, PostgreSQL, Sichere Secrets. Illustration mit Laptop und Administrationsoberfläche.

Strapi ist ein Open-Source Headless CMS: Es verwaltet Inhalte und liefert sie per API aus, die Darstellung übernimmt eine separate Website oder App. Sie definieren Inhaltstypen im Browser, Strapi erzeugt daraus REST- und auf Wunsch GraphQL-Schnittstellen. Diese Anleitung zeigt, wie Sie Strapi 5 mit Docker Compose und PostgreSQL auf einem Linux-Host betreiben, die Pflicht-Secrets erzeugen und den Dienst für den Produktivbetrieb absichern.

Voraussetzungen

  • Docker Engine >= 20.10 und das Docker Compose Plugin v2 (docker compose, nicht das veraltete docker-compose) auf einem Linux-Host (x86-64 oder ARM64), einer VM oder einem NAS mit Docker-Unterstützung, 2 CPU-Kerne empfohlen. Installation siehe Docker und Docker Compose auf Linux installieren.
  • Node.js 20 oder neuer (Strapi 5 unterstützt laut engines-Angabe Version 20 bis 26, empfohlen eine LTS-Version wie 22) auf dem Rechner, auf dem Sie das Projekt anlegen. Nur für npx create-strapi-app nötig.
  • Mindestens 2 GB freier Arbeitsspeicher für den Docker-Build-Prozess (node-gyp, Sharp-Kompilierung) und 5 GB freier Speicherplatz (node_modules, Docker-Images, Datenbank).
  • openssl für die Secrets (auf gängigen Linux-Hosts vorinstalliert).
  • Für den Produktivbetrieb: ein Reverse Proxy (Nginx, Caddy, Traefik) mit TLS, siehe Caddy als Reverse Proxy einrichten.
EigenschaftWert
Aktuelle Versionv5.56.0 (30.09.2026)
Docker-ImageSelf-Build (node:22-alpine), kein offizielles Image
Datenbank (empfohlen)PostgreSQL 16
Port1337 (HTTP, Admin-Panel + API)
RAM (Minimum)2 GB (Build), 512 MB (Betrieb)
LizenzMIT (Community Edition)

Schritt 1: Strapi-Projekt anlegen und Projektordner vorbereiten

Strapi braucht einen Projektordner mit package.json, package-lock.json und src/, den das offizielle CLI anlegt. Führen Sie auf Ihrem Rechner oder direkt auf dem Server aus:

npx create-strapi-app@latest mein-strapi --no-run

Wählen Sie im Assistenten PostgreSQL als Datenbank. --no-run verhindert den lokalen Start, Strapi läuft nur in Docker.

Wechseln Sie in den Projektordner und legen Sie die Unterordner an:

cd mein-strapi
mkdir -p public/uploads config

Die Datei .dockerignore hält lokale node_modules aus dem Build heraus:

cat <<'EOF' > .dockerignore
node_modules/
.tmp/
.cache/
.git/
build/
.env
EOF

Erstellen Sie das Dockerfile. Es lehnt sich an das Community-Beispiel der Strapi-Dokumentation an und nutzt node:22-alpine:

cat <<'EOF' > Dockerfile
FROM node:22-alpine AS base

# Abhängigkeiten für Sharp (Bildverarbeitung) und node-gyp
RUN apk add --no-cache \
    build-base \
    python3 \
    vips-dev

ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

WORKDIR /opt/app

COPY package.json package-lock.json ./
RUN npm install --legacy-peer-deps

ENV PATH=/opt/app/node_modules/.bin:$PATH

WORKDIR /opt/app
COPY . .

RUN chown -R node:node /opt/app
USER node

EXPOSE 1337

CMD ["npm", "run", "develop"]
EOF

Verifizieren: Alle Pflichtdateien liegen im Projektordner:

ls -la
# Erwartete Ausgabe (Auszug):
# -rw-r--r-- 1 user user  ... Dockerfile
# -rw-r--r-- 1 user user  ... .dockerignore
# -rw-r--r-- 1 user user  ... package.json
# -rw-r--r-- 1 user user  ... package-lock.json
# drwxr-xr-x 2 user user  ... config/
# drwxr-xr-x 3 user user  ... public/
# drwxr-xr-x 4 user user  ... src/

Schritt 2: Pflicht-Secrets generieren und .env anlegen

Strapi startet nicht, solange die Schlüssel Platzhalter enthalten. Erzeugen Sie die Werte einzeln mit openssl:

# Jeden Befehl einzeln ausführen und den Wert notieren
openssl rand -base64 32   # APP_KEY_1
openssl rand -base64 32   # APP_KEY_2
openssl rand -base64 32   # ADMIN_JWT_SECRET
openssl rand -base64 32   # JWT_SECRET
openssl rand -base64 32   # API_TOKEN_SALT
openssl rand -base64 32   # TRANSFER_TOKEN_SALT
openssl rand -base64 32   # ENCRYPTION_KEY
openssl rand -base64 32   # DATABASE_PASSWORD

Legen Sie die .env an und ersetzen Sie alle Platzhalter in Großbuchstaben durch Ihre Werte:

# Strapi Server
HOST=0.0.0.0
PORT=1337
NODE_ENV=development

# Datenbank (PostgreSQL)
DATABASE_CLIENT=postgres
DATABASE_HOST=strapiDB
DATABASE_PORT=5432
DATABASE_NAME=strapi
DATABASE_USERNAME=strapi
DATABASE_PASSWORD=IHR_SICHERES_PASSWORT

# Pflicht-Secrets (alle 6 individuell generieren!)
APP_KEYS=IHR_APP_KEY_1,IHR_APP_KEY_2
ADMIN_JWT_SECRET=IHR_ADMIN_JWT_SECRET
JWT_SECRET=IHR_JWT_SECRET
API_TOKEN_SALT=IHR_API_TOKEN_SALT
TRANSFER_TOKEN_SALT=IHR_TRANSFER_TOKEN_SALT
ENCRYPTION_KEY=IHR_ENCRYPTION_KEY

Hinweis zu APP_KEYS: Der Wert ist eine kommagetrennte Liste, tragen Sie mindestens zwei Schlüssel ein. Vorlagen für Strapi 4 fehlt oft ENCRYPTION_KEY, der ab Strapi 5 Pflicht ist.

Verifizieren: Die .env enthält keinen Platzhalter mehr:

grep -c "IHR_\|toBeModified\|SICHERES" .env
# Erwartete Ausgabe: 0  (kein Platzhalter mehr vorhanden)

Schritt 3: compose.yaml und Datenbankkonfiguration erstellen

Legen Sie die compose.yaml im Projektordner an. Sie definiert strapi (Self-Build) und strapiDB (PostgreSQL 16) in einem gemeinsamen Netzwerk. Konfiguration, Quellcode und Uploads liegen als Bind-Mounts im Projektordner und überstehen Neustarts. Der PostgreSQL-Port ist nur an localhost gebunden:

services:
  strapi:
    container_name: strapi
    build: .
    image: strapi:latest
    restart: unless-stopped
    env_file: .env
    environment:
      NODE_ENV: development
    volumes:
      - ./config:/opt/app/config
      - ./src:/opt/app/src
      - ./.env:/opt/app/.env
      - ./public/uploads:/opt/app/public/uploads
    ports:
      - "1337:1337"
    networks:
      - strapi
    depends_on:
      strapiDB:
        condition: service_healthy

  strapiDB:
    container_name: strapiDB
    restart: unless-stopped
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ${DATABASE_USERNAME}
      POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
      POSTGRES_DB: ${DATABASE_NAME}
    volumes:
      - strapi-data:/var/lib/postgresql/data/
    ports:
      - "127.0.0.1:5432:5432"
    networks:
      - strapi
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DATABASE_USERNAME} -d ${DATABASE_NAME}"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  strapi-data:

networks:
  strapi:
    name: strapi
    driver: bridge

DATABASE_HOST=strapiDB muss exakt dem Service-Namen entsprechen. Im Container zeigt localhost auf den Strapi-Container selbst, nicht auf die Datenbank.

Damit nach längerer Inaktivität keine Verbindungsfehler auftreten, setzen Sie den Connection-Pool auf min: 0. Erstellen Sie config/database.js:

cat <<'EOF' > config/database.js
module.exports = ({ env }) => ({
  connection: {
    client: env('DATABASE_CLIENT', 'postgres'),
    connection: {
      host: env('DATABASE_HOST', 'strapiDB'),
      port: env.int('DATABASE_PORT', 5432),
      database: env('DATABASE_NAME', 'strapi'),
      user: env('DATABASE_USERNAME', 'strapi'),
      password: env('DATABASE_PASSWORD', ''),
      ssl: env.bool('DATABASE_SSL', false),
    },
    pool: {
      min: 0,  // Pflicht in Docker: verhindert KnexTimeoutError nach Inaktivität
      max: 10,
    },
    debug: false,
  },
});
EOF

Verifizieren: Die Compose-Syntax ist fehlerfrei:

docker compose config --quiet
# Keine Ausgabe = valide YAML-Syntax

Schritt 4: Container bauen und starten

Der erste Build dauert 10 bis 15 Minuten, weil native Abhängigkeiten wie die Bildbibliothek Sharp kompiliert werden. Folgende Builds nutzen den Cache:

docker compose up -d --build

Verfolgen Sie die Logs:

docker compose logs -f strapi

Strapi ist bereit, sobald diese Zeilen erscheinen:

# Erwartete Ausgabe (Auszug):
# Project information
# ...
# Welcome back!
# To manage your project, go to the administration panel at:
# http://0.0.0.0:1337/admin

Verifizieren:

docker compose ps
# NAME        IMAGE           COMMAND   STATUS                    PORTS
# strapi      strapi:latest   ...       Up 2 minutes              0.0.0.0:1337->1337/tcp
# strapiDB    postgres:16-…   ...       Up 3 minutes (healthy)    127.0.0.1:5432->5432/tcp

curl -I http://localhost:1337
# HTTP/1.1 302 Found   (Weiterleitung zur /admin-Anmeldeseite)

Schritt 5: Admin-Account erstellen und Admin-Panel einrichten

Öffnen Sie http://localhost:1337/admin (auf einem entfernten Server http://SERVER-IP:1337/admin). Beim ersten Aufruf registrieren Sie den ersten Administrator mit Vorname, Nachname, E-Mail und Passwort. Legen Sie dieses Konto sofort an, solange der Port nur Ihnen zugänglich ist, sonst kann ein Dritter es übernehmen.

Im Content-Type Builder (linke Seitenleiste) legen Sie Ihre Inhaltsmodelle an. Strapi erzeugt die passenden REST-Endpunkte, etwa /api/artikel für den Inhaltstyp „Artikel“. GraphQL steht nach Installation des GraphQL-Plugins (Marketplace → GraphQL) unter /graphql zur Verfügung.

Verifizieren: Nach dem Login antwortet die API:

curl http://localhost:1337/_health
# HTTP 204 No Content (Strapi ist erreichbar)

curl -s http://localhost:1337/api | head -c 100
# {"data":null,"error":{"status":403,...}}
# HTTP 403 ist korrekt, alle Endpunkte sind standardmäßig geschützt

Schritt 6: Produktivbetrieb absichern

Für den Produktivbetrieb sind drei Änderungen Pflicht:

1. NODE_ENV auf production setzen: Ändern Sie NODE_ENV in der .env und im Block environment: der compose.yaml auf production. Der Eintrag in der compose.yaml überschreibt die .env, eine Änderung nur dort bliebe wirkungslos. Im Dockerfile ergänzen Sie vor EXPOSE die Zeile RUN ["npm", "run", "build"] und ändern CMD auf ["npm", "run", "start"]. Ohne Build findet npm run start kein Admin-Panel.

2. Port auf Loopback beschränken: Ändern Sie die Port-Bindung des Strapi-Services:

    ports:
      - "127.0.0.1:1337:1337"

Strapi ist dann nur noch für einen lokalen Reverse Proxy erreichbar. Den PostgreSQL-Port entfernen Sie in Produktion ganz.

3. Reverse Proxy mit TLS vorschalten: Richten Sie Nginx, Caddy oder Traefik vor Strapi ein, für Traefik siehe Traefik als Docker-Reverse-Proxy mit automatischem HTTPS. Nach allen Änderungen Image neu bauen:

docker compose build --no-cache
docker compose up -d

Verifizieren:

docker compose ps
# Alle Services: STATUS = Up

# In Produktion darf kein direkter Internetzugriff möglich sein:
# curl http://IHRE-SERVER-IP:1337 → Connection refused

Schritt 7: Updates und Backups

Für ein Update passen Sie die Strapi-Paketversionen in der package.json an (oder nutzen npx @strapi/upgrade minor) und bauen das Image neu. Sichern Sie vorher die Datenbank:

# Datenbank-Backup vor dem Update
docker exec strapiDB pg_dump -U strapi strapi > strapi_backup_$(date +%Y%m%d).sql

# Image neu bauen und Container neu starten
docker compose build --no-cache
docker compose up -d

Automatische Sicherung mit Rotation beschreibt MySQL & PostgreSQL Backup automatisieren mit cron. Sichern Sie außerdem ./src, ./config und ./public/uploads: Dort liegen Inhaltstypen und Medien.

Verifizieren:

docker compose ps
# Alle Services wieder Up nach dem Neustart

docker compose logs strapi --tail=20
# Keine Fehler zu Datenbank-Verbindung oder fehlenden Secrets

Troubleshooting / Typische Fehler

  • ECONNREFUSED oder ENOTFOUND localhost: DATABASE_HOST in der .env ist auf localhost statt auf den Compose-Service-Namen (strapiDB) gesetzt. Lösung: DATABASE_HOST=strapiDB.
  • KnexTimeoutError: Knex: Timeout acquiring a connection: Der Connection-Pool hat min > 0. Lösung: in config/database.js pool.min auf 0 setzen.
  • Admin-Panel: „Invalid token“ oder „Forbidden“: Eines der sechs Secrets enthält noch einen Platzhalter. Alle sechs (APP_KEYS, ADMIN_JWT_SECRET, JWT_SECRET, API_TOKEN_SALT, TRANSFER_TOKEN_SALT, ENCRYPTION_KEY) brauchen zufällige Werte.
  • Build-Fehler „vips-dev not found“ oder Sharp-Kompilierungsfehler: vips-dev fehlt in der apk add-Zeile des Dockerfiles. Hilft das nicht, empfiehlt die Strapi-Dokumentation node:22-slim statt Alpine.
  • „Database already exists“ beim ersten Start: Das PostgreSQL-Volume enthält Daten eines früheren Versuchs. Lösung bei einer Neuinstallation: docker compose down -v (löscht alle Volumes), dann neu starten.
  • „ENCRYPTION_KEY is required“: Ab Strapi 5 Pflicht, in Vorlagen für Strapi 4 fehlt der Wert. Ergänzen und neu starten.
  • Upload-Dateien nach Container-Neustart verloren: Der Mount ./public/uploads:/opt/app/public/uploads fehlt in der compose.yaml.
  • ARM64/Apple Silicon langsam: platform: linux/amd64 im Compose erzwingt x86-Emulation. Weglassen, node:22-alpine läuft auf ARM64 nativ.

Häufige Fragen

Brauche ich zwingend PostgreSQL oder geht auch SQLite?

SQLite genügt für lokale Tests. Für den Produktivbetrieb ist PostgreSQL die bessere Wahl: SQLite verarbeitet Schreibzugriffe nur nacheinander und erlaubt keine zweite Strapi-Instanz auf derselben Datenbank.

Wie generiere ich sichere Werte für APP_KEYS, JWT_SECRET und Co.?

Mit openssl rand -base64 32 für jeden Wert oder mit Node.js: node -e "console.log(require('crypto').randomBytes(32).toString('base64'))". Für APP_KEYS mindestens zwei kommagetrennte Strings eintragen, z.B. APP_KEYS=wert1,wert2.

Kann ich Strapi mit einer bestehenden Datenbank verbinden?

Nein. Strapi ist nicht für bestehende Datenbanken ausgelegt, die Datenbank muss beim ersten Start leer sein. Strapi legt die Tabellen selbst an.

Wo sind Content-Types nach einem Container-Neustart?

Die Definitionen liegen als Dateien unter ./src. Mit dem Mount ./src:/opt/app/src bleiben sie erhalten, ohne ihn gehen sie beim Neuerstellen des Containers verloren.

Gibt es eine fertige Alternative zum Self-Build?

Es gibt Community-Images, aber keine offiziellen: Strapi kennzeichnet die Dockerfile-Beispiele der Dokumentation ausdrücklich als Gefälligkeit für die Community. vshadbolt/strapi wird für Strapi 5 gepflegt (Stand 01.10.2026: 5.55.1, AMD64 und ARM64). naskio/strapi endet bei 5.30.1 (letzte Aktualisierung November 2025). Prüfen Sie bei Fremd-Images Herkunft und Aktualität selbst.

Wie aktualisiere ich Strapi auf eine neue Version?

Wie in Schritt 7: Backup von Datenbank und ./src, Paketversionen anpassen, docker compose build --no-cache und docker compose up -d. Updates können Schema-Migrationen enthalten, lesen Sie vorher die Release Notes.

Kann ich Strapi hinter Nginx oder Traefik betreiben?

Ja, das ist der empfohlene Aufbau: Strapi an 127.0.0.1:1337 binden, TLS im Reverse Proxy terminieren. Die Strapi-Dokumentation enthält Beispiele für Nginx, Apache und Caddy.

Fazit

Strapi 5 eignet sich für Teams, die ihr Content-Backend selbst betreiben wollen. Der Aufbau mit Docker Compose und PostgreSQL verlangt mehr Vorbereitung als ein einzelnes docker run: Projekt per CLI anlegen, sechs Secrets erzeugen, Connection-Pool anpassen und für den Produktivbetrieb Build, Port-Bindung und Reverse Proxy einrichten. Danach entstehen neue Inhaltstypen samt API ohne Programmieraufwand.

Weiterführende Anleitungen und Quellen

Offizielle Quellen: Strapi Docker-Installationsdokumentation (docs.strapi.io) | Strapi GitHub-Repository | Strapi Datenbankkonfiguration