Odchudzone Dockerfile i punkty wejścia powłoki
Twórz wieloetapowe skrypty budowania oraz solidne nakładki entrypoint z obsługą sygnałów i szablonami konfiguracji.
Odchudzone Dockerfile i punkty wejścia powłoki to bezpłatna lekcja DevOps Bootcamp na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej DevOps Bootcamp, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs DevOps Bootcamp zawiera 4 lekcji w sumie.
Dlaczego odchudzone pliki Dockerfile mają znaczenie w DevOps
W środowiskach produkcyjnych każdy megabajt obrazu Docker wiąże się z kosztami: wolniejszym pobieraniem, większą powierzchnią ataku i niepotrzebnie zajętym miejscem w rejestrze. Odchudzone pliki Dockerfile w połączeniu z solidnymi skryptami entrypointu są oznaką dojrzałych praktyk DevOps.
- Wielostopniowe budowanie oddziela narzędzia potrzebne podczas budowania od końcowego obrazu uruchomieniowego, znacznie zmniejszając jego rozmiar.
- Skrypty pośredniczące entrypointu to niewielkie skrypty powłoki, które inicjalizują kontenery: tworzą pliki konfiguracji na podstawie szablonów, sprawdzają zmienne środowiskowe, obsługują sygnały, a na końcu wykonują główny proces za pomocą
exec. - Razem stanowią podstawę niezawodnych i przenośnych obciążeń kontenerowych w Kubernetes, ECS i środowiskach bezpośrednio na serwerach.
W tej lekcji omówiono oba obszary od początku do końca, wykorzystując gotowe do zastosowania w produkcji wzorce, które można bezpośrednio włączyć do potoków.
Budowa wielostopniowego pliku Dockerfile
Wielostopniowy plik Dockerfile wykorzystuje wiele instrukcji FROM. Każdy etap jest odizolowanym zestawem warstw; do następnego etapu należy kopiować wyłącznie potrzebne artefakty.
- Etap 0 (builder): instaluje kompilatory, narzędzia do uruchamiania testów i zależności wymagane podczas budowania.
- Etap 1 (runtime): bazuje na minimalnym obrazie (np.
alpine,distroless) i kopiuje wyłącznie skompilowane pliki binarne lub pakiety aplikacji. - Końcowy obraz nigdy nie zawiera
gcc,makeani kodu źródłowego, chyba że zostaną one jawnie skopiowane.
W instrukcji COPY należy użyć --from=<stage>, aby pobierać pliki między granicami etapów. Etapy należy nazywać za pomocą AS <name>, aby zwiększyć czytelność i umożliwić wybieranie konkretnego etapu za pomocą docker build --target.
# ---- 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"]Minimalizowanie warstw i unieważnianie cache
Każda instrukcja RUN, COPY i ADD tworzy nową warstwę. Nieprawidłowa kolejność instrukcji niepotrzebnie unieważnia pamięć podręczną budowania, spowalniając potoki CI.
- Manifesty zależności (
package.json,go.mod,requirements.txt) należy kopiować przed kodem źródłowym, aby instalowanie zależności było niezależnie buforowane. - Powiązane polecenia należy łączyć za pomocą
&&i czyścić w tej samej warstwieRUN, aby uniknąć pozostawiania pamięci podręcznej pakietów w warstwach pośrednich. - Należy używać opcji
--no-cachew menedżerach pakietów i usuwać pliki list po instalacji.
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"]Sekrety BuildKit i przekazywanie SSH
Prywatne rejestry, klucze SSH i tokeny API nigdy nie powinny pojawiać się w warstwach obrazu. Docker BuildKit udostępnia dwa bezpieczne mechanizmy:
--secret: montuje plik z sekretem w ramach pojedynczego krokuRUN, nie zapisując go w warstwie. Dostęp do niego uzyskuje się przez/run/secrets/<id>.--ssh: przekazuje gniazdo agenta SSH hosta do procesu budowania, dzięki czemugit clonemoże uwierzytelnić się bez umieszczania kluczy prywatnych w obrazie.
BuildKit należy włączyć za pomocą DOCKER_BUILDKIT=1 lub przez docker buildx build. Dyrektywa # syntax=docker/dockerfile:1 odblokowuje te funkcje.
# 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 .Tworzenie solidnego skryptu entrypointu
Skrypt pośredniczący entrypointu to skrypt powłoki ustawiony jako ENTRYPOINT w pliku Dockerfile. Jego zadaniem jest przygotowanie środowiska uruchomieniowego przed przekazaniem sterowania głównemu procesowi.
Dobrze zorganizowany skrypt pośredniczący powinien działać w następującej kolejności:
- Krok 1: Ustawić
set -euo pipefail, aby każda awaria powodowała szybkie przerwanie działania. - Krok 2: Sprawdzić wymagane zmienne środowiskowe i natychmiast przerwać działanie, wyświetlając pomocny komunikat.
- Krok 3: Utworzyć pliki konfiguracji na podstawie szablonów i zmiennych środowiskowych.
- Krok 4: Zarejestrować programy obsługi sygnałów na potrzeby kontrolowanego zamykania.
- Krok 5:
exec "$@"— zastąpić powłokę głównym procesem, aby PID 1 należał do aplikacji, a nie do skryptu pośredniczącego.
Końcowe exec ma kluczowe znaczenie: bez niego sygnały wysyłane przez Kubernetes lub środowisko uruchomieniowe Docker nie są przekazywane procesowi potomnemu.
#!/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 "$@"Szablonowanie konfiguracji za pomocą envsubst
envsubst (z pakietu GNU gettext, dostępnego w Alpine jako gettext) zastępuje symbole zastępcze ${VAR} w pliku szablonu bieżącymi wartościami zmiennych środowiskowych.
- Należy dołączyć do obrazu szablon konfiguracji
*.tmpl; entrypoint renderuje go podczas uruchamiania. - Listę zmiennych należy jawnie przekazać do
envsubst, aby uniknąć przypadkowego rozwinięcia niezwiązanych znaków dolara (np. w wyrażeniu regularnym nginx). - Wyrenderowany plik należy zapisać w zapisywalnej lokalizacji, takiej jak
/tmp, lub w przeznaczonym do tego wolumenie konfiguracji.
#!/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 "$@"Obsługa sygnałów i kontrolowane zamykanie
Kontenery otrzymują SIGTERM, gdy są zatrzymywane przez Kubernetes, ECS lub docker stop. Jeśli skrypt pośredniczący entrypointu jest procesem PID 1 i nie przekazuje sygnałów dalej, główny proces zostaje zakończony za pomocą SIGKILL po upływie okresu oczekiwania — może to spowodować utratę żądań lub uszkodzenie danych.
- Należy użyć
trap, aby przechwytywać sygnałySIGTERMiSIGINTw skrypcie pośredniczącym. - Sygnał należy przekazać do procesu potomnego za pomocą
kill -TERM "$child". - Należy użyć
wait "$child", aby wstrzymać działanie do zakończenia procesu potomnego, a następnie przekazać dalej jego kod wyjścia. - Alternatywnie można użyć
exec, aby całkowicie zastąpić powłokę — wtedy system operacyjny dostarcza sygnały bezpośrednio do procesu potomnego i nie trzeba używaćtrap. Jest to preferowany wzorzec w prostych przypadkach.
#!/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 $?Używanie tini jako minimalnego procesu init
Gdy kontener uruchamia procesy potomne (np. powłokę tworzącą procesy robocze), potrzebny jest rzeczywisty proces init do sprzątania procesów zombie. tini to niewielki plik binarny init przeznaczony specjalnie do kontenerów.
- Należy dodać
tinido obrazu i ustawić go jako opakowanie entrypointu. - Sprząta procesy zombie, poprawnie przekazuje sygnały i kończy działanie z kodem statusu procesu potomnego.
- Docker zawiera wbudowany proces tini, aktywowany za pomocą
docker run --init, ale umieszczenie go w obrazie zapewnia spójne działanie w różnych środowiskach uruchomieniowych (Kubernetes, ECS itd.).
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"]Uruchamianie jako użytkownik inny niż root
Kontenery uruchamiane jako root (UID 0) stanowią poważne zagrożenie bezpieczeństwa: ucieczka z kontenera zapewnia pełny dostęp do hosta. Przed uruchomieniem głównego procesu należy zawsze przełączyć się na użytkownika nieuprzywilejowanego.
- Należy utworzyć dedykowanego użytkownika systemowego i grupę w pliku Dockerfile za pomocą
addgroup/adduser(Alpine) lubgroupadd/useradd(Debian). - Właściciela plików aplikacji należy zmienić za pomocą
COPY --chown=appuser:appgroup— jest to wydajniejsze niż osobna warstwaRUN chown. - Na użytkownika należy przełączyć się za pomocą instrukcji
USER. Entrypoint i CMD dziedziczą tego użytkownika. - Ustawienie Kubernetes
securityContext.runAsNonRoot: trueodmówi uruchomienia obrazu, który nadal działa jako root.
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"]Kontrole stanu i sondy gotowości w obrazie
Sondy aktywności i gotowości Kubernetes są definiowane w manifestach, ale można również umieścić instrukcję HEALTHCHECK w pliku Dockerfile na potrzeby samodzielnych środowisk docker run i Docker Compose.
HEALTHCHECK --interval=10s --timeout=3s --start-period=15s --retries=3 CMD ...- W przypadku usług HTTP należy używać
curl --faillubwget -qO-; w przypadku demonów innych niż HTTP należy sprawdzać gniazdo za pomocą/dev/tcp/localhost/PORT. - Należy instalować tylko to, co jest potrzebne: w obrazach distroless nie należy dodawać
curlwyłącznie na potrzeby kontroli stanu — lepiej użyć specjalnie przygotowanego pliku binarnego sondy lub własnego pliku binarnego aplikacji do sprawdzania stanu.
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 80Połączenie wszystkich elementów: produkcyjny entrypoint
Poniżej przedstawiono kompletny, gotowy do użycia w produkcji skrypt pośredniczący entrypointu, łączący wszystkie wzorce z tej lekcji: sprawdzanie zmiennych środowiskowych, szablonowanie konfiguracji, przekazywanie sygnałów i przekazanie sterowania za pomocą exec. Ten wzorzec jest używany w rzeczywistych mikrousługach Node.js, Python i Go wdrażanych w Kubernetes.
- Każda sekcja jest opatrzona komentarzem i służy jako samodokumentujący się szablon.
- Skrypt pośredniczący ma mniej niż 50 wierszy — entrypointy powinny być proste i łatwe do audytowania.
- Należy zwrócić uwagę na końcowe
exec "$@": po zakończeniu całej konfiguracji powłoka zostaje zastąpiona procesem aplikacji, który staje się PID 1 i otrzymuje bezpośrednio wszystkie sygnały systemu operacyjnego.
#!/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 "$@"Sprawdzenie wiedzy: obsługa sygnałów w entrypointach
Proszę sprawdzić, jak dobrze rozumieją Państwo obsługę sygnałów w skryptach entrypointu kontenera.
Podsumowanie: odchudzone pliki Dockerfile i skrypty entrypointu
Omówili Państwo cały proces tworzenia produkcyjnych kontenerów:
- Wielostopniowe budowanie wykorzystuje wiele instrukcji
FROM, aby nie umieszczać kompilatorów i narzędzi budowania w końcowym obrazie, tworząc odchudzone, minimalne warstwy uruchomieniowe. - Kolejność warstw — manifesty zależności należy kopiować przed kodem źródłowym — maksymalizuje trafienia pamięci podręcznej i przyspiesza potoki CI.
- Sekrety BuildKit i montowania SSH chronią dane uwierzytelniające przed zapisaniem w historii obrazu, nie utrudniając uwierzytelnionego budowania.
- Skrypty pośredniczące entrypointu sprawdzają zmienne środowiskowe, tworzą pliki konfiguracji z użyciem
envsubsti przekazują sterowanie aplikacji za pomocąexec "$@". - Obsługa sygnałów wymaga użycia albo
exec(dzięki czemu aplikacja jest procesem PID 1), albo jawnego wzorcatrap+kill+waitw przypadku używania zadań działających w tle. - tini zapewnia sprzątanie procesów zombie i poprawne przekazywanie sygnałów, gdy kontener uruchamia wiele procesów.
- Użytkownicy inni niż root oraz instrukcje
HEALTHCHECKdopełniają bezpieczny i obserwowalny obraz gotowy do produkcyjnych obciążeń Kubernetes.
Konsekwentne łączenie tych wzorców sprawi, że obrazy będą mniejsze, szybciej wdrażane i znacznie bardziej odporne w warunkach produkcyjnych.
Często zadawane pytania
Czy lekcja „Odchudzone Dockerfile i punkty wejścia powłoki” jest bezpłatna?
Tak — pełny tekst „Odchudzone Dockerfile i punkty wejścia powłoki” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu DevOps Bootcamp, przejdź na CoddyKit PRO. Kurs DevOps Bootcamp zawiera 4 lekcji w sumie.
Co nauczysz się w „Odchudzone Dockerfile i punkty wejścia powłoki”?
Twórz wieloetapowe skrypty budowania oraz solidne nakładki entrypoint z obsługą sygnałów i szablonami konfiguracji. Ćwiczysz DevOps Bootcamp z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć DevOps Bootcamp?
Nie wymagamy żadnego doświadczenia. DevOps Bootcamp w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Odchudzone Dockerfile i punkty wejścia powłoki”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji DevOps Bootcamp?
Tak. Każda lekcja DevOps Bootcamp zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Odchudzone Dockerfile i punkty wejścia powłoki
- Szablony konfiguracji za pomocą envsubst i heredoc
- Skryptowanie zasobów chmurowych za pomocą CLI i jq
- sondy kondycji, bramki gotowości i pętle oczekiwania