Appwrite mit Docker installieren: Vollständige Open-Source-BaaS-Plattform
Appwrite liefert mit einem einzigen Docker-Compose-Stack ein komplettes Backend-as-a-Service-Setup: Authentifizierung, Datenbanken, File Storage, Serverless Functions und Messaging – komplett selbst gehostet, kein Vendor-Lock-in.

Wer ein vollständiges Backend für Web- und Mobile-Apps benötigt, greift heute oft zu Firebase oder Supabase – und ist damit sofort im Cloud-Vendor-Lock-in. Appwrite ist die quelloffene Alternative: 56.000+ GitHub-Stars, aktiv gepflegt, und per docker compose up -d auf jedem Linux-Host, jeder VM oder einem leistungsfähigen NAS in Betrieb. Der Stack liefert Authentifizierung (OAuth, JWT, MFA), dokumentbasierte Datenbanken (MongoDB), File Storage, Realtime-WebSockets, Serverless Functions mit 30+ Runtimes sowie Messaging – alles hinter einem einzigen Traefik-Reverse-Proxy mit automatischem HTTPS. Diese Anleitung richtet sich an Entwicklerteams und ambitionierte Selfhoster, die ein produktionstaugliches Backend ohne Cloud-Abhängigkeit selbst betreiben wollen.
Voraussetzungen
- Docker Engine 24.0+ und Docker Compose Plugin v2+ – Docker Desktop reicht für lokale Tests; für Produktion einen Linux-Host (Ubuntu 22.04/24.04 LTS) verwenden. Wie du Docker auf Linux installierst, erklärt die Anleitung Docker und Docker Compose auf Linux installieren.
- Mindestressourcen: 2 CPU-Kerne, 4 GB RAM, 2 GB Swap, 20 GB freier Speicher. Für Produktion sind 4 Kerne und 8 GB RAM empfohlen.
- Öffentliche IP + Domain mit DNS-A-Record (nur für automatisches HTTPS/Let's Encrypt). Für lokale Tests reicht
localhost. - Ports 80 und 443 müssen auf dem Host frei sein. Kein anderer Webserver oder Traefik-Stack darf auf diesen Ports lauschen.
opensslfür die Schlüssel-Generierung (auf fast allen Linux-Systemen vorinstalliert).- Optional: SMTP-Zugangsdaten für E-Mail-Funktionen (Passwort-Reset, Einladungen).
Schritt 1: Projektordner anlegen und Secrets generieren
Lege einen dedizierten Ordner an – der Verzeichnisname wird zum Stack-Namen. Wähle ihn einmal und ändere ihn danach nicht mehr, sonst entstehen neue Volumes und die alten Daten sind (scheinbar) weg.
mkdir -p /opt/appwrite
cd /opt/appwrite
# Verschlüsselungsschlüssel generieren (64 Zeichen hex)
openssl rand -hex 32 # Ergebnis als _APP_OPENSSL_KEY_V1 verwenden
# Executor-Secret generieren (32 Zeichen hex)
openssl rand -hex 16 # Ergebnis als _APP_EXECUTOR_SECRET verwendenWichtig: Notiere beide Werte sofort an einem sicheren Ort. _APP_OPENSSL_KEY_V1 ist der einzige Verschlüsselungsschlüssel für alle sensiblen Daten im Stack – Webhooks, Passwörter, Sessions, Dateien. Geht er verloren, sind alle verschlüsselten Daten unwiederbringlich verloren.
Verifizieren: Prüfe, ob der Ordner existiert und die generierten Werte jeweils 64 bzw. 32 Zeichen lang sind:
ls /opt/appwrite
# Erwartete Ausgabe: leeres Verzeichnis
openssl rand -hex 32 | wc -c
# Erwartete Ausgabe: 65 (64 Zeichen + Zeilenumbruch)Schritt 2: .env-Datei anlegen
Alle sicherheitsrelevanten Werte kommen in eine .env-Datei, die niemals ins Git-Repository eingecheckt werden darf. Ersetze die Platzhalter durch deine generierten Secrets.
# /opt/appwrite/.env
# -------------------------------------------------------
# PFLICHT: Diese Werte VOR dem ersten Start setzen!
# -------------------------------------------------------
# Umgebung: "production" fuer Live, "development" fuer Tests
_APP_ENV=production
# Verschluesselungsschluessel – NIEMALS verlieren oder aendern!
_APP_OPENSSL_KEY_V1=DEIN_64_ZEICHEN_HEX_STRING
# Deine Domain (z. B. appwrite.example.com) – fuer lokale Tests: localhost
_APP_DOMAIN=localhost
# Shared Secret zwischen Appwrite und Function-Executor
_APP_EXECUTOR_SECRET=DEIN_32_ZEICHEN_HEX_STRING
# -------------------------------------------------------
# Datenbank (MongoDB)
# -------------------------------------------------------
_APP_DB_ADAPTER=mongodb
_APP_DB_SCHEMA=appwrite
_APP_DB_USER=appwrite_user
_APP_DB_PASS=SICHERES_DB_PASSWORT
_APP_DB_ROOT_PASS=SICHERES_MONGODB_ROOT_PASSWORT
# -------------------------------------------------------
# Redis (Cache + Queue)
# -------------------------------------------------------
_APP_REDIS_PASS=
# -------------------------------------------------------
# E-Mail (optional – leer lassen um Mail zu deaktivieren)
# -------------------------------------------------------
_APP_SMTP_HOST=
_APP_SMTP_PORT=587
_APP_SMTP_SECURE=tls
_APP_SMTP_USERNAME=
_APP_SMTP_PASSWORD=
# -------------------------------------------------------
# Sonstiges
# -------------------------------------------------------
_APP_LOCALE=de
_APP_STORAGE_LIMIT=30000000
_APP_FUNCTIONS_TIMEOUT=900
_APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io
_APP_CONSOLE_WHITELIST_EMAILS=
_APP_CONSOLE_WHITELIST_IPS=Verifizieren:
grep "_APP_OPENSSL_KEY_V1" /opt/appwrite/.env
# Erwartete Ausgabe: Zeile mit deinem 64-Zeichen-String (kein leerer Wert!)
grep "_APP_ENV" /opt/appwrite/.env
# Erwartete Ausgabe: _APP_ENV=productionSchritt 3: compose.yaml einspielen
Der vollständige Stack umfasst Traefik als Reverse Proxy, den Appwrite-API-Server, den Realtime-Container, mehrere Worker-Prozesse, den Function-Executor, MongoDB und Redis. Alle Images sind mit exakten Versions-Tags gepinnt – beim nächsten Update nur die Tags erhöhen und docker compose pull && docker compose up -d ausführen.
| Service | Image | Zweck |
|---|---|---|
| traefik | traefik:3.6 | Reverse Proxy, SSL-Terminierung (Let's Encrypt) |
| appwrite | appwrite/appwrite:1.9.0 | API-Server + Web-Console (~512 MB) |
| appwrite-realtime | appwrite/appwrite:1.9.0 | WebSocket-Server (/v1/realtime) |
| appwrite-executor | openruntimes/executor:0.7.22 | Serverless Function Runner |
| appwrite-mongo | mongo:8.2.5 | Primäre Datenbank (Standard seit v1.9) |
| appwrite-redis | redis:7.4.7-alpine | Cache + Job Queue |
| Worker (5×) | appwrite/appwrite:1.9.0 | audits, webhooks, functions, mails, messaging, migrations |
| Port (Host→Container) | Protokoll | Beschreibung |
|---|---|---|
| 80:80 | HTTP | Console + API (Traefik) |
| 443:443 | HTTPS | Console + API mit SSL (Let's Encrypt) |
| Volume | Pfad im Container | Inhalt |
|---|---|---|
| appwrite-uploads | /storage/uploads | Hochgeladene Benutzerdateien |
| appwrite-cache | /storage/cache | Interner Cache, Thumbnails |
| appwrite-certificates | /storage/certificates | SSL-Zertifikate (Let's Encrypt) |
| appwrite-functions | /storage/functions | Serverless-Function-Code |
| appwrite-mongodb | /var/lib/mongodb/data | MongoDB-Datenbankdaten (kritisch!) |
| appwrite-redis | /data | Redis-Queue-Persistenz |
# /opt/appwrite/compose.yaml
# Appwrite 1.9.0 – Selbstgehostete BaaS-Plattform
# Alle _APP_*-Variablen werden aus der .env-Datei geladen.
x-logging: &x-logging
logging:
driver: "json-file"
options:
max-file: "5"
max-size: "10m"
services:
traefik:
image: traefik:3.6
container_name: appwrite-traefik
restart: unless-stopped
<<: *x-logging
command:
- --providers.file.directory=/storage/config
- --providers.file.watch=true
- --providers.docker=true
- --providers.docker.exposedByDefault=false
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
- --entrypoints.appwrite_web.address=:80
- --entrypoints.appwrite_websecure.address=:443
- --certificatesresolvers.appwrite.acme.email=${_APP_SYSTEM_EMAIL_ADDRESS:-team@appwrite.io}
- --certificatesresolvers.appwrite.acme.storage=/storage/certificates/acme.json
- --certificatesresolvers.appwrite.acme.httpchallenge=true
- --certificatesresolvers.appwrite.acme.httpchallenge.entrypoint=appwrite_web
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- appwrite-config:/storage/config:ro
- appwrite-certificates:/storage/certificates
networks:
- gateway
- appwrite
depends_on:
- appwrite
appwrite:
image: appwrite/appwrite:1.9.0
container_name: appwrite
restart: unless-stopped
<<: *x-logging
networks:
- appwrite
labels:
- "traefik.enable=true"
- "traefik.constraint-label-stack=appwrite"
- "traefik.docker.network=appwrite"
- "traefik.http.services.appwrite_api.loadbalancer.server.port=80"
- "traefik.http.routers.appwrite_api_http.entrypoints=appwrite_web"
- "traefik.http.routers.appwrite_api_http.rule=PathPrefix(`/`)"
- "traefik.http.routers.appwrite_api_https.entrypoints=appwrite_websecure"
- "traefik.http.routers.appwrite_api_https.rule=PathPrefix(`/`)"
- "traefik.http.routers.appwrite_api_https.tls=true"
- "traefik.http.routers.appwrite_api_https.tls.certresolver=appwrite"
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
- appwrite-sites:/storage/sites:rw
depends_on:
appwrite-mongo:
condition: service_healthy
appwrite-redis:
condition: service_healthy
environment:
- _APP_ENV=${_APP_ENV:-development}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_DOMAIN=${_APP_DOMAIN:-localhost}
- _APP_DOMAIN_TARGET=${_APP_DOMAIN_TARGET:-localhost}
- _APP_DOMAIN_FUNCTIONS=${_APP_DOMAIN_FUNCTIONS:-}
- _APP_EXECUTOR_SECRET=${_APP_EXECUTOR_SECRET}
- _APP_EXECUTOR_HOST=http://appwrite-executor/v1
- _APP_DB_ADAPTER=${_APP_DB_ADAPTER:-mongodb}
- _APP_DB_HOST=appwrite-mongo
- _APP_DB_PORT=${_APP_DB_PORT:-27017}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA:-appwrite}
- _APP_DB_USER=${_APP_DB_USER:-user}
- _APP_DB_PASS=${_APP_DB_PASS:-password}
- _APP_DB_ROOT_PASS=${_APP_DB_ROOT_PASS:-rootsecretpassword}
- _APP_REDIS_HOST=appwrite-redis
- _APP_REDIS_PORT=${_APP_REDIS_PORT:-6379}
- _APP_REDIS_USER=${_APP_REDIS_USER:-}
- _APP_REDIS_PASS=${_APP_REDIS_PASS:-}
- _APP_SMTP_HOST=${_APP_SMTP_HOST:-}
- _APP_SMTP_PORT=${_APP_SMTP_PORT:-587}
- _APP_SMTP_SECURE=${_APP_SMTP_SECURE:-tls}
- _APP_SMTP_USERNAME=${_APP_SMTP_USERNAME:-}
- _APP_SMTP_PASSWORD=${_APP_SMTP_PASSWORD:-}
- _APP_STORAGE_LIMIT=${_APP_STORAGE_LIMIT:-30000000}
- _APP_FUNCTIONS_TIMEOUT=${_APP_FUNCTIONS_TIMEOUT:-900}
- _APP_CONSOLE_WHITELIST_EMAILS=${_APP_CONSOLE_WHITELIST_EMAILS:-}
- _APP_LOCALE=${_APP_LOCALE:-en}
- _APP_SYSTEM_EMAIL_ADDRESS=${_APP_SYSTEM_EMAIL_ADDRESS:-team@appwrite.io}
appwrite-realtime:
image: appwrite/appwrite:1.9.0
container_name: appwrite-realtime
restart: unless-stopped
<<: *x-logging
entrypoint: realtime
networks:
- appwrite
labels:
- "traefik.enable=true"
- "traefik.constraint-label-stack=appwrite"
- "traefik.docker.network=appwrite"
- "traefik.http.services.appwrite_realtime.loadbalancer.server.port=80"
- "traefik.http.routers.appwrite_realtime_ws.entrypoints=appwrite_web"
- "traefik.http.routers.appwrite_realtime_ws.rule=PathPrefix(`/v1/realtime`)"
- "traefik.http.routers.appwrite_realtime_wss.entrypoints=appwrite_websecure"
- "traefik.http.routers.appwrite_realtime_wss.rule=PathPrefix(`/v1/realtime`)"
- "traefik.http.routers.appwrite_realtime_wss.tls=true"
- "traefik.http.routers.appwrite_realtime_wss.tls.certresolver=appwrite"
depends_on:
- appwrite
environment:
- _APP_ENV=${_APP_ENV:-development}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_REDIS_HOST=appwrite-redis
- _APP_REDIS_PORT=${_APP_REDIS_PORT:-6379}
- _APP_REDIS_USER=${_APP_REDIS_USER:-}
- _APP_REDIS_PASS=${_APP_REDIS_PASS:-}
- _APP_DB_HOST=appwrite-mongo
- _APP_DB_ADAPTER=${_APP_DB_ADAPTER:-mongodb}
- _APP_DB_USER=${_APP_DB_USER:-user}
- _APP_DB_PASS=${_APP_DB_PASS:-password}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA:-appwrite}
appwrite-worker-audits:
image: appwrite/appwrite:1.9.0
container_name: appwrite-worker-audits
restart: unless-stopped
<<: *x-logging
entrypoint: worker-audits
networks:
- appwrite
depends_on:
- appwrite-redis
- appwrite-mongo
environment:
- _APP_ENV=${_APP_ENV:-development}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_REDIS_HOST=appwrite-redis
- _APP_REDIS_PORT=${_APP_REDIS_PORT:-6379}
- _APP_DB_HOST=appwrite-mongo
- _APP_DB_ADAPTER=${_APP_DB_ADAPTER:-mongodb}
- _APP_DB_USER=${_APP_DB_USER:-user}
- _APP_DB_PASS=${_APP_DB_PASS:-password}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA:-appwrite}
appwrite-worker-webhooks:
image: appwrite/appwrite:1.9.0
container_name: appwrite-worker-webhooks
restart: unless-stopped
<<: *x-logging
entrypoint: worker-webhooks
networks:
- appwrite
depends_on:
- appwrite-redis
environment:
- _APP_ENV=${_APP_ENV:-development}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_EXECUTOR_SECRET=${_APP_EXECUTOR_SECRET}
- _APP_REDIS_HOST=appwrite-redis
- _APP_REDIS_PORT=${_APP_REDIS_PORT:-6379}
appwrite-worker-functions:
image: appwrite/appwrite:1.9.0
container_name: appwrite-worker-functions
restart: unless-stopped
<<: *x-logging
entrypoint: worker-functions
networks:
- appwrite
- runtimes
depends_on:
- appwrite-redis
- appwrite-executor
environment:
- _APP_ENV=${_APP_ENV:-development}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_REDIS_HOST=appwrite-redis
- _APP_REDIS_PORT=${_APP_REDIS_PORT:-6379}
- _APP_EXECUTOR_SECRET=${_APP_EXECUTOR_SECRET}
- _APP_EXECUTOR_HOST=http://appwrite-executor/v1
- _APP_DB_HOST=appwrite-mongo
- _APP_DB_ADAPTER=${_APP_DB_ADAPTER:-mongodb}
- _APP_DB_USER=${_APP_DB_USER:-user}
- _APP_DB_PASS=${_APP_DB_PASS:-password}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA:-appwrite}
appwrite-executor:
image: openruntimes/executor:0.7.22
container_name: appwrite-executor
restart: unless-stopped
<<: *x-logging
networks:
- appwrite
- runtimes
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- appwrite-builds:/storage/builds:rw
- appwrite-functions:/storage/functions:rw
- /tmp:/tmp:rw
environment:
- OPR_EXECUTOR_INACTIVE_TRESHOLD=${OPR_EXECUTOR_INACTIVE_TRESHOLD:-60}
- OPR_EXECUTOR_MAINTENANCE_INTERVAL=${OPR_EXECUTOR_MAINTENANCE_INTERVAL:-30}
- OPR_EXECUTOR_NETWORK=runtimes
- OPR_EXECUTOR_DOCKER_HUB_USERNAME=${OPR_EXECUTOR_DOCKER_HUB_USERNAME:-}
- OPR_EXECUTOR_DOCKER_HUB_PASSWORD=${OPR_EXECUTOR_DOCKER_HUB_PASSWORD:-}
- OPR_EXECUTOR_SECRET=${_APP_EXECUTOR_SECRET}
- OPR_EXECUTOR_RUNTIME_VERSIONS=${OPR_EXECUTOR_RUNTIME_VERSIONS:-}
appwrite-worker-mails:
image: appwrite/appwrite:1.9.0
container_name: appwrite-worker-mails
restart: unless-stopped
<<: *x-logging
entrypoint: worker-mails
networks:
- appwrite
depends_on:
- appwrite-redis
environment:
- _APP_ENV=${_APP_ENV:-development}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_REDIS_HOST=appwrite-redis
- _APP_REDIS_PORT=${_APP_REDIS_PORT:-6379}
- _APP_SMTP_HOST=${_APP_SMTP_HOST:-}
- _APP_SMTP_PORT=${_APP_SMTP_PORT:-587}
- _APP_SMTP_SECURE=${_APP_SMTP_SECURE:-tls}
- _APP_SMTP_USERNAME=${_APP_SMTP_USERNAME:-}
- _APP_SMTP_PASSWORD=${_APP_SMTP_PASSWORD:-}
appwrite-worker-messaging:
image: appwrite/appwrite:1.9.0
container_name: appwrite-worker-messaging
restart: unless-stopped
<<: *x-logging
entrypoint: worker-messaging
networks:
- appwrite
depends_on:
- appwrite-redis
environment:
- _APP_ENV=${_APP_ENV:-development}
- _APP_REDIS_HOST=appwrite-redis
- _APP_REDIS_PORT=${_APP_REDIS_PORT:-6379}
appwrite-worker-migrations:
image: appwrite/appwrite:1.9.0
container_name: appwrite-worker-migrations
restart: unless-stopped
<<: *x-logging
entrypoint: worker-migrations
networks:
- appwrite
depends_on:
- appwrite-mongo
environment:
- _APP_ENV=${_APP_ENV:-development}
- _APP_OPENSSL_KEY_V1=${_APP_OPENSSL_KEY_V1}
- _APP_DB_HOST=appwrite-mongo
- _APP_DB_ADAPTER=${_APP_DB_ADAPTER:-mongodb}
- _APP_DB_USER=${_APP_DB_USER:-user}
- _APP_DB_PASS=${_APP_DB_PASS:-password}
- _APP_DB_SCHEMA=${_APP_DB_SCHEMA:-appwrite}
- _APP_REDIS_HOST=appwrite-redis
- _APP_REDIS_PORT=${_APP_REDIS_PORT:-6379}
appwrite-mongo:
image: mongo:8.2.5
container_name: appwrite-mongo
restart: unless-stopped
<<: *x-logging
networks:
- appwrite
volumes:
- appwrite-mongodb:/var/lib/mongodb/data:rw
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=${_APP_DB_ROOT_PASS:-rootsecretpassword}
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 10s
retries: 5
start_period: 30s
command: mongod --auth
appwrite-redis:
image: redis:7.4.7-alpine
container_name: appwrite-redis
restart: unless-stopped
<<: *x-logging
networks:
- appwrite
volumes:
- appwrite-redis:/data:rw
command: >
redis-server
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--save ""
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 10s
retries: 5
start_period: 15s
volumes:
appwrite-uploads:
appwrite-cache:
appwrite-config:
appwrite-certificates:
appwrite-functions:
appwrite-builds:
appwrite-sites:
appwrite-mongodb:
appwrite-redis:
networks:
gateway:
name: gateway
appwrite:
name: appwrite
runtimes:
name: runtimesVerifizieren:
cd /opt/appwrite
docker compose config --quiet && echo "YAML valide"
# Erwartete Ausgabe: YAML valideSchritt 4: Stack starten
Beim ersten Start werden alle Images gezogen – insgesamt ca. 2–3 GB. Das kann je nach Internetverbindung 5–15 Minuten dauern. Auf Docker Desktop (Mac/Windows) dauert der vollständige Stack-Start zusätzlich 3–8 Minuten, bis alle Healthchecks grün sind.
cd /opt/appwrite
docker compose up -d
# Logs in Echtzeit verfolgen (Ctrl+C zum Beenden, Stack läuft weiter)
docker compose logs -f appwriteWarte, bis im Log die Zeile Appwrite started successfully erscheint. MongoDB und Redis müssen healthy sein, bevor Appwrite startet – das ist durch die depends_on-Healthchecks im Compose garantiert.
Verifizieren:
cd /opt/appwrite
docker compose ps
# Erwartete Ausgabe (alle Container Up/healthy):
# NAME STATUS
# appwrite Up (healthy)
# appwrite-executor Up
# appwrite-mongo Up (healthy)
# appwrite-realtime Up
# appwrite-redis Up (healthy)
# appwrite-traefik Up
# appwrite-worker-audits Up
# appwrite-worker-functions Up
# appwrite-worker-mails Up
# appwrite-worker-messaging Up
# appwrite-worker-migrations Up
# appwrite-worker-webhooks Up
# Health-Endpunkt prüfen:
curl -s http://localhost/v1/health | head -c 100
# Erwartete Ausgabe: {"status":"pass",...}Schritt 5: Erstes Admin-Konto anlegen
Öffne http://localhost im Browser (bei eigener Domain: https://deine-domain.de). Die Appwrite Console begrüßt dich mit dem Registrierungsformular. Das erste angelegte Konto wird automatisch zum Super-Admin. Trage eine E-Mail-Adresse und ein starkes Passwort ein und klicke auf „Sign Up".
Nach dem Login erstellst du dein erstes Projekt über den Button „Create project". Innerhalb des Projekts findest du alle Services: Auth, Databases, Storage, Functions, Messaging und Realtime – jeweils mit API-Keys und SDK-Konfigurationen für 18+ Plattformen (Web, Flutter, React Native, Python, Node.js, Go, PHP u. v. m.).
Möchtest du den Zugang zur Console auf bestimmte E-Mail-Adressen oder IP-Adressen beschränken, nutze _APP_CONSOLE_WHITELIST_EMAILS bzw. _APP_CONSOLE_WHITELIST_IPS in der .env.
Verifizieren:
# HTTP-Antwort der Console prüfen
curl -I http://localhost
# Erwartete Ausgabe: HTTP/1.1 200 OK (oder 301/302-Weiterleitung auf HTTPS)
# Im Browser: http://localhost – Appwrite Console Login/Registrierung sichtbarSchritt 6: HTTPS mit eigener Domain (Produktion)
Für den produktiven Betrieb mit automatischem Let's-Encrypt-Zertifikat trägst du in der .env deine Domain ein und startest den Stack neu:
cd /opt/appwrite
# .env anpassen:
# _APP_DOMAIN=appwrite.example.com
# _APP_ENV=production
# Stack mit neuer Konfiguration neu erstellen:
docker compose up -d --force-recreate appwrite appwrite-traefikTraefik 3.6 übernimmt die HTTP-01-Challenge automatisch und legt das Zertifikat im Volume appwrite-certificates ab. DNS-A-Record muss vorab auf die öffentliche IP zeigen. Für Reverse-Proxy-Grundlagen und eine alternative Traefik-Einrichtung, die für mehrere Dienste gilt, empfiehlt sich ein Blick in die Anleitung Traefik als Docker-Reverse-Proxy mit automatischem HTTPS.
Verifizieren:
curl -I https://appwrite.example.com/v1/health
# Erwartete Ausgabe: HTTP/2 200
docker compose logs appwrite-traefik | grep -i "certificate"
# Erwartete Ausgabe: ...Storing new certificates...Schritt 7: Updates und Backup
Beim Versions-Update von Appwrite ist die Reihenfolge entscheidend: Images ziehen, Container neu erstellen, dann sofort die Datenbankmigration ausführen. Das Überspringen der Migration führt zu einem inkompatiblen Datenbankschema.
cd /opt/appwrite
# 1. Neue Images laden
docker compose pull
# 2. Container neu erstellen
docker compose up -d
# 3. PFLICHT: Datenbankmigration ausführen (auf Abschluss warten!)
docker compose exec appwrite migrate
# Logs prüfen bis "Migration completed" erscheint:
docker compose logs -f appwrite | grep -i migratFür ein vollständiges Backup sicherst du drei Elemente: die .env-Datei (insbesondere _APP_OPENSSL_KEY_V1), das MongoDB-Volume und das Uploads-Volume. Den Host-Pfad eines Volumes ermittelst du so:
docker volume inspect appwrite_appwrite-mongodb
# Gibt den Mountpoint auf dem Host aus, z. B.:
# "Mountpoint": "/var/lib/docker/volumes/appwrite_appwrite-mongodb/_data"
# MongoDB-Dump direkt aus dem Container:
docker compose exec appwrite-mongo mongodump \
--username root \
--password "$_APP_DB_ROOT_PASS" \
--authenticationDatabase admin \
--out /tmp/dump
docker cp appwrite-mongo:/tmp/dump ./backup-$(date +%Y%m%d)Automatische Benachrichtigungen bei neuen Images lassen sich mit Diun oder WUD einrichten – Appwrite selbst sollte man allerdings immer manuell updaten und die Migration sofort danach ausführen.
Verifizieren:
cd /opt/appwrite
docker compose ps
# Alle Container: Up
docker compose logs appwrite | grep -i "migration"
# Erwartete Ausgabe nach Update: Migration completedTroubleshooting / Typische Fehler
- „appwrite-mongo is unhealthy" – appwrite startet nicht: MongoDB braucht beim ersten Start bis zu 60 Sekunden. Erhöhe
start_periodim Healthcheck auf 60s oder warte 2–3 Minuten und prüfe mitdocker compose logs appwrite-mongo. Alternativmongo:8.2statt des exakten Tags8.2.5testen. - „Executor not reachable" bei Serverless Functions: Der appwrite-executor-Container benötigt Zugriff auf
/var/run/docker.sock. Prüfe den Mount:docker inspect appwrite-executor | grep docker.sock. Auf manchen Systemen muss der laufende User Mitglied derdocker-Gruppe sein. - Port 80/443 bereits belegt: Ein anderer Dienst blockiert die Ports. Prüfen mit
ss -tlnp | grep -E ':80|:443', dann den konkurrierenden Dienst stoppen. - SSL-Zertifikat wird nicht ausgestellt: Let's Encrypt benötigt eine öffentlich erreichbare Domain.
_APP_DOMAIN=localhostoder eine private IP funktioniert nicht für HTTPS. Für lokale Tests HTTP verwenden. - Functions-Runtime fehlt beim ersten Aufruf (Timeout): Beim ersten Aufruf einer Runtime (z. B. Node.js 20) lädt der Executor das Runtime-Image nach. Das dauert 30–120 Sekunden – kein Fehler, sondern erwartetes Verhalten. Im Log:
docker compose logs appwrite-executor. - Daten nach Umbenennung des Projektordners verschwunden: Docker benennt den Stack nach dem Verzeichnisnamen. Die alten Volumes heißen weiterhin
appwrite_appwrite-mongodb– prüfen mitdocker volume ls | grep appwriteund den Stack mit--project-name appwritestarten. - Breaking Change in v1.9 – HTTP 403 statt 401: Ab Version 1.9 gibt die API bei gesperrten Ressourcen HTTP 403 zurück (vorher 401). Wer Statuscodes direkt prüft (ohne SDK), muss die Fehlerbehandlung anpassen.
Häufige Fragen
Wie installiere ich Appwrite am schnellsten?
Mit dem offiziellen One-Liner startest du einen interaktiven Installer, der nach Domain fragt und docker-compose.yml + .env automatisch erzeugt:
docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)/appwrite:/usr/src/code/appwrite:rw" \
--entrypoint=install \
appwrite/appwrite:1.9.0
cd appwrite && docker compose up -dFür volle Kontrolle über die Konfiguration ist der manuelle Weg aus dieser Anleitung vorzuziehen.
Kann ich Appwrite auf einem Raspberry Pi (ARM64) betreiben?
Ja. Seit Version 0.13 liefert Appwrite offizielle Multi-Arch-Images für linux/amd64 und linux/arm64. Ein Raspberry Pi 4 oder 5 mit 4 GB RAM und 64-Bit-OS reicht für Entwicklungs- und Testzwecke. Für Produktion auf ARM64 sind 8 GB RAM empfohlen.
Welche Programmiersprachen werden für Serverless Functions unterstützt?
Appwrite Functions unterstützen 30+ Runtimes: Node.js (14–22), Python (3.9–3.12), PHP (8.0–8.3), Ruby (3.1–3.3), Dart (2.17–3.3), Swift (5.8–5.10), .NET (6–8), Go (1.21–1.22), Bun, Deno, Kotlin, Java (18–21), Rust (1.75) und weitere. Jede Runtime läuft als separater Docker-Container, der vom openruntimes-executor bei Bedarf gestartet wird.
Ich habe keine eigene Domain – kann ich Appwrite trotzdem nutzen?
Für lokale Entwicklung: _APP_DOMAIN=localhost belassen und über http://localhost zugreifen. Für öffentlichen Zugriff ohne eigene Domain eignen sich Dienste wie nip.io (z. B. 192.168.1.100.nip.io) oder ein Tunnel-Dienst wie Cloudflare Tunnel.
Welche Daten muss ich für ein vollständiges Backup sichern?
Drei Elemente sind kritisch: (1) die .env-Datei, insbesondere _APP_OPENSSL_KEY_V1 – ohne diesen Schlüssel sind alle verschlüsselten Daten wertlos, (2) das MongoDB-Volume (appwrite_appwrite-mongodb) mit regelmäßigen mongodump-Snapshots, (3) das Uploads-Volume (appwrite_appwrite-uploads) für hochgeladene Benutzerdateien.
Wie unterscheidet sich Appwrite von PocketBase oder Supabase?
Appwrite ist der umfangreichste der drei BaaS-Kandidaten: Ein vollständiger Stack mit 10+ Containern, MongoDB als Backend, Serverless Functions, Messaging und einem fertigen Web-Dashboard. PocketBase hingegen ist ein einzelnes Go-Binary – ideal für kleine Projekte mit minimaler Infrastruktur. Supabase ist PostgreSQL-basiert und nicht vollständig Open-Source. Appwrite eignet sich am besten für Teams, die ein produktionstaugliches Backend mit breitem Feature-Set selbst betreiben wollen.
Fazit
Appwrite 1.9 liefert in einem einzigen docker compose up -d ein vollständiges Backend-as-a-Service-Setup, das Firebase und Supabase in Breite und Produktionsreife kaum nachsteht. Der wichtigste Schritt vor dem Start ist das sichere Generieren und dauerhafte Speichern von _APP_OPENSSL_KEY_V1 – dieser Schlüssel ist die Achillesferse des gesamten Stacks. In der Produktion sorgt Traefik automatisch für HTTPS, und die 30+ Function-Runtimes machen den Stack zur flexiblen Backend-Plattform für nahezu jede Sprache. Wer neben einem vollständigen BaaS auch ein schlankes Einzel-Backend für kleine Projekte sucht, sollte einen Blick auf PocketBase mit Docker werfen.
Weiterführende Anleitungen und Quellen
- Docker und Docker Compose auf Linux installieren – die Self-Hosting-Grundlage
- Traefik als Docker-Reverse-Proxy mit automatischem HTTPS einrichten
- PocketBase mit Docker: schlankes Go-Backend als Alternative
- Docker Compose absichern: Secrets, Healthchecks und Non-Root
Offizielle Quellen: Appwrite Self-Hosting Installation, Appwrite Environment Variables, Appwrite 1.9.0 Release Notes.