Dies ist die vollständige, Schritt-für-Schritt-Anleitung zur Installation von GateControl. Sie geht von einem frischen Linux-Host mit Root-Zugang ohne bestehendes GateControl-Setup aus. Bestehende Installationen, die auf das empfohlene Verzeichnislayout umziehen möchten, finden die Anleitung in §12 Migration bestehender Installationen.
Für eine Einzeiler-Zusammenfassung siehe die Quick-Start-Sektion in der README.de. Dieses Dokument deckt den kompletten Ablauf von DNS bis zum ersten Login ab.
- Voraussetzungen
- Verzeichnislayout
- Setup-Dateien herunterladen
.envkonfigurieren- Erster Start
- Erster Login
- Erster Peer und erste Route
- Installation verifizieren
- Troubleshooting
- Backup und Restore
- Updates
- Migration bestehender Installationen
| Ressource | Minimum | Empfohlen |
|---|---|---|
| CPU | 1 vCPU | 2 vCPU |
| RAM | 1 GB | 2 GB |
| Platte | 20 GB | 40 GB (mehr für ausführliche Activity-Logs und Caddy-Access-Logs) |
- Betriebssystem: Moderne Linux-Distribution (Debian 11+, Ubuntu 22.04+, Fedora, Rocky, Alma, Alpine). Getestet auf Debian 13.
- Docker Engine: 24.0 oder neuer
- Docker Compose: v2 (seit Docker Engine 23.0 integriert)
- WireGuard-Kernelmodul: auf den meisten modernen Kernels vorhanden. Der Container bringt kein externes Install mit, aber WireGuard-Capabilities (
NET_ADMIN) müssen dem Container gewährt werden können.
Bevor der Container gestartet wird, muss ein DNS-A-Record (optional zusätzlich AAAA für IPv6) auf die öffentliche IP des Hosts zeigen:
gate.example.com. IN A 198.51.100.42
GateControl nutzt diesen Namen für zwei Zwecke:
- Admin-UI via
GC_BASE_URL— Caddy fordert automatisch beim ersten Start ein Let's-Encrypt-Zertifikat dafür an. - WireGuard-Endpunkt, falls du zusätzlich
GC_WG_HOST=gate.example.comsetzt. (GC_WG_HOSTkann auch eine blanke öffentliche IP sein, aber derselbe Hostname vereinfacht Peer-Konfigurationen.)
Jede Domain, für die du später eine Reverse-Proxy-Route einrichtest, braucht einen eigenen A-Record, der auf denselben Host zeigt.
| Port | Protokoll | Zweck | Erreichbar von |
|---|---|---|---|
| 80 | TCP | HTTP → HTTPS Redirect, ACME HTTP-01-Challenge | Internet |
| 443 | TCP | HTTPS für Admin-UI und alle Reverse-Proxy-Routen | Internet |
| 443 | UDP | HTTP/3 (optional, empfohlen) | Internet |
| 51820 | UDP | WireGuard-VPN-Endpunkt | Internet |
| 53 | TCP/UDP auf 127.0.0.1 und auf der VPN-Gateway-IP (10.8.0.1 per Default) |
Interner DNS für VPN-Peers | nur Container (Loopback + WG-Interface) |
Sofern auf dem Host bereits etwas auf 127.0.0.1:53 lauscht (häufige Ursachen: NetworkManager-dnsmasq, libvirt-dnsmasq, bind9), weigert sich der GateControl-Container zu starten. systemd-resolved nutzt 127.0.0.53 und kollidiert nicht. Das Entrypoint-Skript prüft das explizit und beendet sich mit einer klaren Fehlermeldung, falls es einen anderen Listener findet.
Öffne die ersten vier Ports in deiner Cloud-Firewall / iptables / ufw, bevor du den Container startest.
Lege ein dediziertes Deploy-Verzeichnis an — getrennt von einem eventuell geklonten Source-Repository. Der empfohlene Pfad ist /opt/gatecontrol/:
/opt/gatecontrol/
├── docker-compose.yml # Image, Ports, Volume
├── .env # deine Config (Passwörter, Domain etc.)
├── update.sh # Helper-Skript zum Pullen + Neustart
└── data/ # entsteht beim ersten Start — hält DB, Certs, Keys, WG-Config
Warum getrennt vom Source-Repo:
- Klares mentales Modell: "Code" und "Config" werden nie vermischt.
- Das Source-Repo kann jederzeit frisch geklont, aufgeräumt oder aktualisiert werden, ohne Produktiv-Zustand zu verlieren.
- Ein-Zeilen-Backup:
tar czf backup.tar.gz /opt/gatecontrolsichert alles.
mkdir -p /opt/gatecontrol
cd /opt/gatecontrolDrei Varianten. Alle enden mit denselben Dateien in /opt/gatecontrol/.
setup.sh herunterladen und ausführen. Es installiert Docker, falls es fehlt, führt interaktiv durch die .env-Werte, erzeugt sichere Secrets und startet den Container:
cd /opt/gatecontrol
curl -fsSLO https://github.com/CallMeTechie/gatecontrol/releases/latest/download/setup.sh
curl -fsSLO https://github.com/CallMeTechie/gatecontrol/releases/latest/download/docker-compose.yml
curl -fsSLO https://github.com/CallMeTechie/gatecontrol/releases/latest/download/.env.example
bash setup.shDann direkt zu §6 Erster Login — setup.sh erledigt den Rest.
cd /opt/gatecontrol
curl -fsSLO https://github.com/CallMeTechie/gatecontrol/releases/latest/download/docker-compose.yml
curl -fsSLO https://github.com/CallMeTechie/gatecontrol/releases/latest/download/.env.example
curl -fsSLO https://raw.githubusercontent.com/CallMeTechie/gatecontrol/master/update.sh
chmod +x update.sh
cp .env.example .envWeiter mit §4 .env konfigurieren.
Für Hosts ohne Internetzugriff während des Installs das Image-Tarball aus einem Release laden:
curl -fsSLO https://github.com/CallMeTechie/gatecontrol/releases/latest/download/gatecontrol-image.tar.gz
docker load < gatecontrol-image.tar.gz
rm gatecontrol-image.tar.gzWeiter mit docker-compose.yml und .env.example aus Variante B.
Bearbeite /opt/gatecontrol/.env. Drei Werte sind Pflicht; alle anderen haben sinnvolle Defaults.
| Variable | Bedeutung | Beispiel |
|---|---|---|
GC_ADMIN_PASSWORD |
Anfangspasswort für den admin-Account. Wird nur beim ersten Start gelesen — spätere Änderungen laufen über das UI. |
R7!xK2#wPq9$Lm4v |
GC_WG_HOST |
Öffentliche IP oder Hostname, den VPN-Clients anrufen. Muss aus dem Internet auf UDP/51820 erreichbar sein. | gate.example.com oder 198.51.100.42 |
GC_BASE_URL |
Volle URL der Admin-UI. Caddy verwendet den Hostnamen für Let's-Encrypt-Zertifikat. | https://gate.example.com |
| Variable | Warum | Beispiel |
|---|---|---|
GC_CADDY_EMAIL |
Let's Encrypt kontaktiert dich bei Ablauf-Warnungen oder Problemen. Ohne funktioniert, aber du hast keinen Recovery-Kanal. | admin@example.com |
| Variable | Verhalten bei leer |
|---|---|
GC_SECRET |
Ein 48-Byte-Session-Secret wird beim ersten Start generiert und in /data/.session_secret (chmod 600) abgelegt. |
GC_ENCRYPTION_KEY |
Ein 32-Byte-AES-256-Key wird generiert und in /data/.encryption_key (chmod 600) abgelegt. Unbedingt sichern. Ein Restore der DB ohne passenden Key schlägt fehl. |
Die komplette Referenz mit WireGuard-Tuning, Rate-Limits, Timeouts, Client-Update-Repos und Lizenzkey steht in der .env.example im Repository.
cd /opt/gatecontrol
nano .env # oder vim oder ein anderer EditorDie drei Pflichtwerte setzen. Speichern und Editor verlassen.
cd /opt/gatecontrol
docker compose up -d
docker compose logs -fBeim ersten Lauf macht das Entrypoint eine Reihe von Dingen. Zu erwarten ist in etwa diese Reihenfolge:
» Auto-detected egress interface: <name>— das Netzwerk-Interface für VPN-NAT. Bei Fallback aufeth0ist das auf den meisten Cloud-VMs korrekt; Hosts mit ungewöhnlichen NIC-Namen werden automatisch erkannt.» MASQUERADE rule active: 10.8.0.0/24 → <iface>— iptables-NAT-Regel ist aktiv.» Generating WireGuard server keypair...— nur beim Erststart. Der Private-Key landet in./data/wireguard/wg0.confmitchmod 600.» Session secret generated and saved— nur beim Erststart.» Encryption key generated and saved— nur beim Erststart.» Generating dnsmasq config (split-horizon for <hostname> → 10.8.0.1)...— interner DNS.» Exporting Caddy JSON from DB...— auf frischer DB sind noch keine User-Routen vorhanden, aber die Management-UI-Route (GC_BASE_URL-Hostname →127.0.0.1:3000) wird automatisch injiziert. Du musst diese Route nicht manuell anlegen.» Starting services via supervisord...- Caddy bootet, holt sich das Let's-Encrypt-Zertifikat für den
GC_BASE_URL-Hostnamen. Der erste Cert-Fetch dauert 10–30 Sekunden. Achte auf:obtaining certificategefolgt voncertificate obtained successfully- Bei
lookup <hostname>: no such hostoderunable to fetch certificateist der DNS noch nicht propagiert — der ACME-Client retry automatisch.
- WireGuard startet, Interface
wg0kommt hoch. - Node.js-Webapp startet auf
127.0.0.1:3000. Caddy proxied Requests auf demGC_BASE_URL-Hostnamen dorthin.
Log-Tail mit Ctrl+C verlassen — der Container läuft im Hintergrund weiter.
Im Browser GC_BASE_URL öffnen, z.B. https://gate.example.com.
- Username:
admin(konfigurierbar viaGC_ADMIN_USER) - Passwort: Der Wert aus
GC_ADMIN_PASSWORD
Du musst keine Reverse-Proxy-Route für die Admin-UI selbst anlegen. GateControl liest GC_BASE_URL aus und injiziert eine Caddy-Route, die den Hostnamen auf den internen Node.js-Port mappt. Damit ist das Henne-Ei-Problem "Ich brauche die UI, um die UI zu konfigurieren" beim ersten Start gelöst.
- Admin-Passwort ändern — Einstellungen → Profil → Passwort ändern. Ab hier ist
GC_ADMIN_PASSWORDin der.envirrelevant; der Hash liegt in der DB. - SMTP konfigurieren (optional, aber nützlich) — Einstellungen → SMTP. Erforderlich für Route-Authentifizierung mit E-Mail-OTP, für E-Mail-Alerts und die Test-E-Mail-Funktion.
- E-Mail-Alerts konfigurieren (optional) — Einstellungen → E-Mail-Alerts. Auswählen, welche Event-Gruppen Benachrichtigungen auslösen sollen.
- Security-Einstellungen prüfen — Einstellungen → Security. Die Defaults (Passwort-Komplexität, Account-Lockout nach Fehlversuchen) sind sinnvoll; anpassen an die eigene Policy.
- Peers → Neuer Peer
- Namen vergeben (z.B.
laptop-alice). - GateControl generiert automatisch ein Keypair und weist eine IP aus
GC_WG_SUBNETzu (Default10.8.0.0/24, erster Peer erhält10.8.0.2). - Konfiguration herunterladen für eine
.conf-Datei oder den angezeigten QR-Code mit der WireGuard-Mobile-App scannen. - Der Peer zeigt sich als online, sobald der Client den ersten Handshake abgeschlossen hat (Status-Punkt in der Peer-Liste beobachten).
Typischer Fall: einen internen Dienst hinter dem VPN über HTTPS und eine öffentliche Domain bereitstellen.
- DNS-A-Record für
service.example.comanlegen, der auf die öffentliche IP des GateControl-Hosts zeigt. - Routen → Neue Route
- Domain:
service.example.com - Ziel: Peer aus dem Dropdown wählen (z.B.
laptop-alice); die IP wird automatisch eingetragen. Oder manuell eine IP setzen. - Ziel-Port: Der Port, auf dem der Dienst im Peer-Netzwerk lauscht (z.B.
80,8080,5001). - Backend HTTPS aktivieren, falls das Ziel ein Self-Signed-TLS nutzt (z.B. Synology DSM auf Port 5001).
- Speichern.
Innerhalb weniger Sekunden holt Caddy das Zertifikat und beginnt die Route auszuliefern.
cd /opt/gatecontrol
docker compose psErwartet:
NAME IMAGE STATUS PORTS
gatecontrol ghcr.io/callmetechie/gatecontrol:latest Up 2 minutes (healthy)
Das (healthy)-Tag bedeutet, Dockers interner Healthcheck gegen /health ist positiv.
Vom Host aus:
curl -s http://127.0.0.1:3000/health | jqErwartet:
{
"ok": true,
"version": "1.52.0",
"uptime": 42,
"db": true,
"wireguard": true,
"caddy": true
}Aus dem Internet (anonym):
curl -s https://gate.example.com/healthErwartet — anonymen Aufrufern werden keine internen Details geleakt:
{"ok":true}Eingeloggte Admins sehen die vollen Details auch im Browser: öffne GC_BASE_URL/health in derselben Registerkarte, in der du eingeloggt bist.
docker compose logs --tail 100Die mitgelieferte docker-compose.yml rotiert die Container-Logs von gatecontrol und guacd (json-file, 3 × 10 MB). Docker rotiert von sich aus nie; auf Hosts, die vor 1.126 installiert wurden, denselben logging:-Block bei beiden Diensten in der eigenen docker-compose.yml ergänzen und einmal docker compose up -d ausführen.
Nach dem Boot-Strap sollten keine level=error-Zeilen mehr auftauchen. Häufige Nicht-Fehler, die du ignorieren kannst:
dnsmasq warning: interface wg0 does not currently existbeim Start — dnsmasq kommt vor wg-quick hoch;bind-dynamicfängt das ab.storage cleaning happened too recently; skipping for now— Caddy-Self-Log bei jedem Start.
GC_ADMIN_PASSWORD in .env auf ein echtes Passwort setzen und docker compose up -d erneut ausführen. Das Entrypoint verweigert den Start bei Placeholder changeme mit Absicht.
Analog — GC_WG_HOST in .env setzen. Jeder Wert außer gate.example.com läuft durch.
Ein anderer Prozess auf dem Host belegt den DNS-Port, den der Container für seinen internen dnsmasq braucht. Konflikt identifizieren und beseitigen:
ss -lntup | grep ':53 'Häufige Verursacher:
- NetworkManager-dnsmasq —
systemctl disable --now NetworkManager(auf Headless-Servern). - libvirt-dnsmasq —
systemctl disable --now libvirtdoder libvirts Default-Netzwerk umkonfigurieren. - bind9 / named — stoppen oder GateControl auf einen anderen Host verschieben.
systemd-resolved (bindet 127.0.0.53) kollidiert nicht.
Häufigste Ursachen in dieser Reihenfolge:
- DNS noch nicht propagiert. Let's Encrypt muss den Hostnamen aus dem Internet zu diesem Host auflösen.
dig +short gate.example.commuss die öffentliche IP zurückliefern. Bis zu 30 Minuten nach Setzen des Records warten. - Port 80 nicht aus dem Internet erreichbar. ACME-HTTP-01-Challenges kommen auf Port 80. In der Cloud-Firewall freigeben.
- Let's-Encrypt-Rate-Limits erreicht. Bei vielen Neustarts in kurzer Zeit kann das Limit ca. 1 Stunde greifen. Caddy-Log auf
rateLimited-Antworten prüfen.
Caddy wiederholt automatisch mit exponentiellem Backoff — der Container muss nicht neu gestartet werden.
Drei Dinge in dieser Reihenfolge prüfen:
- Container noch beim Booten? Der erste Cert-Fetch braucht 10–30 Sekunden. Warten und neu laden.
- DNS zeigt auf den richtigen Host?
curl -v https://gate.example.com 2>&1 | grep -i "connected"— die IP in den Klammern muss die öffentliche IP dieses Hosts sein. - Jemand hat die Test-Suite gegen das Live-Admin-API laufen lassen? GateControl nutzt
network_mode: hostfür dynamisches L4-Port-Binding. Das Ausführen vonnpm testauf dem Host, während der Container läuft, hat früher die Live-Caddy-Config überschrieben. Seit v1.50.9 gefixt — ältere Versionen updaten, dann ist das Problem dauerhaft weg.
JSON-Antwort aus einem Localhost-Call lesen (curl -s http://127.0.0.1:3000/health). Welches der Felder db, wireguard, caddy false ist, sagt dir, was kaputt ist:
db: false— SQLite-Datei-Permissions falsch oder Platte voll.ls -la /opt/gatecontrol/data/gatecontrol.dbmuss Owner101:_sshzeigen (das ist der Container-Usergatecontrolaus Host-Sicht).wireguard: false—/sys/class/net/wg0fehlt.docker compose logsnach wg-quick-Fehlern durchsuchen.caddy: false— Caddy-Admin-API auf127.0.0.1:2019antwortet nicht.docker compose logsnach Caddy-Crashes durchsuchen.
Die GC_NET_INTERFACE-Autodetection wählt das Default-Route-Interface. Bei Hosts mit ungewöhnlichen Namen (z.B. Container-in-Container) kann das fehlschlagen und auf eth0 zurückfallen. GC_NET_INTERFACE explizit in .env setzen und neustarten:
ip route | awk '/^default/ {print $5; exit}' # echten Interface-Namen ermittelnAlles unter /opt/gatecontrol/ — insbesondere:
.env— deine Config (Passwörter, Domain).data/gatecontrol.db— die Datenbank (Peers, Routen, User, Sessions, Logs).data/.encryption_key— der AES-256-Key für verschlüsselte DB-Spalten. Ohne diesen Key ist die DB wertlos.data/.session_secret— Cookie-Signing-Key. Ein Verlust entwertet nur bestehende Sessions; nicht kritisch.data/wireguard/wg0.conf— WireGuard-Server-Private-Key. Wird bei Neuinstall neu erzeugt, bestehende Peers bräuchten dann neue Configs.data/caddy/— Zertifikate und Private-Keys. Let's Encrypt kann sie neu ausstellen, aber Rate-Limits bei häufigen Restores beachten.
BACKUP=/backup/gatecontrol-$(date +%F).tar.gz
tar czf "$BACKUP" -C /opt gatecontrol
chmod 600 "$BACKUP" # enthält Secrets — entsprechend schützenDas Archiv ist self-contained und kann die komplette Installation wiederherstellen. Verschlüsselt oder auf einem Access-kontrollierten Volume speichern.
Einstellungen → Backup → Full backup download. Liefert eine portable JSON-Datei mit Peers, Routen, Route-Auth-Configs, ACL-Regeln, Settings, Webhooks und verschlüsselten Keys. Wiederherstellen via Backup hochladen auf derselben Seite — funktioniert über Instanzen hinweg, solange der Encryption-Key identisch ist (oder dieselbe Instanz ist).
Das In-UI-Backup enthält keine Caddy-Zertifikate — die werden nach Restore automatisch neu ausgestellt.
Nach jedem automatischen Backup kann GateControl eine verschlüsselte Kopie per SFTP (Schlüssel-Anmeldung mit einem von GateControl erzeugten Schlüssel — dessen öffentlichen Schlüssel auf dem Ziel in ~/.ssh/authorized_keys eintragen), SMB, S3-kompatiblen Speicher oder WebDAV hochladen (Einstellungen → Backup). Die Dateien heißen gatecontrol-JJJJMMTT-HHmmss.gcbk; pro Ziel bleiben nur die neuesten n davon, andere Dateien werden nie angefasst. Sie sind mit deiner Passphrase verschlüsselt (scrypt + AES-256-GCM) und enthalten — solange du es nicht abschaltest — den Encryption-Key; Passphrase + Datei genügen also für eine Wiederherstellung auf neuer Hardware: die .gcbk unter Einstellungen → Backup → Wiederherstellen hochladen und die Passphrase eingeben. Ohne GateControl: node src/bin/offsite-decrypt.js <datei.gcbk> -o backup.json [-k encryption_key] (im Container: docker exec -it gatecontrol node /app/src/bin/offsite-decrypt.js …). Die Passphrase außerhalb des Servers aufbewahren — sie lässt sich nicht wiederherstellen. Ein NAS im LAN hinter einem Gateway ist über eine interne L4-Route (SSH- oder SMB-Port) erreichbar: als Ziel die VPN-Adresse des Servers (Standard 10.8.0.1) und den Listen-Port der Route eintragen.
update.sh ist ein Host-Helfer (nicht Teil des Container-Images) und hält den Server auf dem neuesten GHCR-Image. Aus dem Deploy-Verzeichnis ausführen:
cd /opt/gatecontrol
./update.shAblauf:
- Pullt
ghcr.io/callmetechie/gatecontrol:latest(Pull-Fehler = Abbruch). - Vergleicht den Image-Digest des laufenden Containers mit dem von
:latest— nicht dendocker pull-Output. (docker pullmeldet „Image is up to date", sobald:latestlokal schon vorliegt, der Container aber noch ein älteres Image fährt — das würde das Update sonst stillschweigend überspringen.) - Gleich → loggt
Already up to date, Exit 0 (kein Neustart). - Ungleich (oder kein laufender Container) →
docker compose up -d --force-recreate --wait, blockiert bis der Healthcheck grün ist. Ein kaputtes Image ist damit ein fehlgeschlagener Deploy (Exit 1), keine still ungesunde Instanz. - Loggt nach
/var/log/gatecontrol-update.logund stdout.
Aus dem Deploy-Verzeichnis ausführen (/opt/gatecontrol). Das Skript verweigert (Exit 3) den Recreate aus einem anderen Verzeichnis als dem, aus dem der Container deployt wurde: ein Lauf aus dem Source-Checkout würde dessen docker-compose.yml nutzen, ein anderes /data-Volume mounten und die Live-Datenbank überschreiben.
Host-Cron-Job (als root, auf das Deploy-Verzeichnis zeigend):
# /etc/cron.d/gatecontrol-update
*/5 * * * * root /opt/gatecontrol/update.sh
*/5 (alle 5 Minuten) ist für dieses Feature Pflicht, in beiden Modi — der Update-Status im Dashboard, die Aktualitätsprüfung und der „Update jetzt"-Trigger setzen alle voraus, dass das Skript mindestens alle 5 Minuten läuft. Ein Tagesintervall (z.B. 0 3 * * *) ist hier ungeeignet: Eine manuelle „Update jetzt"-Anforderung bliebe bis zu 24 Stunden unbearbeitet liegen, und das Dashboard zeigte dauerhaft einen falschen „veraltet"-Status. Ein systemd-Timer mit derselben 5-Minuten-Taktung funktioniert genauso.
Der Update-Modus wird in der Server-Karte Einstellungen → Auto-Update gewählt. Derselbe */5-Cron läuft in beiden Modi — nur was er pro Lauf tut, unterscheidet sich:
- Automatisch — jeder Cron-Lauf vergleicht die Digests und deployt, sobald
:latestvom laufenden Container abweicht. Der Server zieht jeden Release binnen Minuten nach, vollständig unbeaufsichtigt. - Manuell — jeder Cron-Lauf pollt nur das
pending-update-Flag, das der Button „Update jetzt" im Dashboard schreibt. Kein Flag → er tut nichts (kein Recreate, kein Änderungsrauschen). Beim Klick auf „Update jetzt" wird das Flag gesetzt und der nächste Cron-Lauf (binnen 5 Minuten) führt den Deploy aus. Genau deshalb ist*/5auch im Manuell-Modus Pflicht: Ein langsameres Intervall verzögert deinen manuellen Trigger um die volle Cron-Periode.
Automatisches Rollback: Vor jedem Recreate taggt update.sh das laufende Image als ghcr.io/callmetechie/gatecontrol:rollback. Wird das neue Image nicht innerhalb von GC_WAIT_TIMEOUT gesund, zeigt :latest lokal wieder auf dieses Image und der Container wird ohne Pull daraus neu erstellt (docker-compose.yml bleibt unverändert). Das Dashboard zeigt dann „Update … fehlgeschlagen — Vorversion wiederhergestellt", das Skript endet mit Exit 1. Die ID des fehlerhaften Images landet in data/.auto-update-bad-image; im Automatik-Modus wird dieses Image übersprungen, bis ein neueres :latest erscheint — ein kaputtes Release wird also nicht alle 5 Minuten erneut ausgerollt. Im Manuell-Modus versucht „Update jetzt" es bewusst noch einmal. Scheitert auch das Rollback, endet das Skript mit Exit 4, das Dashboard zeigt „Update und Rollback fehlgeschlagen" und du musst eingreifen (docker compose ps, docker compose logs gatecontrol). Der :rollback-Tag hält genau ein Vorgänger-Image auf der Platte; das Prune (nur ungetaggte Images) entfernt es nie.
Wartungsfenster (Automatik-Modus): Unter Einstellungen → Auto-Update lassen sich automatische Deploys auf ein Zeitfenster beschränken, z. B. 03:00–05:00 in Europe/Berlin (auch über Mitternacht, z. B. 23:00–02:00). Der Server schreibt es in data/.auto-update-config.json; außerhalb des Fensters zieht update.sh das neue Image zwar schon, protokolliert aber outside maintenance window und meldet den Status waiting_window — ausgerollt wird beim ersten Cron-Lauf im Fenster. „Update jetzt" rollt sofort aus, unabhängig vom Fenster. Die Zeitzone kommt aus den tzdata des Hosts (/usr/share/zoneinfo); fehlen sie, rechnet date in UTC und das Skript warnt im Log. Das Fenster braucht die update.sh ab GateControl 1.126 — ein älteres Skript ignoriert es und rollt weiter jederzeit aus.
Datenbank-Sicherung vor Migrationen: Muss eine neue Version die Datenbank migrieren, kopiert GateControl sie vorher nach data/backups/pre-migration/gatecontrol-v<von>-v<bis>-<Zeitstempel>.db (nur für den Besitzer lesbar, die letzten 3 bleiben; Download unter Einstellungen → Backup). Scheitert die Kopie (Platte voll, Rechte), startet der Container nicht und update.sh rollt auf das vorherige Image zurück. Notausgang: GC_SKIP_PRE_MIGRATION_BACKUP=1 in der .env.
Stellschrauben (Umgebungsvariablen): GC_IMAGE, GC_CONTAINER, GC_WAIT_TIMEOUT (Default 150 s), GC_UPDATE_LOG, COMPOSE_DIR.
Falls du update.sh bereits aus einer früheren GateControl-Version per Cron betreibst, musst du sie einmalig durch die aktuelle (modusbewusste) Version aus diesem Release ersetzen. Das ältere Skript kennt weder den Automatisch/Manuell-Modus noch das pending-update-Flag. Solange du es nicht austauschst, zeigt das Dashboard eine mode_mismatch-Warnung und der Manuell-Modus wird nicht wirksam — das veraltete Skript deployt (bzw. überspringt) weiter, unabhängig vom in den Einstellungen gewählten Modus. Kopiere die neue update.sh ins Deploy-Verzeichnis, dann verschwindet die Warnung beim nächsten Cron-Lauf. Dasselbe gilt für das Wartungsfenster (1.126): update.sh einmal ersetzen, sonst wirkt das Fenster nicht.
cd /opt/gatecontrol
docker compose pull
docker compose up -dDowntime liegt bei etwa 10–30 Sekunden während Container-Restart und Caddy-Reload des persistierten Zustands. Datenmigration ist nie nötig — Migrationen laufen automatisch beim Container-Start mit Per-Step-Commits, sodass ein gescheiterter Schritt keine erfolgreichen Schritte zurückrollt.
curl -s http://127.0.0.1:3000/health | jq .versionDer Versions-String muss zum gepullten Tag passen.
Falls du eine ältere GateControl-Installation mit einem Named Docker Volume hast (der historische Default), kannst du mit ca. 15 Sekunden Downtime auf das empfohlene Layout in /opt/gatecontrol/ umziehen. Named Volumes sind vom Host-Dateisystem aus unsichtbar und machen Backups umständlich; Bind-Mounts lösen das.
Schritte:
# 1. Quelle verifizieren
docker inspect gatecontrol --format '{{range .Mounts}}{{.Type}} {{.Source}}{{"\n"}}{{end}}'
# Zeigt "volume <pfad>" → weiter. Zeigt "bind <pfad>" → schon fertig.
# 2. Neue Location vorbereiten
mkdir -p /opt/gatecontrol
cp /pfad/zum/alten/.env /opt/gatecontrol/.env
cat > /opt/gatecontrol/docker-compose.yml <<'EOF'
services:
gatecontrol:
image: ghcr.io/callmetechie/gatecontrol:latest
container_name: gatecontrol
network_mode: host
cap_add:
- NET_ADMIN
volumes:
- ./data:/data
env_file:
- .env
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
EOF
# 3. Alt stoppen, Daten kopieren, Neu starten (kurze Downtime)
cd /pfad/zum/alten # wo das alte docker-compose.yml liegt
docker compose down
mkdir -p /opt/gatecontrol/data
VOL_PATH=$(docker volume inspect <alter-volume-name> --format '{{.Mountpoint}}')
cp -a "$VOL_PATH"/. /opt/gatecontrol/data/
chown -R 101:102 /opt/gatecontrol/data
cd /opt/gatecontrol
docker compose up -d
# 4. Verifizieren
docker inspect gatecontrol --format '{{range .Mounts}}{{.Type}} {{.Source}}{{"\n"}}{{end}}'
# sollte jetzt zeigen: bind /opt/gatecontrol/data
curl -s http://127.0.0.1:3000/health | jqDas alte Named Volume mindestens 24 Stunden als Fallback liegen lassen. Wenn du sicher bist, dass das neue Setup läuft:
docker volume rm <alter-volume-name>- Bug-Reports / Feature-Requests: GitHub Issues
- Security-Meldungen: siehe SECURITY.md
- Diskussionen: GitHub Discussions
Beim Öffnen eines Issues die Ausgabe von Folgendem beilegen:
docker compose ps
docker compose logs --tail 200
curl -s http://127.0.0.1:3000/healthund sensible Werte (Passwörter, Tokens, Private-Keys) vor dem Posten schwärzen.