Firecrawl selbst hosten: Web-Scraping-API mit Docker Compose
Firecrawl macht aus Webseiten sauberes Markdown für RAG und Agenten. Die Anleitung zeigt den Selbstbetrieb mit den fertigen Images von ghcr.io statt Build aus dem Quellcode, die Funktionen ohne Sprachmodell, warum die API nur lokal lauschen sollte, ein Backup der Queue-Datenbank mit echtem Restore und das Update auf eine neue Version.
Geprüft am 30.10.2026 · für Firecrawl 2.11.626
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

Firecrawl wandelt Webseiten in sauberes Markdown, HTML oder Linklisten um und ist damit ein beliebter Baustein für RAG-Pipelines, Agenten und Recherche-Workflows. Das Projekt firecrawl/firecrawl steht unter AGPL-3.0, hat rund 196.000 Sterne auf GitHub und veröffentlicht fast täglich neue Versionen. Neben dem Cloud-Dienst lässt sich die API selbst betreiben. Diese Anleitung zeigt den Betrieb mit den vorgebauten Images des Projekts, eine dauerhafte Queue-Datenbank, Backup mit echter Wiederherstellung, das Update auf eine neue Version und die Fehlerbilder, die im Labortest tatsächlich aufgetreten sind.
Voraussetzungen
- Linux-Server mit x86-64- oder ARM64-CPU (die Images gibt es für linux/amd64 und linux/arm64), mindestens 4 Kerne und 8 GB RAM. Im Test belegte die API allein rund 2,5 GB, der ganze Stack gut 3 GB im Leerlauf; die Compose-Datei erlaubt der API bis 8 GB und Playwright bis 4 GB.
- Rund 6 GB freier Speicher für die Images: API 2,75 GB, Playwright 2,13 GB, Queue-Datenbank 639 MB, dazu RabbitMQ und Redis.
- Docker Engine mit Docker Compose Plugin auf Debian 12 oder Ubuntu 24.04. Fehlt Docker noch? Unsere Docker-Compose-Grundlage zeigt die Installation Schritt für Schritt.
- Ausgehender Internetzugang vom Server, denn Firecrawl ruft die Zielseiten selbst ab.
- Optional ein Reverse Proxy mit TLS und Zugangsschutz, falls andere Rechner die API nutzen sollen.
Schritt 1: Warum vorgebaute Images statt Build
Die docker-compose.yaml im Wurzelverzeichnis des Repositorys ist auf Entwickler ausgelegt: Für API, Playwright-Dienst und die Queue-Datenbank stehen aktive build:-Zeilen, die passenden Images ghcr.io/firecrawl/firecrawl, ghcr.io/firecrawl/playwright-service und ghcr.io/firecrawl/nuq-postgres sind nur auskommentiert. Ein Build braucht Git-Checkout, Zeit und Rechenleistung auf dem Server. Die fertigen Images baut das Projekt bei jedem Release selbst, deshalb nutzen wir sie.
Wichtig für das Pinning: Die API-Images tragen Versionstags ohne führendes v. Das GitHub-Release heißt v2.11.626, das Image ghcr.io/firecrawl/firecrawl:2.11.626. Am 30.10.2026 zeigten 2.11.626, 2.11.626-production und latest auf denselben Digest. Playwright und die Queue-Datenbank gibt es dagegen nur als latest. Wer exakt reproduzierbar arbeiten will, hängt dort den Digest an, etwa ghcr.io/firecrawl/nuq-postgres:latest@sha256:d719b361….
Gegenüber der Repository-Datei haben wir außerdem vier Dinge geändert: Die experimentellen FoundationDB-Dienste fehlen (Standard ist die PostgreSQL-Queue), die Datenbank bekommt ein Volume, der API-Port lauscht nur auf 127.0.0.1, und alle Dienste starten nach einem Neustart des Servers wieder.
Verifizieren: Das Tag ist abrufbar, wenn docker pull ghcr.io/firecrawl/firecrawl:2.11.626 ohne not found durchläuft. v2.11.626 als Tag liefert dagegen einen Fehler.
Schritt 2: Projektordner, compose.yaml und .env anlegen
mkdir -p /opt/firecrawl && cd /opt/firecrawl
printf 'FIRECRAWL_TAG=2.11.626\nPOSTGRES_USER=postgres\nPOSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
chmod 600 .env
Die compose.yaml entspricht 1:1 der im Labor getesteten Datei. POSTGRES_DB bleibt bewusst bei postgres, weil das Image die pg_cron-Aufräumjobs fest in dieser Datenbank einrichtet.
x-common-env: &common-env
REDIS_URL: redis://redis:6379
REDIS_RATE_LIMIT_URL: redis://redis:6379
PLAYWRIGHT_MICROSERVICE_URL: http://playwright-service:3000/scrape
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: postgres
POSTGRES_HOST: nuq-postgres
POSTGRES_PORT: 5432
USE_DB_AUTHENTICATION: "false"
NUM_WORKERS_PER_QUEUE: ${NUM_WORKERS_PER_QUEUE:-8}
CRAWL_CONCURRENT_REQUESTS: ${CRAWL_CONCURRENT_REQUESTS:-10}
MAX_CONCURRENT_JOBS: ${MAX_CONCURRENT_JOBS:-5}
BROWSER_POOL_SIZE: ${BROWSER_POOL_SIZE:-5}
LOGGING_LEVEL: ${LOGGING_LEVEL:-info}
services:
api:
image: ghcr.io/firecrawl/firecrawl:${FIRECRAWL_TAG}
command: node dist/src/harness.js --start-docker
environment:
<<: *common-env
HOST: "0.0.0.0"
PORT: 3002
EXTRACT_WORKER_PORT: 3004
WORKER_PORT: 3005
NUQ_RABBITMQ_URL: amqp://rabbitmq:5672
HARNESS_STARTUP_TIMEOUT_MS: 60000
ENV: local
ports:
- "127.0.0.1:3002:3002"
ulimits:
nofile:
soft: 65535
hard: 65535
depends_on:
redis:
condition: service_started
playwright-service:
condition: service_started
rabbitmq:
condition: service_healthy
nuq-postgres:
condition: service_healthy
cpus: 4.0
mem_limit: 8G
memswap_limit: 8G
restart: unless-stopped
playwright-service:
image: ghcr.io/firecrawl/playwright-service:latest
environment:
PORT: 3000
MAX_CONCURRENT_PAGES: ${CRAWL_CONCURRENT_REQUESTS:-10}
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
tmpfs:
- /tmp/.cache:noexec,nosuid,size=1g
cpus: 2.0
mem_limit: 4G
memswap_limit: 4G
restart: unless-stopped
redis:
image: redis:alpine
command: redis-server --bind 0.0.0.0
restart: unless-stopped
rabbitmq:
image: rabbitmq:3-management
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "check_running"]
interval: 5s
timeout: 5s
retries: 3
start_period: 5s
restart: unless-stopped
nuq-postgres:
image: ghcr.io/firecrawl/nuq-postgres:latest
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: postgres
volumes:
- nuq-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d postgres"]
interval: 5s
timeout: 5s
retries: 5
start_period: 5s
restart: unless-stopped
volumes:
nuq-data:
| Eckdaten | Wert |
|---|---|
| Images | ghcr.io/firecrawl/firecrawl:2.11.626, ghcr.io/firecrawl/playwright-service:latest, ghcr.io/firecrawl/nuq-postgres:latest (PostgreSQL 17.11 mit pg_cron), redis:alpine, rabbitmq:3-management |
| Port | 3002 (API, nur 127.0.0.1); alle anderen Dienste bleiben im internen Netz |
| Volumes | nuq-data für die Queue-Datenbank; Redis und RabbitMQ halten nur flüchtige Daten |
| Env | FIRECRAWL_TAG, POSTGRES_USER, POSTGRES_PASSWORD; optional NUM_WORKERS_PER_QUEUE, CRAWL_CONCURRENT_REQUESTS, MAX_CONCURRENT_JOBS, BROWSER_POOL_SIZE, LOGGING_LEVEL |
| Authentifizierung | USE_DB_AUTHENTICATION=false, also keine API-Keys |
Verifizieren: docker compose config --quiet gibt nichts aus. Meldet Compose eine leere Variable, fehlt ein Eintrag in der .env.
Schritt 3: Stack starten und Version prüfen
docker compose up -d --pull always
docker compose ps
curl -s http://127.0.0.1:3002/v0/health/readiness
Der erste Pull dauerte im Test gut drei Minuten. Die API startet erst, wenn Datenbank und RabbitMQ gesund sind, und antwortet rund 15 Sekunden später:
{"status":"ok"}
Die Readiness-Route ist nur ein Lebenszeichen und prüft weder Queue noch Browser. Die tatsächlich laufende Version verrät das Image selbst: In /app/BUILD_SHA steht der Git-Commit, aus dem es gebaut wurde.
docker compose exec api cat BUILD_SHA
docker compose images api
cccc2a6e554be566e1a504def286fca281b69024
firecrawl-api-1 ghcr.io/firecrawl/firecrawl 2.11.626 linux/amd64 21e793792e2a 2.75GB
Der Commit cccc2a6e ist genau der, auf den das Git-Tag v2.11.626 zeigt. So prüfen Sie nach jedem Update, ob wirklich die erwartete Version läuft.
Verifizieren: docker compose ps zeigt fünf Dienste mit Status Up, Datenbank und RabbitMQ zusätzlich (healthy), und die Readiness-Route liefert {"status":"ok"}.
Schritt 4: Scrape, Crawl und Map ohne Sprachmodell nutzen
Der eigentliche Funktionstest ist ein Scrape. Im Selbstbetrieb braucht er keinen API-Key:
curl -s -X POST http://127.0.0.1:3002/v2/scrape \
-H 'Content-Type: application/json' \
-d '{"url":"https://example.com","formats":["markdown"],"timeout":60000}'
{"success":true,"data":{"markdown":"This domain is for use in documentation examples ...",
"metadata":{"title":"Example Domain","statusCode":200,"proxyUsed":"basic","creditsUsed":1}}}
Mit "formats":["links","html"] kommen Linkliste und HTML zurück. Ein Crawl folgt Links innerhalb einer Website und läuft asynchron: Der POST liefert eine ID, den Fortschritt fragen Sie danach ab. Im Test mit der Übungsseite books.toscrape.com und einem Limit von fünf Seiten:
ID=$(curl -s -X POST http://127.0.0.1:3002/v2/crawl -H 'Content-Type: application/json' \
-d '{"url":"https://books.toscrape.com/","limit":5,"scrapeOptions":{"formats":["markdown"],"onlyMainContent":true}}' | jq -r .id)
curl -s http://127.0.0.1:3002/v2/crawl/$ID | jq '{status,total,completed}'
{"status":"completed","total":5,"completed":5}
Ein laufender Crawl lässt sich mit curl -X DELETE http://127.0.0.1:3002/v2/crawl/$ID abbrechen, die Antwort lautet {"status":"cancelled"}. Die Map-Route sammelt URLs einer Domain, ohne die Seiten zu laden; für https://docs.firecrawl.dev mit "limit":5 kamen fünf URLs zurück. Bei example.com blieb die Liste leer.
Nicht alles aus der Cloud ist im Selbstbetrieb dabei. Laut Dokumentation brauchen Screenshots und Seitenaktionen die separate Fire-engine, KI-Funktionen ein Sprachmodell über OpenAI-kompatible API oder Ollama. Im Test bestätigte sich das: Das Format screenshot lieferte success: true, aber ohne Bild und mit der Warnung The engine used does not support the following features: screenshot. Ein JSON-Format mit Prompt endete ohne konfiguriertes Modell mit HTTP 500 und UNKNOWN_ERROR.
Verifizieren: Der Scrape liefert "success":true und "statusCode":200, der Crawl erreicht "status":"completed" mit so vielen Seiten wie erwartet.
Schritt 5: Zugriff absichern
Mit USE_DB_AUTHENTICATION=false nimmt die API jede Anfrage an. Im Test antworteten Anfragen ohne Key und mit dem erfundenen Key Bearer fc-falsch gleichermaßen mit HTTP 200; im Log steht dazu nur You're bypassing authentication. Wer die API erreicht, lässt Ihren Server beliebige Seiten abrufen. Deshalb lauscht der Port nur auf 127.0.0.1; für andere Rechner gehört ein Reverse Proxy mit TLS und Basic Auth oder Token-Prüfung davor, oder der Zugriff läuft über VPN.
Die Datenbank-Authentifizierung mit API-Keys ist laut SELF_HOST.md keine Ein-Schalter-Lösung: Sie braucht ein eigenes Datenbankschema und weitere Konfiguration. Ebenso bleiben die Admin-Routen und die Queue-Oberfläche aus, solange BULL_AUTH_KEY und ADMIN_INTERNAL_PORT nicht gesetzt sind.
Verifizieren: ss -ltn | grep 3002 zeigt nur 127.0.0.1:3002. Von einem anderen Rechner aus muss die Verbindung zu Port 3002 scheitern.
Schritt 6: Queue-Datenbank sichern und wiederherstellen
Firecrawl speichert Jobs und Crawl-Ergebnisse im Schema nuq der Queue-Datenbank. Die Repository-Datei definiert dafür kein Volume, nach einem docker compose down wären die Ergebnisse weg. Mit dem Volume aus Schritt 2 überstanden sie im Test einen Neustart des ganzen Stacks. Beachten Sie die eingebauten pg_cron-Jobs: Sie löschen abgeschlossene Einzeljobs nach einer Stunde. Firecrawl ist also kein Archiv; Ergebnisse, die Sie behalten wollen, speichert Ihre Anwendung selbst. Das Backup schützt laufende und kürzlich abgeschlossene Crawls.
docker compose exec -T nuq-postgres pg_dump -U postgres -d postgres -n nuq -Fc > nuq-$(date +%F).dump
cp .env compose.yaml /pfad/zum/backup/
Für den Restore-Test haben wir einen Crawl als Marker angelegt, gesichert und dann einen Totalverlust simuliert: API stoppen, Datenbank-Container und Volume löschen, Datenbank frisch starten. Die neue Datenbank enthielt 0 Jobs zum Marker-Crawl. Danach:
docker compose stop api
docker compose rm -sf nuq-postgres
docker volume rm firecrawl_nuq-data
docker compose up -d nuq-postgres
# warten, bis im Log "PostgreSQL init process complete" steht
docker compose exec -T nuq-postgres pg_restore -U postgres -d postgres --clean --if-exists --exit-on-error < nuq-2026-10-30.dump
docker compose up -d
Den Volume-Namen zeigt docker volume ls. Der Restore endete mit Exit-Code 0, die Marker-Jobs waren wieder da (zwei abgeschlossen, einer fehlgeschlagen), und die API lieferte den alten Crawl unter seiner ID vollständig aus. Die 37 pg_cron-Jobs legt das Image beim Neuanlegen der Datenbank selbst an, sie gehören nicht ins Backup.
Verifizieren: curl -s http://127.0.0.1:3002/v2/crawl/<ID> liefert nach dem Restore wieder "status":"completed" mit den gesicherten Seiten.
Schritt 7: Auf eine neue Version aktualisieren
Firecrawl veröffentlicht fast jeden Tag ein Release. Ein automatisches Update auf latest ist deshalb riskant; besser ist ein bewusster Wechsel des Tags. Im Test lief der Stack zunächst mit 2.11.625, dann haben wir auf 2.11.626 gewechselt:
docker compose exec -T nuq-postgres pg_dump -U postgres -d postgres -n nuq -Fc > nuq-vor-update.dump
sed -i 's/^FIRECRAWL_TAG=.*/FIRECRAWL_TAG=2.11.626/' .env
docker compose pull api
docker compose up -d
docker compose exec api cat BUILD_SHA
Compose meldete Container firecrawl-api-1 Recreate, die API war nach 15 Sekunden wieder bereit, BUILD_SHA wechselte von 180db640… (Tag v2.11.625) auf cccc2a6e… (Tag v2.11.626), und der vorher angelegte Crawl war weiter abrufbar. Vergleichen Sie vor größeren Sprüngen die docker-compose.yaml der Zielversion mit Ihrer Datei; laut Projekt kann sie sich zwischen Releases ändern. Playwright und die Queue-Datenbank aktualisieren Sie mit docker compose pull ohne Tag-Wechsel.
Verifizieren: docker compose images api zeigt das neue Tag, BUILD_SHA den Commit des neuen Git-Tags, und ein Test-Scrape liefert "success":true.
Troubleshooting
| Symptom | Ursache und Lösung |
|---|---|
API im Status Restarting (1), Log: password authentication failed for user "postgres" (Code 28P01), curl liefert keine Antwort | POSTGRES_PASSWORD in der .env passt nicht mehr zur bestehenden Datenbank. Das Passwort wirkt nur beim ersten Anlegen des Volumes. Altes Passwort zurücksetzen und docker compose up -d api. |
HTTP 200, aber "success":false und SCRAPE_DNS_RESOLUTION_ERROR | Domain existiert nicht oder ist falsch geschrieben. Achtung: Der HTTP-Status ist 200. Skripte müssen das Feld success auswerten, nicht nur den Statuscode. |
HTTP 400, URL must have a valid top-level domain or be an IP address | Hostname ohne Domain-Endung, etwa ein Compose-Dienstname wie http://intranet/. Vollständigen Namen oder IP angeben. |
SCRAPE_ALL_ENGINES_FAILED bei Seiten im internen Docker-Netz | Im Test scheiterte eine Seite unter einer privaten Docker-IP; das Log meldete Scrape via playwright deemed unsuccessful due to proxy inadequacy. Firecrawl ist für öffentliche Seiten gedacht; interne Quellen besser direkt anbinden. |
Warnung does not support the following features: screenshot | Screenshots und Seitenaktionen brauchen die nicht enthaltene Fire-engine. |
HTTP 500 UNKNOWN_ERROR bei JSON-Format mit Prompt | Kein Sprachmodell konfiguriert. OPENAI_API_KEY mit OPENAI_BASE_URL oder OLLAMA_BASE_URL setzen. |
failed to resolve reference ...: not found beim Pull | Tag mit v geschrieben. Die Images heißen 2.11.626, nicht v2.11.626. |
Allgemein helfen docker compose ps --all und docker compose logs --tail=200 api.
Häufige Fragen
Brauche ich einen OpenAI-Key?
Nein. Scrape, Crawl, Map und Markdown-Umwandlung liefen im Test ohne jedes Sprachmodell. Erst JSON-Extraktion per Prompt und andere KI-Formate brauchen einen Anbieter, lokal etwa Ollama.
Gibt es eine Weboberfläche?
Nein. Firecrawl ist im Selbstbetrieb eine reine HTTP-API. Clients sind curl, die offiziellen SDKs, die Firecrawl-CLI oder ein lokaler MCP-Server, die sich laut Dokumentation auf eine eigene API-URL umstellen lassen.
Darf ich jede Website crawlen?
Technisch ja, rechtlich nicht automatisch. Beachten Sie Nutzungsbedingungen, robots.txt, Urheberrecht und Datenschutz der Zielseiten, und drosseln Sie die Last über CRAWL_CONCURRENT_REQUESTS und limit.
Fazit
Mit den vorgebauten Images ist Firecrawl in wenigen Minuten einsatzbereit, ohne Build auf dem Server. Für den Alltag zählen drei Anpassungen gegenüber der Repository-Datei: festes API-Tag statt Build, ein Volume für die Queue-Datenbank und der Port nur auf 127.0.0.1. Die größten Stolperfallen sind die fehlende Authentifizierung und Fehler, die mit HTTP 200 zurückkommen.
Weiterführende Anleitungen und Quellen
- Docker und Docker Compose auf Linux installieren: die Selfhosting-Grundlage
- Docker Compose absichern: Secrets, Healthchecks und Non-Root
- n8n mit Docker Compose: Workflows, die Firecrawl per HTTP ansprechen können
- firecrawl/firecrawl auf GitHub
- SELF_HOST.md im Repository
- Offizielle Self-Hosting-Dokumentation
- Release Notes
- Container-Images auf ghcr.io


