# Docker: Von der Entwicklung zur Produktion > Vollständige Docker-Anleitung zur Containerisierung von Anwendungen. Dockerfile, Docker Compose, Multi-Stage-Builds und Produktions-Deployment mit praktischen Beispielen. - Published: 2026-01-21 - Updated: 2026-04-10 - Author: SharpSkill - Tags: docker, containerization, devops, docker compose, deployment - Reading time: 18 min --- Docker revolutioniert die Art und Weise, wie Anwendungen entwickelt, getestet und bereitgestellt werden. Durch die Kapselung einer Anwendung und ihrer Abhängigkeiten in einem portablen Container beseitigt Docker das berüchtigte Problem "Es funktioniert auf meinem Rechner" und gewährleistet Konsistenz über alle Umgebungen hinweg. Dieser Leitfaden behandelt den vollständigen Weg vom ersten Dockerfile bis zum Produktions-Deployment. > **Docker im Jahr 2026** > > Docker Desktop 5.x bringt erhebliche Leistungsverbesserungen, darunter native containerd-Unterstützung, optimierte Ressourcenverwaltung und nahtlose Kubernetes-Integration. Multi-Architektur-Images (ARM/x86) sind mittlerweile Standardpraxis. ## Grundlagen der Containerisierung Ein Container ist eine leichtgewichtige Softwareeinheit, die Code, Runtime, Systembibliotheken und Einstellungen bündelt. Im Gegensatz zu virtuellen Maschinen, die Hardware virtualisieren, teilen Container den Kernel des Host-Systems, was sie schneller startbar und ressourcenschonender macht. ```bash # terminal # Docker installation on Ubuntu sudo apt update sudo apt install -y docker.io # Add user to docker group (avoids sudo) sudo usermod -aG docker $USER # Verify installation docker --version # Docker version 26.1.0, build 1234567 # First container: downloads image and runs docker run hello-world ``` Dieser Befehl lädt das `hello-world`-Image von Docker Hub herunter und startet einen Container, der eine Bestätigungsnachricht anzeigt. ```bash # terminal # List running containers docker ps # List all containers (including stopped) docker ps -a # List downloaded images docker images # Remove a container docker rm # Remove an image docker rmi ``` Diese grundlegenden Befehle verwalten den Lebenszyklus von Containern und Images. ## Das erste Dockerfile erstellen Ein Dockerfile enthält Anweisungen zum Erstellen eines Docker-Images. Jede Anweisung erzeugt eine Schicht im finalen Image und ermöglicht so Caching und Wiederverwendung. ```dockerfile # Dockerfile # Base image: Node.js 22 on Alpine Linux (lightweight) FROM node:22-alpine # Set working directory in the container WORKDIR /app # Copy dependency files first (cache optimization) COPY package*.json ./ # Install dependencies RUN npm ci --only=production # Copy source code COPY . . # Expose port (documentation) EXPOSE 3000 # Startup command CMD ["node", "server.js"] ``` Die Reihenfolge der Anweisungen ist entscheidend für die Cache-Optimierung. Dateien, die sich selten ändern (package.json), sollten vor dem Quellcode kopiert werden. ```bash # terminal # Build image with a tag docker build -t my-app:1.0 . # Run the container docker run -d -p 3000:3000 --name my-app-container my-app:1.0 # Check logs docker logs my-app-container # Access container shell docker exec -it my-app-container sh ``` Das Flag `-d` führt den Container im Hintergrund aus, `-p` verbindet Port 3000 des Containers mit Port 3000 des Hosts. > **Alpine vs Debian** > > Alpine-Images sind deutlich kleiner (etwa 5 MB gegenüber 120 MB bei Debian). Allerdings verwenden sie musl libc anstelle von glibc, was zu Inkompatibilitäten mit einigen nativen Abhängigkeiten führen kann. Bei Problemen empfiehlt es sich, Debian-basierte Images (node:22-slim) zu bevorzugen. ## Multi-Stage-Builds für die Produktion Multi-Stage-Builds erstellen optimierte Produktions-Images, indem sie die Build-Umgebung von der Laufzeitumgebung trennen. Nur die notwendigen Artefakte werden in das finale Image aufgenommen. ```dockerfile # Dockerfile.production # ============================================ # Stage 1: Build # ============================================ FROM node:22-alpine AS builder WORKDIR /app # Copy and install dependencies (including devDependencies) COPY package*.json ./ RUN npm ci # Copy source code COPY . . # Build the application (TypeScript, bundling, etc.) RUN npm run build # ============================================ # Stage 2: Production # ============================================ FROM node:22-alpine AS production # Non-root user for security RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 WORKDIR /app # Copy only necessary files from builder stage COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/package.json ./ # Switch to non-root user USER nodejs # Environment variables ENV NODE_ENV=production ENV PORT=3000 EXPOSE 3000 # Startup command CMD ["node", "dist/server.js"] ``` Dieser Ansatz reduziert die Größe des finalen Images erheblich, da Build-Tools, devDependencies und Quelldateien ausgeschlossen werden. ```bash # terminal # Build with specific file docker build -f Dockerfile.production -t my-app:production . # Compare image sizes docker images | grep my-app # my-app production abc123 150MB # my-app 1.0 def456 450MB ``` Die Größenreduktion kann je nach Projekt 60-70% erreichen, was die Deployment-Zeiten verbessert und die Angriffsfläche verringert. ## Docker Compose für lokale Orchestrierung Docker Compose vereinfacht die Verwaltung von Multi-Container-Anwendungen. Eine YAML-Datei deklariert alle Services, deren Konfigurationen und Abhängigkeiten. ```yaml # docker-compose.yml version: "3.9" services: # Main application app: build: context: . dockerfile: Dockerfile ports: - "3000:3000" environment: - NODE_ENV=development - DATABASE_URL=postgresql://postgres:secret@db:5432/myapp - REDIS_URL=redis://cache:6379 volumes: # Mount source code for hot-reload - ./src:/app/src - ./package.json:/app/package.json depends_on: db: condition: service_healthy cache: condition: service_started networks: - app-network # PostgreSQL database db: image: postgres:16-alpine environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: secret POSTGRES_DB: myapp volumes: # Data persistence - postgres_data:/var/lib/postgresql/data # Initialization script - ./init.sql:/docker-entrypoint-initdb.d/init.sql ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 networks: - app-network # Redis cache cache: image: redis:7-alpine ports: - "6379:6379" volumes: - redis_data:/data command: redis-server --appendonly yes networks: - app-network # Named volumes for persistence volumes: postgres_data: redis_data: # Dedicated network for isolation networks: app-network: driver: bridge ``` Die Services kommunizieren über ihre Namen (db, cache) durch das interne Docker-Netzwerk. Healthchecks stellen sicher, dass Abhängigkeiten bereit sind, bevor die Anwendung startet. ```bash # terminal # Start all services docker compose up -d # View logs from all services docker compose logs -f # Logs from a specific service docker compose logs -f app # Stop and remove containers docker compose down # Removal including volumes (caution: data loss) docker compose down -v # Rebuild after Dockerfile changes docker compose up -d --build ``` ## Verwaltung von Secrets und Umgebungsvariablen Die sichere Verwaltung von Secrets ist in der Produktion entscheidend. Docker bietet je nach Kontext verschiedene Ansätze. ```yaml # docker-compose.override.yml (development only) version: "3.9" services: app: env_file: - .env.development environment: - DEBUG=true ``` Für die Produktion bieten Docker Secrets höhere Sicherheit. ```yaml # docker-compose.production.yml version: "3.9" services: app: build: context: . dockerfile: Dockerfile.production secrets: - db_password - api_key environment: - NODE_ENV=production - DATABASE_PASSWORD_FILE=/run/secrets/db_password - API_KEY_FILE=/run/secrets/api_key secrets: db_password: file: ./secrets/db_password.txt api_key: file: ./secrets/api_key.txt ``` Der Anwendungscode liest Secrets aus gemounteten Dateien. ```javascript // config/secrets.js const fs = require('fs'); const path = require('path'); // Utility function to read Docker secrets function readSecret(secretName) { const secretPath = `/run/secrets/${secretName}`; // Check if secret file exists if (fs.existsSync(secretPath)) { return fs.readFileSync(secretPath, 'utf8').trim(); } // Fallback to classic environment variables const envVar = secretName.toUpperCase(); return process.env[envVar]; } module.exports = { databasePassword: readSecret('db_password'), apiKey: readSecret('api_key'), }; ``` Dieser Ansatz vermeidet die Offenlegung von Secrets in Umgebungsvariablen oder Docker-Images. ## Optimierung von Docker-Images Verschiedene Techniken reduzieren die Image-Größe und verbessern die Leistung. ```dockerfile # Dockerfile.optimized FROM node:22-alpine AS base # Install necessary tools in a single layer RUN apk add --no-cache \ dumb-init \ && rm -rf /var/cache/apk/* # ============================================ # Stage: Dependencies # ============================================ FROM base AS deps WORKDIR /app # Copy only lock files for caching COPY package.json package-lock.json ./ # Install with mounted npm cache (BuildKit) RUN --mount=type=cache,target=/root/.npm \ npm ci --only=production # ============================================ # Stage: Builder # ============================================ FROM base AS builder WORKDIR /app COPY package.json package-lock.json ./ RUN --mount=type=cache,target=/root/.npm \ npm ci COPY . . RUN npm run build # ============================================ # Stage: Production # ============================================ FROM base AS production # Image metadata LABEL maintainer="team@example.com" LABEL version="1.0" LABEL description="Production-ready Node.js application" # Non-root user RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 WORKDIR /app # Copy production dependencies COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules # Copy build output COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist COPY --from=builder --chown=nodejs:nodejs /app/package.json ./ USER nodejs ENV NODE_ENV=production # dumb-init as PID 1 for signal handling ENTRYPOINT ["dumb-init", "--"] CMD ["node", "dist/server.js"] ``` Die Verwendung von `dumb-init` gewährleistet die korrekte Behandlung von Unix-Signalen und ermöglicht ein graceful Herunterfahren des Containers. ```bash # terminal # Enable BuildKit for advanced features export DOCKER_BUILDKIT=1 # Build with cache and detailed output docker build --progress=plain -t my-app:optimized . # Analyze image layers docker history my-app:optimized # Detailed image inspection docker inspect my-app:optimized ``` > **Image-Sicherheit** > > Images sollten regelmäßig mit Tools wie Trivy oder Snyk auf Schwachstellen gescannt werden. Basis-Images müssen regelmäßig aktualisiert werden, um Sicherheitspatches einzuschließen. ## Erweiterte Docker-Netzwerke Docker bietet verschiedene Netzwerktreiber für unterschiedliche Anwendungsfälle. ```yaml # docker-compose.networking.yml version: "3.9" services: # Publicly accessible frontend frontend: build: ./frontend ports: - "80:80" networks: - frontend-network - backend-network # API accessible only from frontend api: build: ./api networks: - backend-network - database-network # No external ports exposed # Isolated database database: image: postgres:16-alpine networks: - database-network # Accessible only by API networks: frontend-network: driver: bridge backend-network: driver: bridge internal: true # No internet access database-network: driver: bridge internal: true ``` Diese Konfiguration isoliert Services nach dem Prinzip der geringsten Berechtigung. Die Datenbank ist nur über die API erreichbar. ```bash # terminal # Inspect Docker networks docker network ls # Details of a specific network docker network inspect app-network # Create a custom network docker network create --driver bridge --subnet 172.28.0.0/16 custom-network # Connect a container to an existing network docker network connect custom-network my-container ``` ## Volumes und Datenpersistenz Docker Volumes bewahren Daten über den Lebenszyklus eines Containers hinaus. ```yaml # docker-compose.volumes.yml version: "3.9" services: app: image: my-app:latest volumes: # Named volume for persistent data - app_data:/app/data # Bind mount for development - ./uploads:/app/uploads:rw # Read-only mount for configuration - ./config:/app/config:ro backup: image: alpine volumes: # Access same volume for backups - app_data:/data:ro - ./backups:/backups command: | sh -c "tar czf /backups/backup-$$(date +%Y%m%d).tar.gz /data" volumes: app_data: driver: local driver_opts: type: none device: /path/to/host/data o: bind ``` Die Unterscheidung zwischen benannten Volumes und Bind Mounts ist wichtig: Volumes werden von Docker verwaltet, während Bind Mounts direkt das Host-Dateisystem nutzen. ```bash # terminal # List volumes docker volume ls # Inspect a volume docker volume inspect app_data # Remove orphaned volumes docker volume prune # Backup a volume docker run --rm -v app_data:/data -v $(pwd):/backup alpine \ tar czf /backup/volume-backup.tar.gz /data ``` ## Produktions-Deployment Ein robuster Deployment-Workflow umfasst Build, Tests und Push in eine Registry. ```bash # deploy.sh #!/bin/bash set -e # Variables REGISTRY="registry.example.com" IMAGE_NAME="my-app" VERSION=$(git describe --tags --always) echo "Building version: $VERSION" # Build production image docker build \ -f Dockerfile.production \ -t $REGISTRY/$IMAGE_NAME:$VERSION \ -t $REGISTRY/$IMAGE_NAME:latest \ --build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \ --build-arg VERSION=$VERSION \ . # Security scan echo "Running security scan..." docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/trivy image $REGISTRY/$IMAGE_NAME:$VERSION # Push to registry echo "Pushing to registry..." docker push $REGISTRY/$IMAGE_NAME:$VERSION docker push $REGISTRY/$IMAGE_NAME:latest echo "Deployment complete: $REGISTRY/$IMAGE_NAME:$VERSION" ``` Für Server-Deployments passt eine separate Produktions-Compose-Datei die Konfiguration an. ```yaml # docker-compose.prod.yml version: "3.9" services: app: image: registry.example.com/my-app:latest restart: always deploy: replicas: 3 resources: limits: cpus: "0.5" memory: 512M reservations: cpus: "0.25" memory: 256M update_config: parallelism: 1 delay: 10s failure_action: rollback healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s logging: driver: "json-file" options: max-size: "10m" max-file: "3" ``` Diese Konfiguration definiert Ressourcenzuweisung, Update-Strategie und Healthchecks für ein zuverlässiges Deployment. ```bash # terminal # Production deployment with Docker Compose docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d # Zero-downtime update (rolling update) docker compose -f docker-compose.yml -f docker-compose.prod.yml pull docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps app # Rollback if issues occur docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps \ --scale app=0 && \ docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps app ``` ## Überwachung und Debugging von Containern Container-Monitoring ist in der Produktion unverzichtbar. ```bash # terminal # Real-time statistics for all containers docker stats # Statistics for a specific container with custom format docker stats my-app --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" # Inspect processes in a container docker top my-app # Real-time Docker events docker events --filter container=my-app # Copy files from/to a container docker cp my-app:/app/logs/error.log ./error.log ``` Für tiefgehendes Debugging stehen verschiedene Techniken zur Verfügung. ```bash # terminal # Interactive shell in a running container docker exec -it my-app sh # Execute a single command docker exec my-app cat /app/config/settings.json # Start a container in debug mode docker run -it --rm --entrypoint sh my-app:latest # Inspect environment variables docker exec my-app printenv # Analyze logs with filters docker logs my-app --since 1h --tail 100 | grep ERROR ``` ```yaml # docker-compose.monitoring.yml version: "3.9" services: app: # ... existing configuration labels: - "prometheus.scrape=true" - "prometheus.port=3000" - "prometheus.path=/metrics" prometheus: image: prom/prometheus:latest ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus_data:/prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.retention.time=15d' grafana: image: grafana/grafana:latest ports: - "3001:3000" volumes: - grafana_data:/var/lib/grafana environment: - GF_SECURITY_ADMIN_PASSWORD=secret volumes: prometheus_data: grafana_data: ``` Dieser Monitoring-Stack ermöglicht das Sammeln und Visualisieren von Container-Metriken. ## Fazit Docker transformiert den Entwicklungszyklus durch die Gewährleistung von Konsistenz über alle Umgebungen hinweg. Containerisierung bringt Portabilität, Isolation und Reproduzierbarkeit — wesentliche Eigenschaften für moderne Anwendungen. ### Docker-Checkliste für die Produktion - ✅ Multi-Stage-Builds für optimierte Images - ✅ Nicht-Root-Benutzer in Containern - ✅ Healthchecks für alle Services konfiguriert - ✅ Secrets über Docker Secrets oder sichere Umgebungsvariablen verwaltet - ✅ Ressourcenlimits (CPU, Speicher) definiert - ✅ Volumes für kritische Datenpersistenz - ✅ Zentralisiertes Logging mit Dateirotation - ✅ Sicherheitsscan der Images vor dem Deployment - ✅ Update-Strategie ohne Ausfallzeit - ✅ Isoliertes Netzwerk zwischen Services Die Beherrschung von Docker ist eine grundlegende Fähigkeit für jeden modernen Entwickler. Von der lokalen Umgebung bis zum Produktions-Deployment standardisiert Docker Workflows und vereinfacht den Betrieb. Die hier vorgestellten Konzepte bilden eine solide Grundlage für die Erkundung von Kubernetes und der Container-Orchestrierung im großen Maßstab. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/de/blog/devops/docker-from-development-to-production