Von Tom Fischer | Werkstudent Java Development
Lesedauer: 12 Minuten
Zielgruppe: Studierende, Junior Devs ohne Docker-Erfahrung
⏱️ 30-Sekunden-Zusammenfassung
Docker klang für mich immer kompliziert: Container, Images, Volumes, Networks… WTF? Aber nach 3 Monaten bei Java Fleet verstehe ich: Docker löst DAS Problem – „Auf meinem Rechner funktioniert’s aber!“ Hier zeige ich dir, wie Docker wirklich funktioniert, ohne dass du direkt zum Kubernetes-Experten werden musst. Von den Basics über Docker Compose bis zu meinen echten Projekten. Learning by doing. 🐳
👋 Hi! Tom hier
Hi! 👋
Tom hier. In der Uni haben wir Docker nicht genutzt. Alles lief lokal: PostgreSQL auf dem Laptop installiert, Maven lokal, Java lokal. Hat funktioniert. Irgendwie.
Bei Java Fleet? Erster Tag. Nova zeigt mir das Projekt:
Nova: „Tom, pull das Repo und starte die App.“
Ich: „Okay, cool. Wo ist die Installationsanleitung für PostgreSQL?“
Nova: „Brauchst du nicht. Mach einfach docker-compose up.“
Ich: „…Docker was?“
Nova (lacht): „Welcome to the real world! ☕“
Warum dieser Artikel? Weil ich vor 3 Monaten NULL Ahnung von Docker hatte. Und jetzt verstehe ich’s. Und wenn ich’s verstanden hab, kannst du es auch verstehen.
Ich erkläre dir Docker wie jemand, der es gerade selbst lernt – nicht wie ein Experte der seit 10 Jahren damit arbeitet.
Zeit, das anzugehen! 🔧
🐳 Was ist Docker überhaupt?
Die Grundidee (ELI5)
Das Problem: Du entwickelst eine App auf deinem Laptop:
- Java 17
- PostgreSQL 15
- Redis 7
- Node 18 für Frontend
Dein Kollege hat:
- Java 11
- PostgreSQL 12
- Kein Redis
- Node 16
Ergebnis: „Auf meinem Rechner funktioniert’s aber!“ 😅
Die Lösung: Docker Ein Container ist wie eine kleine, isolierte Box mit ALLEM was die App braucht.
Metapher (von Elyndra geklaut): Container = Versandbox mit ALLEM drin. Du schickst nicht nur das Produkt, sondern auch das Verpackungsmaterial, die Anleitung, das Werkzeug. Beim Empfänger: Box auf, funktioniert sofort.
Container vs. Virtual Machine
Das dachte ich am Anfang: „Container = Mini-VM?“
Nova: „Nope!“
Der Unterschied:
Virtual Machine:
- Komplettes Betriebssystem
- Eigener Kernel
- 2-10 GB groß
- Startet in Minuten
- Heavy resource usage
Container:
- Nutzt Host-OS-Kernel
- Nur App + Dependencies
- 50-500 MB groß
- Startet in Sekunden
- Lightweight
In der Praxis bedeutet das: Ich kann 10 Container gleichzeitig auf meinem Laptop laufen lassen. 10 VMs? Forget it, mein Laptop würde brennen. 🔥
🏗️ Docker Konzepte erklärt
Image vs. Container
Image = Rezept
- Blueprint für Container
- Read-only
- Kann geteilt werden
Container = Kuchen
- Laufende Instanz eines Images
- Read-write
- Kann gestoppt, gestartet, gelöscht werden
Beispiel:
# Image herunterladen docker pull postgres:15 # Container aus Image erstellen und starten docker run -d --name my-db postgres:15
Was passiert:
- Docker lädt
postgres:15Image (einmal) - Docker erstellt Container
my-dbaus diesem Image - PostgreSQL läuft jetzt in dem Container
Wichtig zu verstehen: Du kannst 10 Container aus dem gleichen Image erstellen. Jeder läuft isoliert.
Dockerfile – Das Rezept
Was ist ein Dockerfile? Eine Textdatei mit Anweisungen, wie ein Image gebaut wird.
Mein erstes Dockerfile (für Java-App):
# Basis-Image (Java 17) FROM eclipse-temurin:17-jdk-alpine # Working Directory im Container WORKDIR /app # pom.xml und source code kopieren COPY pom.xml . COPY src ./src # Maven Build RUN ./mvnw clean package -DskipTests # App starten CMD ["java", "-jar", "target/myapp-1.0.0.jar"]
Was das macht:
- Nimm Java 17 Alpine Image (klein und schnell)
- Erstelle
/appVerzeichnis - Kopiere Code rein
- Baue die App mit Maven
- Wenn Container startet, führe JAR aus
Das hab ich nicht verstanden am Anfang: Jede Zeile (FROM, COPY, RUN) erstellt eine „Layer“. Docker cached Layers. Wenn sich nichts ändert, nutzt Docker Cache. = Schnellere Builds!
Docker Hub – Der App Store für Images
Was ist Docker Hub? Öffentliche Registry für Docker Images. Wie GitHub, aber für Container.
Populäre Images:
postgres– PostgreSQL Datenbankredis– Key-Value Storenginx– Webservernode– Node.js Runtimemysql– MySQL Datenbank
Beispiel:
docker pull nginx:latest
Das lädt das neueste NGINX Image von Docker Hub.
Wichtig: Nutze IMMER Tags (Versionen). Nicht nginx:latest, sondern nginx:1.25. latest kann sich ändern und Breaking Changes bringen.
🚀 Mein erster Docker-Container
PostgreSQL in 30 Sekunden
Früher (ohne Docker):
- PostgreSQL installieren (10 Schritte)
- User anlegen
- Datenbank erstellen
- Password konfigurieren
- Connection-String in App eintragen
- Hoffen dass es funktioniert
Jetzt (mit Docker):
docker run -d \ --name postgres-dev \ -e POSTGRES_PASSWORD=dev123 \ -e POSTGRES_DB=myapp \ -p 5432:5432 \ postgres:15
Done. PostgreSQL läuft.
Was bedeuten die Flags?
-d= Detached mode (läuft im Hintergrund)--name= Container-Name-e= Environment Variable (Config)-p= Port Mapping (Host:Container)
Connection-String in meiner App:
spring.datasource.url=jdbc:postgresql://localhost:5432/myapp spring.datasource.username=postgres spring.datasource.password=dev123
Mind = Blown. Das war SO einfach! 🤯
Container-Management
Laufende Container anzeigen:
docker ps
Output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES abc123def456 postgres:15 "docker..." 2 minutes ago Up 2 minutes 0.0.0.0:5432->5432/tcp postgres-dev
Alle Container (auch gestoppte):
docker ps -a
Container stoppen:
docker stop postgres-dev
Container starten:
docker start postgres-dev
Container löschen:
docker rm postgres-dev
WICHTIG: Wenn du Container löschst, sind DATEN WEG! (Außer du nutzt Volumes – dazu gleich mehr)
📦 Docker Volumes – Daten persistent machen
Das Problem
Situation: Ich hab PostgreSQL Container. Datenbank läuft. Ich erstelle Tabellen, füge Daten ein. Alles gut.
Dann stoppe ich den Container und lösche ihn:
docker stop postgres-dev docker rm postgres-dev
Neuen Container starten:
docker run -d --name postgres-dev -p 5432:5432 postgres:15
Problem: Datenbank ist LEER! Alle Daten weg! 😱
Warum? Container sind ephemeral (vergänglich). Wenn du sie löschst, sind die Daten weg.
Die Lösung: Volumes
Volumes = Persistenter Speicher außerhalb des Containers
Named Volume erstellen:
docker volume create postgres-data
Container mit Volume starten:
docker run -d \ --name postgres-dev \ -e POSTGRES_PASSWORD=dev123 \ -v postgres-data:/var/lib/postgresql/data \ -p 5432:5432 \ postgres:15
Was das -v Flag macht: postgres-data (Host-Volume) wird in /var/lib/postgresql/data (Container-Pfad) gemountet.
Jetzt:
- Container löschen → Daten bleiben
- Neuen Container mit gleichem Volume starten → Daten sind wieder da
Mind = Blown again! 🎉
Bind Mounts vs. Volumes
Bind Mount: Verbindet Host-Verzeichnis direkt mit Container.
docker run -d \ -v /home/tom/data:/var/lib/postgresql/data \ postgres:15
Volume: Docker managed den Speicher (irgendwo in /var/lib/docker/volumes).
Wann was?
- Volumes: Production, Datenbanken (Docker managed Speicher)
- Bind Mounts: Development (direkt in dein Projekt-Verzeichnis)
Bei Java Fleet: Bind Mount für Source Code (Hot Reload), Volume für Datenbanken.
🎼 Docker Compose – Multi-Container Magic
Das Problem mit einzelnen Containern
Meine App braucht:
- PostgreSQL
- Redis
- Backend (Java)
- Frontend (Node)
Mit docker run:
docker run -d --name postgres ... docker run -d --name redis ... docker run -d --name backend ... docker run -d --name frontend ...
Probleme:
- 4 Kommandos
- Manuelles Network-Setup
- Environment Variables überall verteilt
- Wie starte ich alles zusammen?
Die Lösung: Docker Compose
Eine Datei: docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: dev123
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- postgres
- redis
environment:
DB_HOST: postgres
REDIS_HOST: redis
frontend:
build: ./frontend
ports:
- "3000:3000"
depends_on:
- backend
volumes:
postgres-data:
Alles starten:
docker-compose up -d
Alles stoppen:
docker-compose down
DAS ist Game-Changer Level: Maximum! 🚀
Docker Compose Commands
Services starten:
docker-compose up -d
Services stoppen:
docker-compose down
Rebuild und starten:
docker-compose up -d --build
Logs anschauen:
docker-compose logs -f
Nur einen Service starten:
docker-compose up postgres
Services neu starten:
docker-compose restart backend
🏗️ Best Practices die ich gelernt hab
1. Multi-Stage Builds
Problem: Mein erstes Dockerfile war 800 MB groß. 😅
Warum? Ich hatte Maven + Source Code + Dependencies ALLES im finalen Image.
Lösung: Multi-Stage Build
# Stage 1: Build FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /app COPY pom.xml . COPY src ./src RUN mvn clean package -DskipTests # Stage 2: Runtime FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY --from=builder /app/target/*.jar app.jar CMD ["java", "-jar", "app.jar"]
Ergebnis:
- Build-Image: 800 MB
- Runtime-Image: 180 MB
Was passiert: Stage 1 baut die App. Stage 2 nimmt NUR die JAR-Datei. Maven, Source Code, etc. bleiben in Stage 1 (wird verworfen).
Nova hat mir das gezeigt und gesagt: „Dein Image sollte nur das enthalten, was es zum Laufen braucht.“
2. .dockerignore nutzen
Problem: Docker COPY kopiert ALLES. Auch node_modules/, target/, .git/.
Lösung: .dockerignore
# Java target/ *.class *.jar *.war # Maven .mvn/ mvnw mvnw.cmd # IDE .idea/ .vscode/ *.iml # Git .git/ .gitignore # Docker Dockerfile docker-compose.yml # OS .DS_Store Thumbs.db
Ergebnis:
- Schnellere Builds
- Kleinere Images
- Weniger Cache-Invalidierung
3. Layer Caching nutzen
Dockerfile-Reihenfolge ist wichtig!
Schlecht:
FROM eclipse-temurin:17 # Code kopieren (ändert sich oft) COPY . . # Dependencies installieren (ändert sich selten) RUN mvn clean package
Problem: Bei jeder Code-Änderung werden Dependencies neu geladen.
Gut:
FROM eclipse-temurin:17 # Dependencies zuerst (ändert sich selten) COPY pom.xml . RUN mvn dependency:go-offline # Dann Code (ändert sich oft) COPY src ./src RUN mvn package -DskipTests
Warum besser: Wenn nur Source Code ändert, wird mvn dependency:go-offline aus Cache genommen. Spart 5 Minuten pro Build!
Cassian’s Regel: „Was sich selten ändert, kommt nach oben. Was sich oft ändert, nach unten.“
4. Health Checks
Problem: Container läuft. Aber ist die App ready?
Lösung: Health Check
FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY app.jar . HEALTHCHECK --interval=30s --timeout=3s \ CMD curl -f http://localhost:8080/actuator/health || exit 1 CMD ["java", "-jar", "app.jar"]
In docker-compose.yml:
services:
backend:
build: .
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 3s
retries: 3
Was das bringt: Docker weiß, ob App ready ist. Andere Services warten, bis Health Check grün ist.
5. Environment Variables richtig nutzen
Schlecht: Hardcoded Values im Dockerfile.
ENV DB_HOST=localhost ENV DB_PASSWORD=secret123
Gut: In docker-compose.yml oder .env File.
.env Datei:
DB_HOST=postgres DB_PASSWORD=dev123 REDIS_HOST=redis
docker-compose.yml:
services:
backend:
build: .
env_file:
- .env
Warum besser:
- Secrets nicht im Git
- Pro Umgebung unterschiedlich (dev, prod)
- Einfach zu ändern
Wichtig: .env in .gitignore!
💡 Praxis-Tipps aus echten Projekten
Mein aktuelles Setup bei Java Fleet
Projekt-Struktur:
project/ ├── backend/ │ ├── src/ │ ├── pom.xml │ └── Dockerfile ├── frontend/ │ ├── src/ │ ├── package.json │ └── Dockerfile ├── docker-compose.yml ├── .env └── .dockerignore
docker-compose.yml (simplified):
version: '3.8'
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
backend:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${DB_NAME}
SPRING_DATASOURCE_USERNAME: ${DB_USER}
SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
SPRING_REDIS_HOST: redis
volumes:
postgres-data:
Mein täglicher Workflow:
Morgens:
docker-compose up -d
Code ändern, testen…
Feierabend:
docker-compose down
Das war’s. Keine lokale DB-Installation, keine Port-Konflikte, keine „es funktioniert auf meinem Rechner“-Probleme.
Debugging in Docker
Container-Logs anschauen:
docker logs backend docker logs -f backend # Follow mode (live)
In Container reingehen (Shell):
docker exec -it backend sh
Was ich dann mache:
# Umgebung checken env # Prozesse anschauen ps aux # Network-Verbindungen testen ping postgres curl http://localhost:8080/actuator/health # Dateisystem checken ls -la /app
Super hilfreich zum Debuggen!
Common Issues und Lösungen
Problem: „Port already in use“
Error starting userland proxy: listen tcp 0.0.0.0:5432: bind: address already in use
Lösung:
# Finde Prozess auf Port 5432 lsof -i :5432 # Stoppe den Prozess docker stop <container-name>
Problem: „Cannot connect to database“
Debugging:
# Ist Container running? docker ps # Logs checken docker logs postgres # Network checken docker network ls docker network inspect project_default
Häufigster Fehler: Host ist nicht localhost, sondern Container-Name! (postgres, nicht localhost)
Problem: „No space left on device“
Docker Images aufräumen:
# Ungenutzte Images löschen docker image prune -a # Ungenutzte Volumes löschen docker volume prune # Alles löschen (stopped containers, unused networks, images, build cache) docker system prune -a --volumes
Vorsicht: Das löscht wirklich ALLES. Mach Backup von wichtigen Volumes!
🔧 Docker Tools die ich nutze
1. Docker Desktop
GUI für Docker (Mac/Windows).
Features:
- Container/Images visualisiert
- Logs anschauen ohne CLI
- Resource-Usage sehen
- Kubernetes integriert (advanced)
Für Anfänger: Gut zum Verstehen. Für Profis: CLI ist schneller.
2. Lazydocker
Terminal UI für Docker.
# Installation (macOS) brew install lazydocker # Starten lazydocker
Was es kann:
- Container/Images/Volumes verwalten
- Logs live anschauen
- In Container exec’en
- Alles mit Keyboard-Shortcuts
Nova nutzt das und sagt: „Besser als Docker Desktop, schneller als CLI.“
3. Dive
Tool zum Analysieren von Docker Images.
# Installation brew install dive # Image analysieren dive backend:latest
Zeigt:
- Alle Layer eines Images
- Größe pro Layer
- Was wurde hinzugefügt/geändert
- Verschwendeten Space (wasted space)
Super für Optimierung!
❓ FAQ
Q: Soll ich Docker Desktop nutzen oder Docker Engine CLI?
A: Für Anfänger: Docker Desktop (GUI hilft beim Verstehen). Später: CLI reicht völlig. Ich nutze beides – Desktop für Monitoring, CLI für Commands.
Q: Wie viel RAM/CPU braucht Docker?
A: Docker Desktop default: 2 GB RAM, 2 CPUs. Für Development mit mehreren Containern: 4-8 GB RAM empfohlen. Check Docker Desktop Settings → Resources.
Q: Kann ich Docker auf Linux ohne Docker Desktop nutzen?
A: Ja! Linux braucht kein Docker Desktop. Nur Docker Engine installieren. Läuft nativer und performanter.
Q: Was ist der Unterschied zwischen docker run und docker-compose up?
A:
docker run: Startet EINEN Container (low-level)docker-compose up: Startet MEHRERE Container aus YAML-Datei (high-level, orchestration)
Für Single-Container: docker run okay. Für Multi-Container: IMMER docker-compose.
Q: Sollte ich Container für Production nutzen oder nur für Development?
A: Beides! Container sind perfekt für Production. Aber: Für Production brauchst du Orchestration (Kubernetes, Docker Swarm). Das ist next level. Für jetzt: Development reicht völlig.
Q: Was sind die Unterschiede zwischen Docker und Podman?
A: Podman ist Docker-Alternative (daemonless, rootless). Im Job-Kontext: Docker ist Standard. Lern erst Docker, dann kannst du Podman easy verstehen (sehr ähnliche Commands).
Q: Wie sichere ich meine Docker-Daten wenn ich Laptop wechsle?
A:
# Volume backup docker run --rm -v postgres-data:/data -v $(pwd):/backup alpine tar czf /backup/postgres-backup.tar.gz /data # Volume restore docker run --rm -v postgres-data:/data -v $(pwd):/backup alpine sh -c "tar xzf /backup/postgres-backup.tar.gz"
Oder: Nutze Bind Mounts in dein Projekt-Verzeichnis → dann ist alles in Git.
Q: Bernd hat gesagt „Docker ist überhyped, VMs waren besser“. Hat er recht? 🤔
A: Real talk: Bernd hat einen Punkt, lowkey. VMs sind isolierter (Security). Aber für Development? Docker ist SO viel schneller und einfacher.
Bernds Perspektive (vermutlich): „2006 haben wir VMs genutzt. Hat funktioniert. Warum jetzt Container?“
Moderne Perspektive: Container sind leichtgewichtig, starten in Sekunden, perfekt für Microservices. VMs sind heavy, aber manchmal notwendig (komplette OS-Isolation).
Bottom line: Für Development & moderne Microservices: Docker. Für Full-Isolation oder Legacy-Apps: VMs. Beides hat seinen Place. Bernd hatte Recht für seine Zeit. Jetzt ist Container-Zeit. 🐳
🎓 Weiterführende Ressourcen
Für Vertiefung:
- Docker Official Documentation – Umfangreiches Handbuch
- Docker Curriculum – Hands-on Tutorial
- Play with Docker – Docker im Browser testen
YouTube:
- TechWorld with Nana – Docker Tutorial for Beginners
- NetworkChuck – Docker explained in 100 Seconds
Unsere nächsten Themen:
- Testing-Strategien – Unit, Integration, End-to-End (Coming Soon)
- Spring Boot Best Practices – Was ich bei Java Fleet lerne (Coming Soon)
💬 Real Talk: Mittagspause-Gespräch
Java Fleet Küche, 12:30 Uhr. Tom und Nova holen sich Mittagessen aus dem Kühlschrank.
Tom: „Nova, Quick Question: Warum löscht ihr nicht einfach den Container und macht docker-compose up neu? Statt Volume-Management?“
Nova (lacht): „Weil dann alle meine Test-Daten weg sind! Stell dir vor, du hast 100 User-Accounts in der DB angelegt für Tests. Container neu starten → alles weg. No way!“
Tom: „Ahh, makes sense. Aber dann müssen wir Volumes committen?“
Nova: „Nein, Volumes kommen NICHT in Git. Die Daten sind lokal. Für initiale Daten nutzen wir SQL-Scripts in src/main/resources/data.sql.“
Kat (kommt rein): „Redet ihr über Docker?“
Tom: „Yeah, ich verstehe noch nicht ganz wann ich Volumes nutze und wann Bind Mounts.“
Kat: „Simple rule: Volumes für Datenbanken, Bind Mounts für Source Code.“
Nova: „Exactly! Wenn du Hot Reload willst – Code ändern ohne Container neu zu bauen – brauchst du Bind Mount.“
Tom: „Und bei Production?“
Kat: „Production = nur Volumes. Managed von Docker oder Cloud-Provider. Bind Mounts sind Development-Only.“
Tom: „Okay, eine Frage noch: Multi-Stage Builds. Warum so kompliziert?“
Nova: „Weil dein Image sonst riesig ist. Maven + Dependencies + Source + Compiler = 1 GB. Runtime braucht nur JAR + JRE = 200 MB.“
Kat: „Und kleinere Images bedeuten: Schnellere Deployments, weniger Speicher, schnellere Startzeit.“
Tom: „Also: Build-Image für Build-Stuff, Runtime-Image für Runtime-Stuff?“
Nova: „Exactly! Trennung of Concerns, auch bei Docker.“
Kat (grinst): „Tom, du lernst schnell. Vor 3 Monaten wusstest du nicht mal was Docker ist!“
Tom (lacht): „True! Jetzt kann ich wenigstens docker-compose up ohne Panik.“ ☕
📌 Zusammenfassung
Was ich in 3 Monaten gelernt hab:
- Docker löst DAS Problem – „Works on my machine“ → „Works everywhere“
- Container ≠ VMs – Leichtgewichtig, schnell, effizient
- Images sind Blueprints – Container sind Instanzen
- Volumes für Persistenz – Sonst sind Daten weg
- Docker Compose = Essential – Multi-Container einfach managed
- Multi-Stage Builds – Keep Images lean
- Layer Caching nutzen – Schnellere Builds
Bottom Line: Docker ist mächtig. Und am Anfang overwhelming. Aber: Start mit Basics (Container starten/stoppen), dann Docker Compose, dann Optimization. Schritt für Schritt. Ich lerne noch immer jeden Tag dazu.
Die wichtigste Lektion von Nova: „Docker ist ein Tool. Wie IntelliJ oder Git. Du lernst es by doing, nicht by reading.“
Fragen? Docker-Horror-Stories? Bessere Workflows?
Schreib mir! Learning in Public bedeutet auch: Von anderen lernen. 🚀
Tom Fischer
Werkstudent bei Java Fleet Systems Consulting
„From ‚Docker WTF?‘ to ‚docker-compose up‘ in 3 months“
📝 Letzte Aktualisierung: Oktober 2024

