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).
This commit is contained in:
+235
-271
@@ -9,375 +9,339 @@ title: 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); }
|
||||
:root {
|
||||
--color-foreground: #1a1a2e;
|
||||
--color-highlight: #d63384;
|
||||
--color-dimmed: #4a4a6a;
|
||||
}
|
||||
section.invert {
|
||||
--color-foreground: #fff;
|
||||
}
|
||||
section {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
h1 {
|
||||
color: #a02060;
|
||||
}
|
||||
section.invert h1 {
|
||||
color: #fff;
|
||||
}
|
||||
h2 {
|
||||
color: #1f2937;
|
||||
}
|
||||
section.invert h2 {
|
||||
color: #f48fb1;
|
||||
}
|
||||
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>
|
||||
|
||||
<!-- _class: lead -->
|
||||
<!-- _class: invert -->
|
||||
<!-- _header: '' -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
# Kapitel 7 — Docker
|
||||
## Container für reproducible Setups
|
||||
# Web Engineering
|
||||
|
||||
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
|
||||
|
||||
**Sommersemester 2026** · Sitzung 9 — Docker, Proxies and DBs
|
||||
|
||||
---
|
||||
|
||||
# „Auf meinem Rechner läuft's"
|
||||
<!-- _class: invert -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
Das klassische Software-Problem:
|
||||
# Docker
|
||||
|
||||
- Entwickler: Node 22 + Postgres 16 lokal
|
||||
- Server: Node 18 + Postgres 14
|
||||
- → bricht beim Deploy
|
||||
|
||||
**Lösung:** App + Abhängigkeiten als **Container** verpacken.
|
||||
## Service Orchestration
|
||||
|
||||
---
|
||||
|
||||
# Container vs. VM
|
||||
# Inhalt
|
||||
|
||||
| | 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.
|
||||
1. Architektur: Reverse Proxy + API + DB
|
||||
2. Container vs VM
|
||||
3. `Dockerfile`
|
||||
4. `compose.yml`
|
||||
5. Docker CLI / Compose Befehle
|
||||
6. Live Demo: `dhbw-docker`
|
||||
|
||||
---
|
||||
|
||||
# Docker — die Idee
|
||||
# Architektur
|
||||
|
||||
- **Image** = Schnappschuss (App + Files + OS-Layer)
|
||||
- **Container** = laufende Instanz eines Images
|
||||
- **Dockerfile** = Anleitung zum Image-Bauen
|
||||
- **Registry** = Image-Speicher (Docker Hub, GHCR)
|
||||
|
||||
```sh
|
||||
docker pull node:24
|
||||
docker run node:24 node --version
|
||||
```
|
||||
Browser (localhost:10007)
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ Reverse Proxy │ (nginx / traefik)
|
||||
└─────────┬────────┘
|
||||
┌────┴────┐
|
||||
▼ ▼
|
||||
┌─────────┐ ┌────────┐
|
||||
│ Web │ │ API │
|
||||
│ App │ │ (Node) │
|
||||
└────┬────┘ └────┬───┘
|
||||
└─────┬──────┘
|
||||
▼
|
||||
┌────────┐
|
||||
│ DB │ (Postgres)
|
||||
└────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Dockerfile — Hallo Welt
|
||||
# Container vs VM
|
||||
|
||||
| | VM | Container |
|
||||
|---|----|-----------|
|
||||
| Kernel | eigener | Host-Kernel |
|
||||
| Boot | Minuten | Sekunden |
|
||||
| Größe | GB | MB |
|
||||
| Isolation | stark | namespaces, cgroups |
|
||||
| Image | komplett | Layer (diff) |
|
||||
|
||||
→ **Docker** = Container-Engine + Image-Format + Registry.
|
||||
|
||||
---
|
||||
|
||||
# Dockerfile – Express API
|
||||
|
||||
```dockerfile
|
||||
FROM node:24-alpine
|
||||
FROM node:lts-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
COPY ./package*.json ./
|
||||
RUN npm ci --no-audit --no-fund --no-progress --loglevel=error
|
||||
|
||||
COPY . .
|
||||
COPY ./ ./
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["node", "src/server.js"]
|
||||
ENV NODE_ENV="production"
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["npm", "start"]
|
||||
```
|
||||
|
||||
```sh
|
||||
docker build -t mein-app .
|
||||
docker run -p 3000:3000 mein-app
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Dockerfile-Schichten verstehen
|
||||
|
||||
Jedes `RUN`/`COPY`/`ADD` = neue Schicht.
|
||||
|
||||
```dockerfile
|
||||
# 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.
|
||||
**`npm ci`** statt `npm i` für reproduzierbare Builds.
|
||||
|
||||
---
|
||||
|
||||
# Multi-Stage Build
|
||||
|
||||
```dockerfile
|
||||
# Stage 1: Build mit allem
|
||||
FROM node:24-alpine AS builder
|
||||
FROM node:lts-slim AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm ci && npm run build
|
||||
RUN npm run build
|
||||
|
||||
# Stage 2: nur Runtime
|
||||
FROM node:24-alpine
|
||||
FROM node:lts-slim AS runtime
|
||||
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"]
|
||||
EXPOSE 8080
|
||||
CMD ["node", "dist/index.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.
|
||||
→ kleines Final-Image, keine Build-Tools im Runtime.
|
||||
|
||||
---
|
||||
|
||||
# .dockerignore
|
||||
|
||||
Verhindert dass Müll ins Image kommt:
|
||||
|
||||
```
|
||||
node_modules
|
||||
dist
|
||||
.git
|
||||
.env
|
||||
*.log
|
||||
build/
|
||||
dist/
|
||||
.DS_Store
|
||||
README.md
|
||||
.github/
|
||||
coverage
|
||||
```
|
||||
|
||||
Schneller Build, kleineres Image, weniger Secrets im Image.
|
||||
Spart Build-Kontext und verhindert Geheimnis-Leaks.
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: lead -->
|
||||
|
||||
# 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
|
||||
# compose.yml – Services
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
DATABASE_URL: postgres://user:pass@db:5432/app
|
||||
depends_on:
|
||||
- db
|
||||
name: dhbw-docker-app
|
||||
|
||||
services:
|
||||
api:
|
||||
build: ./api
|
||||
container_name: dhbw-api
|
||||
environment:
|
||||
DATABASE_USERNAME: ${DATABASE_USERNAME}
|
||||
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
|
||||
DATABASE_NAME: ${DATABASE_NAME}
|
||||
DATABASE_HOST: db
|
||||
DATABASE_PORT: 5432
|
||||
ports:
|
||||
- "10000:8080"
|
||||
networks: [net, data]
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# compose.yml – Datenbank + Proxy
|
||||
|
||||
```yaml
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_USER: user
|
||||
POSTGRES_PASSWORD: pass
|
||||
POSTGRES_DB: app
|
||||
POSTGRES_USER: ${DATABASE_USERNAME}
|
||||
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
|
||||
POSTGRES_DB: ${DATABASE_NAME}
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
- dbdata:/var/lib/postgresql/data
|
||||
networks: [data]
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $$DATABASE_USERNAME"]
|
||||
interval: 5s
|
||||
retries: 5
|
||||
|
||||
proxy:
|
||||
image: nginx:alpine
|
||||
ports: ["10007:80"]
|
||||
volumes: ["./nginx.conf:/etc/nginx/nginx.conf:ro"]
|
||||
networks: [net]
|
||||
depends_on: [api]
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
dbdata:
|
||||
|
||||
networks:
|
||||
net:
|
||||
data:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# compose-Commands
|
||||
# Docker Compose – Befehle
|
||||
|
||||
```sh
|
||||
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
|
||||
```bash
|
||||
docker compose -p "dhbw-docker-app" \
|
||||
-f compose.yml \
|
||||
--env-file .env \
|
||||
up \
|
||||
--build \
|
||||
--remove-orphans \
|
||||
--force-recreate
|
||||
```
|
||||
|
||||
docker compose logs -f app # Logs streamen
|
||||
docker compose exec app sh # Shell im laufenden Container
|
||||
docker compose ps # Status sehen
|
||||
| Flag | Bedeutung |
|
||||
|------|-----------|
|
||||
| `-p` | Projektname |
|
||||
| `-f` | mehrere Compose-Files möglich |
|
||||
| `--env-file` | `.env` einlesen |
|
||||
| `--build` | Images neu bauen |
|
||||
| `--remove-orphans` | verwaiste Container weg |
|
||||
| `--force-recreate` | Container neu starten |
|
||||
|
||||
---
|
||||
|
||||
# Compose – Alltag
|
||||
|
||||
```bash
|
||||
docker compose up -d # detached starten
|
||||
docker compose ps # Status
|
||||
docker compose logs -f api # Logs streamen
|
||||
docker compose exec api sh # Shell im Container
|
||||
docker compose down -v # Stop + Volumes löschen
|
||||
docker compose restart api # Neustart einzelner Service
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Networking in compose
|
||||
|
||||
Standard: alle Services im selben `default`-Netzwerk.
|
||||
# Health Checks
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
environment:
|
||||
# `db` ist der Service-Name → DNS-Hostname
|
||||
DATABASE_URL: postgres://...@db:5432/app
|
||||
api:
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
start_period: 5s
|
||||
```
|
||||
|
||||
**Service-Namen werden zu Hostnamen.** Kein localhost zwischen Containern.
|
||||
`depends_on: { condition: service_healthy }` wartet, bis Health passt.
|
||||
|
||||
---
|
||||
|
||||
# Volumes — Daten überleben
|
||||
# Networks: Segmentierung
|
||||
|
||||
- **`net`** – öffentlich erreichbar (Proxy — App)
|
||||
- **`data`** – nur intern (App — DB)
|
||||
- DB ist **nicht** vom Internet erreichbar
|
||||
- Service-DNS: `api`, `db`, `proxy` (Container-Name = Hostname)
|
||||
|
||||
→ Defense in Depth.
|
||||
|
||||
---
|
||||
|
||||
# Volumes – Daten persistent
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
db-data: # named volume (managed by Docker)
|
||||
|
||||
services:
|
||||
db:
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data # named
|
||||
- ./backups:/backups # bind mount
|
||||
dbdata: # benannt
|
||||
- ./code:/app # bind mount (dev)
|
||||
- /app/node_modules # anonymous (überschreibt bind)
|
||||
```
|
||||
|
||||
**Named Volumes** für DB-Daten. **Bind Mounts** für lokale Config.
|
||||
`docker compose down` ohne `-v` lässt Volumes leben.
|
||||
|
||||
---
|
||||
|
||||
# Environment + Secrets
|
||||
# Live Demo
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
env_file: .env # ganze .env-Datei laden
|
||||
environment:
|
||||
NODE_ENV: production # einzeln setzen
|
||||
DATABASE_URL: ${DATABASE_URL} # vom Host
|
||||
Repo: https://github.com/nextlevelshit/dhbw-docker
|
||||
|
||||
```bash
|
||||
git clone https://github.com/nextlevelshit/dhbw-docker
|
||||
cd dhbw-docker
|
||||
cp .env.example .env
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
**Secrets nicht ins compose.yml.** Lieber `.env` (gitignored) oder Docker Secrets.
|
||||
→ http://localhost:10007
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: lead -->
|
||||
# 100 Concepts of Docker (Exkurs)
|
||||
|
||||
# CI/CD
|
||||
- Image · Container · Volume · Network
|
||||
- Dockerfile · Layer Cache · BuildKit
|
||||
- Compose · Stack · Swarm · Kubernetes
|
||||
- Registry · Tag · Digest
|
||||
- Dev/Prod-Parity (12-Factor)
|
||||
- Secrets · Configs
|
||||
- Logging Drivers
|
||||
- Health Checks · Restart Policies
|
||||
|
||||
---
|
||||
|
||||
# Docker in GitHub Actions
|
||||
|
||||
```yaml
|
||||
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 |
|
||||
|
||||
```sh
|
||||
# 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
|
||||
|
||||
```dockerfile
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
|
||||
```
|
||||
|
||||
```javascript
|
||||
// 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**
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: aufgabe -->
|
||||
|
||||
# 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.
|
||||
|
||||
Reference in New Issue
Block a user