Schlanke Dockerfiles und Shell-Entrypoints schreiben
Erstellen Sie mehrstufige Build-Skripte und robuste Entrypoint-Shims mit Signalverarbeitung und Konfigurations-Templating.
Schlanke Dockerfiles und Shell-Entrypoints schreiben ist eine kostenlose DevOps Bootcamp-Lektion auf CoddyKit. Dies ist Lektion 1 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des DevOps Bootcamp-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der DevOps Bootcamp-Kurs umfasst insgesamt 4 Lektionen.
Warum schlanke Dockerfiles für DevOps wichtig sind
In Produktionsumgebungen verursacht jedes Megabyte in einem Docker-Image Kosten: langsamere Downloads, größere Angriffsflächen und unnötig belegten Speicher in der Registry. Schlanke Dockerfiles in Kombination mit robusten Shell-Entrypoints sind ein Kennzeichen ausgereifter DevOps-Praxis.
- Multi-Stage-Builds trennen Werkzeuge zur Build-Zeit vom finalen Laufzeit-Image und reduzieren dadurch die Größe erheblich.
- Entrypoint-Shims sind kleine Shell-Skripte, die Container initialisieren: Sie erstellen Konfigurationsdateien aus Templates, validieren Umgebungsvariablen, verarbeiten Signale und führen schließlich den Hauptprozess mit
execaus. - Zusammen bilden sie das Rückgrat zuverlässiger, portabler Container-Workloads in Kubernetes, ECS und Bare-Metal-Umgebungen.
Diese Lektion behandelt beide Bereiche durchgehend und zeigt produktionsreife Muster, die Sie direkt in Ihre Pipelines übernehmen können.
Aufbau eines Multi-Stage-Dockerfiles
Ein Multi-Stage-Dockerfile verwendet mehrere FROM-Anweisungen. Jede Stage ist ein isolierter Satz von Layern; Sie kopieren nur die Artefakte in die nächste Stage, die Sie benötigen.
- Stage 0 (builder): installiert Compiler, Test-Runner und Build-Abhängigkeiten.
- Stage 1 (runtime): beginnt mit einer minimalen Basis (z. B.
alpine,distroless) und kopiert nur kompilierte Binärdateien oder App-Bundles. - Das finale Image enthält niemals
gcc,makeoder Quellcode, sofern Sie diese nicht ausdrücklich kopieren.
Verwenden Sie --from=<stage> in COPY, um Dateien über Stage-Grenzen hinweg zu übernehmen. Benennen Sie Stages mit AS <name>, um die Lesbarkeit zu verbessern und mit docker build --target gezielt eine Stage auszuwählen.
# ---- Stage 0: builder ----
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags='-s -w' -o /app/server ./cmd/server
# ---- Stage 1: runtime ----
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]Layer minimieren und den Cache gezielt ungültig machen
Jede RUN-, COPY- und ADD-Anweisung erstellt einen neuen Layer. Ungünstig angeordnete Anweisungen machen den Build-Cache unnötig ungültig und verlangsamen dadurch CI-Pipelines.
- Kopieren Sie Abhängigkeitsdateien (
package.json,go.mod,requirements.txt) vor dem Quellcode, damit die Installation der Abhängigkeiten unabhängig gecacht wird. - Verketten Sie zusammengehörige Befehle mit
&&und bereinigen Sie sie im selbenRUN-Layer, damit Paket-Caches nicht in Zwischen-Layern zurückbleiben. - Verwenden Sie bei Paketmanagern
--no-cacheund entfernen Sie die Listendateien nach der Installation.
FROM python:3.12-slim AS builder
WORKDIR /app
# 1. Install deps first (cached until requirements change)
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# 2. Copy source (cache busted only when code changes)
COPY src/ ./src/
FROM python:3.12-slim
COPY --from=builder /install /usr/local
COPY --from=builder /app/src /app/src
WORKDIR /app
CMD ["python", "-m", "src.main"]BuildKit-Secrets und SSH-Weiterleitung
Private Registries, SSH-Schlüssel und API-Token dürfen niemals in Image-Layern erscheinen. Docker BuildKit stellt zwei sichere Mechanismen bereit:
--secret: bindet eine Secret-Datei innerhalb eines einzelnenRUN-Schritts ein, ohne sie in einem Layer zu speichern. Zugriff über/run/secrets/<id>.--ssh: leitet den SSH-Agent-Socket des Hosts in den Build weiter, sodass sichgit cloneauthentifizieren kann, ohne private Schlüssel einzubetten.
Aktivieren Sie BuildKit mit DOCKER_BUILDKIT=1 oder über docker buildx build. Die Direktive # syntax=docker/dockerfile:1 schaltet diese Funktionen frei.
# syntax=docker/dockerfile:1
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
# Mount NPM token as a secret — never stored in the image
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) \
npm ci --prefer-offline
# Usage at build time:
# DOCKER_BUILDKIT=1 docker build \
# --secret id=npm_token,src=~/.npmrc-token \
# -t myapp:latest .Einen robusten Entrypoint-Shim schreiben
Der Entrypoint-Shim ist ein Shell-Skript, das im Dockerfile als ENTRYPOINT festgelegt wird. Seine Aufgabe besteht darin, die Laufzeitumgebung vorzubereiten, bevor die Kontrolle an den Hauptprozess übergeben wird.
Ein gut strukturierter Shim folgt dieser Reihenfolge:
- Schritt 1: Setzen Sie
set -euo pipefail, damit jeder Fehler den Prozess frühzeitig abbricht. - Schritt 2: Validieren Sie erforderliche Umgebungsvariablen und brechen Sie bei Fehlern mit einer hilfreichen Meldung sofort ab.
- Schritt 3: Erstellen Sie Konfigurationsdateien aus Umgebungsvariablen anhand von Templates.
- Schritt 4: Registrieren Sie Signal-Handler für ein ordnungsgemäßes Herunterfahren.
- Schritt 5:
exec "$@"— ersetzen Sie die Shell durch den Hauptprozess, sodass PID 1 die Anwendung und nicht der Shim ist.
Das abschließende exec ist entscheidend: Ohne diesen Aufruf werden von Kubernetes oder der Docker-Laufzeit gesendete Signale nicht an den Kindprozess weitergeleitet.
#!/usr/bin/env bash
set -euo pipefail
# Step 2: validate required env vars
REQUIRED_VARS=(DATABASE_URL APP_SECRET PORT)
for var in "${REQUIRED_VARS[@]}"; do
if [[ -z "${!var:-}" ]]; then
echo "[entrypoint] ERROR: required env var '$var' is not set" >&2
exit 1
fi
done
# Step 3: template config (see next scene)
# Step 4: signal handling (see scene after that)
# Step 5: hand off to CMD
exec "$@"Konfigurations-Templating mit envsubst
envsubst (aus dem GNU-Paket gettext, in Alpine als gettext verfügbar) ersetzt Platzhalter der Form ${VAR} in einer Template-Datei durch die aktuellen Werte der entsprechenden Umgebungsvariablen.
- Liefern Sie ein
*.tmpl-Konfigurations-Template im Image mit; der Entrypoint rendert es beim Start. - Übergeben Sie die Variablenliste explizit an
envsubst, damit nicht versehentlich andere Dollarzeichen erweitert werden (z. B. in einem nginx-Regulären Ausdruck). - Schreiben Sie die gerenderte Datei in einen beschreibbaren Pfad wie
/tmpoder in ein dediziertes Konfigurations-Volume.
#!/usr/bin/env bash
# Template: /etc/nginx/conf.d/app.conf.tmpl contains:
# server { listen ${NGINX_PORT}; server_name ${SERVER_NAME}; ... }
export NGINX_PORT=${NGINX_PORT:-8080}
export SERVER_NAME=${SERVER_NAME:-localhost}
envsubst '${NGINX_PORT} ${SERVER_NAME}' \
< /etc/nginx/conf.d/app.conf.tmpl \
> /etc/nginx/conf.d/app.conf
echo "[entrypoint] nginx config rendered:"
grep -E 'listen|server_name' /etc/nginx/conf.d/app.conf
exec "$@"Signalverarbeitung und ordnungsgemäßes Herunterfahren
Container empfangen SIGTERM, wenn sie von Kubernetes, ECS oder docker stop gestoppt werden. Wenn Ihr Entrypoint-Shim PID 1 ist und Signale nicht weiterleitet, wird der Hauptprozess nach Ablauf der Grace Period mit SIGKILL beendet — dadurch können Anfragen verloren gehen oder Daten beschädigt werden.
- Verwenden Sie
trap, umSIGTERMundSIGINTim Shim abzufangen. - Leiten Sie das Signal mit
kill -TERM "$child"an die PID des Kindprozesses weiter. - Verwenden Sie
wait "$child", um zu warten, bis der Kindprozess beendet wird, und übernehmen Sie anschließend dessen Exit-Code. - Alternativ können Sie die Shell mit
execvollständig ersetzen — dann stellt das Betriebssystem die Signale direkt dem Kindprozess zu, und Sie benötigen keintrap. Dies ist das bevorzugte Muster für einfache Fälle.
#!/usr/bin/env bash
set -euo pipefail
# Start main process in background
"$@" &
child=$!
# Forward SIGTERM and SIGINT to the child
trap 'echo "[entrypoint] caught SIGTERM, forwarding..."; kill -TERM "$child"' TERM
trap 'echo "[entrypoint] caught SIGINT, forwarding..."; kill -INT "$child"' INT
# Wait for child to exit and capture its exit code
wait "$child"
exit $?tini als minimalen Init-Prozess verwenden
Wenn Ihr Container Kindprozesse startet (z. B. eine Shell, die Worker forkt), benötigen Sie einen echten Init-Prozess, der Zombie-Prozesse aufräumt. tini ist eine speziell für Container entwickelte, sehr kleine Init-Binärdatei.
- Fügen Sie
tinizu Ihrem Image hinzu und legen Sie es als Entrypoint-Wrapper fest. - Es räumt Zombie-Kindprozesse auf, leitet Signale korrekt weiter und beendet sich mit dem Statuscode des Kindprozesses.
- Docker liefert ein integriertes tini mit, das durch
docker run --initaktiviert wird. Wenn Sie es jedoch in das Image einbetten, ist das Verhalten über verschiedene Laufzeiten hinweg konsistent (Kubernetes, ECS usw.).
FROM node:20-alpine
# Install tini for proper signal handling and zombie reaping
RUN apk add --no-cache tini
WORKDIR /app
COPY --chown=node:node . .
RUN npm ci --omit=dev
USER node
# tini wraps CMD; forwards SIGTERM and reaps zombies
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "server.js"]Als Benutzer ohne Root-Rechte ausführen
Container, die als root (UID 0) ausgeführt werden, stellen ein erhebliches Sicherheitsrisiko dar: Ein Ausbruch aus dem Container gewährt vollständigen Zugriff auf den Host. Wechseln Sie stets zu einem nicht privilegierten Benutzer, bevor Sie den Hauptprozess ausführen.
- Erstellen Sie im Dockerfile mit
addgroup/adduser(Alpine) odergroupadd/useradd(Debian) einen dedizierten Systembenutzer und eine dedizierte Systemgruppe. - Ändern Sie den Besitzer der App-Dateien mit
COPY --chown=appuser:appgroup— das ist effizienter als ein separaterRUN chown-Layer. - Wechseln Sie mit der
USER-Anweisung zu diesem Benutzer. Entrypoint und CMD übernehmen diesen Benutzer. securityContext.runAsNonRoot: truein Kubernetes verweigert den Start eines Images, das weiterhin als Root ausgeführt wird.
FROM python:3.12-slim
# Create non-root user
RUN groupadd --gid 1001 appgroup && \
useradd --uid 1001 --gid appgroup --shell /bin/bash --create-home appuser
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy app files with correct ownership in a single layer
COPY --chown=appuser:appgroup src/ ./src/
COPY --chown=appuser:appgroup entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
USER appuser
ENTRYPOINT ["/entrypoint.sh"]
CMD ["python", "-m", "src.main"]Health Checks und Readiness-Probes im Image
Die Liveness- und Readiness-Probes von Kubernetes werden in Manifesten definiert. Sie können jedoch auch ein HEALTHCHECK in das Dockerfile aufnehmen, um es bei eigenständigem docker run und in Docker-Compose-Umgebungen zu verwenden.
HEALTHCHECK --interval=10s --timeout=3s --start-period=15s --retries=3 CMD ...- Verwenden Sie für HTTP-Dienste
curl --failoderwget -qO-; prüfen Sie bei Nicht-HTTP-Daemons einen Socket mit/dev/tcp/localhost/PORT. - Installieren Sie nur, was Sie benötigen: Vermeiden Sie es bei distroless-Images, nur für Health Checks
curlhinzuzufügen — verwenden Sie stattdessen eine speziell dafür entwickelte Probe-Binärdatei oder die eigene Health-Binärdatei der Anwendung.
FROM nginx:1.27-alpine
COPY nginx.conf /etc/nginx/nginx.conf
COPY dist/ /usr/share/nginx/html/
# Lightweight health check using bash TCP pseudo-device
# (no curl needed — works on any image with bash)
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \
CMD bash -c 'exec 3<>/dev/tcp/localhost/80 && echo -e "GET /health HTTP/1.0\r\n" >&3 && cat <&3 | grep -q "200 OK"' || exit 1
EXPOSE 80Alles zusammenführen: ein produktionsreifer Entrypoint
Das Folgende ist ein vollständiger, produktionsreifer Entrypoint-Shim, der alle Muster dieser Lektion kombiniert: Validierung von Umgebungsvariablen, Konfigurations-Templating, Signalweiterleitung und die Übergabe per exec. Dieses Muster wird in realen Node.js-, Python- und Go-Microservices verwendet, die auf Kubernetes bereitgestellt werden.
- Jeder Abschnitt ist kommentiert und dient als selbstdokumentierendes Template.
- Der Shim bleibt unter 50 Zeilen — Entrypoints sollten einfach und leicht überprüfbar sein.
- Beachten Sie das abschließende
exec "$@": Nach Abschluss der gesamten Vorbereitung wird die Shell durch den Anwendungsprozess ersetzt, sodass dieser zu PID 1 wird und alle Betriebssystemsignale direkt empfängt.
#!/usr/bin/env bash
# entrypoint.sh — production-grade container entrypoint shim
set -euo pipefail
# ── 1. Validate required environment variables ──────────────────
REQUIRED=(DATABASE_URL APP_SECRET PORT LOG_LEVEL)
for var in "${REQUIRED[@]}"; do
[[ -n "${!var:-}" ]] || { echo "[entrypoint] FATAL: $var is not set" >&2; exit 1; }
done
# ── 2. Set safe defaults for optional variables ─────────────────
export HOST=${HOST:-0.0.0.0}
export WORKERS=${WORKERS:-2}
# ── 3. Render config template ───────────────────────────────────
if [[ -f /etc/app/app.conf.tmpl ]]; then
envsubst '${DATABASE_URL} ${PORT} ${LOG_LEVEL} ${HOST}' \
< /etc/app/app.conf.tmpl \
> /etc/app/app.conf
echo "[entrypoint] config rendered at /etc/app/app.conf"
fi
# ── 4. Wait for dependent services (optional, fast) ─────────────
if [[ -n "${WAIT_FOR_HOST:-}" ]]; then
echo "[entrypoint] waiting for ${WAIT_FOR_HOST}:${WAIT_FOR_PORT:-5432}..."
until bash -c "exec 3<>/dev/tcp/${WAIT_FOR_HOST}/${WAIT_FOR_PORT:-5432}" 2>/dev/null; do
sleep 1
done
echo "[entrypoint] dependency ready"
fi
# ── 5. Hand off to CMD (PID 1 becomes the application) ──────────
echo "[entrypoint] starting: $*"
exec "$@"Wissenscheck: Signalverarbeitung in Entrypoints
Testen Sie Ihr Verständnis der Signalverarbeitung in Container-Entrypoint-Skripten.
Zusammenfassung: schlanke Dockerfiles und Shell-Entrypoints
Sie haben die gesamte Bandbreite der Erstellung produktionsreifer Container behandelt:
- Multi-Stage-Builds verwenden mehrere
FROM-Anweisungen, um Compiler und Build-Werkzeuge aus dem finalen Image herauszuhalten, und erzeugen dadurch schlanke, minimale Laufzeit-Layer. - Reihenfolge der Layer — kopieren Sie Abhängigkeitsdateien vor dem Quellcode — maximiert Cache-Treffer und beschleunigt CI-Pipelines.
- BuildKit-Secrets und SSH-Mounts halten Zugangsdaten aus der Image-Historie heraus, ohne authentifizierte Builds zu beeinträchtigen.
- Entrypoint-Shims validieren Umgebungsvariablen, erstellen mit
envsubstKonfigurationsdateien aus Templates und übergeben die Kontrolle mitexec "$@"an die Anwendung. - Signalverarbeitung erfordert entweder
exec(sodass die Anwendung PID 1 ist) oder bei Verwendung von Hintergrundjobs ein explizites Muster austrap+kill+wait. - tini übernimmt das Aufräumen von Zombie-Prozessen und die korrekte Signalweiterleitung, wenn der Container mehrere Prozesse startet.
- Benutzer ohne Root-Rechte und
HEALTHCHECK-Anweisungen vervollständigen ein sicheres, überwachbares Image für produktive Kubernetes-Workloads.
Wenn Sie diese Muster konsequent kombinieren, werden Ihre Images kleiner, schneller bereitzustellen und unter Produktionsbedingungen deutlich robuster.
Häufig gestellte Fragen
Ist die Lektion „Schlanke Dockerfiles und Shell-Entrypoints schreiben“ kostenlos?
Ja — der vollständige Text von „Schlanke Dockerfiles und Shell-Entrypoints schreiben“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des DevOps Bootcamp-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der DevOps Bootcamp-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Schlanke Dockerfiles und Shell-Entrypoints schreiben“?
Erstellen Sie mehrstufige Build-Skripte und robuste Entrypoint-Shims mit Signalverarbeitung und Konfigurations-Templating. Du übst DevOps Bootcamp mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um DevOps Bootcamp zu starten?
Keine Vorkenntnisse erforderlich. DevOps Bootcamp auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 1 von 4.
Wie lange dauert die Lektion „Schlanke Dockerfiles und Shell-Entrypoints schreiben“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser DevOps Bootcamp-Lektion Code schreiben und ausführen?
Ja. Jede DevOps Bootcamp-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Schlanke Dockerfiles und Shell-Entrypoints schreiben
- Konfigurationen mit envsubst und Heredocs templatisieren
- Cloud-Ressourcen per CLI und jq skripten
- Health-Probes, Readiness-Gates und Warteschleifen