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

Grist mit Docker Compose selbst hosten: Tabelle und Datenbank für Teams

Schritt für Schritt zu einer eigenen Grist-Instanz mit Docker Compose: offizielles Image, Caddy als Reverse Proxy mit Login, Quick Setup mit Boot-Key, Formel-Sandbox, getestetes Backup und Restore sowie die typischen Fehler bei APP_HOME_URL, Rechten und Sitzungsgeheimnis.

Geprüft am 03.10.2026 · für Grist 1.7.20, Caddy 2

Mit KI erstellt – redaktionelle Prüfung ausstehend

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

Grafik mit der Überschrift „Grist selbst hosten mit Docker“, Karten für Tabellen, Backup und Sandbox sowie einem Laptop mit Tabellen-Mockup

Grist verbindet die Oberfläche einer Tabellenkalkulation mit einer relationalen Datenbank: typisierte Spalten, verknüpfte Tabellen, Python-Formeln und Zugriffsregeln bis auf Zeilenebene. Für Teams, die Excel-Listen per Mail verschicken oder Airtable-Daten ins eigene Haus holen möchten, ist das eine ernsthafte Alternative. Diese Anleitung zeigt Betrieb mit Docker Compose, Admin-Absicherung sowie Backup und Restore.

Voraussetzungen

Jedes Grist-Dokument ist eine eigene SQLite-Datei, eine externe Datenbank brauchen kleine Teams nicht. Die offizielle Doku nennt als bewährte Ausstattung für mittlere Last 2 CPUs, 8 GB RAM und 20 GB Speicher; für ein einzelnes Vorlagendokument reichen laut Hersteller 200 MB RAM mit Sandbox. In unserem Test belegte der Grist-Container mit einem offenen Dokument rund 244 MiB.

  • Linux-Server oder VM mit 2 Kernen, 4 GB RAM und 20 GB freiem Speicher, x86_64 (bei gVisor ab Sandy Bridge mit XSAVE) oder ARM64
  • Docker Engine mit Compose-Plugin; das Grist-Image ist rund 1,4 GB groß
  • ein DNS-Name wie grist.example.de, der auf den Server zeigt, und offene Ports 80 und 443
  • Shell-Zugriff für openssl und das Container-Log
EckdatenWert
Imagegristlabs/grist:1.7.20 (amd64, arm64)
Port im Container8484 (nur intern)
Volume/persist (Dokumente, home.sqlite3, Sitzungen)
Wichtige VariablenAPP_HOME_URL, GRIST_SESSION_SECRET, GRIST_ADMIN_EMAIL, GRIST_SANDBOX_FLAVOR, GRIST_FORWARD_AUTH_HEADER
LizenzApache-2.0 (Community edition)

Zum Projektstand: Das Repository gristlabs/grist-core hatte am 3. Oktober 2026 rund 11.900 GitHub-Sterne, der letzte Push stammt vom 2. Oktober 2026, das aktuelle Release v1.7.20 vom 28. September 2026. Pinnen Sie den Tag 1.7.20 statt latest. Das Image gristlabs/grist enthält einen Schalter zur lizenzpflichtigen Vollversion; wer ausschließlich Open-Source-Code betreiben möchte, nimmt gristlabs/grist-oss.

Schritt 1: Projektordner, Compose-Datei und Caddyfile anlegen

Legen Sie einen Ordner /opt/grist an und darin die drei Dateien. Grist bekommt keinen veröffentlichten Port, Caddy ist der einzige Eingang. GRIST_FORCE_LOGIN sperrt anonyme Zugriffe, GRIST_SINGLE_ORG legt einen Team-Bereich an, GRIST_SANDBOX_FLAVOR=gvisor isoliert die Python-Formeln.

services:
  grist:
    image: gristlabs/grist:${GRIST_TAG}
    restart: unless-stopped
    environment:
      APP_HOME_URL: https://${GRIST_DOMAIN}
      GRIST_SESSION_SECRET: ${GRIST_SESSION_SECRET}
      GRIST_ADMIN_EMAIL: ${GRIST_ADMIN_EMAIL}
      GRIST_SINGLE_ORG: ${GRIST_TEAM}
      GRIST_FORCE_LOGIN: "true"
      GRIST_SANDBOX_FLAVOR: gvisor
      GRIST_FORWARD_AUTH_HEADER: X-Forwarded-User
      GRIST_TELEMETRY_LEVEL: "off"
    # bewusst kein ports: Grist darf nur über Caddy erreichbar sein
    volumes:
      - ./persist:/persist
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:8484/status"]
      interval: 30s
      timeout: 5s
      start_period: 30s
      retries: 3

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    environment:
      GRIST_DOMAIN: ${GRIST_DOMAIN}
      GRIST_ADMIN_EMAIL: ${GRIST_ADMIN_EMAIL}
      GRIST_ADMIN_HASH: ${GRIST_ADMIN_HASH}
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
    depends_on:
      - grist

volumes:
  caddy_data:

Das Caddyfile schützt nur den Pfad /auth/login mit Benutzername und Passwort. Nach der Anmeldung setzt Caddy den Header X-Forwarded-User, Grist legt daraus eine Sitzung an. Auf allen anderen Pfaden entfernt Caddy diesen Header.

{$GRIST_DOMAIN} {
	handle /auth/login* {
		basic_auth {
			{$GRIST_ADMIN_EMAIL} {$GRIST_ADMIN_HASH}
		}
		reverse_proxy grist:8484 {
			header_up X-Forwarded-User {http.auth.user.id}
		}
	}
	handle {
		reverse_proxy grist:8484 {
			header_up -X-Forwarded-User
		}
	}
}

Die .env enthält die Werte. Das Sitzungsgeheimnis erzeugen Sie mit openssl rand -hex 32, den Passwort-Hash mit Caddy. Der Hash gehört in einfache Anführungszeichen, sonst wertet Compose seine Dollarzeichen als Variablen aus.

docker run --rm caddy:2 caddy hash-password --plaintext 'IhrStarkesPasswort'
GRIST_TAG=1.7.20
GRIST_DOMAIN=grist.example.de
GRIST_ADMIN_EMAIL=admin@example.de
GRIST_TEAM=firma
# mit: openssl rand -hex 32
GRIST_SESSION_SECRET=HIER_ZUFALLSWERT_EINSETZEN
# bcrypt-Hash, in einfachen Anführungszeichen
GRIST_ADMIN_HASH='$2a$14$HIER_HASH_EINSETZEN'

Der Benutzername ist zugleich die E-Mail-Adresse des Grist-Kontos. Weitere Personen ergänzen Sie als Zeilen im Block basic_auth.

Verifizieren: docker compose config --quiet läuft ohne Ausgabe durch, und docker compose config | grep APP_HOME_URL zeigt Ihre echte HTTPS-Adresse.

Schritt 2: Grist starten und Health prüfen

Starten Sie den Stack und warten Sie, bis der Healthcheck greift. Beim ersten Start legt Grist home.sqlite3, grist-sessions.db und docs in persist an.

cd /opt/grist
docker compose up -d
docker compose ps
docker compose exec grist curl -s localhost:8484/status
docker compose logs grist | grep -E 'Grist version|gvisor check'

Im Test meldete das Log gvisor check ok und Grist version is 1.7.20, der Status-Endpunkt antwortete mit Grist server(home,docs,static) is alive..

Verifizieren: docker compose ps zeigt den Grist-Container als healthy, und curl -I https://grist.example.de/ liefert eine Weiterleitung auf /boot.

Schritt 3: Erstanmeldung mit Boot-Key und Quick Setup

Eine frische Installation lässt niemanden direkt an die Daten. Grist schreibt beim Start einen zufälligen Boot-Key ins Log, der Serverzugriff beweist. Lesen Sie ihn mit docker compose logs grist | grep 'BOOT KEY' aus und fügen Sie ihn auf der Startseite ein.

Grist-Startseite „Welcome to Grist“ mit Eingabefeld für den Boot-Key
Erster Aufruf: Grist verlangt den Boot-Key aus dem Log.

Danach bestätigen Sie die Admin-Adresse und durchlaufen den Assistenten in fünf Schritten. Unter „Server“ prüft „Test URL“ die Basis-URL, dort wählen Sie die Community edition. Die Sandbox ist per Umgebungsvariable festgelegt.

Quick-Setup-Assistent von Grist, Schritt Sandboxing mit aktivem gVisor
Assistent, Schritt 2: gVisor ist aktiv.

Bei der Authentifizierung ist „Forwarded headers“ bereits aktiv. „Backups“ überspringen Sie mit „No external storage“, wenn Sie wie unten auf Dateiebene sichern. Im letzten Schritt wählen Sie „Locked down“: Nur der Admin legt Team-Bereiche an, persönliche Bereiche und Playground sind aus. Mit „Anwenden und online gehen!“ startet Grist neu und ist in Betrieb.

Grist Quick Setup, Standardrechte mit Voreinstellung Locked down
Voreinstellung „Locked down“ für die Standardrechte.

Verifizieren: Die Seite meldet „Grist ist online!“, und ein Aufruf von https://grist.example.de/ in einem privaten Fenster leitet auf /auth/login um, wo Caddy nach Benutzername und Passwort fragt.

Schritt 4: Admin-Zugang absichern

Grist vertraut dem Header X-Forwarded-User blind. Im Test genügte ein Aufruf von /auth/login direkt auf Port 8484 mit dem Header X-Forwarded-User: admin@example.com, um eine vollwertige Admin-Sitzung zu erhalten. Über Caddy dagegen blieb derselbe gefälschte Header wirkungslos, die API lieferte nur eine leere Liste. Deshalb hat der Grist-Dienst in der Compose-Datei bewusst kein ports:, und Caddy entfernt den Header auf allen Pfaden außer dem Login.

Entfernen Sie nach dem Setup im Admin-Bereich unter „Sicherheitseinstellungen“ den Boot-Key mit „Remove boot key“; im Test tauchte er auch nach einem Neustart nicht mehr im Log auf. Vorher muss die Anmeldung über Caddy funktionieren, sonst sperren Sie sich aus.

Sicherheitseinstellungen im Grist-Admin-Bereich mit Forward-Auth und gVisor
Sicherheitseinstellungen nach der Einrichtung.

Grenzen: Mit Basic Auth gibt es kein sauberes Abmelden, weil der Browser die Zugangsdaten zwischenspeichert. Für größere Teams ist Forward-Auth mit Authelia oder Authentik besser. OIDC und SAML direkt in Grist brauchen laut Doku einen Aktivierungsschlüssel der Vollversion.

Verifizieren: Die Sicherheitseinstellungen zeigen „forward-auth“, „OK: gvisor“, beim Sitzungsgeheimnis „konfiguriert“ und beim Boot-Key „deaktiviert“. curl -s -o /dev/null -w '%{http_code}' -u admin@example.de:falsch https://grist.example.de/auth/login liefert 401.

Schritt 5: Erstes Dokument anlegen und Sandbox prüfen

Legen Sie über „Neu hinzufügen“ ein Dokument an, etwa ein Inventar mit den Spalten Name, Standort und Anzahl. Für Skripte erzeugen Sie in den Profil-Einstellungen einen API-Schlüssel. Für den Restore-Nachweis lohnt eine eindeutige Markerzeile.

curl -s -H "Authorization: Bearer $GRIST_API_KEY" \
  https://grist.example.de/api/docs/DOC_ID/tables/Geraete/records

Ob die Sandbox greift, prüfen Sie mit einer Formelspalte, wie es die Doku empfiehlt: import glob und in der nächsten Zeile len(glob.glob('/etc/*')). In der Sandbox sieht die Formel kein Dateisystem des Containers.

Grist-Tabelle Geraete mit Markerzeile und zwei Formelspalten
Testdokument mit Markerzeile und Sandbox-Formel.

Verifizieren: Die Sandbox-Formel liefert 0, während docker compose exec grist sh -c 'ls /etc | wc -l' im Test 69 Einträge zählte. Im Ordner persist/docs liegt eine neue Datei mit der Endung .grist.

Schritt 6: Backup und Restore

Alles, was zählt, liegt in persist: die Dokumente in docs, Benutzer, Rechte und Dokumentnamen in home.sqlite3 und die Sitzungen in grist-sessions.db. Ohne home.sqlite3 erscheint ein Dokument nicht mehr in der Oberfläche. Sichern Sie daher den ganzen Ordner bei kurz gestopptem Grist, damit keine SQLite-Datei mitten im Schreiben kopiert wird. Im Test dauerte das unter einer Sekunde.

cd /opt/grist
mkdir -p backup
docker compose stop grist
tar czf backup/grist-persist-$(date +%F).tar.gz -C persist .
docker compose start grist
tar tzf backup/grist-persist-$(date +%F).tar.gz

Kopieren Sie das Archiv auf ein zweites System. Für den Restore ersetzen Sie bei gestopptem Grist den Ordner vollständig; die Besitzrechte setzt der Containerstart selbst.

docker compose stop grist
rm -rf persist && mkdir persist
tar xzf backup/grist-persist-2026-10-03.tar.gz -C persist
docker compose start grist

Im Test haben wir nach dem Backup eine weitere Zeile angelegt, dann persist komplett gelöscht. Grist startete leer, leitete wieder auf /boot um, und der alte API-Schlüssel scheiterte mit invalid API key. Nach dem Restore war die Markerzeile samt Formelwerten zurück, die nachträgliche Zeile fehlte erwartungsgemäß, und der alte API-Schlüssel funktionierte wieder, weil er in home.sqlite3 liegt.

Verifizieren: Nach dem Restore zeigt docker compose ps wieder healthy, und die API liefert Ihre Markerzeile mit HTTP 200.

Schritt 7: Update, Rollback und Entfernen

Grist erscheint laut Doku etwa wöchentlich und migriert Daten beim Start selbst. Sichern Sie wie in Schritt 6, erhöhen Sie GRIST_TAG und ziehen Sie das neue Image.

docker compose pull grist
docker compose up -d grist
docker compose logs grist | grep 'Grist version'

Im Test lief der Wechsel von 1.7.20 auf 1.7.19 und zurück ohne Fehler, das Dokument blieb lesbar. Das war nur ein Patch-Schritt. Bei größeren Sprüngen kann eine Migration Daten umschreiben, die ältere Versionen nicht verstehen; sicherer Rollback ist dann das Backup, nicht nur das alte Image.

Zum Entfernen genügt docker compose down -v. Achtung: Der Ordner persist bleibt dabei liegen; erst rm -rf /opt/grist löscht alle Dokumente und Konten endgültig. Prüfen Sie vorher, ob ein aktuelles Backup außerhalb des Servers existiert.

Verifizieren: Der Admin-Bereich zeigt unter „Version“ die erwartete Versionsnummer und meldet bei „Aktualisierungen“ den Stand.

Typische Fehler

  • Weiterleitung auf http://grist.example.de:8484/: APP_HOME_URL zeigt auf eine interne Adresse wie http://localhost:8484. Grist baut Login-Weiterleitungen daraus, im Test landete der Browser auf Port 8484 und kam nicht weiter. Tragen Sie die öffentliche HTTPS-Adresse ein.
  • SQLITE_READONLY: attempt to write a readonly database: Der Container läuft mit einem fremden user:, dem persist nicht gehört. Grist startet dann in einer Schleife neu. Lassen Sie user: weg, der Start korrigiert die Besitzrechte selbst.
  • Alle Benutzer plötzlich abgemeldet: GRIST_SESSION_SECRET wurde geändert. Im Test war die alte Sitzung danach anonym. Fehlt die Variable ganz, startet Grist trotzdem; der Admin-Bereich meldet nur eine Warnung mit GRIST_SESSION_SECRET: not set.
  • Health-Check und Websockets im Admin-Bereich rot: Der Container prüft sich über die öffentliche Adresse. Erreicht er sie aus dem eigenen Netz nicht, steht dort connect ECONNREFUSED, obwohl Browser problemlos arbeiten. Prüfen Sie DNS und Hairpin-NAT, bevor Sie den Proxy umbauen.
  • Keine Live-Aktualisierung: Ein anderer Proxy reicht Websockets nicht durch. Nginx braucht proxy_http_version 1.1 sowie die Header Upgrade und Connection.

Häufige Fragen

Brauche ich GRIST_DEFAULT_EMAIL?

Die Self-Managed-Doku nennt GRIST_DEFAULT_EMAIL als Admin-Adresse; die offiziellen Compose-Beispiele und der aktuelle Assistent nutzen GRIST_ADMIN_EMAIL, das im Log als installAdminEmail erscheint. Ohne Anmeldesystem meldet GRIST_DEFAULT_EMAIL jeden Besucher unter dieser Adresse an. Im Test mit Forward-Auth und GRIST_FORCE_LOGIN blieb ein Aufruf ohne Login trotzdem anonym; nötig ist die Variable in diesem Aufbau nicht.

Ist Grist eine Alternative zu Baserow oder NocoDB?

Ja, mit anderem Schwerpunkt. Grist rechnet mit Python-Formeln und speichert jedes Dokument als portable SQLite-Datei. Baserow und NocoDB setzen auf eine zentrale PostgreSQL-Datenbank.

Welche Funktionen fehlen in der Community edition?

Laut Hersteller sind Automationen, E-Mail-Benachrichtigungen, Admin-Kontrollen für Benutzer, Audit-Logs, KI-Assistent sowie OIDC und SAML der Vollversion vorbehalten. Diese startet als 30-Tage-Test und braucht danach einen Aktivierungsschlüssel.

Kann ich Snapshots in S3 statt Dateibackups nutzen?

Grist synchronisiert Dokumente auf Wunsch in einen versionierten S3-Bucket. Das ersetzt nicht die Sicherung von home.sqlite3 mit Benutzern und Rechten.

Testumfang

Getestet wurden auf einem Docker-Testhost mit Grist 1.7.20 (und 1.7.19 für den Rollback) der Start mit Healthcheck, Boot-Key und Quick Setup, Forward-Auth über Caddy samt Header-Fälschung direkt und über den Proxy, ein Dokument mit Markerzeile und Sandbox-Formel, Backup und Restore nach Totalverlust, Boot-Key-Entfernung sowie die Fehlerbilder zu Rechten, Sitzungsgeheimnis und APP_HOME_URL. Nur aus der Doku stammen die automatische Zertifikatsausstellung durch Caddy (TLS endete im Test vor dem Proxy), S3-Snapshots, OIDC, SAML und die Funktionen der Vollversion.

Fazit

Grist ist schnell installiert, und dank Boot-Key und Assistent startet eine frische Instanz nicht mehr offen im Netz. Entscheidend sind zwei Punkte: Port 8484 nie veröffentlichen und persist als Ganzes sichern. Dann bekommen kleine Teams eine solide Mischung aus Tabelle und Datenbank mit Daten in lesbaren SQLite-Dateien.

Weiterführende Anleitungen und Quellen

GristDocker ComposeSelfhostingAirtable-AlternativeTabellenkalkulationBackupCaddy