0Pricing
DevOps Bootcamp · Урок

Компактные Dockerfile и точки входа Shell

Создавайте многоэтапные скрипты сборки и надёжные оболочки точек входа с обработкой сигналов и шаблонизацией конфигурации

«Компактные Dockerfile и точки входа Shell» — бесплатный урок DevOps Bootcamp на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения DevOps Bootcamp, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс DevOps Bootcamp содержит 4 уроков всего.

Почему компактные Dockerfile важны для DevOps

В производственных средах каждый мегабайт в образе Docker имеет свою цену: более медленная загрузка, большая поверхность атаки и напрасно занятое место в хранилище реестра. Компактные Dockerfile в сочетании с надёжными сценариями оболочки для точки входа — признак зрелой практики DevOps.

  • Многоэтапные сборки отделяют инструменты, необходимые во время сборки, от итогового образа среды выполнения, значительно уменьшая его размер.
  • Обёртки точки входа — это небольшие сценарии оболочки, которые подготавливают контейнеры: создают файлы конфигурации по шаблонам, проверяют переменные окружения, обрабатывают сигналы и, наконец, запускают основной процесс через exec.
  • Вместе они образуют основу надёжных и переносимых контейнерных рабочих нагрузок в Kubernetes, ECS и средах на физических серверах.

В этом уроке рассматриваются оба направления от начала до конца, а готовые шаблоны промышленного уровня можно сразу добавить в свои конвейеры.

Анатомия многоэтапного Dockerfile

Многоэтапный Dockerfile использует несколько инструкций FROM. Каждый этап представляет собой изолированный набор слоёв; в следующий этап копируются только нужные артефакты.

  • Этап 0 (builder): устанавливает компиляторы, средства запуска тестов и зависимости сборки.
  • Этап 1 (runtime): начинается с минимальной базовой системы (например, alpine, distroless) и содержит только скомпилированные двоичные файлы или пакеты приложения.
  • В итоговом образе никогда не будет gcc, make или исходного кода, если только Вы явно не скопируете их.

Используйте --from=<stage> в COPY, чтобы переносить файлы через границы этапов. Для удобства чтения и выборочной сборки с помощью docker build --target задавайте этапам имена через AS <name>.

# ---- 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"]

Минимизация слоёв и сброс кэша

Каждая инструкция RUN, COPY и ADD создаёт новый слой. Неправильный порядок инструкций без необходимости делает кэш сборки недействительным, из-за чего конвейеры CI работают медленно.

  • Копируйте манифесты зависимостей (package.json, go.mod, requirements.txt) до копирования исходного кода, чтобы установка зависимостей кэшировалась независимо.
  • Объединяйте связанные команды с помощью && и очищайте временные данные в том же слое RUN, чтобы кэш пакетов не оставался в промежуточных слоях.
  • Используйте --no-cache в менеджерах пакетов и удаляйте файлы со списками после установки.
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 и перенаправление SSH

Приватные реестры, ключи SSH и токены API никогда не должны появляться в слоях образа. Docker BuildKit предоставляет два безопасных механизма:

  • --secret: подключает файл с секретом внутри одного шага RUN, не встраивая его в слой. Доступ к нему осуществляется через /run/secrets/<id>.
  • --ssh: передаёт сокет агента SSH с хоста в сборку, поэтому git clone может пройти аутентификацию без встраивания закрытых ключей.

Включите BuildKit с помощью DOCKER_BUILDKIT=1 или через docker buildx build. Директива # syntax=docker/dockerfile:1 открывает доступ к этим возможностям.

# 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 .

Создание надёжной обёртки точки входа

Обёртка точки входа — это сценарий оболочки, заданный в Dockerfile с помощью ENTRYPOINT. Её задача — подготовить среду выполнения перед передачей управления основному процессу.

Хорошо структурированная обёртка выполняет действия в следующем порядке:

  • Шаг 1: установите set -euo pipefail, чтобы любая ошибка сразу прерывала выполнение.
  • Шаг 2: проверьте обязательные переменные окружения и при их отсутствии немедленно завершите работу с понятным сообщением.
  • Шаг 3: создайте файлы конфигурации по шаблонам на основе переменных окружения.
  • Шаг 4: зарегистрируйте обработчики сигналов для корректного завершения работы.
  • Шаг 5: exec "$@" — замените оболочку основным процессом, чтобы PID 1 был представлен приложением, а не обёрткой.

Итоговая команда exec критически важна: без неё сигналы, отправленные Kubernetes или средой выполнения Docker, не передаются дочернему процессу.

#!/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 "$@"

Создание конфигурации по шаблону с помощью envsubst

envsubst (из пакета GNU gettext, доступного в Alpine как gettext) заменяет заполнители ${VAR} в файле-шаблоне текущими значениями переменных окружения.

  • Поместите в образ шаблон конфигурации *.tmpl; точка входа создаст на его основе итоговый файл при запуске.
  • Явно передавайте список переменных в envsubst, чтобы случайно не заменять посторонние знаки доллара, например в регулярном выражении NGINX.
  • Записывайте созданный файл в доступный для записи путь, например /tmp, или в отдельный том конфигурации.
#!/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 "$@"

Обработка сигналов и корректное завершение работы

Контейнеры получают SIGTERM, когда Kubernetes, ECS или docker stop останавливают их. Если обёртка точки входа имеет PID 1 и не передаёт сигналы дальше, основной процесс после истечения периода ожидания будет принудительно завершён сигналом SIGKILL, что может привести к потере запросов или повреждению данных.

  • Используйте trap, чтобы перехватывать SIGTERM и SIGINT в обёртке.
  • Передавайте сигнал дочернему процессу по его PID с помощью kill -TERM "$child".
  • Используйте wait "$child", чтобы ожидать завершения дочернего процесса, а затем передать его код завершения.
  • В качестве альтернативы используйте exec, полностью заменяя оболочку: тогда ОС доставляет сигналы непосредственно дочернему процессу, и trap не требуется. Для простых случаев это предпочтительный шаблон.
#!/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 в качестве минимального процесса инициализации

Если контейнер порождает дочерние процессы, например оболочка запускает рабочие процессы, требуется настоящий процесс инициализации, который будет удалять процессы-зомби. tini — это небольшая двоичная программа инициализации, специально созданная для контейнеров.

  • Добавьте tini в образ и задайте его как обёртку точки входа.
  • Он удаляет дочерние процессы-зомби, правильно передаёт сигналы и завершается с кодом состояния дочернего процесса.
  • Docker поставляется со встроенным tini, который активируется с помощью docker run --init, но встраивание его в образ обеспечивает одинаковое поведение в разных средах выполнения (Kubernetes, ECS и других).
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"]

Запуск от имени пользователя без прав root

Контейнеры, работающие от имени root (UID 0), представляют серьёзную угрозу безопасности: выход из контейнера даёт полный доступ к узлу. Всегда переключайтесь на непривилегированного пользователя перед запуском основного процесса.

  • Создайте в Dockerfile отдельного системного пользователя и группу с помощью addgroup / adduser (Alpine) или groupadd / useradd (Debian).
  • Изменяйте владельца файлов приложения с помощью COPY --chown=appuser:appgroup — это эффективнее, чем создавать отдельный слой RUN chown.
  • Переключайтесь на этого пользователя с помощью инструкции USER. Точка входа и CMD унаследуют этого пользователя.
  • Kubernetes с параметром securityContext.runAsNonRoot: true откажется запускать образ, который по-прежнему работает от имени 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"]

Проверки состояния и пробы готовности в образе

Пробы работоспособности и готовности Kubernetes задаются в манифестах, но Вы также можете встроить HEALTHCHECK в Dockerfile для автономных сред docker run и Docker Compose.

  • HEALTHCHECK --interval=10s --timeout=3s --start-period=15s --retries=3 CMD ...
  • Для HTTP-служб используйте curl --fail или wget -qO-; для служб без HTTP проверяйте сокет с помощью /dev/tcp/localhost/PORT.
  • Устанавливайте только необходимое: в образах distroless не добавляйте curl только ради проверок состояния — используйте специально предназначенную для этого двоичную программу или собственную программу проверки состояния приложения.
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

Объединяем всё вместе: производственная точка входа

Ниже приведена полноценная обёртка точки входа промышленного уровня, объединяющая все шаблоны из этого урока: проверку окружения, создание конфигурации по шаблону, передачу сигналов и передачу управления через exec. Этот шаблон используется в реальных микрослужбах на Node.js, Python и Go, развёрнутых в Kubernetes.

  • Каждый раздел снабжён комментариями и служит самодокументируемым шаблоном.
  • Объём обёртки не превышает 50 строк — точки входа должны быть простыми и удобными для аудита.
  • Обратите внимание на итоговую команду exec "$@": после завершения всей подготовки оболочка заменяется процессом приложения, поэтому приложение получает PID 1 и напрямую принимает все сигналы ОС.
#!/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 "$@"

Проверка знаний: обработка сигналов в точках входа

Проверьте своё понимание обработки сигналов в сценариях точек входа контейнеров.

Итоги: компактные Dockerfile и точки входа оболочки

Вы изучили весь стек создания производственных контейнеров:

  • Многоэтапные сборки используют несколько инструкций FROM, чтобы компиляторы и инструменты сборки не попадали в итоговый образ, создавая компактные минимальные слои среды выполнения.
  • Порядок слоёв — копирование манифестов зависимостей до исходного кода — увеличивает число попаданий в кэш и ускоряет конвейеры CI.
  • Секреты BuildKit и подключения SSH не позволяют учётным данным попадать в историю образа, не нарушая сборки с аутентификацией.
  • Обёртки точки входа проверяют переменные окружения, создают файлы конфигурации по шаблонам с помощью envsubst и передают управление приложению через exec "$@".
  • Обработка сигналов требует либо exec (чтобы приложение имело PID 1), либо явного шаблона trap + kill + wait при использовании фоновых задач.
  • tini добавляет удаление процессов-зомби и корректную передачу сигналов, когда контейнер запускает несколько процессов.
  • Пользователи без прав root и инструкции HEALTHCHECK завершают создание защищённого и наблюдаемого образа, готового к работе в Kubernetes.

Последовательно применяйте эти шаблоны, и Ваши образы станут меньше, будут быстрее развёртываться и значительно надёжнее работать в производственных условиях.

Часто задаваемые вопросы

Урок «Компактные Dockerfile и точки входа Shell» бесплатный?

Да — полный текст урока «Компактные Dockerfile и точки входа Shell» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс DevOps Bootcamp, подпишись на CoddyKit PRO. Курс DevOps Bootcamp содержит 4 уроков всего.

Чему я научусь в уроке «Компактные Dockerfile и точки входа Shell»?

Создавайте многоэтапные скрипты сборки и надёжные оболочки точек входа с обработкой сигналов и шаблонизацией конфигурации Ты практикуешь DevOps Bootcamp с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать DevOps Bootcamp?

Предыдущий опыт не требуется. DevOps Bootcamp на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «Компактные Dockerfile и точки входа Shell»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке DevOps Bootcamp?

Да. Каждый урок DevOps Bootcamp включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Компактные Dockerfile и точки входа Shell
  2. Шаблонизация конфигураций с envsubst и heredoc
  3. Создание облачных ресурсов через CLI и jq
  4. Проверки состояния, барьеры готовности и циклы ожидания
← Назад к DevOps Bootcamp