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

Baserow mit Docker Compose selbst hosten: Airtable-Alternative für KMU

Baserow verbindet Tabellen, Formulare und Automatisierungen in einer selbst gehosteten Oberfläche und ist damit eine ernsthafte Airtable-Alternative für kleine und mittlere Unternehmen. Diese Anleitung zeigt den vollständigen Weg mit Docker Compose: von der .env-Datei über den ersten Admin-Account bis zu einem getesteten Backup- und Wiederherstellungsverfahren.

Illustration zur Anleitung: Baserow selbst hosten mit Datenbank, Formularen und Workflows KI-generiert

Wer im Unternehmen Listen, Projekttabellen, Inventare oder Bewerbungsprozesse verwaltet, landet schnell bei einer Tabellenkalkulation, die niemand mehr überblickt, oder bei einem Cloud-Dienst wie Airtable, dessen Daten dann außerhalb des eigenen Zugriffs liegen. Baserow schließt genau diese Lücke: Es ist eine No-Code-Datenbank mit Weboberfläche, in der Fachabteilungen Tabellen anlegen, Beziehungen zwischen Datensätzen herstellen, öffentliche Formulare zur Dateneingabe teilen und einfache Automatisierungen bauen können. Der Unterschied zum Cloud-Dienst: Der Stack läuft auf der eigenen Hardware, die Daten liegen in einer PostgreSQL-Datenbank, auf die Sie jederzeit selbst zugreifen können.

Diese Anleitung beschreibt eine vollständige Installation mit der offiziellen Docker-Compose-Datei des Projekts. Dabei geht es nicht nur um den ersten Start, sondern um den Betrieb: Welche Ressourcen der Stack wirklich belegt, wie der Reverse Proxy mit TLS davor gesetzt wird, wie ein Backup aussieht, das sich auch tatsächlich zurückspielen lässt, und welche Fehlerbilder beim ersten Aufsetzen typischerweise auftreten. Die im Text beschriebenen Befehle und Ausgaben stammen aus einem Testlauf am 18. September 2026 auf einem Ubuntu-Host mit Docker 29.1.3 und Docker Compose 2.40.3. Wo etwas nicht selbst geprüft werden konnte, ist das ausdrücklich vermerkt.

Nutzen und Grenzen

Baserow richtet sich an Teams, die strukturierte Daten pflegen wollen, ohne dafür eine Anwendung entwickeln zu lassen. Der typische Einsatz im Mittelstand sind Projektübersichten, Kundenlisten, Geräteinventare, Wartungsprotokolle, Bewerbungs- und Angebotsverfolgung oder Materialbestellungen. Die Stärke liegt darin, dass eine Fachabteilung die Struktur selbst anpassen kann, ohne dass die IT für jede neue Spalte ein Ticket bearbeiten muss.

Drei Funktionen sind dabei besonders relevant. Erstens die Formularansicht: Jede Tabelle kann eine öffentlich teilbare Eingabemaske bekommen, über die auch Personen ohne Benutzerkonto Datensätze anlegen. Das ersetzt in vielen Fällen ein separates Formular-Tool. Zweitens die Automatisierungen, mit denen sich Abläufe wie Benachrichtigungen bei neuen Einträgen oder Webhooks an andere Systeme abbilden lassen. Drittens die REST-API, über die jede Tabelle programmatisch erreichbar ist. Im Praxistest wurde die komplette Erstkonfiguration, also Arbeitsbereich, Datenbank, Tabelle und Formularansicht, ausschließlich über diese API angelegt, was zeigt, wie vollständig sie ist.

Ebenso wichtig sind die Grenzen. Baserow ist keine klassische relationale Datenbank für Entwickler und kein Ersatz für ein ERP oder ein CRM mit Buchhaltungsanbindung. Wer komplexe Geschäftslogik, Rechnungsläufe oder Lagerbuchungen abbilden will, baut sich in Baserow ein fragiles Konstrukt. Auch als Datenbank hinter einer eigenen Anwendung ist es die falsche Wahl, weil die Daten zwar in PostgreSQL liegen, das interne Schema aber ein Implementierungsdetail ist und sich mit Versionen ändern kann. Der unterstützte Zugriffsweg ist die API, nicht ein direkter SQL-Zugriff auf die Tabellen.

Der dritte Punkt betrifft das Lizenzmodell und ist für die Beschaffung wichtig. Baserow ist kein durchgehend freies Projekt, sondern folgt einem Open-Core-Ansatz. Das Repository weist auf GitHub keine einheitliche Standardlizenz aus, sondern wird dort als sonstige Lizenz geführt, weil es mehrere Lizenztexte für verschiedene Codeteile enthält. Der Kern steht unter einer freien Lizenz, während die als Premium und Enterprise gekennzeichneten Funktionen unter einer eigenen kommerziellen Lizenz stehen und einen bezahlten Schlüssel erfordern.

Für die Einordnung im Unternehmen hilft eine grobe Trennung entlang der beobachteten Modulstruktur:

  • Im freien Kern liegen die Funktionen, die den Alltag ausmachen: Tabellen mit Feldtypen und Beziehungen, die verschiedenen Ansichten einschließlich der Formularansicht, die Automatisierungen, Webhooks und die vollständige REST-API.
  • Als Premium und Enterprise gekennzeichnet sind vor allem Verwaltungsfunktionen, darunter erweiterte Rollen- und Rechtekonzepte, Single Sign-on über SAML, das Audit-Log sowie ein eigener Code-Runner für Automatisierungen.
  • Die Datenbankmigrationen der Premium- und Enterprise-Module laufen auch ohne gekauften Schlüssel mit. Im Testlauf waren die entsprechenden Schritte in den Startprotokollen deutlich sichtbar. Der Code ist also vorhanden, die Funktionen sind nur nicht freigeschaltet.

Für den normalen Tabellen-, Formular- und Automatisierungsbetrieb ohne Schlüssel bleibt der freie Kern nutzbar. Wer aber feingranulare Berechtigungen oder eine Anmeldung über einen zentralen Identitätsanbieter braucht, sollte vor der Einführung prüfen, ob die benötigte Funktion im freien Teil enthalten ist, statt das später im Projekt festzustellen.

Voraussetzungen und Ressourcen

Baserow ist in der hier beschriebenen Variante kein einzelner Container, sondern ein Verbund aus acht Diensten. Dieser Aufbau erklärt den Ressourcenbedarf, der deutlich über dem eines kleinen Einzelcontainers liegt.

  • backend: die eigentliche Anwendung mit der REST-API, führt beim Start auch die Datenbankmigrationen aus.
  • web-frontend: die Weboberfläche, die der Nutzer im Browser sieht.
  • celery: verarbeitet Hintergrundaufgaben wie Automatisierungen und Webhooks.
  • celery-export-worker: getrennter Worker für Exporte, damit große Ausgaben den normalen Betrieb nicht blockieren.
  • celery-beat-worker: löst zeitgesteuerte Aufgaben aus, etwa das Aufräumen des Papierkorbs.
  • db: PostgreSQL mit der Erweiterung pgvector, im Test in Version 15.19 auf Debian-Basis.
  • redis: Zwischenspeicher und Warteschlange für die Celery-Worker, im Test in Version 6.2.24.
  • caddy: interner Reverse Proxy, verteilt Anfragen auf Backend und Weboberfläche und liefert hochgeladene Dateien aus.
  • volume-permissions-fixer: kurzlebiger Hilfscontainer, der beim Start die Dateirechte auf dem Medienverzeichnis korrigiert und sich danach beendet.

Im Testlauf belegte der laufende Stack im Leerlauf, also mit einem Benutzer und einer kleinen Tabelle, insgesamt rund 2000 MiB Arbeitsspeicher. Die Verteilung war dabei aufschlussreich: Das Backend lag bei etwa 600 MiB, die drei Celery-Worker zusammen bei rund 1010 MiB, die Weboberfläche bei etwa 281 MiB. PostgreSQL mit rund 70 MiB, Redis mit knapp 15 MiB und Caddy mit knapp 13 MiB fielen dagegen kaum ins Gewicht. Das bedeutet in der Praxis: Der überwiegende Teil des Speichers geht an die Python-Prozesse, nicht an die Datenhaltung.

Für den Speicherplatz sind vor allem die Images relevant. Das Backend-Image belegt allein 1,5 GB, die Weboberfläche 540 MB, das PostgreSQL-Image mit der pgvector-Erweiterung 612 MB und Caddy 88,7 MB. Rechnen Sie mit rund 3 GB nur für die Images, bevor überhaupt eigene Daten dazukommen. Der Download dieser Images dauerte im Test mehrere Minuten und war der zeitaufwendigste Teil der Installation.

RessourceEmpfehlungBeobachtung im Test
Arbeitsspeichermindestens 4 GB, komfortabel 8 GBrund 2000 MiB im Leerlauf belegt
CPU-Kerne2 Kerne für kleine Teamsauf 2 Kernen gestartet, im Leerlauf unter 2 Prozent Last
Speicherplatzmindestens 10 GB freirund 3 GB allein für die Container-Images
DockerDocker 19.03 und Compose 1.19 laut Doku als Minimumgetestet mit Docker 29.1.3 und Compose 2.40.3
Architekturamd64 und arm64 laut Projektangabennur amd64 selbst getestet
Startdauererste Migration einplanenrund elf Minuten bis alle Dienste gesund waren

Ein Hinweis zur Architektur: Das Projekt gibt an, Images für amd64 und arm64 bereitzustellen, was einen Betrieb auf ARM-Systemen wie einem Raspberry Pi 5 oder einem ARM-Server grundsätzlich ermöglicht. Diese Anleitung wurde ausschließlich auf amd64 getestet. Unabhängig von der Architektur ist die Speicheranforderung der begrenzende Faktor, weshalb ein Raspberry Pi mit 4 GB an der unteren Grenze liegt.

Projekt- und Versionsstand mit Beleg

Bevor ein Projekt in den Betrieb geht, lohnt ein Blick auf die Pflegesituation. Die folgenden Zahlen wurden am 18. September 2026 direkt über die GitHub-API abgerufen, nicht aus Sekundärquellen übernommen.

# Projektdaten direkt aus der GitHub-API holen
curl -s https://api.github.com/repos/baserow/baserow

# Aktuellstes Release abfragen
curl -s https://api.github.com/repos/baserow/baserow/releases/latest
KennzahlWert am 18.09.2026
Repositorybaserow/baserow
Sterne5932
Forks735
Letzter Push17.09.2026
Aktuelles Release2.3.4 vom 15.09.2026
Archiviertnein
Lizenzangabe auf GitHubsonstige Lizenz, mehrere Lizenztexte im Repository

Ein Push vom Vortag und ein Release aus derselben Woche sprechen für ein aktiv gepflegtes Projekt. Die Zahl von 1241 offenen Vorgängen sollte nicht als Warnsignal gelesen werden, denn bei einem Projekt dieser Größe sammeln sich dort überwiegend Funktionswünsche.

Wichtig für die Reproduzierbarkeit dieser Anleitung: In der offiziellen compose-Datei stand zum Abrufzeitpunkt die Vorgabeversion 2.3.3 hinterlegt, während das aktuelle Release bereits 2.3.4 war. Die Datei im Hauptzweig hinkt dem Release also um eine Patchversion hinterher. Setzen Sie die Version deshalb in der eigenen .env-Datei ausdrücklich, statt sich auf den Vorgabewert zu verlassen. Im Test wurde mit gesetzter Variable durchgehend 2.3.4 verwendet, was sich über den Befehl docker compose config --images vor dem Start überprüfen lässt.

Dateien aus dem offiziellen Repository holen

Für die hier beschriebene Variante werden drei Dateien benötigt: die compose-Datei, die Beispiel-Umgebungsdatei und die Caddy-Konfiguration. Letztere wird gern übersehen, ist aber zwingend erforderlich, weil die compose-Datei sie per Bind-Mount aus dem aktuellen Verzeichnis einbindet. Fehlt sie, startet Caddy ohne funktionierende Weiterleitung.

# Arbeitsverzeichnis anlegen und hineinwechseln
mkdir -p /opt/baserow
cd /opt/baserow

# Die drei benoetigten Dateien direkt aus dem Repository laden
curl -o docker-compose.yml https://raw.githubusercontent.com/baserow/baserow/master/docker-compose.yml
curl -o Caddyfile https://raw.githubusercontent.com/baserow/baserow/master/Caddyfile
curl -o .env.example https://raw.githubusercontent.com/baserow/baserow/master/.env.example

# Beispieldatei als Vorlage fuer die eigene Konfiguration kopieren
cp .env.example .env

Laden Sie die Dateien bewusst aus dem Repository und schreiben Sie sie nicht aus einem Blogartikel ab. Die compose-Datei des Projekts ist über 500 Zeilen lang und enthält allein im Umgebungsblock des Backends mehrere hundert Variablennamen. Eine abgetippte Kurzfassung verliert genau die Zeilen, die später im Betrieb wichtig werden.

Die .env-Datei mit Platzhaltern

Drei Werte sind Pflicht und haben in der compose-Datei keine Vorgabe: der Anwendungsschlüssel, das Datenbankkennwort und das Redis-Kennwort. Sie sind mit einem Ausrufezeichen als zwingend markiert, was bedeutet, dass der Start ohne sie abbricht. Erzeugen Sie diese Werte zufällig und speichern Sie sie in einem Kennwortmanager, nicht in einem Ticketsystem.

# Fuer jeden der drei Werte einmal einzeln ausfuehren
tr -dc 'a-z0-9' < /dev/urandom | head -c 50; echo

Die folgende .env-Datei zeigt eine vollständige Minimalkonfiguration für einen Betrieb hinter einem eigenen Reverse Proxy. Alle Geheimnisse sind ausdrücklich Platzhalter und müssen ersetzt werden.

# Datei: /opt/baserow/.env
# Version ausdruecklich festlegen, nicht dem Vorgabewert der compose-Datei vertrauen
BASEROW_VERSION=2.3.4

# PLATZHALTER: alle drei Werte durch eigene Zufallswerte ersetzen
SECRET_KEY=HIER_EIGENEN_ZUFALLSWERT_EINSETZEN
BASEROW_JWT_SIGNING_KEY=HIER_EIGENEN_ZUFALLSWERT_EINSETZEN
DATABASE_PASSWORD=HIER_EIGENES_DB_KENNWORT_EINSETZEN
REDIS_PASSWORD=HIER_EIGENES_REDIS_KENNWORT_EINSETZEN

# Die Adresse, unter der Nutzer Baserow im Browser aufrufen
BASEROW_PUBLIC_URL=https://baserow.example.de

# Nur auf der lokalen Schnittstelle veroeffentlichen, davor ein eigener Reverse Proxy
HOST_PUBLISH_IP=127.0.0.1
WEB_FRONTEND_PORT=18100
WEB_FRONTEND_SSL_PORT=18143

# Datenbankbenutzer und Datenbankname des mitgelieferten PostgreSQL
DATABASE_USER=baserow
DATABASE_NAME=baserow

# Migrationen beim Start ausfuehren, Vorlagen nicht synchronisieren
MIGRATE_ON_STARTUP=true
SYNC_TEMPLATES_ON_STARTUP=false

Zwei Einstellungen verdienen eine Erklärung. Die Variable für die öffentliche Adresse ist der häufigste Grund für eine scheinbar kaputte Installation, weil Caddy und die Weboberfläche ihre Weiterleitungen daran ausrichten. Die Variable zur Vorlagensynchronisierung steuert, ob beim Start die mitgelieferten Beispielvorlagen in die Datenbank geschrieben werden. Auf false gesetzt verkürzt das den ersten Start spürbar, weshalb sie im Testlauf deaktiviert war. Wer die Vorlagen im Produktivbetrieb nutzen möchte, lässt den Wert auf true und plant einen längeren ersten Start ein.

Wichtige Parameter der compose-Datei

Die offizielle compose-Datei definiert ihre Backend-Variablen über einen wiederverwendbaren Block, der in vier Diensten eingebunden wird: im Backend selbst und in den drei Celery-Workern. Das ist der Grund, warum eine Änderung an einer einzigen Variable automatisch für alle vier Dienste gilt. Die folgende Tabelle erklärt die Parameter, die im Alltag wirklich gesetzt werden.

ParameterBedeutungVorgabe in der compose-Datei
SECRET_KEYSchlüssel für Sitzungen und Signaturen, Pflichtwert ohne Vorgabekeine, Start bricht ab
DATABASE_PASSWORDKennwort des PostgreSQL-Benutzers, Pflichtwertkeine, Start bricht ab
REDIS_PASSWORDKennwort für Redis, Pflichtwertkeine, Start bricht ab
BASEROW_JWT_SIGNING_KEYeigener Schlüssel für Anmeldetoken, laut Beispieldatei empfohlenleer
BASEROW_PUBLIC_URLAdresse, unter der Nutzer die Anwendung aufrufenhttp://localhost
HOST_PUBLISH_IPSchnittstelle, auf der die Ports veröffentlicht werden0.0.0.0, also alle Schnittstellen
WEB_FRONTEND_PORTHostport für unverschlüsselten Zugriff auf Caddy80
WEB_FRONTEND_SSL_PORTHostport für verschlüsselten Zugriff auf Caddy443
BASEROW_VERSIONTag der verwendeten Container-Images2.3.3 zum Abrufzeitpunkt
MIGRATE_ON_STARTUPführt Datenbankmigrationen beim Start austrue
SYNC_TEMPLATES_ON_STARTUPschreibt die mitgelieferten Vorlagen in die Datenbanktrue
BASEROW_ENABLE_SECURE_PROXY_SSL_HEADERwertet den Weiterleitungs-Header eines vorgelagerten Proxys ausleer

Die Vorgabe für die Veröffentlichungsschnittstelle ist sicherheitsrelevant. Steht sie auf allen Schnittstellen, sind die Ports 80 und 443 des Hosts unmittelbar belegt und von außen erreichbar. Die offizielle Dokumentation weist an dieser Stelle ausdrücklich darauf hin, dass Docker beim Veröffentlichen von Ports die Regeln der ufw-Firewall umgehen kann und ein Container damit trotz aktiver Firewall öffentlich erreichbar wird. Genau deshalb gehört der Wert auf die lokale Schnittstelle, sobald ein eigener Reverse Proxy davor steht.

Installation und erster Start

Vor dem Start empfiehlt sich eine Prüfung der Konfiguration. Der Befehl löst alle Variablen auf und meldet fehlende Pflichtwerte, ohne etwas zu starten. Anschließend zeigt eine zweite Abfrage, welche Image-Tags tatsächlich verwendet werden. Das ist die schnellste Kontrolle, ob die Versionsvariable wirklich greift.

# Konfiguration pruefen, ohne Container zu starten
docker compose config --quiet

# Kontrolle, welche Image-Tags verwendet werden
docker compose config --images

Im Testlauf lieferte die zweite Abfrage die erwarteten Tags, darunter viermal das Backend-Image in Version 2.3.4, dazu die Weboberfläche in derselben Version, pgvector/pgvector:pg15, redis:6 und caddy:2.11.4. Erst wenn diese Ausgabe stimmt, beginnt der eigentliche Start.

# Stack im Hintergrund starten, laedt beim ersten Mal rund 3 GB an Images
docker compose up -d

# Danach den Status aller Dienste abfragen
docker compose ps

Planen Sie für diesen Schritt Zeit ein. Im Test dauerte allein der Download der Images mehrere Minuten, und danach lief die erste Datenbankmigration weiter. Etwa sechs Minuten nach dem Start meldete der Statusbefehl alle Dienste als laufend, das Backend war zu diesem Zeitpunkt aber noch als unhealthy markiert, während PostgreSQL, Redis, die Weboberfläche und die drei Celery-Worker bereits als healthy galten. Ein Blick in das Protokoll zeigte den Grund: Das Backend arbeitete zu diesem Zeitpunkt noch die Migrationen ab, sichtbar an einer langen Folge von Zeilen nach dem Muster Applying baserow_enterprise.0033_samlauthprovidermodel_email_attr_key_and_more OK. Nach rund elf Minuten meldeten alle acht Dienste healthy. Ein unhealthy-Status in den ersten Minuten ist also erwartbar und kein Fehler.

Funktions- und Healthcheck

Baserow bringt einen eigenen Endpunkt für die Zustandsprüfung mit, der sich gut für Monitoring eignet. Wichtig ist dabei ein Detail, das im Test für Verwirrung sorgte und weiter unten im Fehlerteil ausführlich erklärt wird: Der Aufruf muss mit dem Hostnamen erfolgen, der in der Konfiguration der öffentlichen Adresse hinterlegt ist.

# Zustandspruefung ueber den Reverse Proxy
curl -s -w "\nHTTP %{http_code}\n" http://localhost:18100/api/_health/

# Einstellungen der Instanz abrufen, liefert JSON
curl -s http://localhost:18100/api/settings/

# Zustandspruefung direkt im Backend-Container, umgeht den Proxy
docker compose exec backend curl -s http://localhost:8000/api/_health/

Im Testlauf antwortete der erste Befehl mit der Zeichenfolge OK und dem Statuscode 200. Der zweite lieferte ein JSON-Objekt mit Feldern wie allow_new_signups, show_admin_signup_page und einem Abschnitt zur Captcha-Konfiguration. Der dritte Befehl bestätigte, dass das Backend auch unabhängig vom Proxy antwortet, was bei der Fehlersuche hilft, um Proxy-Probleme von Anwendungsproblemen zu trennen. Ein Aufruf der Startseite lieferte den Statuscode 302, also eine Weiterleitung zur Anmeldung, was dem erwarteten Verhalten entspricht.

Für eine dauerhafte Überwachung eignet sich der Zustandsendpunkt gut, weil er ohne Anmeldung auskommt. Ergänzend lohnt eine Prüfung der Container-Zustände, weil ein einzelner ausgefallener Celery-Worker die Weboberfläche nicht sofort unbrauchbar macht, aber Automatisierungen und Exporte stillstehen lässt.

# Alle Dienste mit Zustand auflisten
docker compose ps --format "table {{.Service}}\t{{.Status}}"

# Bei Auffaelligkeiten das Protokoll eines Dienstes ansehen
docker compose logs backend --tail 50

Erstkonfiguration: Admin, Datenbank und Formular

Nach dem Start ist die Instanz leer und es existiert kein Benutzer. Der erste Aufruf der Weboberfläche führt deshalb auf eine Registrierungsseite. Der erste dort angelegte Account erhält automatisch Verwaltungsrechte. Das ist der Grund, warum diese Instanz nach dem Start nicht unbeaufsichtigt und nicht öffentlich erreichbar stehen sollte, denn wer sich zuerst registriert, ist Administrator.

Im Praxistest wurde dieser Schritt über die API nachvollzogen, weil sich das Ergebnis dabei eindeutig belegen lässt. Die Antwort auf die Registrierung enthielt ein Benutzerobjekt mit dem Feld is_staff auf true, was die Verwaltungsrechte des ersten Accounts bestätigt.

# Ersten Account anlegen, dieser wird automatisch Administrator
curl -s -X POST \
  -H "Content-Type: application/json" \
  -d '{"name":"Admin","email":"admin@example.de","password":"PLATZHALTER_STARKES_KENNWORT","authenticate":true}' \
  http://localhost:18100/api/user/

Die Antwort enthält neben dem Benutzerobjekt ein Zugriffstoken, mit dem sich die weiteren Schritte ausführen lassen. In der Weboberfläche entstehen dieselben Objekte über die Schaltflächen im linken Seitenbereich. Der Aufbau ist dreistufig: Ein Arbeitsbereich enthält Datenbanken, eine Datenbank enthält Tabellen, und eine Tabelle kann mehrere Ansichten haben, darunter die Formularansicht.

# Token aus der vorherigen Antwort in eine Variable uebernehmen
TOKEN="HIER_DAS_TOKEN_AUS_DER_ANTWORT"

# Arbeitsbereich anlegen
curl -s -X POST \
  -H "Authorization: JWT $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"KMU Betrieb"}' \
  http://localhost:18100/api/workspaces/

# Datenbank im Arbeitsbereich mit der ID 1 anlegen
curl -s -X POST \
  -H "Authorization: JWT $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Kundendaten","type":"database"}' \
  http://localhost:18100/api/applications/workspace/1/

# Tabelle mit Kopfzeile in der Datenbank mit der ID 1 anlegen
curl -s -X POST \
  -H "Authorization: JWT $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Anfragen","data":[["Name","E-Mail","Anliegen"]],"first_row_header":true}' \
  http://localhost:18100/api/database/tables/database/1/

# Formularansicht fuer die Tabelle mit der ID 1 anlegen
curl -s -X POST \
  -H "Authorization: JWT $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Kontaktformular","type":"form"}' \
  http://localhost:18100/api/database/views/table/1/

Im Test liefen alle vier Aufrufe erfolgreich durch. Die Antwort auf den letzten Aufruf ist dabei die interessanteste, weil sie zeigt, wie die Formularfreigabe funktioniert. Neben Feldern wie submit_text und submit_action enthielt sie das Feld public mit dem Wert false sowie ein Feld slug mit einer langen zufälligen Zeichenfolge. Die Formularansicht existiert also zunächst, ist aber noch nicht öffentlich. Erst wenn die Ansicht in der Weboberfläche freigegeben wird, ist sie über eine Adresse mit dieser zufälligen Zeichenfolge erreichbar. Das ist ein sinnvoller Aufbau, weil die Adresse nicht erratbar ist. Es bedeutet aber auch, dass jeder, der diese Adresse kennt, Datensätze anlegen kann. Für Formulare mit personenbezogenen Daten sollten Sie deshalb zusätzlich den Kennwortschutz der Ansicht aktivieren, den das Feld public_view_has_password abbildet.

Persistente Daten, Volumes und Rechte

Die compose-Datei legt vier benannte Volumes an. Deren Namen setzen sich aus dem Projektnamen, der standardmäßig dem Verzeichnisnamen entspricht, und dem Volume-Namen zusammen. Wer die Anleitung in /opt/baserow ausführt, findet die Volumes entsprechend unter baserow_pgdata und so weiter.

VolumeInhaltIm Backup erforderlich
pgdataDatenverzeichnis von PostgreSQL mit allen Tabellen und Benutzernja, aber als Datenbankauszug statt als Dateikopie
mediavon Nutzern hochgeladene Dateien und Bilderja, als Dateikopie
caddy_datavon Caddy verwaltete Zertifikate und Kontodatenoptional, wird bei Bedarf neu erzeugt
caddy_configLaufzeitkonfiguration von Caddynein, wird beim Start neu erzeugt

Beim Thema Rechte lohnt ein genauer Blick, weil die compose-Datei dafür einen eigenen Hilfscontainer mitbringt. Er läuft beim Start einmal durch und setzt den Eigentümer des Medienverzeichnisses auf die Kennung 9999, unter der die Anwendung im Container läuft. Ohne diesen Schritt gehörte das frisch angelegte Volume dem Systemverwalter, und die Anwendung könnte keine hochgeladenen Dateien speichern. Im Test ließ sich das Ergebnis direkt bestätigen: Das Medienverzeichnis gehörte der Kennung 9999 in Benutzer und Gruppe.

# Volumes des Projekts auflisten
docker volume ls

# Eigentuemer und Rechte im Medien-Volume pruefen
docker run --rm -v baserow_media:/m alpine ls -ldn /m

Wenn Sie die Volumes durch Verzeichnisse auf dem Host ersetzen, um sie einfacher zu sichern, müssen Sie diese Rechtevergabe selbst übernehmen. Ein Verzeichnis, das dem falschen Benutzer gehört, führt dazu, dass Uploads mit einem Schreibfehler abbrechen, während der Rest der Anwendung normal funktioniert.

Sichere Netzwerkfreigabe, Reverse Proxy und TLS

Der mitgelieferte Caddy übernimmt bereits die interne Verteilung der Anfragen: Adressen unterhalb von /api/, /ws/ und einigen weiteren Pfaden gehen an das Backend, Dateien unterhalb von /media/ werden direkt aus dem Volume ausgeliefert, alles Übrige geht an die Weboberfläche. Diese Aufteilung selbst nachzubauen ist fehleranfällig, insbesondere weil die Weboberfläche auf funktionierende Websocket-Verbindungen über /ws/ angewiesen ist. Die Projektdokumentation empfiehlt deshalb ausdrücklich, einen vorhandenen eigenen Proxy einfach auf diesen internen Caddy weiterleiten zu lassen, statt die Dienste einzeln anzusprechen.

Damit ergibt sich ein zweistufiger Aufbau. Der interne Caddy hört nur auf der lokalen Schnittstelle des Hosts auf einem hohen Port. Davor steht der eigene Proxy, der das Zertifikat verwaltet und die Verbindung nach außen verschlüsselt. Entscheidend ist dabei, dass die öffentliche Adresse in der .env-Datei exakt der Adresse entspricht, unter der Nutzer die Anwendung aufrufen, einschließlich des Schemas https. Weicht sie ab, erzeugt die Anwendung Weiterleitungen und Links auf die falsche Adresse.

# Datei: /etc/nginx/sites-available/baserow.conf
server {
    listen 443 ssl;
    http2 on;
    server_name baserow.example.de;

    ssl_certificate     /etc/letsencrypt/live/baserow.example.de/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/baserow.example.de/privkey.pem;

    # Uploads koennen gross werden, Vorgabe von 1 MB reicht nicht
    client_max_body_size 100M;

    location / {
        proxy_pass http://127.0.0.1:18100;

        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Notwendig fuer die Echtzeitaktualisierung der Oberflaeche
        proxy_http_version 1.1;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_read_timeout 3600s;
    }
}

server {
    listen 80;
    server_name baserow.example.de;
    return 301 https://$host$request_uri;
}

Zu dieser Konfiguration gehören drei Einstellungen in der .env-Datei. Die öffentliche Adresse wird auf https://baserow.example.de gesetzt. Die Veröffentlichungsschnittstelle wird auf 127.0.0.1 gesetzt, damit der interne Caddy von außen gar nicht erreichbar ist. Und die Variable BASEROW_ENABLE_SECURE_PROXY_SSL_HEADER wird auf true gesetzt, damit die Anwendung den vom Proxy gesetzten Protokoll-Header auswertet und nicht fälschlich annimmt, die Verbindung sei unverschlüsselt. Diese Kombination entspricht der Empfehlung der Projektdokumentation für den Betrieb hinter einem eigenen Proxy und wurde in dieser Form nicht selbst unter einem echten Zertifikat getestet, weil der Praxistest ohne öffentlichen Namen lief.

Ein Punkt aus der Dokumentation verdient besondere Aufmerksamkeit, weil er häufig unterschätzt wird: Docker umgeht beim Veröffentlichen von Ports die Regeln der ufw-Firewall. Eine Firewallregel, die den Port 80 sperrt, verhindert also nicht zwingend, dass ein Container auf diesem Port von außen erreichbar ist. Verlassen Sie sich deshalb nicht auf die Firewall allein, sondern binden Sie die Ports ausdrücklich an die lokale Schnittstelle.

Backup und Wiederherstellung

Ein Backup, das nie zurückgespielt wurde, ist eine Vermutung. Der folgende Ablauf wurde deshalb im Test vollständig durchgeführt, einschließlich der Wiederherstellung und einer Kontrolle, ob die Daten wirklich auf dem Stand des Backups waren.

Zu sichern sind zwei Dinge: die Datenbank und das Medien-Volume. Für die Datenbank ist ein Auszug mit pg_dump im komprimierten Eigenformat die richtige Wahl, weil er sich selektiv und versionsübergreifend einspielen lässt. Eine Dateikopie des Datenverzeichnisses im laufenden Betrieb ist dagegen unzuverlässig.

# Verzeichnis fuer die Sicherung anlegen
mkdir -p /opt/baserow/backup

# Datenbankauszug im komprimierten Eigenformat erstellen
docker compose exec -T db pg_dump -U baserow -Fc baserow > /opt/baserow/backup/baserow_db.dump

# Medien-Volume als Archiv sichern, Volume nur lesend eingebunden
docker run --rm \
  -v baserow_media:/data:ro \
  -v /opt/baserow/backup:/backup \
  alpine tar czf /backup/media.tar.gz -C /data .

Im Test erzeugte der erste Befehl eine Datei von rund 1,1 MB für eine frisch aufgesetzte Instanz mit einem Benutzer und einer kleinen Tabelle. Das Medienarchiv war erwartungsgemäß nahezu leer, weil keine Dateien hochgeladen worden waren. Beide Befehle liefen ohne Fehlerausgabe durch.

Für die Wiederherstellung gilt eine Reihenfolge, die eingehalten werden muss. Die Anwendungsdienste müssen gestoppt sein, bevor die Datenbank ersetzt wird, sonst halten Backend und Celery-Worker offene Verbindungen und der Löschvorgang schlägt fehl. PostgreSQL und Redis bleiben dabei laufen.

# Anwendungsdienste stoppen, Datenbank und Redis weiterlaufen lassen
docker compose stop backend web-frontend celery celery-beat-worker celery-export-worker

# Bestehende Datenbank entfernen und leer neu anlegen
docker compose exec -T db psql -U baserow -d postgres -c "DROP DATABASE baserow;"
docker compose exec -T db psql -U baserow -d postgres -c "CREATE DATABASE baserow OWNER baserow;"

# Auszug einspielen
docker compose exec -T db pg_restore -U baserow -d baserow --no-owner < /opt/baserow/backup/baserow_db.dump

# Medien-Volume zurueckspielen
docker run --rm \
  -v baserow_media:/data \
  -v /opt/baserow/backup:/backup \
  alpine tar xzf /backup/media.tar.gz -C /data

# Dienste wieder starten
docker compose start backend web-frontend celery celery-beat-worker celery-export-worker

Der Test prüfte die Wirksamkeit mit einer einfachen Gegenprobe. Nach dem Backup wurde eine zusätzliche Tabelle mit dem Namen NachBackup angelegt. Eine Abfrage der Tabellenliste in der Datenbank lieferte daraufhin die beiden Einträge Anfragen und NachBackup. Nach der Wiederherstellung lieferte dieselbe Abfrage nur noch Anfragen. Die nachträglich angelegte Tabelle war also verschwunden, der Stand des Backups war exakt wiederhergestellt.

# Gegenprobe: welche Tabellen kennt die Instanz
docker compose exec -T db psql -U baserow -d baserow -tAc "select name from database_table order by id;"

Nach dem Neustart der Dienste antwortete der Zustandsendpunkt wieder mit OK und dem Statuscode 200, und eine Anmeldung mit den Zugangsdaten aus der Zeit vor dem Backup war erfolgreich. Damit ist belegt, dass die Wiederherstellung nicht nur die Daten zurückbringt, sondern auch die Benutzerkonten und die Anmeldung funktionieren. Planen Sie nach dem Start einige Minuten ein, bis die Zustandsprüfungen von unhealthy auf healthy wechseln.

Updates und Rollback-Grenzen

Eine Aktualisierung läuft über einen Wechsel der Versionsvariable und einen Neustart der Container. Weil die Migration beim Start automatisch ausgeführt wird, passt die Anwendung das Datenbankschema dabei selbstständig an.

# Vor jedem Update ein frisches Backup erstellen
docker compose exec -T db pg_dump -U baserow -Fc baserow > /opt/baserow/backup/vor_update.dump

# Neue Version in der .env-Datei eintragen, danach Images laden
docker compose pull

# Container mit den neuen Images neu erstellen
docker compose up -d

# Migrationsfortschritt im Protokoll verfolgen
docker compose logs -f backend

Der entscheidende Punkt beim Thema Rollback ist genau diese automatische Migration. Sie verändert das Datenbankschema in eine Richtung. Wenn Sie nach einem Update die Versionsvariable wieder auf die ältere Version zurückstellen, trifft die ältere Anwendung auf ein neueres Schema, das sie nicht kennt. Ein Zurückstellen der Version allein ist deshalb kein funktionierender Rollback. Der verlässliche Weg zurück ist immer die Kombination aus älterem Image und dem Datenbankauszug, der vor dem Update erstellt wurde. Das bedeutet in der Konsequenz: Alle Änderungen, die nach dem Update in der Anwendung vorgenommen wurden, gehen bei einem Rollback verloren. Wer das nicht akzeptieren kann, testet Updates vorher auf einer Kopie der Instanz.

Ein zweiter Punkt betrifft Sprünge über mehrere Hauptversionen hinweg. Die Projektdokumentation beschreibt für den Wechsel der PostgreSQL-Version einen gesonderten Weg über ein Upgrade-Image, das in der compose-Datei als auskommentierte Alternative zum regulären Datenbank-Image hinterlegt ist. Wer von einer älteren Installation mit einer älteren PostgreSQL-Version kommt, sollte diesen Abschnitt der Dokumentation lesen, bevor er die Container austauscht. Dieser Wechsel wurde hier nicht selbst getestet, weil die Testinstallation neu aufgesetzt wurde.

Typische Fehler mit Diagnose und Lösung

Die folgenden Fehlerbilder traten im Testlauf tatsächlich auf oder ergeben sich unmittelbar aus der Konfiguration. Sie sind als Einstieg in die Fehlersuche gedacht.

Alle Seitenaufrufe liefern 404, obwohl alle Dienste gesund sind. Das war im Test das auffälligste Fehlerbild. Ein Aufruf über die IP-Adresse 127.0.0.1 lieferte auf der Startseite und auf dem Zustandsendpunkt jeweils den Statuscode 404 mit einer JSON-Antwort, in der statusCode auf 404 und statusMessage auf Server Error stand. Derselbe Aufruf über den Namen localhost auf demselben Port lieferte dagegen auf der Startseite den Statuscode 302 und auf dem Zustandsendpunkt die Antwort OK mit Statuscode 200. Die Ursache liegt in der Caddy-Konfiguration: Sie prüft, ob der Hostname der Anfrage in der konfigurierten öffentlichen Adresse enthalten ist, und leitet nur dann an Backend und Weboberfläche weiter. Da die öffentliche Adresse im Test auf http://localhost:18100 gesetzt war, passte die Anfrage mit der IP-Adresse nicht auf diese Bedingung. Die Lösung besteht darin, den Hostnamen zu verwenden, der in der öffentlichen Adresse steht, oder weitere Adressen über die Variable BASEROW_EXTRA_PUBLIC_URLS zu ergänzen. Wer diesen Zusammenhang nicht kennt, sucht den Fehler sehr lange in der Anwendung, obwohl er in der Adresskonfiguration liegt.

Das Backend bleibt minutenlang unhealthy. Im Test meldete der Statusbefehl sechs Minuten nach dem Start alle Dienste als laufend, das Backend aber als unhealthy, während die übrigen sieben Dienste bereits healthy waren. Das Protokoll zeigte eine lange Folge von Migrationsschritten. Nach rund elf Minuten war auch das Backend gesund. Warten Sie in dieser Phase ab und prüfen Sie das Protokoll, statt Container neu zu starten. Ein Neustart während der Migration verlängert den Vorgang nur.

Der Start bricht mit einer Meldung über einen fehlenden Pflichtwert ab. Die Dokumentation nennt dafür die Meldung über einen fehlenden verbindlichen Wert beim Auflösen der Umgebungsoption. Ursache sind die drei Pflichtvariablen für Anwendungsschlüssel, Datenbankkennwort und Redis-Kennwort. Prüfen Sie mit dem Konfigurationsbefehl, ob die .env-Datei im selben Verzeichnis liegt wie die compose-Datei, denn nur dort wird sie automatisch gelesen.

Die verwendete Version ist eine andere als erwartet. Die compose-Datei im Hauptzweig enthielt zum Abrufzeitpunkt als Vorgabe die Version 2.3.3, während 2.3.4 aktuell war. Wer die Versionsvariable nicht setzt, betreibt also unbemerkt eine ältere Version. Prüfen Sie das vor jedem Start mit dem Befehl zur Anzeige der verwendeten Images.

Uploads schlagen fehl, der Rest funktioniert. Das deutet auf Rechteprobleme im Medienverzeichnis hin. Prüfen Sie, ob der Eigentümer des Volumes die Kennung 9999 ist. Wenn Sie das Volume durch ein Hostverzeichnis ersetzt haben, müssen Sie den Eigentümer selbst setzen, weil der mitgelieferte Hilfscontainer nur auf das Volume wirkt.

Die Oberfläche lädt, aktualisiert sich aber nicht in Echtzeit. Das ist typisch für einen vorgelagerten Proxy ohne Unterstützung für Websocket-Verbindungen. Ergänzen Sie in der Proxy-Konfiguration die Weiterleitung der Verbindungsaufwertung, wie im Beispiel oben gezeigt, und erhöhen Sie die Zeitüberschreitung beim Lesen.

Die Wiederherstellung der Datenbank schlägt fehl. Häufigste Ursache sind offene Verbindungen. Der Löschbefehl für die Datenbank scheitert, solange Backend oder Celery-Worker verbunden sind. Stoppen Sie diese fünf Dienste zuerst und lassen Sie nur Datenbank und Redis laufen. Im Test verlief die Wiederherstellung in dieser Reihenfolge fehlerfrei.

Saubere Deinstallation

Bei der Deinstallation lohnt es sich, zwei Fälle zu unterscheiden. Der erste Befehl stoppt und entfernt die Container, lässt die Volumes und damit alle Daten aber unberührt. Das ist der richtige Weg, wenn Sie den Stack nur vorübergehend abbauen oder auf eine andere Konfiguration umstellen wollen.

# Container und Netzwerk entfernen, Daten in den Volumes bleiben erhalten
docker compose down

Der zweite Befehl entfernt zusätzlich die Volumes. Damit sind die PostgreSQL-Datenbank mit allen Tabellen, Benutzern und Datensätzen sowie alle hochgeladenen Dateien unwiderruflich gelöscht. Es gibt danach keinen Weg zurück außer einem vorher erstellten Backup. Erstellen Sie deshalb vor diesem Schritt einen Datenbankauszug und sichern Sie das Medien-Volume, wenn auch nur der geringste Zweifel besteht, ob die Daten noch gebraucht werden.

# Zuerst sichern, falls die Daten noch gebraucht werden koennten
docker compose exec -T db pg_dump -U baserow -Fc baserow > /opt/baserow/backup/vor_deinstallation.dump

# ACHTUNG: entfernt Container UND Volumes, alle Daten sind danach weg
docker compose down -v

# Kontrolle, ob wirklich keine Volumes des Projekts mehr vorhanden sind
docker volume ls

Im Testlauf entfernte der Befehl die vier Volumes und das Netzwerk, was die anschließende Auflistung der Volumes bestätigte: Es waren keine Projekt-Volumes mehr vorhanden. Wer zusätzlich Speicherplatz freigeben will, entfernt die heruntergeladenen Images, was bei diesem Stack rund 3 GB zurückgewinnt.

# Nicht mehr verwendete Images entfernen
docker image prune -a

Einordnung für kleine und mittlere Unternehmen

Baserow ist kein Tool, das man nebenbei betreibt. Acht Container, rund 2 GB Arbeitsspeicher im Leerlauf und ein erster Start von rund elf Minuten sind Werte, die im Vergleich zu vielen anderen selbst gehosteten Werkzeugen deutlich über dem Durchschnitt liegen. Dem steht gegenüber, dass der Aufbau sauber getrennt ist: Datenbank, Zwischenspeicher, Hintergrundverarbeitung und Weboberfläche laufen jeweils in eigenen Diensten, was den Betrieb bei wachsender Nutzung berechenbar macht.

Für die Entscheidung im Unternehmen sind drei Fragen ausschlaggebend. Erstens: Reicht der freie Kern für den geplanten Einsatz, oder werden Funktionen benötigt, die nur mit einem kostenpflichtigen Schlüssel verfügbar sind? Diese Frage gehört an den Anfang, nicht ans Ende des Projekts. Zweitens: Ist ein Backup- und Wiederherstellungsverfahren eingerichtet und mindestens einmal erprobt worden? Der in dieser Anleitung beschriebene Ablauf lässt sich in wenigen Minuten als Skript automatisieren und gehört in jede Installation. Drittens: Wer ist fachlich für die Struktur der Daten verantwortlich? Die Stärke des Werkzeugs, dass Fachabteilungen selbst Tabellen anlegen können, wird ohne klare Zuständigkeit schnell zur Sammlung unverbundener Einzeltabellen.

Wer diese drei Punkte geklärt hat, bekommt mit Baserow eine solide Grundlage für strukturierte Daten, die im eigenen Haus bleiben. Wer nur eine schlanke Tabellenlösung ohne Formulare und Automatisierungen braucht, ist mit einem leichtgewichtigeren Werkzeug besser bedient, weil der Ressourcenbedarf dieses Stacks in dem Fall in keinem Verhältnis zum Nutzen steht.

Passende Anleitungen auf S-EDV

Quellen

BaserowDocker ComposeSelfhostingAirtable-AlternativeDatenbankBackupPostgreSQL