0Pricing
Linux Command Line & Bash Scripting Mastery · Lekcja

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 Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Linux Command Line & Bash Scripting Mastery 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, make ani 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 warstwie RUN, aby uniknąć pozostawiania pamięci podręcznej pakietów w warstwach pośrednich.
  • Należy używać opcji --no-cache w 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 kroku RUN, 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 czemu git clone moż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ły SIGTERM i SIGINT w 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ć tini do 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) lub groupadd / useradd (Debian).
  • Właściciela plików aplikacji należy zmienić za pomocą COPY --chown=appuser:appgroup — jest to wydajniejsze niż osobna warstwa RUN chown.
  • Na użytkownika należy przełączyć się za pomocą instrukcji USER. Entrypoint i CMD dziedziczą tego użytkownika.
  • Ustawienie Kubernetes securityContext.runAsNonRoot: true odmó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 --fail lub wget -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ć curl wyłą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 80

Połą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 envsubst i 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 wzorca trap + kill + wait w 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 HEALTHCHECK dopeł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 Linux Command Line & Bash Scripting Mastery, przejdź na CoddyKit PRO. Kurs Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery 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ąć Linux Command Line & Bash Scripting Mastery?

Nie wymagamy żadnego doświadczenia. Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery?

Tak. Każda lekcja Linux Command Line & Bash Scripting Mastery 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

  1. Odchudzone Dockerfile i punkty wejścia powłoki
  2. Szablony konfiguracji za pomocą envsubst i heredoc
  3. Skryptowanie zasobów chmurowych za pomocą CLI i jq
  4. sondy kondycji, bramki gotowości i pętle oczekiwania
← Powrót do Linux Command Line & Bash Scripting Mastery