Files
uni/slides/dhbw-2026/07_docker.md
T
libretech 351e3dfc23 branch-coexist: 3 neue decks 223015b-2026 / 223015c-2026 / dhbw-2026 daneben main
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).
2026-05-14 21:54:15 +02:00

7.4 KiB
Raw Blame History

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
<style> :root { --color-foreground: #1a1a2e; --color-highlight: #d63384; } section.invert { --color-foreground: #fff; } section { font-size: 1.4rem; } h1 { color: #a02060; } section.invert h1 { color: #fff; } h2 { color: #1f2937; } pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; } pre code { background: transparent; color: inherit; } code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; } a { color: var(--color-highlight); } </style>

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

  • alpine oder distroless als Base
  • Multi-stage builds für kleinere Images
  • .dockerignore anlegen
  • Non-root user im Container (USER node)
  • COPY nach RUN npm ci für Cache-Hit
  • Tags statt latest für Reproducibility
  • Health-Checks für Orchestration
  • Secrets via env, niemals im Image

Selbstlernen — App dockerizen

  1. Dockerfile für eure Node-API (multi-stage)
  2. .dockerignore mit node_modules, .env, .git
  3. compose.yml mit App + Postgres
  4. docker compose up lokal starten
  5. 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.