per user-request einen branch der main-content UND rewrite-content trägt — saubere separierung statt symlink/merge-conflict. struktur: - slides/<course>/ → main-content restored, eigene assets (kein new-demo-leak) - slides/<course>-2026/ → rewrite-content, eigene assets (komplett kopiert + new demos) Makefile: COURSES += 223015b-2026 223015c-2026 dhbw-2026 mit eigenen NAME/KAPITEL/DEPLOY/KLAUSUR-konfig. Klausur für b+c=1, dhbw=leer. Deploy-paths /hdm/223015b-2026 etc. make build durchlaufen für alle 6 decks, 0 fehler. dedup: 223015c+dhbw assets identisch in beiden ordnern (kein neu-demos auf b ergänzt). 223015b 86 → 120 files in -2026 (= +34 neue eroeffner/usb-c-drei-achsen/container-vs-codec/etc).
7.4 KiB
marp, theme, paginate, backgroundColor, header, footer, title
| marp | theme | paginate | backgroundColor | header | footer | title |
|---|---|---|---|---|---|---|
| true | gaia | true | Web Engineering – DHBW Stuttgart | Michael Czechowski – SoSe 2026 | Docker und Service Orchestration |
Kapitel 7 — Docker
Container für reproducible Setups
„Auf meinem Rechner läuft's"
Das klassische Software-Problem:
- Entwickler: Node 22 + Postgres 16 lokal
- Server: Node 18 + Postgres 14
- → bricht beim Deploy
Lösung: App + Abhängigkeiten als Container verpacken.
Container vs. VM
| Container | Virtual Machine | |
|---|---|---|
| Startzeit | ms | min |
| Größe | MB | GB |
| Kernel | shared (Host-Linux) | eigener |
| Isolation | Prozess-Level | Hardware-Level |
| Overhead | minimal | hoch |
Container = leichtgewichtige Prozess-Isolation.
Docker — die Idee
- Image = Schnappschuss (App + Files + OS-Layer)
- Container = laufende Instanz eines Images
- Dockerfile = Anleitung zum Image-Bauen
- Registry = Image-Speicher (Docker Hub, GHCR)
docker pull node:24
docker run node:24 node --version
Dockerfile — Hallo Welt
FROM node:24-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 3000
CMD ["node", "src/server.js"]
docker build -t mein-app .
docker run -p 3000:3000 mein-app
Dockerfile-Schichten verstehen
Jedes RUN/COPY/ADD = neue Schicht.
# Schlecht — npm install nach JEDER Code-Änderung
COPY . .
RUN npm ci
# Gut — package.json zuerst, npm install gecached
COPY package*.json ./
RUN npm ci
COPY . .
Build-Cache spart Minuten.
Multi-Stage Build
# Stage 1: Build mit allem
FROM node:24-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build
# Stage 2: nur Runtime
FROM node:24-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/server.js"]
Finales Image: kleiner, sicherer (kein Build-Tools drin).
Image-Größen reduzieren
| Base | Größe |
|---|---|
node:24 |
~1.1 GB |
node:24-slim |
~280 MB |
node:24-alpine |
~180 MB |
| Distroless | ~150 MB |
alpine = Alpine Linux, sehr klein. Distroless = nur Runtime, kein Shell.
.dockerignore
Verhindert dass Müll ins Image kommt:
node_modules
.git
.env
*.log
build/
dist/
.DS_Store
README.md
.github/
Schneller Build, kleineres Image, weniger Secrets im Image.
docker compose
Multi-Container — die Realität
Eine App braucht oft:
- App-Server
- Datenbank
- Cache (Redis)
- Reverse Proxy
docker compose orchestriert das.
compose.yml — Beispiel
services:
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://user:pass@db:5432/app
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
compose-Commands
docker compose up # alles starten (Foreground)
docker compose up -d # detached (background)
docker compose down # alles stoppen + entfernen
docker compose down -v # ... und Volumes löschen
docker compose logs -f app # Logs streamen
docker compose exec app sh # Shell im laufenden Container
docker compose ps # Status sehen
Networking in compose
Standard: alle Services im selben default-Netzwerk.
services:
app:
environment:
# `db` ist der Service-Name → DNS-Hostname
DATABASE_URL: postgres://...@db:5432/app
Service-Namen werden zu Hostnamen. Kein localhost zwischen Containern.
Volumes — Daten überleben
volumes:
db-data: # named volume (managed by Docker)
services:
db:
volumes:
- db-data:/var/lib/postgresql/data # named
- ./backups:/backups # bind mount
Named Volumes für DB-Daten. Bind Mounts für lokale Config.
Environment + Secrets
services:
app:
env_file: .env # ganze .env-Datei laden
environment:
NODE_ENV: production # einzeln setzen
DATABASE_URL: ${DATABASE_URL} # vom Host
Secrets nicht ins compose.yml. Lieber .env (gitignored) oder Docker Secrets.
CI/CD
Docker in GitHub Actions
name: Build + Push
on:
push:
branches: [main]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: user/app:latest
Container-Registry
| Registry | Stärke |
|---|---|
| Docker Hub | Standard, kostenlos für Public |
| GHCR (GitHub) | mit Repo verbunden |
| GitLab Registry | mit Repo verbunden |
| AWS ECR | für AWS-Hosting |
# Tag + Push
docker tag mein-app ghcr.io/user/mein-app:v1.0.0
docker push ghcr.io/user/mein-app:v1.0.0
Health-Checks
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
// In Express
app.get('/health', (req, res) => res.json({ ok: true }));
Orchestrators (k8s, ECS, Coolify) entscheiden via HealthCheck, ob neu starten.
Best Practices
alpineoder distroless als Base- Multi-stage builds für kleinere Images
.dockerignoreanlegen- Non-root user im Container (
USER node) COPYnachRUN npm cifür Cache-Hit- Tags statt
latestfür Reproducibility - Health-Checks für Orchestration
- Secrets via env, niemals im Image
Selbstlernen — App dockerizen
- Dockerfile für eure Node-API (multi-stage)
- .dockerignore mit node_modules, .env, .git
- compose.yml mit App + Postgres
docker compose uplokal starten- Health-Check auf
/health-Endpoint
Bonus: GitHub-Actions-Workflow, der Image bei jedem Push baut.
Zusammenfassung
Heute gelernt:
- Container vs. VM — Prozess-Isolation, leicht + schnell
- Dockerfile mit
FROM,COPY,RUN,CMD - Multi-stage Builds für kleine Production-Images
- alpine + Distroless als Base
- docker compose für Multi-Container-Setups
- Volumes für persistente Daten
- Service-Namen als Hostnamen im compose-Netzwerk
- CI/CD mit GitHub Actions + Registry
Nächste Stunde: Best Practices — Git, README, SOLID, 12-Factor.