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

Wakapi mit Docker Compose: WakaTime-Statistik selbst hosten

Wakapi ist ein schlanker, WakaTime-kompatibler Server für Coding-Statistiken. Die Anleitung zeigt die Installation mit Docker Compose und PostgreSQL 17, die Anbindung der Editor-Plugins, einen Heartbeat-Test per curl, Sicherung mit pg_dump, eine echte Wiederherstellung und das Update von 2.18.1 auf 2.18.2.

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

Illustration zur Anleitung Wakapi mit Docker selbst hosten mit Laptop, Coding-Statistik-Dashboard und Datenbank-Symbol

Wer wissen möchte, wie viel Zeit in welches Projekt, welche Sprache und welchen Editor fließt, landet schnell bei WakaTime, dessen Auswertung in der Cloud des Anbieters läuft. Wakapi ist ein schlanker, in Go geschriebener Server, der die WakaTime-API nachbildet. Sie behalten also die gewohnten Plugins und schicken die Heartbeats an den eigenen Server. Diese Anleitung zeigt den Betrieb mit Docker Compose und PostgreSQL, die Anbindung der Clients, eine Sicherung samt Wiederherstellung und das Update auf eine neue Version.

Voraussetzungen

Wakapi selbst ist sehr genügsam, den größten Teil des Speichers belegt PostgreSQL. Im Test lagen beide Container zusammen bei rund 125 MiB RAM.

  • Linux-Server oder VM mit 1 bis 2 CPU-Kernen, 1 GB RAM (2 GB mit Reserve) und 5 GB freiem Speicher, x86_64 oder ARM64
  • Docker Engine mit Compose-Plugin v2
  • Eine Domain mit TLS-Reverse-Proxy, falls Clients außerhalb des LAN senden sollen
  • Auf den Entwickler-Rechnern ein WakaTime-Plugin für den jeweiligen Editor
  • openssl zum Erzeugen von Zufallswerten, optional jq für API-Tests
EckdatenWert
Imageghcr.io/muety/wakapi:2.18.2 (auch 2.18, 2, latest)
Datenbankpostgres:17, Volume wakapi-db-data unter /var/lib/postgresql/data
Port3000 (HTTP, Weboberfläche und API)
Wichtige VariablenWAKAPI_DB_TYPE, WAKAPI_PASSWORD_SALT, WAKAPI_COOKIE_KEY, WAKAPI_PUBLIC_URL, WAKAPI_ALLOW_SIGNUP
LizenzMIT

Schritt 1: Compose-Datei anlegen

Grundlage ist die compose.yml aus dem Projekt. Sie baut das Image allerdings lokal mit build: ., was für den Betrieb unpraktisch ist. Die folgende Fassung nutzt stattdessen das fertige Image aus der GitHub Container Registry, ergänzt einen Healthcheck für PostgreSQL und lässt den SMTP-Teil weg. Speichern Sie sie als /opt/wakapi/compose.yaml:

services:
  wakapi:
    image: ghcr.io/muety/wakapi:${WAKAPI_TAG}
    init: true
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      WAKAPI_DB_TYPE: "postgres"
      WAKAPI_DB_NAME: "wakapi"
      WAKAPI_DB_USER: "wakapi"
      WAKAPI_DB_HOST: "db"
      WAKAPI_DB_PORT: "5432"
      WAKAPI_DB_PASSWORD_FILE: "/run/secrets/db_password"
      WAKAPI_PASSWORD_SALT_FILE: "/run/secrets/password_salt"
      WAKAPI_PUBLIC_URL: "${WAKAPI_PUBLIC_URL}"
      WAKAPI_ALLOW_SIGNUP: "${WAKAPI_ALLOW_SIGNUP}"
      WAKAPI_LEADERBOARD_ENABLED: "false"
      WAKAPI_COOKIE_KEY: "${WAKAPI_COOKIE_KEY}"
      WAKAPI_INSECURE_COOKIES: "${WAKAPI_INSECURE_COOKIES}"
      TZ: "Europe/Berlin"
    secrets:
      - source: password_salt
        target: password_salt
        uid: "65532"
        gid: "65532"
        mode: 0400
      - source: db_password
        target: db_password
        uid: "65532"
        gid: "65532"
        mode: 0400
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_USER: "wakapi"
      POSTGRES_DB: "wakapi"
      POSTGRES_PASSWORD_FILE: "/run/secrets/db_password"
    volumes:
      - wakapi-db-data:/var/lib/postgresql/data
    secrets:
      - db_password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U wakapi -d wakapi"]
      interval: 10s
      timeout: 5s
      retries: 10

secrets:
  password_salt:
    environment: WAKAPI_PASSWORD_SALT
  db_password:
    environment: WAKAPI_DB_PASSWORD

volumes:
  wakapi-db-data: {}

Die Secrets gehören der UID 65532, weil das Distroless-Image als Benutzer nonroot läuft. depends_on mit service_healthy verhindert, dass Wakapi vor der Datenbank startet. Die öffentliche Bestenliste ist ab Werk eingeschaltet und für eine interne Instanz meist unerwünscht, daher WAKAPI_LEADERBOARD_ENABLED: "false". PostgreSQL 17 nutzt noch den klassischen Pfad /var/lib/postgresql/data; wer auf Version 18 wechselt, muss das Volume auf /var/lib/postgresql umhängen.

Verifizieren: docker compose config --quiet im Projektordner gibt nichts aus und endet mit Exit-Code 0. Fehlt später die .env, warnt Compose an dieser Stelle über leere Variablen.

Schritt 2: Geheimnisse erzeugen und starten

Die .env enthält den Image-Tag, die öffentliche Adresse und drei Zufallswerte. Das Passwort-Salt wird in jeden Passwort-Hash eingerechnet: Geht es verloren, kann sich niemand mehr mit dem bisherigen Passwort anmelden. Der Cookie-Key signiert die Sitzungen.

mkdir -p /opt/wakapi && cd /opt/wakapi
cat > .env <<EOF
WAKAPI_TAG=2.18.2
WAKAPI_PUBLIC_URL=https://wakapi.example.de
WAKAPI_ALLOW_SIGNUP=true
WAKAPI_INSECURE_COOKIES=true
WAKAPI_PASSWORD_SALT=$(openssl rand -hex 24)
WAKAPI_DB_PASSWORD=$(openssl rand -hex 24)
WAKAPI_COOKIE_KEY=$(openssl rand -base64 32)
EOF
chmod 600 .env

Den Cookie-Key sollten Sie nicht weglassen, auch wenn die Projektdoku ihn als optional führt. Ohne ihn erzeugt Wakapi bei jedem Start einen neuen Schlüssel. Im Test lieferte eine gültige Sitzung vor dem Neustart HTTP 200 auf /summary, danach nur noch eine 302-Weiterleitung zum Login. Mit gesetztem WAKAPI_COOKIE_KEY blieb die Sitzung über den Neustart hinweg gültig. Starten Sie den Stack anschließend:

docker compose up -d
docker compose ps
docker compose logs --no-log-prefix wakapi | grep "Wakapi version"
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/api/health

Beim ersten Start legt Wakapi das Schema an und führt eine Reihe von Migrationen aus. Der Test startete zunächst mit 2.18.1, um in Schritt 8 das Update zu prüfen. Der Container war nach rund 20 Sekunden healthy, die Ausgabe sah so aus:

news-wakapi-db-1       postgres:17                   Up 39 seconds (healthy)
news-wakapi-wakapi-1   ghcr.io/muety/wakapi:2.18.1   Up 18 seconds (healthy)
INFO Wakapi version="v2.18.1"
200

Verifizieren: Beide Container stehen auf healthy, das Log nennt die erwartete Version und /api/health antwortet mit 200.

Schritt 3: Erstes Konto anlegen und Registrierung schließen

Öffnen Sie http://SERVER:3000/signup oder Ihre Domain und legen Sie ein Konto an. Die E-Mail-Adresse ist optional, wird aber für Passwort-Resets und Wochenberichte gebraucht. Das erste Konto einer leeren Datenbank erhält automatisch Administratorrechte; in der Tabelle users stand im Test bei diesem Konto is_admin = t, bei den folgenden f.

Wakapi-Registrierungsformular mit Feldern für Benutzername, E-Mail-Adresse und Passwort
Das erste Konto auf einer frischen Instanz wird automatisch Administrator.

Legen Sie anschließend alle Konten für Ihr Team an und setzen Sie dann in der .env WAKAPI_ALLOW_SIGNUP=false. Ein docker compose up -d erzeugt den Container neu. Für spätere Konten sieht Wakapi Einladungscodes vor (Option WAKAPI_INVITE_CODES, ab Werk aktiv).

Verifizieren: Ein erneuter Registrierungsversuch per curl -d "username=gast&password=Testtest123&password_repeat=Testtest123" http://localhost:3000/signup liefert im Test HTTP 403 mit dem Text „Registration is disabled on this server“.

Schritt 4: WakaTime-Clients anbinden

Unter Settings im Reiter API Keys finden Sie den Hauptschlüssel Ihres Kontos. Dort lassen sich auch zusätzliche Schlüssel anlegen, wahlweise nur lesend, etwa für ein Dashboard, das Statistiken abfragt, aber keine Heartbeats senden darf.

Wakapi-Einstellungen im Reiter API Keys mit maskiertem Haupt-Schlüssel und Formular für weitere Schlüssel
Unter Settings und API Keys liegt der Schlüssel für die .wakatime.cfg; zusätzliche Schlüssel lassen sich nur lesend anlegen.

Auf jedem Entwickler-Rechner tragen Sie Server und Schlüssel in die Datei ~/.wakatime.cfg ein. Wichtig ist das Suffix /api in der URL:

[settings]
api_url = https://wakapi.example.de/api
api_key = IHR-API-KEY

Ob der Server Heartbeats annimmt, prüfen Sie unabhängig vom Editor direkt per curl. Wakapi erwartet den API-Key Base64-kodiert im Header Authorization: Basic, genau wie die WakaTime-Clients ihn senden:

KEY=IHR-API-KEY
AUTH=$(printf %s "$KEY" | base64 -w0)
curl -s -w "\n%{http_code}\n" \
  -A "wakatime/v1.131.0 (linux-6.12-x86_64) go1.24 vscode/1.105.0 vscode-wakatime/25.3.0" \
  -H "Authorization: Basic $AUTH" -H "Content-Type: application/json" \
  -d "{\"entity\":\"/home/dev/kundenportal/app.ts\",\"type\":\"file\",\"category\":\"coding\",
       \"project\":\"kundenportal\",\"language\":\"TypeScript\",\"time\":$(date +%s)}" \
  http://localhost:3000/api/heartbeat

Die Antwort lautet {"responses":[[{"data":null,"error":null},201]]} mit Status 201. Für viele Heartbeats auf einmal gibt es /api/users/current/heartbeats.bulk, das ein JSON-Array annimmt. Ein falscher Schlüssel führt zu HTTP 401.

Verifizieren: Der curl-Aufruf liefert 201, und in der Weboberfläche taucht das Projekt kundenportal unter Projects auf.

Schritt 5: Statistik prüfen

Wakapi fasst die Heartbeats zu Zeitspannen zusammen. Im Test erzeugten elf Heartbeats im Abstand von je zwei Minuten genau 20 Minuten Projektzeit, die Lücken dazwischen zählen also als durchgehend gearbeitet. Die Daten lesen Sie über die WakaTime-kompatible API aus:

curl -s -H "Authorization: Basic $AUTH" \
  "http://localhost:3000/api/compat/wakatime/v1/users/current/stats/last_7_days" \
  | jq -c '.data|{total_seconds,human_readable_total,projects:[.projects[]|{name,text}]}'
{"total_seconds":3600,"human_readable_total":"1 hrs 0 mins",
 "projects":[{"name":"restore-marker","text":"0 hrs 40 mins"},
             {"name":"sedv-demo","text":"0 hrs 20 mins"}]}
Wakapi-Dashboard mit Gesamtzeit, Heartbeat-Anzahl, Projekt-Balkendiagramm sowie Sprach- und Editor-Diagrammen
Nach den Test-Heartbeats zeigt das Dashboard Projekte, Sprachen und Editor für die letzten sieben Tage.

Der Editor wird aus dem User-Agent abgeleitet, nicht aus einem JSON-Feld. Heartbeats, die im Test ohne WakaTime-User-Agent per curl kamen, landeten im Dashboard unter dem Editor „Unknown“; mit dem User-Agent eines VS-Code-Plugins erschien korrekt „Vscode“.

Verifizieren: Die API meldet eine Gesamtzeit größer null, und das Dashboard zeigt unter Past 7 Days dieselben Projekte und Sprachen.

Schritt 6: Datenbank sichern

Alle Nutzdaten liegen in PostgreSQL. Das Wakapi-Image ist distroless und enthält keine Shell, Sicherungen laufen deshalb über den Datenbank-Container. Das Format -Fc ist komprimiert und lässt sich gezielt mit pg_restore einspielen. Sichern Sie zusätzlich die .env, denn ohne das alte Passwort-Salt sind die gespeicherten Passwort-Hashes wertlos.

cd /opt/wakapi && mkdir -p backup
docker compose exec -T db pg_dump -U wakapi -d wakapi -Fc > backup/wakapi-$(date +%F).dump
cp .env backup/env-$(date +%F)
ls -la backup

Im Test war der Dump mit zwei Konten und 47 Heartbeats rund 40 KB groß. Legen Sie den Befehl in einen nächtlichen Cronjob und kopieren Sie den Ordner backup auf ein anderes System, etwa mit restic.

Verifizieren: ls -la backup zeigt Dump und .env-Kopie; der Dump muss deutlich größer als null Byte sein. Ob er sich einspielen lässt, beweist erst Schritt 7.

Schritt 7: Wiederherstellung durchspielen

Eine Sicherung ist erst etwas wert, wenn die Rücksicherung einmal funktioniert hat. Im Test diente ein Projekt namens restore-marker mit 23 Heartbeats als Markierung. Stoppen Sie zuerst Wakapi, damit keine offene Verbindung das Löschen der Datenbank blockiert, und spielen Sie den Dump dann in eine frisch angelegte, leere Datenbank:

docker compose stop wakapi
docker compose exec -T db dropdb -U wakapi --maintenance-db=postgres wakapi
docker compose exec -T db createdb -U wakapi --maintenance-db=postgres -O wakapi wakapi
docker compose exec -T db pg_restore -U wakapi -d wakapi --exit-on-error --no-owner \
  < backup/wakapi-2026-10-21.dump; echo "restore exit=$?"
docker compose start wakapi
vorher:  users=2 hb=47 marker=23
leer:    tables=0
restore exit=0
nachher: users=2 hb=47 marker=23
health=200 nach 2x2s

Zwei Stolpersteine traten dabei auf. dropdb und createdb kennen keine Option -d und melden invalid option -- 'd'; die Wartungsdatenbank wird mit --maintenance-db=postgres angegeben. Und wer den Dump in die bestehende, noch gefüllte Datenbank einspielt, bekommt ERROR: relation "aliases" already exists, weil Wakapi das Schema bereits angelegt hat.

Verifizieren: Nach dem Start meldet /api/health 200, die Stats-API zeigt wieder das Projekt restore-marker, und der API-Key aus der Sicherung wird weiter akzeptiert.

Schritt 8: Auf eine neue Version aktualisieren

Der Test startete bewusst mit Version 2.18.1 und wurde dann auf 2.18.2 angehoben. Ändern Sie den Tag in der .env, ziehen Sie das Image und erstellen Sie den Container neu. Sichern Sie vorher die Datenbank wie in Schritt 6.

sed -i 's/^WAKAPI_TAG=.*/WAKAPI_TAG=2.18.2/' .env
docker compose pull wakapi
docker compose up -d
docker compose logs --no-log-prefix wakapi | grep -E "idx_user_project_time|Wakapi version"
INFO Wakapi version="v2.18.2"
INFO index 'idx_user_project_time' needs to be created, this may take a while

Version 2.18.2 legt laut Release-Notes einen neuen Index auf der Tabelle heartbeats an. Auf der kleinen Testdatenbank dauerte das etwa eine Sekunde; bei Millionen Heartbeats warnt das Projekt vor Stunden. Wer nicht so lange warten will, legt den Index vor dem Update selbst an: create index idx_user_project_time on heartbeats (user_id, project, time);. Die Release-Notes von 2.18.1 weisen außerdem auf fehlschlagende Migrationen unter MariaDB hin, PostgreSQL ist davon nicht betroffen. Nach dem Update waren alle Heartbeats und Statistiken unverändert vorhanden, und die vor dem Update erzeugte Sitzung blieb dank festem Cookie-Key gültig.

Verifizieren: docker compose exec wakapi /app/wakapi -version gibt 2.18.2 aus, die Fußzeile der Weboberfläche zeigt v2.18.2 @ postgres.

Troubleshooting

SymptomUrsacheLösung
Nach jedem Neustart abgemeldetKein WAKAPI_COOKIE_KEY, Schlüssel wird zufällig neu erzeugtFesten Base64-Wert in die .env
Heartbeats ergeben HTTP 401API-Key falsch oder nicht Base64-kodiertSchlüssel aus API Keys kopieren, Header prüfen
Editor erscheint als „Unknown“Kein WakaTime-User-AgentEchte Plugins senden ihn; bei Skripten mit -A setzen
relation "aliases" already existsRestore in eine gefüllte DatenbankDatenbank löschen, leer anlegen, dann pg_restore
invalid option -- 'd' bei dropdbFalscher Parameter--maintenance-db=postgres
Erster Start nach Update dauert sehr langeIndexaufbau auf großer Heartbeat-TabelleIndex vorab manuell anlegen, Healthcheck-Startzeit erhöhen

Hinter einem Reverse Proxy mit HTTPS empfiehlt die Projektdoku WAKAPI_INSECURE_COOKIES=false, damit das Sitzungscookie nur verschlüsselt übertragen wird. Lassen Sie den Wert auf true, solange Sie die Instanz noch per HTTP über Port 3000 aufrufen, sonst verwirft der Browser das Cookie.

Häufige Fragen

Kann ich WakaTime und Wakapi parallel nutzen?

Ja. Die WakaTime-CLI kann im Abschnitt [api_urls] der .wakatime.cfg weitere Ziele mit eigenem Schlüssel bekommen. Alternativ leitet Wakapi Heartbeats über Settings und Integrations an WakaTime weiter.

Reicht SQLite statt PostgreSQL?

Für eine einzelne Person ja, SQLite ist der Standard des Images und liegt unter /data/wakapi.db. Für Teams und große Datenmengen ist PostgreSQL robuster und lässt sich im laufenden Betrieb konsistent sichern.

Lassen sich alte WakaTime-Daten übernehmen?

Wakapi bietet in den Einstellungen einen Import über die WakaTime-API an. Dafür ist der WakaTime-API-Key nötig; der Import ist standardmäßig erlaubt und lässt sich mit WAKAPI_IMPORT_ENABLED=false abschalten.

Fazit

Wakapi ist eine leichte Alternative zur WakaTime-Cloud: Die bekannten Editor-Plugins bleiben, die Daten liegen auf dem eigenen Server. Die Einrichtung mit PostgreSQL ist in einer Viertelstunde erledigt, Sicherung und Wiederherstellung funktionieren mit Bordmitteln von PostgreSQL. Wichtig sind ein fester Cookie-Key, die gesicherte .env mit dem Passwort-Salt und die abgeschaltete Registrierung. Vor Updates lohnt der Blick in die Release-Notes.

Weiterführende Anleitungen und Quellen

WakapiWakaTimeDocker ComposePostgreSQLSelfhostingBackupEntwickler