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.

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 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.
# 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-worldDieser Befehl lädt das hello-world-Image von Docker Hub herunter und startet einen Container, der eine Bestätigungsnachricht anzeigt.
# terminal
# List running containers
docker ps
# List all containers (including stopped)
docker ps -a
# List downloaded images
docker images
# Remove a container
docker rm <container_id>
# Remove an image
docker rmi <image_name>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
# 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.
# 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 shDas Flag -d führt den Container im Hintergrund aus, -p verbindet Port 3000 des Containers mit Port 3000 des Hosts.
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.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 /app/dist ./dist
COPY /app/node_modules ./node_modules
COPY /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.
# 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 450MBDie 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.
# 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: bridgeDie Services kommunizieren über ihre Namen (db, cache) durch das interne Docker-Netzwerk. Healthchecks stellen sicher, dass Abhängigkeiten bereit sind, bevor die Anwendung startet.
# 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 --buildBereit für deine DevOps-Interviews?
Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.
Verwaltung von Secrets und Umgebungsvariablen
Die sichere Verwaltung von Secrets ist in der Produktion entscheidend. Docker bietet je nach Kontext verschiedene Ansätze.
# docker-compose.override.yml (development only)
version: "3.9"
services:
app:
env_file:
- .env.development
environment:
- DEBUG=trueFür die Produktion bieten Docker Secrets höhere Sicherheit.
# 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.txtDer Anwendungscode liest Secrets aus gemounteten Dateien.
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.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 \
npm ci --only=production
# ============================================
# Stage: Builder
# ============================================
FROM base AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN \
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 /app/node_modules ./node_modules
# Copy build output
COPY /app/dist ./dist
COPY /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.
# 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:optimizedImages 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.
# 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: trueDiese Konfiguration isoliert Services nach dem Prinzip der geringsten Berechtigung. Die Datenbank ist nur über die API erreichbar.
# 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-containerVolumes und Datenpersistenz
Docker Volumes bewahren Daten über den Lebenszyklus eines Containers hinaus.
# 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: bindDie Unterscheidung zwischen benannten Volumes und Bind Mounts ist wichtig: Volumes werden von Docker verwaltet, während Bind Mounts direkt das Host-Dateisystem nutzen.
# 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 /dataProduktions-Deployment
Ein robuster Deployment-Workflow umfasst Build, Tests und Push in eine Registry.
# 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.
# 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.
# 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.
# 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.logFür tiefgehendes Debugging stehen verschiedene Techniken zur Verfügung.
# 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# 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
Fang an zu üben!
Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.
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.
Tags
Teilen
Verwandte Artikel

Kubernetes Interview: Pods, Services und Deployments im Detail erklärt
Die drei zentralen Kubernetes-Bausteine — Pods, Services und Deployments — mit produktionsreifen YAML-Manifesten, Networking-Internals und typischen Interviewfragen.

Die wichtigsten DevOps-Interviewfragen: Vollständiger Leitfaden 2026
Vorbereitung auf DevOps-Interviews mit den entscheidenden Fragen zu CI/CD, Kubernetes, Docker, Terraform und SRE-Praktiken. Mit ausführlichen Antworten.