🐳 Modul 4: Container und Docker
Lernziele: Sie verstehen die Unterschiede zwischen Containern und VMs, kennen die Docker-Architektur, können Dockerfiles schreiben, Docker Compose nutzen und Container für Netzwerk-Automatisierung einsetzen.
⏱️ Dauer: ca. 90 Minuten | 🔧 Praxis: 6 Übungen
Ab diesem Modul arbeiten wir auf der GitLab Linux-Maschine! Dort ist Docker installiert und dort laufen später auch die Container in der CI/CD-Pipeline.
So verbinden Sie sich: Öffnen Sie auf Ihrem Desktop MRemoteNG und verbinden Sie sich mit der GitLab-Maschine (SSH).

Alle Befehle und Dateien in diesem und den folgenden Modulen werden auf der Linux-Konsole ausgeführt. Dateien erstellen wir mit vi.
Teil 1: Container vs. Virtuelle Maschinen
Die Umzugskarton-Analogie
Stellen Sie sich vor, Sie ziehen um. Es gibt zwei Strategien:
Strategie A: Ganzes Haus mitnehmen (VM)
Sie packen nicht nur Ihre Möbel ein – Sie nehmen das gesamte Haus mit. Fundament, Wände, Dach, Heizung, alles. Funktioniert überall, aber extrem schwer und langsam zu transportieren.
Strategie B: Umzugskartons (Container)
Sie packen nur das ein, was Sie wirklich brauchen – Möbel, Bücher, Kleidung. Das neue Haus (Betriebssystem) ist schon da. Schnell, leicht, effizient.
Technische Architektur im Vergleich
Der fundamentale Unterschied liegt in der Virtualisierungsebene:
Vergleichstabelle: VM vs. Container
| Eigenschaft | Virtuelle Maschine | Container |
|---|---|---|
| Startzeit | Minuten | Sekunden |
| Größe | Gigabytes (10-50 GB) | Megabytes (50-500 MB) |
| Ressourcen | Fest reserviert | Dynamisch geteilt |
| Isolation | Komplett (eigenes OS) | Prozess-Level |
| Betriebssystem | Beliebig (Linux, Windows) | Teilt Host-Kernel |
| Instanzen pro Host | 10-20 | 100-1000+ |
- VMs: Wenn Sie ein anderes OS brauchen (Windows auf Linux), komplette Isolation kritisch ist, oder Legacy-Software läuft
- Container: Für Microservices, CI/CD-Pipelines, konsistente Entwicklungsumgebungen, und wenn Geschwindigkeit zählt
Die Magie hinter Containern: Linux-Kernel-Features
Container sind keine neue Technologie – sie nutzen Linux-Features, die seit Jahren existieren:
- Namespaces: Isolieren Prozesse, Netzwerk, Dateisystem (jeder Container sieht nur "sein" System)
- cgroups: Limitieren CPU, RAM, I/O pro Container
- Union Filesystems: Ermöglichen Layer-basierte Images (nur Änderungen speichern)
Teil 2: Docker-Architektur verstehen
Docker ist wie ein Fertiggericht-System: Es gibt Rezepte (Dockerfiles), fertige Gerichte im Kühlregal (Images), und die Mikrowelle zum Aufwärmen (Docker Engine).
Die Docker-Komponenten
Docker Daemon (dockerd)
Der Hintergrund-Dienst, der Container erstellt, startet und verwaltet. Läuft als Root-Prozess auf dem Host.
Docker CLI
Die Kommandozeile (docker-Befehl). Kommuniziert mit dem Daemon über eine REST-API.
Docker Images
Read-only Templates mit allen Abhängigkeiten. Bestehen aus Layern – jede Änderung wird als neuer Layer gestapelt.
Container
Laufende Instanz eines Images. Hat einen schreibbaren Layer oben drauf (wird beim Löschen verworfen).
Registry
Image-Ablage. Docker Hub (öffentlich), oder private Registries (GitLab Container Registry, Harbor, Nexus).
Dockerfile
Text-Datei mit Bauanleitung für ein Image. Jede Zeile erzeugt einen Layer.
Der Docker-Workflow visualisiert
┌─────────────┐ docker build ┌─────────────┐
│ Dockerfile │ ──────────────────▶ │ Image │
└─────────────┘ └──────┬──────┘
│
docker push
│
▼
┌─────────────┐ ┌─────────────┐
│ Registry │ ◀──────────────────│ Docker Hub │
│ (GitLab) │ docker pull │ / Private │
└──────┬──────┘ └─────────────┘
│
docker run
│
▼
┌─────────────┐
│ Container │ ◀── Laufende Instanz
└─────────────┘
Image-Layer: Das Zwiebel-Prinzip
Images bestehen aus Schichten (Layers) – wie eine Zwiebel. Jeder Befehl im Dockerfile erzeugt einen neuen Layer:
┌────────────────────────────────────┐ │ Layer 5: CMD ansible-playbook │ ← Startbefehl ├────────────────────────────────────┤ │ Layer 4: RUN ansible-galaxy... │ ← Collections ├────────────────────────────────────┤ │ Layer 3: RUN pip install... │ ← Python-Pakete ├────────────────────────────────────┤ │ Layer 2: RUN apt-get update... │ ← System-Pakete ├────────────────────────────────────┤ │ Layer 1: python:3.11-slim │ ← Basis-Image └────────────────────────────────────┘
- Caching: Unveränderte Layer werden wiederverwendet → schnellere Builds
- Sharing: Mehrere Images teilen sich gleiche Basis-Layer → weniger Speicher
- Best Practice: Häufig ändernde Befehle ans Ende des Dockerfiles
Teil 3: Docker-Befehle meistern
Images verwalten

# Image von Docker Hub herunterladen
docker pull python:3.11
# Mit spezifischem Tag
docker pull python:3.11-slim
# Alle lokalen Images anzeigen
docker images
# Image löschen
docker rmi python:3.11
# Ungenutzte Images aufräumen
docker image prune -aContainer starten und verwalten

# Container interaktiv starten (mit Terminal)
docker run -it python:3.11 bash
# Container im Hintergrund starten
docker run -d --name mein-python python:3.11 sleep infinity
# Container mit automatischem Löschen nach Exit
docker run --rm -it python:3.11 python -c "print('Hello!')"
# Laufende Container anzeigen
docker ps
# Alle Container (auch gestoppte)
docker ps -a
# Container stoppen
docker stop mein-python
# Container starten (nach Stop)
docker start mein-python
# Container löschen
docker rm mein-python
# Alle gestoppten Container löschen
docker container pruneIn laufende Container verbinden
# Shell in laufendem Container öffnen
docker exec -it mein-python bash
# Einzelnen Befehl ausführen
docker exec mein-python cat /etc/os-release
# Als Root einloggen (falls User anders)
docker exec -it --user root mein-python bashDateien und Volumes
# Verzeichnis in Container mounten (Bind Mount)
docker run -it -v ${PWD}:/workspace python:3.11 bash
# Benanntes Volume erstellen und nutzen
docker volume create mein-volume
docker run -it -v mein-volume:/data python:3.11 bash
# Datei in Container kopieren
docker cp lokale-datei.txt mein-python:/tmp/
# Datei aus Container kopieren
docker cp mein-python:/tmp/ergebnis.txt ./Netzwerk
# Port weiterleiten (Host:Container)
docker run -d -p 8080:80 nginx
# Container im Host-Netzwerk (kein NAT)
docker run --network host nginx
# Eigenes Netzwerk erstellen
docker network create mein-netzwerk
# Container in Netzwerk starten
docker run -d --network mein-netzwerk --name web nginx
# Netzwerke anzeigen
docker network lsMachen Sie sich mit Docker vertraut:
- Docker-Installation prüfen:
# Version anzeigen docker --version # Detaillierte Infos docker info # Test-Container ausführen docker run hello-world
🔍 Vergrößerndocker info zeigt Details zur Docker-Installation und Konfiguration - Interaktiven Ubuntu-Container starten:
# Ubuntu-Container mit Bash docker run -it ubuntu:22.04 bash # Im Container: System erkunden cat /etc/os-release whoami pwd ls -la # Container verlassen exit - Container-Lebenszyklus verstehen:
# Container im Hintergrund starten docker run -d --name test-container ubuntu:22.04 sleep 300 # Status prüfen docker ps # In Container verbinden docker exec -it test-container bash exit # Container stoppen und löschen docker stop test-container docker rm test-container
Teil 4: Dockerfiles schreiben
Ein Dockerfile ist wie ein Kochrezept: Schritt für Schritt wird beschrieben, wie das Image gebaut wird.
Dockerfile-Befehle
| Befehl | Beschreibung | Beispiel |
|---|---|---|
| FROM | Basis-Image | FROM python:3.11-slim |
| RUN | Befehl ausführen (beim Build) | RUN pip install ansible |
| COPY | Dateien ins Image kopieren | COPY requirements.txt /app/ |
| ADD | Wie COPY, kann auch URLs/Archive | ADD app.tar.gz /app/ |
| WORKDIR | Arbeitsverzeichnis setzen | WORKDIR /app |
| ENV | Umgebungsvariable setzen | ENV ANSIBLE_HOST_KEY_CHECKING=False |
| EXPOSE | Port dokumentieren | EXPOSE 8080 |
| CMD | Standard-Befehl (überschreibbar) | CMD ["python", "app.py"] |
| ENTRYPOINT | Haupt-Befehl (nicht überschreibbar) | ENTRYPOINT ["ansible-playbook"] |
Beispiel: Python-Netzwerk-Tools Container

# Basis-Image: Schlankes Python
FROM python:3.11-slim
# Metadaten
LABEL maintainer="netzwerk-team@firma.de"
LABEL description="Python mit Netzwerk-Automatisierungs-Tools"
# System-Pakete installieren
RUN apt-get update && apt-get install -y --no-install-recommends \
openssh-client \
iputils-ping \
dnsutils \
curl \
&& rm -rf /var/lib/apt/lists/*
# Python-Pakete installieren
COPY requirements.txt /tmp/
RUN pip install --no-cache-dir -r /tmp/requirements.txt
# Arbeitsverzeichnis
WORKDIR /workspace
# Standard-Befehl
CMD ["python3"]netmiko==4.2.0
napalm==4.1.0
paramiko==3.4.0
netaddr==0.9.0
jinja2==3.1.2
pyyaml==6.0.1
requests==2.31.0
nornir==3.4.1
nornir-netmiko==1.0.1Image bauen und taggen

# Image bauen (im Verzeichnis mit Dockerfile)
docker build -t netzwerk-tools:latest .
# Mit Version taggen
docker build -t netzwerk-tools:1.0.0 .
# Für GitLab Registry taggen
docker build -t 198.18.133.100:5050/workshop/netzwerk-tools:1.0.0 .
# Build-Cache ignorieren (sauberer Build)
docker build --no-cache -t netzwerk-tools:latest .- Projektverzeichnis erstellen:
mkdir docker-netzwerk cd docker-netzwerk - requirements.txt anlegen:
vi requirements.txtFügen Sie folgenden Inhalt ein (mit
iin den Insert-Modus wechseln, dann:wqzum Speichern):📄 requirements.txtnetmiko==4.2.0 netaddr==0.9.0 paramiko==3.4.0 jinja2==3.1.2 pyyaml==6.0.1 - Dockerfile erstellen:
vi Dockerfile📄 DockerfileFROM python:3.11-slim RUN apt-get update && apt-get install -y --no-install-recommends \ openssh-client iputils-ping \ && rm -rf /var/lib/apt/lists/* COPY requirements.txt /tmp/ RUN pip install --no-cache-dir -r /tmp/requirements.txt WORKDIR /workspace CMD ["python3"] - Image bauen:
docker build -t netzwerk-python:1.0 . - Container testen:
docker run -it --rm netzwerk-python:1.0📄 Im Python-Promptfrom netaddr import IPNetwork for ip in IPNetwork('192.168.1.0/30'): print(ip) exit()
Bauen Sie einen Container mit Netzwerk-Analyse-Tools:
- Neues Verzeichnis erstellen:
mkdir docker-nettools cd docker-nettoolsvi Dockerfile📄 DockerfileFROM ubuntu:22.04 LABEL maintainer="ihr-name" LABEL purpose="Network Troubleshooting Tools" # Interaktive Prompts vermeiden ENV DEBIAN_FRONTEND=noninteractive # Netzwerk-Tools installieren RUN apt-get update && apt-get install -y --no-install-recommends \ iputils-ping \ traceroute \ dnsutils \ netcat-openbsd \ curl \ wget \ tcpdump \ nmap \ iproute2 \ net-tools \ iperf3 \ mtr \ && rm -rf /var/lib/apt/lists/* WORKDIR /work CMD ["/bin/bash"] - Image bauen:
docker build -t nettools:1.0 . - Tools testen:
# Container starten docker run -it --rm nettools:1.0 # Im Container: ping -c 3 8.8.8.8 dig google.com traceroute google.com exit
Teil 5: Volumes und persistente Daten
Container sind flüchtig wie Seifenblasen – wenn sie weg sind, sind die Daten auch weg. Für persistente Daten brauchen Sie Volumes.
Drei Arten von Volumes
Bind Mounts
Host-Verzeichnis direkt einbinden. Gut für Entwicklung.
-v /host/pfad:/container/pfadNamed Volumes
Docker-verwaltete Volumes. Portabel und einfach.
-v volume-name:/container/pfadtmpfs Mounts
Im RAM, nicht persistent. Für temporäre/sensible Daten.
--tmpfs /container/pfad# Bind Mount: Lokales Verzeichnis einbinden
docker run -it -v ${PWD}:/workspace python:3.11 bash
# Named Volume erstellen und nutzen
docker volume create ansible-data
docker run -it -v ansible-data:/data python:3.11 bash
# Volume inspizieren
docker volume inspect ansible-data
# Alle Volumes anzeigen
docker volume ls
# Ungenutzte Volumes löschen
docker volume prune- Verzeichnis erstellen:
mkdir docker-volume-test cd docker-volume-testvi hello.py📄 hello.py#!/usr/bin/env python3 from datetime import datetime from netaddr import IPNetwork print(f"=== Netzwerk-Analyse Tool ===") print(f"Ausgeführt: {datetime.now()}") print() subnet = "10.100.0.0/29" print(f"Subnet: {subnet}") print("Nutzbare IPs:") for ip in IPNetwork(subnet).iter_hosts(): print(f" - {ip}") - Mit Bind Mount ausführen:
docker run --rm -v ${PWD}:/workspace netzwerk-python:1.0 python /workspace/hello.py - Interaktiv entwickeln:
# Container starten, Verzeichnis gemountet docker run -it --rm -v ${PWD}:/workspace netzwerk-python:1.0 bash # Im Container: Skript bearbeiten und testen cd /workspace python hello.py # Änderungen am Host sichtbar! exit - Named Volume für persistente Daten:
# Volume erstellen docker volume create test-data # Daten hineinschreiben docker run --rm -v test-data:/data python:3.11 bash -c "echo 'Persistent!' > /data/test.txt" # Daten lesen (in neuem Container!) docker run --rm -v test-data:/data python:3.11 cat /data/test.txt # Volume aufräumen docker volume rm test-data
Teil 6: Ansible im Container
Ansible in einem Container zu betreiben bringt enorme Vorteile:Konsistente Versionen, keine Abhängigkeitskonflikte, und perfekt für CI/CD-Pipelines.
Ansible-Container Dockerfile
# Ansible Container für NDFC/Netzwerk-Automatisierung
FROM python:3.11-slim
# Labels für Wartbarkeit
LABEL maintainer="netzwerk-team@firma.de"
LABEL version="1.0"
LABEL description="Ansible mit Cisco DCNM/NDFC Collections"
# Umgebungsvariablen
ENV ANSIBLE_HOST_KEY_CHECKING=False
ENV ANSIBLE_RETRY_FILES_ENABLED=False
ENV PYTHONUNBUFFERED=1
# System-Abhängigkeiten
RUN apt-get update && apt-get install -y --no-install-recommends \
openssh-client \
sshpass \
git \
&& rm -rf /var/lib/apt/lists/*
# Python-Pakete installieren
RUN pip install --no-cache-dir \
ansible-core==2.15.* \
ansible-pylibssh \
jmespath \
netaddr \
requests \
paramiko
# Ansible Collections installieren
RUN ansible-galaxy collection install \
cisco.dcnm \
ansible.netcommon \
ansible.utils
# Arbeitsverzeichnis
WORKDIR /ansible
# Ansible-Version anzeigen beim Start
CMD ["ansible", "--version"]Container bauen und nutzen
# Image bauen
docker build -t ansible-ndfc:1.0 -f Dockerfile.ansible .
# Ansible-Version prüfen
docker run --rm ansible-ndfc:1.0
# Playbook ausführen (mit gemounteten Dateien)
docker run --rm \
-v ${PWD}:/ansible \
-v $env:USERPROFILE.ssh:/root/.ssh:ro \
ansible-ndfc:1.0 \
ansible-playbook -i inventory.yml playbook.yml
# Interaktiv für Tests
docker run -it --rm \
-v ${PWD}:/ansible \
ansible-ndfc:1.0 bash- Ansible-Container Verzeichnis erstellen:
mkdir docker-ansible cd docker-ansible - Dockerfile erstellen:
vi Dockerfile📄 DockerfileFROM python:3.11-slim ENV ANSIBLE_HOST_KEY_CHECKING=False ENV PYTHONUNBUFFERED=1 RUN pip install --no-cache-dir \ ansible-core==2.15.* \ jmespath \ netaddr RUN ansible-galaxy collection install ansible.utils WORKDIR /ansible CMD ["ansible", "--version"]docker build -t ansible-test:1.0 . - Test-Playbook erstellen:
vi playbook.yml📄 playbook.yml--- - name: Container-Test Playbook hosts: localhost connection: local gather_facts: yes vars: vlans: - id: 100 name: SERVERS - id: 200 name: CLIENTS - id: 300 name: MANAGEMENT tasks: - name: System-Informationen anzeigen ansible.builtin.debug: msg: | Ansible läuft in Container! Hostname: {{ ansible_hostname }} Python: {{ ansible_python_version }} - name: VLAN-Konfiguration generieren ansible.builtin.debug: msg: "VLAN {{ item.id }}: {{ item.name }}" loop: "{{ vlans }}" - name: IP-Berechnung mit netaddr ansible.builtin.debug: msg: "Gateway für 10.{{ item.id }}.0.0/24: 10.{{ item.id }}.0.1" loop: "{{ vlans }}" - Playbook im Container ausführen:
docker run --rm -v ${PWD}:/ansible ansible-test:1.0 ansible-playbook playbook.yml - Interaktive Ansible-Shell:
docker run -it --rm -v ${PWD}:/ansible ansible-test:1.0 bash # Im Container: ansible localhost -m debug -a "msg='Hello from Container!'" ansible localhost -m setup | head -50 exit
Teil 7: Docker Compose
Docker Compose ist wie ein Orchester-Dirigent: Es startet mehrere Container gleichzeitig und koordiniert sie.
Warum Docker Compose?
- Multi-Container Apps: Web-Server + Datenbank + Cache gleichzeitig
- Deklarativ: YAML-Datei beschreibt gewünschten Zustand
- Networking: Container können sich per Namen erreichen
- Entwicklung: Komplexe Setups mit einem Befehl starten
docker-compose.yml Struktur
version: '3.8'
services:
# Service 1: Web-Anwendung
web:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
depends_on:
- api
# Service 2: API Backend
api:
build: ./api
environment:
- DATABASE_URL=postgres://db:5432/app
depends_on:
- db
# Service 3: Datenbank
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:Compose-Befehle
# Alle Services starten
docker compose up
# Im Hintergrund starten
docker compose up -d
# Logs anzeigen
docker compose logs -f
# Status prüfen
docker compose ps
# In Service verbinden
docker compose exec web sh
# Alles stoppen
docker compose down
# Stoppen und Volumes löschen
docker compose down -vNetzwerk-Automatisierung mit Compose
version: '3.8'
services:
# Ansible Automation Container
ansible:
build: .
volumes:
- ./playbooks:/ansible/playbooks
- ./inventory:/ansible/inventory
- ./group_vars:/ansible/group_vars
- $env:USERPROFILE.ssh:/root/.ssh:ro
environment:
- ANSIBLE_HOST_KEY_CHECKING=False
- NDFC_HOST=${NDFC_HOST}
- NDFC_USER=${NDFC_USER}
- NDFC_PASSWORD=${NDFC_PASSWORD}
working_dir: /ansible
command: tail -f /dev/null # Container am Leben halten
# Git-Server für lokale Tests (optional)
gitea:
image: gitea/gitea:latest
ports:
- "3000:3000"
volumes:
- gitea-data:/data
volumes:
gitea-data:In dieser Übung erstellen Sie ein Multi-Container-Setup mit Docker Compose — aber es wird absichtlich ein Fehler eingebaut sein! Nutzen Sie Ihr Troubleshooting-Wissen, um das Problem zu finden und zu lösen.
- Projektverzeichnis erstellen:
mkdir compose-demo cd compose-demo - docker-compose.yml anlegen:
vi docker-compose.yml📄 docker-compose.ymlversion: '3.8' services: # Python Netzwerk-Tools nettools: image: python:3.11-slim volumes: - ./scripts:/scripts working_dir: /scripts command: tail -f /dev/null networks: - automation-net # Web-UI für Dokumentation docs: image: nginx:alpine ports: - "8080:80" volumes: - ./docs:/usr/share/nginx/html:ro networks: - automation-net # Redis für Caching (Beispiel) cache: image: redis:alpine networks: - automation-net networks: automation-net: driver: bridge - Dateien für Services erstellen:
mkdir scripts mkdir docsvi scripts/analyze.py📄 scripts/analyze.py#!/usr/bin/env python3 import socket print("=== Container Netzwerk-Info ===") print(f"Hostname: {socket.gethostname()}") print(f"IP: {socket.gethostbyname(socket.gethostname())}") print() # Test: Andere Container erreichen for host in ['docs', 'cache']: try: ip = socket.gethostbyname(host) print(f"{host}: {ip} ✓") except: print(f"{host}: nicht erreichbar")vi docs/index.html📄 docs/index.html<!DOCTYPE html> <html> <head><title>Automation Docs</title></head> <body> <h1>🚀 Netzwerk-Automatisierung</h1> <p>Dokumentation läuft in einem Container!</p> <ul> <li>nettools: Python mit Netzwerk-Libraries</li> <li>docs: Diese Nginx-Seite</li> <li>cache: Redis für Caching</li> </ul> </body> </html> - Services starten:
docker compose up -d💥 Hier passiert ein Fehler!Port
8080ist auf dieser Maschine bereits durch GitLab belegt. Docker wird eine Fehlermeldung ausgeben, dass der Port nicht gebunden werden kann. - 🔍 Troubleshooting — Finden Sie das Problem:
Nutzen Sie die Befehle aus Teil 8, um den Fehler zu diagnostizieren:
# Status prüfen — welcher Service läuft nicht? docker compose ps # Logs anschauen — was sagt die Fehlermeldung? docker compose logs docs # Welcher Prozess belegt Port 8080? ss -tlnp | grep 8080💡 Lösung anzeigen
Problem: Port 8080 ist bereits von GitLab belegt. Der
docs-Service kann nicht starten.Lösung: Ändern Sie den Port-Mapping in der
docker-compose.ymlauf einen freien Port:# Services stoppen docker compose down # docker-compose.yml bearbeiten: Port ändern vi docker-compose.yml # Ändern Sie "8080:80" zu "8888:80" # Neu starten docker compose up -d # Jetzt sollte es funktionieren! docker compose ps - Services testen (nach dem Fix):
# Web-UI im Browser öffnen: http://198.18.133.100:8888 # Python-Script im Container ausführen docker compose exec nettools python /scripts/analyze.py # Redis testen docker compose exec cache redis-cli ping - Aufräumen:
docker compose down
Teil 8: Docker Troubleshooting
Wenn Container nicht tun, was sie sollen, sind diese Befehle Ihre besten Freunde:
Container-Logs analysieren
# Logs eines Containers anzeigen
docker logs container-name
# Live-Logs (wie tail -f)
docker logs -f container-name
# Letzte 100 Zeilen
docker logs --tail 100 container-name
# Mit Zeitstempeln
docker logs -t container-name
# Logs seit bestimmter Zeit
docker logs --since 10m container-nameContainer inspizieren
# Alle Infos zu einem Container
docker inspect container-name
# Spezifische Infos extrahieren (IP-Adresse)
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container-name
# Environment-Variablen anzeigen
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' container-name
# Mounted Volumes anzeigen
docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}' container-nameIn laufenden Container debuggen
# Shell im Container öffnen
docker exec -it container-name bash
# Oder für Alpine-basierte Images:
docker exec -it container-name sh
# Als Root einloggen
docker exec -it --user root container-name bash
# Prozesse im Container anzeigen
docker exec container-name ps aux
# Netzwerk-Status prüfen
docker exec container-name netstat -tlnp
docker exec container-name cat /etc/resolv.confRessourcen-Verbrauch überwachen

# Live-Statistiken aller Container
docker stats
# Für bestimmte Container
docker stats container-name
# Einmalig (nicht live)
docker stats --no-streamHäufige Probleme und Lösungen
| Problem | Diagnose | Lösung |
|---|---|---|
| Container startet nicht | docker logs | Fehlermeldung prüfen, oft fehlende Env-Vars |
| Port schon belegt | netstat -tlnp | Anderen Port wählen: -p 8081:80 |
| Volume-Berechtigung | ls -la /mount | chmod auf Host oder --user |
| Container beendet sofort | docker ps -a | CMD/ENTRYPOINT prüfen, Prozess läuft nicht |
| Kein Internet im Container | docker exec ... ping 8.8.8.8 | Docker-Netzwerk oder Firewall prüfen |
| Image zu groß | docker history image | Multi-stage Build, slim-Image nutzen |
- Container beendet sofort? Starten Sie mit
tail -f /dev/nullals CMD - Keine Shell im Container? Nutzen Sie
docker cpum Dateien zu extrahieren - Netzwerk-Probleme? Testen Sie mit
--network host
Teil 9: Best Practices für Container in CI/CD
Dockerfile-Optimierung
# ✅ GOOD: Spezifische Version nutzen
FROM python:3.11.7-slim-bookworm
# ✅ GOOD: Kombinierte RUN-Befehle (weniger Layer)
RUN apt-get update && apt-get install -y --no-install-recommends \
openssh-client \
&& rm -rf /var/lib/apt/lists/*
# ✅ GOOD: Requirements zuerst (Cache nutzen)
COPY requirements.txt /tmp/
RUN pip install --no-cache-dir -r /tmp/requirements.txt
# ✅ GOOD: Code als letztes (ändert sich oft)
COPY . /app
# ✅ GOOD: Non-root User
RUN useradd -m appuser
USER appuser
# ✅ GOOD: HEALTHCHECK definieren
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8080/health || exit 1Image-Größe reduzieren
# Stage 1: Build
FROM python:3.11 AS builder
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt
# Stage 2: Runtime (schlank)
FROM python:3.11-slim
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/*
COPY app/ /app/
CMD ["python", "/app/main.py"]Security Best Practices
✅ Tun
- • Spezifische Image-Tags nutzen
- • Non-root User verwenden
- • Secrets als Env-Vars (nicht im Image)
- • Images regelmäßig updaten
- • Minimale Base-Images (slim, alpine)
- • .dockerignore nutzen
❌ Vermeiden
- • latest Tag in Produktion
- • Root im Container
- • Passwörter im Dockerfile
- • Veraltete Base-Images
- • Unnötige Pakete installieren
- • SSH im Container
Warum Docker in CI/CD-Pipelines?
Stellen Sie sich vor: Ein Ansible-Playbook läuft auf dem Laptop eines Kollegen perfekt, aber auf dem CI/CD-Server schlägt es fehl. Warum? Andere Python-Version, fehlende Libraries, unterschiedliche Ansible-Collections. Genau dieses Problem löst Docker in Pipelines.
🎯 Die Kernvorteile
Jeder Pipeline-Run startet mit einem frischen Container aus dem gleichen Image. Keine "Altlasten" von vorherigen Runs — kein "bei mir funktioniert's".
Das Dockerfile ist die Dokumentation der Umgebung. Python 3.11, Ansible 2.15, cisco.dcnm Collection 3.5 — alles festgehalten und reproduzierbar.
Entwickler-Laptop, Staging, Produktion — überall der gleiche Container. Was lokal funktioniert, funktioniert auch in der Pipeline.
Kein pip install bei jedem Run — alles ist schon im Image. Pipeline startet in Sekunden statt Minuten.
Jeder Job läuft isoliert. Ein fehlerhaftes Playbook kann den CI/CD-Server nicht beschädigen — der Container wird einfach gelöscht.
Neues Ansible-Update bricht etwas? Einfach das Image-Tag auf die vorherige Version zurücksetzen. Sofort wieder lauffähig.
Montag: Pipeline läuft mit Ansible 2.15.3. Mittwoch: Server-Update installiert Ansible 2.16.0. Donnerstag: Pipeline bricht ab wegen Breaking Changes in der neuen Version. Niemand versteht warum — es hat "doch gestern noch funktioniert".
Das Image ansible-ndfc:1.0 enthält immer exakt Ansible 2.15.3. Egal ob Server-Updates passieren — der Container ist davon nicht betroffen. Erst wenn Sie bewusst ein neues Image ansible-ndfc:1.1 bauen, ändert sich die Umgebung.
CI/CD-Integration (GitLab CI)
stages:
- build
- test
- deploy
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
build-image:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
run-ansible:
stage: deploy
image: $IMAGE_TAG
script:
- ansible-playbook -i inventory.yml playbook.yml
only:
- main.dockerignore nicht vergessen
# Git
.git
.gitignore
# IDE
.vscode
.idea
*.swp
# Python
__pycache__
*.pyc
.pytest_cache
.venv
venv
# Secrets (NIEMALS ins Image!)
*.pem
*.key
.env
secrets/
# Build-Artefakte
*.log
.coverage
htmlcov/❓ Quiz: Testen Sie Ihr Docker-Wissen (5 Min)
Versuchen Sie, die Fragen ohne Zurückblättern zu beantworten.
1. Was ist der Hauptunterschied zwischen einem Container und einer VM?
Antwort anzeigen
Eine VM virtualisiert die komplette Hardware inkl. eigenem Betriebssystem (Hypervisor-Ebene). Ein Container teilt sich den Kernel des Host-Betriebssystems und isoliert nur den Prozess. Dadurch sind Container deutlich leichter (MB statt GB) und starten in Sekunden statt Minuten.
2. Was ist der Unterschied zwischen einem Docker Image und einem Container?
Antwort anzeigen
Ein Image ist ein Read-only Template (wie eine Blaupause). Ein Container ist eine laufende Instanz eines Images mit einem zusätzlichen schreibbaren Layer. Aus einem Image können beliebig viele Container gestartet werden.
3. Warum sollten Sie Docker in CI/CD-Pipelines einsetzen?
Antwort anzeigen
Docker sorgt für einen definierten Zustand bei jedem Pipeline-Run. Jeder Job startet mit einem frischen Container — gleiche Versionen, gleiche Abhängigkeiten, keine Altlasten. Was lokal funktioniert, funktioniert auch in der Pipeline.
4. Was macht der Befehl docker run -v ${PWD}:/workspace python:3.11 bash?
Antwort anzeigen
Er startet einen Python-3.11-Container interaktiv mit Bash und mountet das aktuelle Verzeichnis ($PWD) als Bind Mount unter /workspace im Container. Änderungen an Dateien sind sowohl im Container als auch auf dem Host sichtbar.
5. Was ist Docker Compose und wann nutzt man es?
Antwort anzeigen
Docker Compose ist ein Tool zum Definieren und Starten von Multi-Container-Anwendungenmit einer YAML-Datei. Statt mehrere docker run-Befehle auszuführen, beschreibt man alle Services, Netzwerke und Volumes deklarativ und startet alles mit docker compose up.
Zusammenfassung
✅ Das haben Sie gelernt
- ☑️ Container vs. VMs: Unterschiede in Architektur und Anwendung
- ☑️ Docker-Architektur: Daemon, CLI, Images, Container, Registry
- ☑️ Dockerfiles schreiben und optimieren
- ☑️ Volumes für persistente Daten nutzen
- ☑️ Docker Compose für Multi-Container-Setups
- ☑️ Netzwerk-Tools (Ansible, Python, netmiko) in Containern
- ☑️ Troubleshooting: logs, exec, inspect
- ☑️ Best Practices für CI/CD-Integration
📋 Spickzettel: Docker-Befehle
Im nächsten Modul lernen Sie, wie Container in CI/CD-Pipelines eingesetzt werden. Dort bauen wir automatisierte Workflows, die Ansible-Container nutzen, um Netzwerk-Konfigurationen zu deployen.