Docker hat veraendert, wie wir Software bauen, ausliefern und ausfuehren. Anstatt "es funktioniert auf meinem Rechner" garantiert Docker, dass deine Anwendung ueberall gleich laeuft - auf deinem Laptop, auf dem Rechner eines Kollegen, in CI/CD und in der Produktion. In diesem Leitfaden gehen wir von null bis zum Deployment einer echten Anwendung.
Was ist Docker?
Docker ist eine Plattform, die deine Anwendung und alle ihre Abhaengigkeiten in eine standardisierte Einheit namens Container verpackt. Ein Container ist ein isolierter, leichtgewichtiger Prozess, der den Kernel des Host-Betriebssystems teilt, aber sein eigenes Dateisystem, Netzwerk und seinen eigenen Prozessraum hat.
Container vs Virtuelle Maschinen
| Aspekt | Container | Virtuelle Maschinen |
|---|---|---|
| Start | Sekunden | Minuten |
| Groesse | MBs | GBs |
| Betriebssystem | Teilt Host-Kernel | Vollstaendiges Gast-BS |
| Isolation | Prozess-Ebene | Hardware-Ebene |
| Leistung | Fast nativ | Overhead durch Hypervisor |
| Dichte | Hunderte pro Host | Dutzende pro Host |
Docker installieren
# macOS brew install --cask docker # Ubuntu/Debian curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER # Verify installation docker --version docker run hello-world
Kernkonzepte
Images
Ein Image ist eine schreibgeschuetzte Vorlage mit Anweisungen zum Erstellen eines Containers. Stell es dir als Snapshot deiner Anwendung und ihrer Umgebung vor.
# Pull an image from Docker Hub docker pull node:20-alpine # List local images docker images # Remove an image docker rmi node:20-alpine
Container
Ein Container ist eine laufende Instanz eines Images. Du kannst Container erstellen, starten, stoppen und loeschen.
# Run a container docker run -d --name my-app -p 3000:3000 node:20-alpine # List running containers docker ps # List all containers (including stopped) docker ps -a # Stop a container docker stop my-app # Remove a container docker rm my-app # View logs docker logs my-app # Execute a command inside a running container docker exec -it my-app sh
Ein Dockerfile schreiben
Ein Dockerfile ist eine Textdatei mit Anweisungen zum Erstellen eines Images. Jede Anweisung erstellt einen Layer.
Einfaches Dockerfile fuer eine Node.js-App
# Use an official Node.js runtime as base image FROM node:20-alpine # Set working directory WORKDIR /app # Copy package files first (better caching) COPY package.json package-lock.json ./ # Install dependencies RUN npm ci --only=production # Copy application code COPY . . # Expose the port the app runs on EXPOSE 3000 # Command to run the application CMD ["node", "server.js"]
Bauen und Ausfuehren
# Build the image docker build -t my-node-app . # Run the container docker run -d -p 3000:3000 my-node-app # Visit http://localhost:3000
Multi-Stage Builds
Multi-Stage Builds halten deine Produktions-Images klein, indem sie die Build-Umgebung von der Runtime trennen.
# Stage 1: Build FROM node:20-alpine AS builder WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . RUN npm run build # Stage 2: Production FROM node:20-alpine AS runner WORKDIR /app COPY /app/dist ./dist COPY /app/node_modules ./node_modules COPY /app/package.json ./ EXPOSE 3000 CMD ["node", "dist/server.js"]
Dies erzeugt ein Image mit nur der kompilierten Ausgabe und den Produktionsabhaengigkeiten - kein Quellcode, keine Entwicklungsabhaengigkeiten, keine Build-Werkzeuge.
Next.js Multi-Stage Beispiel
FROM node:20-alpine AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci FROM node:20-alpine AS builder WORKDIR /app COPY /app/node_modules ./node_modules COPY . . RUN npm run build FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production COPY /app/public ./public COPY /app/.next/standalone ./ COPY /app/.next/static ./.next/static EXPOSE 3000 CMD ["node", "server.js"]
Volumes: Persistente Daten
Standardmaessig gehen Daten in einem Container verloren, wenn der Container entfernt wird. Volumes loesen dieses Problem.
# Create a named volume docker volume create my-data # Run with a volume docker run -d -v my-data:/app/data my-app # Bind mount (map host directory to container) docker run -d -v $(pwd)/data:/app/data my-app # List volumes docker volume ls
Networking
Docker erstellt isolierte Netzwerke, damit Container miteinander kommunizieren koennen.
# Create a custom network docker network create my-network # Run containers on the same network docker run -d --name api --network my-network my-api docker run -d --name db --network my-network postgres:16 # Containers can reach each other by name # From "api" container: postgres://db:5432
Docker Compose
Docker Compose ermoeglicht es dir, Multi-Container-Anwendungen mit einer einzigen YAML-Datei zu definieren und auszufuehren.
docker-compose.yml
services: api: build: ./api ports: - "3000:3000" environment: - DATABASE_URL=postgres://user:pass@db:5432/mydb depends_on: - db db: image: postgres:16-alpine environment: - POSTGRES_USER=user - POSTGRES_PASSWORD=pass - POSTGRES_DB=mydb volumes: - pgdata:/var/lib/postgresql/data ports: - "5432:5432" redis: image: redis:7-alpine ports: - "6379:6379" volumes: pgdata:
Befehle
# Start all services docker compose up -d # View logs docker compose logs -f # Stop all services docker compose down # Rebuild and restart docker compose up -d --build # Scale a service docker compose up -d --scale api=3
.dockerignore
Wie .gitignore verhindert diese Datei, dass unnoetige Dateien in das Image kopiert werden.
node_modules .git .env *.md .next dist coverage
Best Practices fuer die Produktion
1. Verwende kleine Basis-Images
# Bad: 1GB+ FROM node:20 # Good: ~180MB FROM node:20-alpine
2. Nicht als Root ausfuehren
FROM node:20-alpine RUN addgroup -S app && adduser -S app -G app USER app WORKDIR /home/app COPY . .
3. Verwende spezifische Image-Tags
# Bad: can change unexpectedly FROM node:latest # Good: pinned version FROM node:20.11-alpine3.19
4. Nutze den Build-Cache
Ordne deine Dockerfile-Anweisungen von den am wenigsten bis zu den am haeufigsten geaenderten:
FROM node:20-alpine WORKDIR /app # These change rarely - cached COPY package.json package-lock.json ./ RUN npm ci --only=production # This changes often - not cached COPY . .
5. Health Checks
HEALTHCHECK \ CMD wget -qO- http://localhost:3000/health || exit 1
6. Verwende Umgebungsvariablen
ENV NODE_ENV=production ENV PORT=3000
Cheat Sheet fuer haeufige Docker-Befehle
# Images docker build -t name:tag . # Build image docker images # List images docker rmi image_name # Remove image docker image prune # Remove unused images # Containers docker run -d -p 3000:3000 image # Run detached docker ps # List running docker stop container_name # Stop docker rm container_name # Remove docker logs -f container_name # Follow logs docker exec -it container sh # Shell into container # Compose docker compose up -d # Start services docker compose down # Stop services docker compose logs -f # Follow all logs docker compose ps # List services # Cleanup docker system prune -a # Remove everything unused
Von Docker zu Kubernetes
Docker verwaltet einzelne Container. Wenn du Hunderte von Containern ueber mehrere Server orchestrieren musst, brauchst du Kubernetes. Docker und Kubernetes ergaenzen sich:
- Docker: baut und fuehrt Container aus
- Kubernetes: orchestriert Container im grossen Massstab (Scheduling, Skalierung, Selbstheilung)
Wenn dich der naechste Schritt interessiert, schau dir meinen Artikel zur Einfuehrung in Kubernetes an.
Fazit
Docker ist eine grundlegende Faehigkeit fuer moderne Entwickler. Es beseitigt Umgebungsinkonsistenzen, vereinfacht das Deployment und ist die Grundlage fuer Container-Orchestrierung mit Kubernetes. Beginne mit einem einfachen Dockerfile, wechsle zu Docker Compose fuer Multi-Service-Apps und uebernimm Multi-Stage Builds und Sicherheits-Best-Practices, waehrend du waechst.
Der beste Weg, Docker zu lernen, ist ein Projekt zu containerisieren, an dem du bereits arbeitest. Fang heute an.