Zum Inhalt springen

[ Anolilab — Artikel / Self-Hosting ]

Docker-App mit HTTPS auf einem eigenen Server: Caddy und Docker Compose

Schritt für Schritt vom frischen Ubuntu-Server zur eigenen App unter deiner Domain, mit automatischem HTTPS, abgesichertem SSH und Backups.

Artikel von
Daniel Bannert · Gründer · Anolilab

Kurz gesagt

Ein kleiner Server mit 1 vCPU und 1 bis 2 GB RAM reicht für eine kleine App. Docker Compose startet die App und den Webserver Caddy davor. Caddy holt das HTTPS-Zertifikat selbst und erneuert es automatisch, du brauchst keinen certbot-Cronjob. Wichtig: Nur Caddy veröffentlicht Ports, die App nicht.

Diese Anleitung bringt eine App, die schon in Docker läuft, auf einen frischen Ubuntu-Server (24.04 oder 26.04 LTS) und unter deine eigene Domain, mit gültigem HTTPS-Zertifikat.

Was du brauchst

  • Einen Server (VPS) mit 1 vCPU und 1 bis 2 GB RAM für eine kleine App. Jeder Anbieter funktioniert; welcher zu dir passt, steht im VPS-Anbieter-Vergleich.
  • Eine Domain, deren DNS-Einträge du bearbeiten kannst.
  • Eine App, die in Docker läuft und auf einem Port lauscht (hier 3000).

Schritt für Schritt

  1. Domain auf den Server zeigen lassen

    Lege einen A-Eintrag für app.deine-firma.de mit der IPv4-Adresse des Servers an, bei IPv6 zusätzlich einen AAAA-Eintrag. Mach das zuerst: Caddy bekommt erst ein Zertifikat, wenn die Domain auf den Server zeigt. Prüfen mit dig +short app.deine-firma.de.

  2. Server absichern

    Eigenen Benutzer anlegen, SSH-Login nur noch mit Schlüssel, Firewall auf die nötigen Ports beschränken.
  3. Docker installieren

    Über das offizielle Paket-Repository von Docker, danach docker compose version prüfen.
  4. Compose-Datei und Caddyfile anlegen

    Die App ohne veröffentlichte Ports, Caddy davor auf Port 80 und 443.
  5. Starten

    docker compose up -d und im Caddy-Log verfolgen, wie das Zertifikat ausgestellt wird.

Server absichern

Melde dich einmal als root an, lege einen normalen Benutzer an und gib ihm deinen SSH-Schlüssel:

Terminal
adduser deploy
usermod -aG sudo deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy

Dann Passwort- und root-Login abschalten. Ubuntu liest zuerst die Dateien in /etc/ssh/sshd_config.d/, und bei den meisten Einstellungen gilt der erste gefundene Wert. Eine Datei mit 00- vorne gewinnt deshalb auch gegen Vorgaben, die manche Anbieter-Images dort ablegen:

Terminal
printf "PasswordAuthentication no\nPermitRootLogin no\n" | sudo tee /etc/ssh/sshd_config.d/00-hardening.conf
sudo sshd -t
sudo systemctl restart ssh.service

Lass die aktuelle Sitzung offen und teste den Login als deploy in einem zweiten Terminal, bevor du dich abmeldest. Sonst sperrst du dich bei einem Tippfehler aus.

Nur die nötigen Ports öffnen:

Terminal
sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp
sudo ufw enable

Docker umgeht ufw

Jeder Port, den du in Compose mit ports: veröffentlichst, ist aus dem Internet erreichbar, auch wenn ufw ihn blockiert. Docker leitet den Verkehr an den ufw-Regeln vorbei (Docker-Dokumentation). Deshalb veröffentlicht unten nur Caddy Ports, die App nicht.

Docker installieren

Installiere Docker über das offizielle Paket-Repository, wie in der Docker-Anleitung für Ubuntu beschrieben. Das bequeme Skript von get.docker.com empfiehlt Docker selbst nur für Test- und Entwicklungsumgebungen, nicht für Produktion. Danach:

Terminal
sudo usermod -aG docker deploy   # danach ab- und wieder anmelden
docker compose version

Die Gruppe docker gibt dem Benutzer faktisch root-Rechte. Füge nur Benutzer hinzu, denen du das zutraust.

Die Compose-Datei

Lege ~/app/compose.yaml an:

compose.yaml
services:
    app:
        image: ghcr.io/deine-firma/deine-app:1.0.0 # oder build: .
        restart: unless-stopped
        environment:
            - NODE_ENV=production
        # absichtlich kein "ports:": nur Caddy spricht mit der App

    caddy:
        image: caddy:2
        restart: unless-stopped
        ports:
            - "80:80"
            - "443:443"
            - "443:443/udp" # HTTP/3
        volumes:
            - ./conf:/etc/caddy
            - caddy_data:/data
            - caddy_config:/config

volumes:
    caddy_data:
    caddy_config:

Und ~/app/conf/Caddyfile:

conf/Caddyfile
app.deine-firma.de {
    encode
    reverse_proxy app:3000
}

app ist der Name des Compose-Dienstes; Dockers internes DNS löst ihn auf. encode ohne Angaben komprimiert mit zstd und gzip. Caddy bekommt den Ordner conf statt der einzelnen Datei, weil das Caddy-Image davon abrät, das Caddyfile direkt einzubinden: Manche Editoren ersetzen beim Speichern die Datei, und der Container sieht die Änderung dann nicht.

Starten

Terminal
cd ~/app
docker compose up -d
docker compose logs -f caddy

Kurz darauf meldet das Caddy-Log, dass das Zertifikat ausgestellt wurde. Öffne https://app.deine-firma.de, und die App ist online.

Das Volume caddy_data enthält deine Zertifikate und Schlüssel. Lösch es nicht: Sonst muss Caddy alles neu anfordern, und du kannst an die Rate Limits von Let's Encrypt stoßen.

Updates ausrollen

Terminal
docker compose pull app && docker compose up -d app
docker image prune -f

Für ein Deployment bei jedem Push kann ein GitHub-Actions-Job per SSH auf den Server gehen und dieselben zwei Befehle ausführen.

Backups

Der Server ist jetzt die einzige Kopie von allem, was deine App schreibt. Mindestens:

  • Daten in ein benanntes Volume oder einen Bind-Mount legen, nie nur in den Container.
  • Die Datenbank regelmäßig sichern, bei Postgres zum Beispiel mit docker compose exec -T db pg_dump -U postgres app > backup.sql.
  • Sicherungen vom Server wegkopieren, etwa mit restic auf S3-kompatiblen Speicher. Die meisten Anbieter verkaufen außerdem automatische Server-Backups oder Snapshots gegen Aufpreis; schalte sie ein.

Fehlersuche

SymptomWahrscheinliche Ursache
Caddy-Log: no such host oder die ACME-Prüfung schlägt fehlDNS zeigt noch nicht auf den Server, oder Port 80 ist in der Firewall des Anbieters gesperrt
502 Bad GatewayDie App lauscht im Container nur auf localhost statt auf 0.0.0.0:3000
HTTP geht, HTTPS nichtPort 443 ist in der Cloud-Firewall des Anbieters nicht offen (die ist unabhängig von ufw)

Wie es weitergeht

Wenn du lieber auf „Deploy“ klickst, als Compose-Dateien zu schreiben, bringt dir Coolify eine Oberfläche wie bei Heroku oder Vercel auf denselben Server. Und wie du nach demselben Muster eine Automatisierungsplattform betreibst, zeigt n8n selbst hosten.

[ Anolilab — Blog ]

Mehr Praxis zu KI, Webentwicklung und Infrastruktur.

Weiterlesen.

[ Anolilab — Kontakt ]

Erzähl mir, was du baust. Ich sage dir, wie ich helfen kann.

Lass uns reden.

Schnelle Antwort.
Wenn du bereit bist, gemeinsam etwas zu bauen, freue ich mich, von dir zu hören.
Klare nächste Schritte.
Nach der Beratung erstelle ich dir einen detaillierten Plan.
Alternativ kannst du mir direkt eine E-Mail senden an hello@anolilab.com