Deployment - vom lokalen Setup zum produktiven System
heat-conduction-app: Referenzprojekt für moderne Backend- und Webarchitektur
Deployment Containerisierung Docker Reverse Proxy Let's Encrypty Backend heat-conduction-app
← Zurück zur Übersicht | ← Meilenstein 4 - Datenbankintegration
Einleitung
Im vorangegangenen Artikel zur Datenbankintegration (Meilenstein 4) wurde die Persistenzschicht der heat-conduction-app umgesetzt. Kern ist eine SQLite-Datenbank mit Repository-Schicht zur gekapselten Anbindung. Materialien werden aus der Datenbank geladen und in der Oberfläche über ein Dropdown zur Auswahl bereitgestellt. Damit sind Backend-Logik, UI und Persistenz der ersten Phase vollständig integriert.
In diesem Meilenstein geht um das Deployment der Anwendung. Ich zeige, wie ich die Flask-Anwendung containerisiert mit Docker und Nginx zunächst lokal betreibe und das Setup anschließend auf einen VPS übertrage, inklusive HTTPS, Reverse Proxy und grundlegender Absicherung.
Ziel ist kein vollständiges Produktionssetup, sondern eine pragmatische, reproduzierbare Lösung für kleine bis mittlere Anwendungen.
Projektkontext
Dieser Artikel ist Teil zur Serie zur heat-conduction-app. Der aktuelle Stand ist im Repository verfügbar, die Anwendung läuft produktiv unter einer eigenen Subdomain.
- Deployment (dieser Artikel)
- Datenbankintegration (M4)
- Erste UI-Demo (M3)
- Erste Berechnung läuft (M2)
- Projektgerüst (M1)
Einführung und Hintergrund?
Einleitung
lesen
Architektur und Deployment-Strategie
Vor der Umsetzung kläre ich die grundlegenden Architektur- und Deploymententscheidungen.
Die klassische Variante ist die direkte Installation der benötigten Dienste auf einem Server oder VPS. Für eine Flask-Anwendung ergibt sich typischerweise ein Setup wie:
Nginx → Supervisor → Gunicorn → Flask
Diese Lösung ist vergleichsweise einfach aufzusetzen und direkt zugänglich, da alle Komponenten über den Paketmanager installiert werden. Debugging erfolgt unmittelbar auf dem System.
Ich entscheide mich hier stattdessen für ein containerisiertes Deployment. Alle Dienste laufen isoliert in Containern innerhalb von Docker. Das erhöht zunächst die Komplexität, bietet aber Vorteile. Die Umgebung ist reproduzierbar, bietet versionierbare Konfigurationen und eine saubere Trennung der Komponenten. Änderungen lassen sich gezielt und kontrolliert ausrollen.
Die Orchestrierung erfolgt über eine zentrale
docker-compose.yml. Damit wird nicht nur die
Anwendung, sondern die gesamte Laufzeitumgebung konsistent
beschrieben.
Der Einsatz eines Reverse-Proxys ist eine bewusste Architekturentscheidung. Alternativ könnte Gunicorn direkt Anfragen entgegennehmen, jedoch übernimmt der Reverse Proxy zentrale Aufgaben. Dazu gehören, das Routing eingehender Requests, die Terminierung von HTTPS und die Trennung zwischen öffentlichem Zugang und internen Services. Weitere Funktionen wie Lastverteilung oder zusätzliche Sicherheitsmechanismen sind damit vorbereitet.
Damit ergibt sich das folgende Zielbild:
[ Client ] Intern/Browser
↓
[ Nginx (Reverse Proxy, HTTPS)]
↓
[ Gunicorn / Flask ]
↓
[ SQLite (Volume)]
Die Umsetzung erfolgt in zwei Schritten. Zunächst ein lokales Setup als reproduzierbare Basis und zur Validierung der Konfiguration. Anschließend wird die Anwendung auf einem VPS unter produktionsnahen Bedingungen betrieben. Dabei stehen HTTPSm Domain-Anbindung und grundlegende Sicherheitsaspekte im Fokus.
Lokales Setup / Deployment (Docker + nginx)
Beim lokalen Deployment wird das containerisierte Setup bereits weitgehend umgesetzt. Ziel ist eine reproduzierbare Basis, die sich später auf den VPS übertragen lässt. HTTPS mittels Let's Encrypt fehlt an dieser Stelle noch und wird im nächsten Abschnitt ergänzt.
Ausgangspunkt ist der aktuelle Projektstand aus dem Repository.
Nach dem Klonen werden die Abhängigkeiten aktualisiert und eine
.env-datei mit den notwendigen Umgebungsvariablen
angelegt (SECRET_KEY,
DATABASE_URI etc.). Damit ist die Anwendung lokal
mit dem Flask-Development-Server lauffähig.
Für den Containerbetrieb kommen drei zusätzliche Bausteine
hinzu, ein Dockerfile, Reverse Proxy Konfiguration
(nginx) und eine zentrale docker-compose.yml.
Dockerisierung
Das Dockerfile beschreibt, wie aus der
Flask-Anwendung ein lauffähiges Image entsteht. Dieses Image
dient als unveränderliche Basis für den späteren Container und
enthält die Anwendung, Abhängigkeiten und Laufzeitumgebung.
Ein mögliches Dockerfile für die heat-conduction-app sieht wie folgt aus:
FROM python:3.11-alpine
RUN apk add --no-cache sqlite
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt gunicorn
COPY . .
RUN mkdir -p instance
COPY entrypoint.sh ./
RUN chmod +x entrypoint.sh
RUN adduser -D flaskuser
RUN chown -R flaskuser:flaskuser /app
USER flaskuser
EXPOSE 8000
ENTRYPOINT [ "./entrypoint.sh" ]
Drei Punkte möchte ich hervorheben:
-
Basis-Image:
python:3.11-alpineals schlanke, ausreichend vollständige Grundlage -
Kapselung der Anwendung: Quellcode wird ins Image kopiert,
nicht benötigte Dateien über
.dockerignoreausgeschlossen -
Startlogik: Auslagerung in ein Shellskript
entrypoint.sh
Das Shellskript übernimmt initiale Schritte vor dem Start der Anwendung:
- Datenbankmigration
- Initiale Daten (Seeding)
- Vorbereitung von Ressourcen (z.B. Übersetzungen)
- Start von Gunicorn
Damit ist sichergestellt, dass der Container in einem
definierten Zustand startet. Die Anwendung selbst läuft hinter
Gunicorn und ist innerhalb des Containers auf Port
8000 erreichbar.
Reverse Proxy mit nginx
Vor die Flask-Anwendung wird ein Reverse Proxy geschaltet. Zum Einsatz kommt nginx als schlanker Webserver mit Proxy-Funktionalität, seine zentrale Rolle ist:
- Annahme aller externen Requests über Port 80/443
- Weiterleitung an interne Services (hier: Flask/Gunicorn)
- Trennung von öffentlichem Zugriff und interner Service-Kommunikation
- Zentrale Verwaltung von HTTPS (TLS-Termination)
Innerhalb des Docker-Netzwerks kommunizieren die Container unverschlüsselt. Die TLS-Verschlüsselung endet am Proxy, wodurch die Anwendung selbst keine Zertifikate verwalten muss.
Für das lokale Setup werden selbstsignierte Zertifikate verwendet. Diese sind für den Browser nicht vertrauenswürdig, reichen aber aus, um HTTPS-Verhalten und Proxy-Konfiguration im lokalen Deployment zu testen.
Auch nginx läuft als Container. Damit ist die gesamte Infrastruktur, Anwendung und Webserver, als Code definiert und reproduzierbar.
Infrastruktur Orchestrierung - docker-compose
Die einzelnen Container werden nicht isoliert gestartet, sondern
über docker compose orchestriert. Ziel ist eine
deklarative Beschreibung aller Services und ihrer Abhängigkeiten
in einer zentralen Konfigurationsdatei.
services:
nginx:
image: nginx:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./nginx/certs:/etc/nginx/certs
heat-conduction-app:
build: ./services/heat-conduction-app
restart: always
env_file:
- .env
volumes:
- heat-instance:/app/instance
expose:
- "8000"
volumes:
heat-instance:
Das minimale Setup besteht aus zwei Services.
-
nginx ist öffentlich erreichbar über Port
80/443und bindet Konfigurations- und Zertifikatsverzeichnisse mit ein. -
heat-conduction-app wird aus dem lokalen
Dockerfile gebaut, erhält Umgebungsvariablen aus
.envund ist intern über Port8000erreichbar.
Wichtige Aspekte:
- Netzwerk: Beide Container laufen im gleichen Docker-Netzwerk und können sich über ihre Servicenamen erreichen.
- Isolation: Nur nginx ist von aussen erreichbar, die heat-conduction-app bleibt intern.
- Persistenz: Daten (z.B. SQlite, Konfigurationen, Zertifikate) werden über Volumes ausserhalb des Containers gespeichert.
Der Compose-Ansatz ersetzt komplexe
docker run-Aufrufe durch eine versionierbare und
reproduzierbare Konfiguration.
(finale) Projektstruktur
Das Ergebnis ist eine Projektstruktur, die klar zwischen Infrastruktur und Anwendung trennt und sich einfach erweitern lässt, z.B. um weitere Services.
project-server
├── docker-compose.yml → zentrale Steuerung
├── nginx → Proxy-Konfiguration und Zertifikate
│ ├── conf.d
│ └── certs
└── services → Anwendungscontainer
└── heat-conduction-app
Ergebnis
Nach dem Build und Start über
docker compose up -d --build läuft die Anwendung
vollständig containerisiert. Nginx nimmt Requests entgegen und
leitet sie über Gunicron and die Flask-Anwendung weiter. Die
Datenhaltung geschieht über ein persistentes Volume. Das lokale
Setup bildet eine stabile, reproduzierbare Grundlage für das
spätere Deployment auf einem VPS.
Produktion (VPS + HTTPS + Security)
Im zweiten Schritt wird die Anwendung auf einem öffentlich erreichbaren VPS betrieben und unter einer eigenen Domain bereitgestellt. Das lokale Setup dient dabei als Grundlage, wird jedoch um zwei Aspekte erweitert, das gültige HTTPS-Zertifikat und grundlegende Absicherung des Systems.
Ziel ist kein vollständig gehärtetes Produktionssystem, sondern eine stabile, nachvollziehbare Umgebung für den Betrieb containerisierter Anwendungen.
Minimales Setup (Server, DNS, Docker)
Als Laufzeitumgebung dient ein einfacher VPS bei einem deutschen Hoster. Für die Anwendung ist kein dezidierter Server notwendig, ein Einstiegsmodell ist ausreichend.
Die Basis bildet ein minimal gehärtetes System:
- SSH-Zugang absichern (kein Root-Login, Key-basierte Authentifizierung)
- Firewall aktivieren (Freigabe SSH-Port und Port 80, 443)
- optional: fail2ban zur automatischen Sperrung auffälliger IPs
Diese Maßnahmen reduzieren die Angriffsfläche, ohne den Fokus auf Infrastruktur zu verschieben.
Für die Erreichbarkeit wird eine Subdomain auf die öffentliche IP des VPS gelegt. Die Zuordnung erfolgt über einen DNS-Eintrag beim Domain-Registrar. Sobald die Domain korrekt auflöst, ist der Server unter dieser Adresse erreichbar. Dies lässt sich mit
$ dig heat-conduction.app.meshing-bytes.de
testen.
Als Laufzeitumgebung wird Docker installiert. Die Container entsprechen dem lokalen Setup und können übernommen werden. Ziel ist eine konsistente Umgebung zwischen Entwicklung und Produktion.
HTTPS mit Let's Encrypt (Prinzip + Umsetzung)
Für den öffentlichen Betrieb ist ein gültiges HTTPS-Zertifikat
erforderlich. Zum Einsatz kommt Let's Encrypt in Kombination mit
certbot.
Das zugrundeliegende Prinzip ist die ACME-Challenge (Automatic Certificate Management Environment):
-
certbotfordert ein Zertifikat für eine Domain an - Let`s Encrypt ptüft über HTTP, ob die Domain auf den Server zeigt
-
nginxstellt eine temporäre Challenge-Datei bereit - bei erfolgreicher Prüfung wird das Zertifikat ausgestellt
Vereinfachter Ablauf:
Certbot → fordert Zertifikat an
↓
Let's Encrypt → prüft Domain über HTTP
↓
nginx → liefert Challenge aus
↓
Certbot → speichert Zertifikat
↓
nginx → nutzt Zertifikat
Die Integration erfolgt ebenfalls über
docker compose. Neben nginx und
Anwendung wird ein zusätzlicher certbot-Service
definiert. Zertifikate und Challenge-Dateien werden über
gemeinsame Volumes zwischen nginx und
certbot bereitgestellt.
Für die initiale Zertifikatserstellung wird
nginx zunächst ohne HTTPS gestartet, sodass ACME
-Challenge erreichbar ist. Anschließend erzeugt
certbot einmalig das Zertifikat. Danach wird
nginx auf HTTPS umgestellt und leitet Requests an
die Anwendung weiter.
Wesentliche Punkte:
- HTTPS wird ausschließlich im Reverse Proxy terminiert
- die Anwendung selbst bleibt unverändert
-
Zertifikate liegen zentral und werden von
nginxgenutzt
Zertifikate sind zeitlich begrenzt gültig und müssen regelmäßig
erneuert werden. Das Renewal erfolgt automatisert, hier verwende
ich einen täglichen Cronjob, der
certbot renew ausführt und nginx neu lädt. An
dieser Stelle eine pragmatische Lösung, certbot ist
in docker-compose integriert, das Renewal erfolgt
jedoch als einfacher externer Prozess.
Betrieb (Security, Logging)
Mit der öffentlichen Erreichbarkeit verschiebt sich der Fokus auf Betrieb und Beobachtbarkeit, mit den Themen Security und Logging.
Security
Neben Netzwerk (Firewall) und Zugriff (SSH) habe ich noch folgende Maßnahmen auf Proxy- und Anwendungsebene umgesetzt:
- HTTPS only (keine unverschlüsselten Zugriffe)
- Security Header im Reverse Proxy
- optional: Rate Limiting
- Input-Validierung auf Anwendungsebene
Dabei schützt der Reverse-Proxy die Anwendung nach aussen, leitet aber alle HTTPS-Requests an die Anwendung weiter, die saubere Verarbeitung von Requests muss daher innerhalb der App durchgeführt werden.
Logging
Die Security-Massnahmen bieten einen Basisschutz, dennoch ist es wichtig die Laufzeitumgebung im Blick zu behalten, hier sind Logs die zentrale Informationsquelle im Betrieb. Mit den drei Ebenen:
- Web → nginx-Logs: zeigen eingehende Requests und Statuscodes
- App → Gunicorn/Flask-Logs: liefern Details zu Fehlern innerhalb der Anwendung
- Zugriff → SSH-Logs: geben Hinweise auf Zugriffsversuche
Die Auswertung erfolgt über docker compose logs und
journalctl -u sshd in Verbindung mit
grep zur Filterung nach relevanten Ereignissen. Auf
der Web und App-Ebene zählen dazu insbesondere:
- Status Code
200→ gültige Requests -
Status Code
400→ fehlerhafte Anfragesyntax -
Status Code
404→ häufig automatisierte Scans (Bots) -
Status Code
500→ Fehler in der Anwendung, hohe Priorität
Ein kurzer regelmäßiger Blick auf die Logs ist sinnvoll, um Fehler und auffällige Abweichungen früh zu erkennen. Dabei ist ein hoher Anteil an automatisierten Requests und Scanversuchen nicht ungewöhnlich für einen öffentlich erreichbaren Server.
Die resultierende Struktur ergänzt das lokale Setup um produktionsspezifische Komponenten:
project-server/
├── docker-compose.yml
├── logs
│ └── certbot-renew.log
├── nginx
│ ├── conf.d
│ ├── ssl
│ └── www
├── renew_certs.sh
└── services
└── heat-conduction-app
Fazit & Ausblick
Im Mittelpunkt dieses Artikels stand das Deployment der Anwendung, zunächst lokal mit Fokus auf Containerisierung, anschließend auf einem öffentlich erreichbaren VPS mit HTTPS und grundlegenden Betriebsaspekten.
Zentral ist der Einsatz von Docker bzw. docker-compose zur
Orchestrierung. Der führt initial zu mehr Aufwand, Dockerfiles,
Compose-Konfiguration, Volumes und das Zusammenspiel der
Services müssen sauber aufgebaut und verstanden werden. Dafür
erhält man ein gekapseltes, reproduzierbares und wartbares
System. Änderungen lassen sich kontrolliert ausrollen, Services
gezielt neustarten. Gleichzeitig wird die Infrastruktur als Code
dokumentiert (docker-compose.yml,
Dockerfile).
Das Deployment erweitert den Blick über die klassische Webentwicklung hinaus. Grundlegende Linux-Kenntnisse (SSH, Logs, Prozesse, Cron) sind notwendig, insbesondere beim Debugging. An dieser Stelle beginnt eine eigene Disziplin.
Ich bin hier kein Server-Administrator, sondern verfolge den Ansatz funktionierende, stabile Systeme auf Basis etablierter Best-Practices. Dazu gehört auch die Erkenntnis, dass öffentlich erreichbare Server permanent automatisiert gescannt werden. Das ist Normalzustand, kein Sonderfall.
Mit der öffentlich erreichbaren Anwendung ist Phase 1 des Referenzprojekts abgeschlossen. Der Prototyp läuft stabil und bildet eine belastbare Grundlage.
Die Umsetzungsschritte zeigen die Bandbreite:
Projektdefinition
↓
Projektgerüst
↓
Backend + Businesslogik
↓
Frontend
↓
Persistenzschicht
↓
Deployment
Eine zentrale Erkenntnis dieser Umsetzungsschritte, Erweiterbarkeit entsteht durch saubere Trennung der Verantwortlichkeiten. Das erhöht den initialen Aufwand, zahlt sich aber mit wachsender Komplexität aus.
Ausblick
Phase 1 endet mit einem funktionalen Prototyp. Für den produktiven Einsatz fehlen noch zentrale Bausteine:
- Authentifizierung und Benutzerverwaltung
- Umstieg auf eine produktive Datenbank
- API-Schnittstelle
- Ausbau der Fachlogik und Features
Mit diesen Schritten entwickelt sich die Anwendung von einem Prototypen zu einer produktionsnahen Webanwendung.
Phase 2 und 3 verschieben den Fokus entsprechend weg von Infrastruktur, hin zu Anwendung, Daten und Nutzerinteraktion.
Feedback, Fragen oder Anregungen sind jederzeit willkommen, gerne per Email oder auch auf LinkedIn.