--- marp: true theme: gaia paginate: true backgroundColor: #fff header: "Web Engineering – DHBW Stuttgart" footer: "Michael Czechowski – SoSe 2026" title: 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) ```sh docker pull node:24 docker run node:24 node --version ``` --- # Dockerfile — Hallo Welt ```dockerfile FROM node:24-alpine WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . EXPOSE 3000 CMD ["node", "src/server.js"] ``` ```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. --- # Multi-Stage Build ```dockerfile # 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 ```yaml 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 ```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 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. ```yaml 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 ```yaml 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 ```yaml 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 ```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** --- # 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.