Gull: Der schlanke URL-Shortener in Go als Docker-Container
Lange Links kürzen – auf der eigenen Domain, ohne Tracking und ohne schweren Dienst. Gull ist ein bewusst minimalistischer URL-Shortener in Go: ein einziger Container, ein Datenordner, keine unnötigen Abhängigkeiten. In diesem Artikel richtest du ihn mit Docker Compose ein, hängst ihn an deinen Reverse Proxy und integrierst Backups und Updates aus der Debian-13-Serie.
Was ist Gull?
Gull ist ein einfacher, moderner URL-Shortener, komplett in Go geschrieben – inspiriert vom ebenfalls quelloffenen Projekt mnml. Statt einer monströsen Suite bekommst du ein einzelnes, schlankes Image (ghcr.io/aeolyus/gull), das seine Daten in einem einzigen Ordner (/data) ablegt. Die Oberfläche ist auf das Nötigste reduziert: eine lange URL eintragen, optional einen eigenen Alias vergeben, fertig.
- Minimalistisch: Kein PHP, keine Datenbank, keine Node-Runtime – ein Go-Binary macht die Arbeit.
- Eigene Aliasse: Im Web-Interface kannst du für jeden Link einen eigenen kurzen Namen vergeben.
- Schlank im Betrieb: Der Container braucht kaum Ressourcen – die Limits aus unserer Compose-Datei sind reine Vorsorge.
- Ehrlich quelloffen: Der Code ist komplett einsehbar, bewusst klein gehalten und leicht prüfbar.
Für wen ist Gull? Für alle, die einen schnellen Shortener für die eigene Domain suchen – ob für Blog-Posts, Statusseiten oder teilbare Links im Chat – und dabei keine Tracking- oder Analyse-Funktionen brauchen.
Voraussetzungen
Wir setzen den Artikel auf dem bekannten Fundament der Debian-13-Serie auf – die Schritte daraus sind hier bereits erledigt:
- Debian-13-Server mit gehärtetem SSH-Zugang (Teil 1 + 2 der Serie)
- Firewall mit nftables/UFW – von außen erreichbar sind nur die Proxy-Ports 80/443 (Teil 3)
- Docker & Docker Compose als normale Dienste (Teil 5)
- Optional: ein laufender Reverse Proxy (Nginx + acme.sh oder Traefik), der Subdomains auf lokale Ports weiterleitet
Alle Pfade legen wir wie gewohnt unter /opt/stacks/ ab – das erleichtert Backups und Wiederherstellung ungemein.
Installation mit Docker Compose
Zuerst legen wir das Stack-Verzeichnis samt Datenordner an – der gehört deinem Benutzer, nicht root:
sudo mkdir -p /opt/stacks/gull/gull-data
sudo chown -R $USER:$USER /opt/stacks/gull
Die docker-compose.yml ist bewusst simpel gehalten. Der Container lauscht intern auf Port 8081; veröffentlicht wird er nur auf 127.0.0.1:50525 – also ausschließlich lokal, ohne den Port ins Netz oder gar ins Internet zu öffnen:
services:
gull:
container_name: gull
volumes:
- ./gull-data/:/data/
network_mode: bridge
ports:
- 127.0.0.1:50525:8081
image: ghcr.io/aeolyus/gull:latest
deploy:
resources:
limits:
cpus: "2.0"
memory: 2G
environment:
- USER_UID=1000
- USER_GID=1000
restart: unless-stopped
networks: {}
Die wichtigsten Bausteine kurz erklärt:
- Volume
./gull-data/:/data/: Alle erzeugten Links liegen persistent im Datenordner auf dem Host – nur dieser Ordner muss gesichert werden. - Bind auf
127.0.0.1:50525: Der Dienst ist von außen nicht direkt erreichbar. Der Reverse Proxy (nächster Abschnitt) ist die einzige Tür nach draußen. USER_UID/USER_GID: Übergibt deine Host-Benutzerkennung an den Container, damit die Dateien im Datenordner dir gehören und nicht root.- Ressourcenlimits &
restart:cpus: 2.0und2GRAM sind großzügige Obergrenzen; der Neustart-Mechanismus zieht den Dienst nach einem Crash oder Reboot automatisch hoch.
# Stack starten und Status prüfen
docker compose up -d
docker compose ps
docker logs gull
Der erste Start dauert nur Sekunden. Danach sollte der lokale Test sofort antworten:
curl -I http://127.0.0.1:50525/
# HTTP/1.1 200 OK
Öffnest du http://127.0.0.1:50525 direkt auf dem Server (z. B. per SSH-Tunnel), siehst du das Gull-Interface mit dem URL-Feld und dem optionalen Alias-Feld.
50525 ist nur ein Beispiel aus unserem Schema. Wichtig ist, dass der Host-Port nicht mit anderen Diensten kollidiert – die Zuordnung findest du in der Dockerliste.Reverse Proxy: Gull nach außen bringen
Weil Gull nur auf 127.0.0.1 lauscht, braucht es einen Reverse Proxy, der die Subdomain nach außen trägt. Im Nginx-Beispiel (aus dem Artikel zu nginx/acme.sh) sieht das Server-Blöckchen so aus:
server {
listen 80;
server_name meine.domain;
location / {
proxy_pass http://127.0.0.1:50525;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Danach Konfiguration testen und neu laden – der sichere Weg aus der Hardening-Serie, ohne die Verbindung zu kappen:
sudo nginx -t
sudo systemctl reload nginx
Fertig: Unter https://meine.domain (mit dem TLS-Zertifikat aus dem acme.sh-Workflow) erreichst du jetzt deinen eigenen Shortener.
0.0.0.0:50525 oder per Portfreigabe in der Firewall. Der Container ist bewusst nur lokal erreichbar – nach außen gehört ausschließlich der Proxy mit TLS.Betrieb, Updates & Backups
Gull ist wartungsarm – trotzdem gelten die Regeln aus der Serie: Updates fahren und Backups testen.
Updates
Von Hand geht der Update-Weg wie immer über Pull und Recreate – ohne Ausfallzeit für den Proxy, da Gull nur kurz neu startet:
cd /opt/stacks/gull
docker compose pull
docker compose up -d
docker image prune -f
Wer automatische Updates mag, nimmt Watchtower aus Teil 5 hinzu – der erledigt genau diese drei Schritte regelmäßig.
Backup mit Borg (aus Teil 4)
Zu sichern ist nur der Stack-Ordner /opt/stacks/gull mit der Compose-Datei und dem Datenordner. Das Backup-Skript nutzt das ausfallsichere Muster aus der Serie: docker compose stop statt down, dynamisches Datum im Archivnamen und ein garantierter Neustart per trap:
#!/bin/sh
cd /opt/stacks/gull || exit 1
# Dienst anhalten – Container & Netzwerke bleiben erhalten
docker compose stop
# Notbremse: Egal ob das Backup klappt oder nicht – Gull startet wieder.
trap 'docker compose start' EXIT
# Borg-Archiv mit dynamischem Datum – keine Kollisionen am Folgetag
borg create --stats /mnt/backup/borg-repo::gull-$(date +%Y-%m-%d) /opt/stacks/gull
borg prune --keep-daily 7 --keep-weekly 4 --keep-monthly 6
Die Wiederherstellung ist simpel: Ordner aus dem Borg-Archiv zurückspielen, docker compose up -d ausführen, fertig. Wer nach der Rootless-Umstellung aus Teil 6 arbeitet, setzt vorher die passende DOCKER_HOST-Variable oder nutzt einen User-Timer.
Sicherheits-Check
Gull ist ein kleiner Dienst mit kleinem Angriffsgesicht – dennoch prüfen wir die üblichen Punkte:
- Kein öffentlicher Port: Bind nur auf
127.0.0.1, Zugriff ausschließlich über den Proxy mit TLS. - Datei-Ownership: Die Daten liegen in
gull-data/und gehören deinem Benutzer – nicht root. - Grundgerüst aus Teil 5: Auch bei Gull lassen sich
no-new-privilegesundcap_dropergänzen – das Compose-Grundgerüst der Serie gilt hier genauso:
services:
gull:
# ... Konfiguration von oben ...
security_opt:
- no-new-privileges=true
cap_drop:
- ALL
cap_drop: ALL funktioniert in der Regel problemlos. Trotzdem gilt: Nach jeder Änderung docker compose up -d und einmal curl -I http://127.0.0.1:50525/ testen, bevor der Proxy dranhängt.Häufige Probleme
| Problem | Lösung |
|---|---|
| Gull ist von außen nicht erreichbar | Erwartetes Verhalten – Gull lauscht nur auf 127.0.0.1:50525. Den Reverse Proxy prüfen: systemctl status nginx, nginx -t, dann systemctl reload nginx. |
| 502 Bad Gateway am Proxy | Container läuft? docker compose ps und docker logs gull prüfen. Häufige Ursache: Compose-Datei geändert, aber Stack nicht neu erstellt – docker compose up -d. |
| Port 50525 ist bereits belegt | Host-Port in der Compose-Datei ändern und die neue Zuordnung in der Dockerliste notieren: docker compose up -d. |
| Dateien im Datenordner gehören root | USER_UID/USER_GID auf deine Benutzerkennung prüfen (id) und ggf. korrigieren; bestehende Dateien mit sudo chown -R $USER:$USER gull-data/ übernehmen. |
| Alle Links sind nach einem Restore weg | Liegt der Datenordner an der richtigen Stelle (/opt/stacks/gull/gull-data)? Beim Restore kompletten Stack-Ordner zurückspielen, dann docker compose up -d. |
| Update ändert nichts / alte Version läuft | Image-Tag :latest cached – nach docker compose pull unbedingt docker compose up -d ausführen, damit der Container neu erstellt wird. |
Fazit
Gull zeigt, wie Selbsthosting im Idealfall aussieht: ein winziges, ehrliches Projekt, das eine einzige Aufgabe richtig macht. In zehn Minuten steht der eigene Shortener – ohne Tracking, ohne externe Abhängigkeiten, mit vollem Zugriff auf die eigenen Links. Zusammen mit dem Reverse Proxy, Watchtower und dem Borg-Backup aus der Serie ist Gull ein wartungsarmer Dauerläufer.
/opt/stacks/gull sichern, Restore üben ③ Der Dienst gehört hinter den Proxy: 127.0.0.1-Bind, nie 0.0.0.0 ④ Härtung aus Teil 5 (no-new-privileges, cap_drop) funktioniert auch hier