// Tutorial · Docker · URL-Shortener

Gull: Der schlanke URL-Shortener in Go als Docker-Container

📅 08.09.2026 ⏱ 8 Min. Lesezeit ✍️ Redaktion

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.0 und 2G RAM 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.

Port frei wählen: 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.

Nicht anders herum: Öffne Gull niemals direkt auf 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.

Restore üben: Ein Backup, das nie getestet wurde, ist kein Backup. Spiele den Gull-Datenordner einmal bewusst in ein Testverzeichnis zurück und prüfe, ob deine Links wieder auftauchen.

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-privileges und cap_drop ergänzen – das Compose-Grundgerüst der Serie gilt hier genauso:
services:
  gull:
    # ... Konfiguration von oben ...
    security_opt:
      - no-new-privileges=true
    cap_drop:
      - ALL
Vor dem Härten testen: Gull ist ein Go-Binary ohne besondere Kernel-Anforderungen – 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

ProblemLösung
Gull ist von außen nicht erreichbarErwartetes 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 ProxyContainer 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 belegtHost-Port in der Compose-Datei ändern und die neue Zuordnung in der Dockerliste notieren: docker compose up -d.
Dateien im Datenordner gehören rootUSER_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 wegLiegt 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äuftImage-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.

Die 4 Merksätze: ① Gull ist ein Go-Binary in einem Container – kein schweres System ② Nur /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
📝
HuuuHosting-Redaktion

Wir betreiben unsere komplette Infrastruktur selbst auf Open-Source-Software und schreiben nur über Tools, die wir im Alltag wirklich einsetzen. Fragen zum Artikel? Schreib uns.