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

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.

Appwrite mit Docker installieren: Vollständige Open-Source-BaaS-Plattform mit Authentifizierung, Datenbanken, Storage, Functions, Messaging und selbst gehosteter Docker-Umgebung. KI-generiert

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

  1. 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.
  2. 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.
  3. Öffentliche IP + Domain mit DNS-A-Record (nur für automatisches HTTPS/Let's Encrypt). Für lokale Tests reicht localhost.
  4. Ports 80 und 443 müssen auf dem Host frei sein. Kein anderer Webserver oder Traefik-Stack darf auf diesen Ports lauschen.
  5. openssl für die Schlüssel-Generierung (auf fast allen Linux-Systemen vorinstalliert).
  6. 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 verwenden

Wichtig: 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=production

Schritt 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.

ServiceImageZweck
traefiktraefik:3.6Reverse Proxy, SSL-Terminierung (Let's Encrypt)
appwriteappwrite/appwrite:1.9.0API-Server + Web-Console (~512 MB)
appwrite-realtimeappwrite/appwrite:1.9.0WebSocket-Server (/v1/realtime)
appwrite-executoropenruntimes/executor:0.7.22Serverless Function Runner
appwrite-mongomongo:8.2.5Primäre Datenbank (Standard seit v1.9)
appwrite-redisredis:7.4.7-alpineCache + Job Queue
Worker (5×)appwrite/appwrite:1.9.0audits, webhooks, functions, mails, messaging, migrations
Port (Host→Container)ProtokollBeschreibung
80:80HTTPConsole + API (Traefik)
443:443HTTPSConsole + API mit SSL (Let's Encrypt)
VolumePfad im ContainerInhalt
appwrite-uploads/storage/uploadsHochgeladene Benutzerdateien
appwrite-cache/storage/cacheInterner Cache, Thumbnails
appwrite-certificates/storage/certificatesSSL-Zertifikate (Let's Encrypt)
appwrite-functions/storage/functionsServerless-Function-Code
appwrite-mongodb/var/lib/mongodb/dataMongoDB-Datenbankdaten (kritisch!)
appwrite-redis/dataRedis-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: runtimes

Verifizieren:

cd /opt/appwrite
docker compose config --quiet && echo "YAML valide"
# Erwartete Ausgabe: YAML valide

Schritt 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 appwrite

Warte, 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 sichtbar

Schritt 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-traefik

Traefik 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 migrat

Fü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 completed

Troubleshooting / Typische Fehler

  1. „appwrite-mongo is unhealthy" – appwrite startet nicht: MongoDB braucht beim ersten Start bis zu 60 Sekunden. Erhöhe start_period im Healthcheck auf 60s oder warte 2–3 Minuten und prüfe mit docker compose logs appwrite-mongo. Alternativ mongo:8.2 statt des exakten Tags 8.2.5 testen.
  2. „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 der docker-Gruppe sein.
  3. 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.
  4. SSL-Zertifikat wird nicht ausgestellt: Let's Encrypt benötigt eine öffentlich erreichbare Domain. _APP_DOMAIN=localhost oder eine private IP funktioniert nicht für HTTPS. Für lokale Tests HTTP verwenden.
  5. 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.
  6. Daten nach Umbenennung des Projektordners verschwunden: Docker benennt den Stack nach dem Verzeichnisnamen. Die alten Volumes heißen weiterhin appwrite_appwrite-mongodb – prüfen mit docker volume ls | grep appwrite und den Stack mit --project-name appwrite starten.
  7. 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 -d

Fü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

  1. Docker und Docker Compose auf Linux installieren – die Self-Hosting-Grundlage
  2. Traefik als Docker-Reverse-Proxy mit automatischem HTTPS einrichten
  3. PocketBase mit Docker: schlankes Go-Backend als Alternative
  4. Docker Compose absichern: Secrets, Healthchecks und Non-Root

Offizielle Quellen: Appwrite Self-Hosting Installation, Appwrite Environment Variables, Appwrite 1.9.0 Release Notes.