WebApps bauen – Teil 3 – Docker mit FastAPI

Wie erstellt man eine eigene WebApp mit REST-API auf Basis von Python und betreibt sie in einem Docker-Container?

REST-API ist eine Benutzer-Schnittstelle, die den Designprinzipien der REST-Architektur entspricht (das soll an dieser Stelle reichen). FastAPI ist ein Framework für Python welches die Entwicklung von REST-APIs ermöglicht. In dieser Anleitung richten wir auf Debian 13 mit Xfce einen vollständigen FastAPI-Entwicklungsserver ein. Wir verwenden Docker Compose, bearbeiten unseren Python-Quellcode mit einem grafischen Editor und testen die fertige API direkt im Browser.

Das Besondere: Änderungen am Programm werden automatisch erkannt. Wir müssen den Docker-Container nicht bei jeder Änderung neu erstellen oder starten.

Die Anleitung eignet sich besonders für Einsteiger, die ihre erste Webanwendung mit Python und Docker entwickeln möchten. Der Bezug zu KI-Anwendungen liegt in der Tatsache, dass diese Anleitung durch KI erstellt wurde und vom Blog-Author geprüft und getestet wurde.

1. Was wir aufbauen

Unser Ziel ist eine kleine Python-Webanwendung, die innerhalb eines Docker-Containers läuft.

Die Anwendung stellt drei API-Endpunkte bereit:

  • / – Gibt eine Begrüßung und den Serverstatus zurück.
  • /zeit – Liefert die aktuelle Serverzeit.
  • /berechnen – Addiert zwei Zahlen und gibt das Ergebnis zurück.

Wir können diese Funktionen direkt über unseren Browser aufrufen.

Die Entwicklungsumgebung besteht aus folgenden Komponenten:

KomponenteAufgabe
Debian 13Betriebssystem
XfceGrafische Desktopoberfläche
Docker EngineAusführung der Container
Docker ComposeKonfiguration und Verwaltung der Anwendung
Python 3.13Programmiersprache
FastAPIFramework zur Erstellung unserer API
UvicornWebserver für FastAPI
geditGrafischer Quellcode-Editor
FirefoxTesten der API im Browser

Die gesamte Anwendung befindet sich in unserem Projektverzeichnis:

~/Projekte/docker-test/
│
├── app.py
├── requirements.txt
├── Dockerfile
└── compose.yaml

Jede dieser Dateien erfüllt eine bestimmte Aufgabe.

2. Voraussetzungen

Bevor wir beginnen, sollten folgende Voraussetzungen erfüllt sein:

  • Debian 13 ist installiert.
  • Eine grafische Desktopumgebung wie Xfce ist vorhanden.
  • Docker Engine funktioniert.
  • Docker Compose ist installiert.
  • Der Benutzer darf Docker-Befehle ausführen.
  • Ein grafischer Texteditor wie gedit ist vorhanden.
  • Eine Internetverbindung besteht, damit Docker benötigte Images und Python-Pakete herunterladen kann.

Zur Debian/Docker Installation gibt es 2 Artikel im Grundlagen-Bereich:

3. Projektverzeichnis erstellen

Zunächst öffnen wir ein Terminal und wechseln in unser Projektverzeichnis.

mkdir -p ~/Projekte/docker-test
cd ~/Projekte/docker-test

Alle weiteren Dateien legen wir in diesem Verzeichnis ab.

4. Unsere erste FastAPI-Anwendung programmieren

Wir erstellen zunächst die eigentliche Python-Anwendung.

Dazu öffnen wir den grafischen Texteditor:

gedit app.py

Wir fügen folgenden Python-Code ein:

from fastapi import FastAPI
from datetime import datetime

app = FastAPI()


@app.get("/")
def startseite():
    return {
        "nachricht": "Hallo von meinem FastAPI-Webserver!",
        "status": "Docker funktioniert"
    }


@app.get("/zeit")
def aktuelle_zeit():
    return {
        "zeit": datetime.now().isoformat()
    }


@app.get("/berechnen")
def berechnen(a: float, b: float):
    return {
        "a": a,
        "b": b,
        "ergebnis": a + b
    }

Anschließend speichern wir die Datei als app.py.

Wie funktioniert dieser Code?

Zunächst importieren wir die benötigten Komponenten:

from fastapi import FastAPI
from datetime import datetime

Mit

app = FastAPI()

erzeugen wir unsere FastAPI-Anwendung. Sie wird komplett durch das verwendete fastAPI Framework erzeugt.

Danach definieren wir drei sogenannte Endpunkte.

Endpunkt 1: Die Startseite

@app.get("/")
def startseite():
    return {
        "nachricht": "Hallo von meinem FastAPI-Webserver!",
        "status": "Docker funktioniert"
    }

Dieser Endpunkt reagiert auf HTTP-GET-Anfragen an die Startadresse /.

FastAPI wandelt das zurückgegebene Python-Dictionary (die Text-Struktur in den geschweiften Klammern) automatisch in eine JSON-Antwort um.

Endpunkt 2: Die aktuelle Uhrzeit

@app.get("/zeit")
def aktuelle_zeit():
    return {
        "zeit": datetime.now().isoformat()
    }

Dieser Endpunkt gibt die aktuelle Zeit des Servers zurück.

Die Zeit stammt aus der Laufzeitumgebung des Containers. Die verwendete Zeitzone hängt von dessen Konfiguration ab.

Endpunkt 3: Zwei Zahlen addieren

@app.get("/berechnen")
def berechnen(a: float, b: float):
    return {
        "a": a,
        "b": b,
        "ergebnis": a + b
    }

Hier erwartet FastAPI zwei Parameter: a und b. Beide werden als Gleitkommazahlen interpretiert.

Die Funktion addiert diese Werte und gibt das Ergebnis als JSON zurück.

Damit haben wir bereits eine kleine, funktionsfähige REST-API programmiert.

5. Python-Abhängigkeiten definieren

Unsere Anwendung verwendet FastAPI und Uvicorn.

Diese Pakete sollen nicht direkt auf Debian installiert werden, sondern ausschließlich innerhalb unseres Docker-Images.

Dafür erstellen wir eine Datei namens requirements.txt.

gedit requirements.txt

Inhalt:

fastapi
uvicorn[standard]

FastAPI ist das Python-Framework, mit dem wir unsere API-Endpunkte erstellen.

Uvicorn ist ein ASGI-Webserver, der unsere FastAPI-Anwendung ausführt und HTTP-Anfragen entgegennimmt.

Die Option [standard] installiert zusätzliche, für Uvicorn nützliche Abhängigkeiten.

Für dieses Lernprojekt verwenden wir die Paketnamen ohne feste Versionsnummern. Bei einem späteren produktiven Projekt sollten wir die Versionen festlegen, damit unsere Anwendung reproduzierbar gebaut werden kann.

6. Das Dockerfile erstellen

Nun benötigt Docker eine Bauanleitung für unser Image.

Diese Bauanleitung heißt Dockerfile.

Wir erstellen die Datei:

gedit Dockerfile

Inhalt:

FROM python:3.13-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

EXPOSE 8000

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

Speichern nicht vergessen.

Was bedeuten die einzelnen Dockerfile-Anweisungen?

FROM – Basis-Image auswählen

FROM python:3.13-slim

Wir verwenden ein schlankes Python-3.13-Image.

Dieses enthält bereits Python und die grundlegenden Komponenten, die wir für unsere Anwendung benötigen.

WORKDIR – Arbeitsverzeichnis festlegen

WORKDIR /app

Innerhalb des Containers verwenden wir /app als Arbeitsverzeichnis.

COPY – Abhängigkeiten übernehmen

COPY requirements.txt .

Damit kopieren wir unsere Datei requirements.txt in das Image.

RUN – Python-Pakete installieren

RUN pip install --no-cache-dir -r requirements.txt

Docker installiert beim Image-Bau FastAPI und Uvicorn.

Die Option --no-cache-dir verhindert, dass pip seinen Download-Cache im Image speichert.

EXPOSE – Anwendungsport dokumentieren

EXPOSE 8000

Unsere Anwendung verwendet Port 8000.

Wichtig: EXPOSE veröffentlicht diesen Port noch nicht automatisch auf unserem Debian-Rechner. Das konfigurieren wir anschließend mit Docker Compose.

CMD – Webserver starten

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

Beim Start des Containers führt Docker Uvicorn aus.

Die einzelnen Parameter haben folgende Bedeutung:

ParameterBedeutung
app:appPython-Modul app.py und darin enthaltenes FastAPI-Objekt app
--host 0.0.0.0Uvicorn lauscht auf allen Netzwerkschnittstellen innerhalb des Containers
--port 8000Webserver verwendet Port 8000
--reloadAnwendung bei Quellcodeänderungen automatisch neu laden

Die Option --reload ist ausdrücklich für unsere Entwicklungsumgebung gedacht.

In einer späteren Produktionsumgebung würden wir den Webserver ohne diese Option betreiben.

7. Docker Compose konfigurieren

Bisher haben wir ein Python-Programm und eine Bauanleitung für das Docker-Image.

Jetzt benötigen wir eine Konfiguration, mit der wir unseren Container bequem starten und verwalten können.

Dafür verwenden wir Docker Compose.

Wir erstellen:

gedit compose.yaml

Inhalt:

services:
  app:
    build: .
    container_name: fastapi-dev

    ports:
      - "127.0.0.1:8000:8000"

    volumes:
      - ./:/app:ro

    environment:
      - PYTHONUNBUFFERED=1

Die einzelnen Einstellungen erklärt

services

services:
  app:

Wir definieren einen Dienst mit dem Namen app.

Später könnten wir hier weitere Dienste ergänzen, beispielsweise PostgreSQL oder ein Frontend.

build

build: .

Docker soll das Image anhand des Dockerfiles im aktuellen Projektverzeichnis erstellen.

container_name

container_name: fastapi-dev

Unser Container erhält den Namen fastapi-dev.

ports

ports:
  - "127.0.0.1:8000:8000"

Damit verbinden wir Port 8000 des Containers mit Port 8000 auf unserem Debian-Rechner.

Die allgemeine Schreibweise lautet:

HOST-IP:HOST-PORT:CONTAINER-PORT

Durch die Angabe 127.0.0.1 beschränken wir den Zugriff auf den lokalen Rechner.

Unsere Entwicklungs-API ist dadurch nicht unmittelbar aus dem Heimnetz oder Internet erreichbar.

Das ist für die ersten Tests eine sinnvolle Sicherheitseinstellung.

volumes

volumes:
  - ./:/app:ro

Diese Einstellung ist für unsere Entwicklungsumgebung besonders interessant.

Sie verbindet den gesamten Projekt-Ordner (docker-test) auf unserem Debian-Rechner mit dem Ordner /app im Container.

Das bedeutet:

Wir bearbeiten unseren Quellcode (der durchaus aus mehreren Dateien bestehen kann) weiterhin mit gedit auf Debian. Der Container verwendet alle Quell-Dateien die dem Ordner /app zugewiesen wurden..

Die Option ro steht für read-only. Der Container darf die eingebundene Datei lesen, aber nicht verändern.

environment

environment:
  - PYTHONUNBUFFERED=1

Diese Einstellung sorgt dafür, dass Python seine Ausgaben ohne die übliche Pufferung an die Container-Ausgabe weitergibt.

Das ist bei der Fehlersuche und beim Anzeigen von Logs hilfreich.

8. Docker-Container erstellen und starten

Jetzt sind alle benötigten Dateien vorhanden.

Unsere Projektstruktur sieht folgendermaßen aus:

~/Projekte/docker-test/
│
├── app.py
├── requirements.txt
├── Dockerfile
└── compose.yaml

Im Terminal wechseln wir in das Projektverzeichnis:

cd ~/Projekte/docker-test

Anschließend starten wir:

docker compose up --build -d

Was passiert bei diesem Befehl?

Docker Compose liest zunächst unsere compose.yaml.

Anschließend wird das Dockerfile verarbeitet.

Falls das Python-Basis-Image noch nicht vorhanden ist, lädt Docker es herunter.

Danach installiert Docker die benötigten Python-Pakete und erstellt unser eigenes Image.

Schließlich wird der Container erzeugt und gestartet.

Die Parameter bedeuten:

ParameterFunktion
upDienste erstellen und starten
--buildImage vor dem Start neu bauen
-dContainer im Hintergrund ausführen

Nach dem Start überprüfen wir den Status:

docker compose ps

Unser Container fastapi-dev sollte als laufend angezeigt werden.

Falls Probleme auftreten, können wir die Ausgaben des Webservers ansehen:

docker compose logs -f

Mit Strg+C verlassen wir die Log-Anzeige. Der im Hintergrund laufende Container bleibt dabei aktiv.

9. FastAPI im Browser testen

Jetzt kommt der spannende Moment.

Wir öffnen Firefox auf unserem Debian-Desktop und geben folgende Adresse ein:

http://localhost:8000

Wenn alles funktioniert, erscheint eine JSON-Antwort:

{
  "nachricht": "Hallo von meinem FastAPI-Webserver!",
  "status": "Docker funktioniert"
}

Damit läuft unser erster eigener FastAPI-Webserver erfolgreich innerhalb eines Docker-Containers.

Die aktuelle Serverzeit abfragen

Als Nächstes öffnen wir:

http://localhost:8000/zeit

Die Antwort sieht beispielsweise so aus:

{
  "zeit": "2026-10-28T11:30:00.123456"
}

Der genaue Wert hängt natürlich vom Zeitpunkt der Anfrage ab.

Eine Berechnung durchführen

Jetzt testen wir unseren dritten Endpunkt:

http://localhost:8000/berechnen?a=25&b=17

FastAPI übernimmt die beiden Zahlen aus der URL.

Die Antwort lautet:

{
  "a": 25.0,
  "b": 17.0,
  "ergebnis": 42.0
}

Damit haben wir bereits eine kleine API, die Parameter entgegennimmt, eine Berechnung durchführt und das Ergebnis als JSON zurückliefert.

10. Änderungen am Quellcode automatisch übernehmen

Ein wesentlicher Vorteil unserer Entwicklungsumgebung ist die direkte Verbindung zwischen dem Quellcode auf Debian und dem laufenden Container.

Wir öffnen die Datei erneut:

gedit ~/Projekte/docker-test/app.py

Dort suchen wir folgende Zeile:

"nachricht": "Hallo von meinem FastAPI-Webserver!"

Wir ändern sie in:

"nachricht": "Meine erste eigene Web-App läuft!"

Anschließend speichern wir die Datei.

Jetzt aktualisieren wir die Startseite im Browser:

http://localhost:8000

Die neue Nachricht sollte bereits angezeigt werden.

Warum funktioniert das ohne einen neuen Docker-Build?

Das liegt an zwei Einstellungen.

Erstens haben wir unseren gesamten Projekt-Ordner als Bind-Mount eingebunden:

volumes:
  - ./:/app:ro

Dadurch verwendet der Container die Datei direkt aus unserem Projektverzeichnis.

Zweitens haben wir Uvicorn mit --reload gestartet.

Uvicorn überwacht den Quellcode auf Änderungen und lädt die Anwendung bei Bedarf automatisch neu. Als Quellcode gelten zunächst einmal nur die Dateien mit der Endung .py

Änderungen an der Compose.yaml oder requirements.txt führen nicht zu einem Rebuild und einem Neustart des Containers.

Unser Entwicklungsablauf sieht deshalb so aus:

Grafischer Editor
       │
       ▼
app.py bearbeiten
       │
       ▼
Datei speichern
       │
       ▼
Docker-Bind-Mount
       │
       ▼
Uvicorn erkennt Änderung
       │
       ▼
FastAPI wird neu geladen
       │
       ▼
Browser aktualisieren

Wichtig: Änderungen an requirements.txt oder am Dockerfile erfordern weiterhin einen neuen Image-Build. Das automatische Neuladen betrifft ausschliesslich unseren eingebundenen Python-Quellcode.

11. Die interaktive FastAPI-Dokumentation kennenlernen

FastAPI besitzt eine besonders praktische Funktion: Es erzeugt automatisch eine interaktive API-Dokumentation.

Diese erreichen wir über:

http://localhost:8000/docs

Dort werden unsere drei Endpunkte angezeigt:

GET /
GET /zeit
GET /berechnen

Wir können jeden Endpunkt aufklappen und direkt ausprobieren.

Bei /berechnen lassen sich beispielsweise die Werte für a und b eingeben.

Anschließend klicken wir auf Execute.

FastAPI zeigt die erzeugte Anfrage und die Antwort des Servers an.

Für die Entwicklung komplexerer Anwendungen ist diese Oberfläche ausgesprochen hilfreich.

Sie ermöglicht es uns, Backend-Funktionen zu testen, bevor überhaupt eine eigene grafische Benutzeroberfläche existiert.

12. Container verwalten

Zum Abschluss schauen wir uns einige nützliche Docker-Compose-Befehle an.

Laufende Container anzeigen:

docker compose ps

Logs anzeigen:

docker compose logs -f

Container stoppen:

docker compose stop

Gestoppten Container erneut starten:

docker compose start

Container entfernen:

docker compose down

Image neu bauen und Container starten:

docker compose up --build -d

Bei docker compose down werden die von Compose verwalteten Container und Netzwerke entfernt.

Unsere Projektdateien auf Debian bleiben erhalten.

13. Warum Docker für die Entwicklung sinnvoll ist

Wir hätten FastAPI und Uvicorn auch direkt auf Debian installieren können.

Mit Docker erhalten wir jedoch eine sauber abgegrenzte Laufzeitumgebung.

Unser Debian-System benötigt selbst keine separate Installation von Python 3.13, FastAPI oder Uvicorn für dieses Projekt.

Diese Komponenten befinden sich im Docker-Image.

Das bringt mehrere Vorteile:

Saubere Trennung: Die Abhängigkeiten unserer Webanwendung sind von den Anwendungen auf dem Debian-Desktop getrennt.

Reproduzierbarkeit: Wir können unsere Entwicklungsumgebung anhand von Dockerfile und Compose-Konfiguration erneut erstellen.

Portabilität: Die Anwendung lässt sich später grundsätzlich auch auf einem anderen Linux-Rechner mit Docker betreiben.

Einfachere Erweiterung: Wir können weitere Container hinzufügen, ohne alle Komponenten direkt auf Debian installieren zu müssen.

Komfortable Entwicklung: Der Quellcode bleibt auf unserer SSD und kann mit einem grafischen Editor bearbeitet werden.

Für die spätere Bereitstellung auf einem anderen Rechner müssen wir gegebenenfalls Pfade, Netzwerkeinstellungen und Konfigurationen anpassen.

14. Wie geht es weiter?

Mit unserer ersten FastAPI-Anwendung haben wir die Grundlage für eine umfangreichere Web-App geschaffen.

Als nächste Ausbaustufen bieten sich an:

  1. PostgreSQL integrieren: Unsere Anwendung erhält eine eigene Datenbank, in der wir Informationen dauerhaft speichern können.
  2. Ein Frontend erstellen: Statt ausschließlich JSON-Antworten darzustellen, entwickeln wir eine grafische Benutzeroberfläche.
  3. Entwicklung und Produktion trennen: Wir richten getrennte Konfigurationen und Datenbanken für Tests und den produktiven Betrieb ein.
  4. C/C++-Berechnungen einbinden: Bei Bedarf ergänzen wir unsere Python-Anwendung um rechenintensive Funktionen.
  5. HTTPS mit Caddy einrichten: Später kann unsere Web-App über eine eigene Domain sicher erreichbar gemacht werden.

Für einen öffentlich erreichbaren Produktivserver sind außerdem Sicherheitsmaßnahmen erforderlich. Dazu gehören unter anderem der Verzicht auf den Entwicklungsmodus --reload, eine geeignete HTTPS-Konfiguration, regelmäßige Updates und eine durchdachte Verwaltung von Zugangsdaten.

Fazit

Mit Debian 13, Docker Compose, Python und FastAPI lässt sich bereits mit wenigen Dateien ein funktionsfähiger Webserver erstellen.

Wir haben gelernt, wie wir ein Docker-Image bauen, einen Container starten, Ports konfigurieren und Quellcode vom Hostsystem einbinden.

Besonders praktisch ist die Kombination aus einer grafischen Linux-Arbeitsumgebung und Docker: Wir bearbeiten unsere Programme bequem mit gedit, während die benötigten Laufzeitkomponenten sauber innerhalb der Container organisiert bleiben.

Unsere erste FastAPI-Anwendung kann bereits HTTP-Anfragen verarbeiten, die aktuelle Serverzeit zurückgeben und einfache Berechnungen durchführen.

Damit ist der Grundstein für eine eigene Full-Stack-Webanwendung gelegt.

Der nächste Teil dieser Artikel-Serie wird sich mit einer Architektur-Frage zur WebApp beschäftigen, also dranbleiben.