Container Image

⌘K

Container Image

Container Image — это упакованный и готовый к запуску набор файлов, библиотек и конфигураций, необходимых для работы приложения в контейнере. Проще говоря, это «снимок» приложения с его зависимостями, который можно запустить на любой машине, где есть контейнерный движок, например Docker или containerd.

В образ входят:

  • базовый слой — операционная система или её минимальная часть (например, Alpine, Ubuntu, Debian);
  • зависимости приложения — библиотеки, фреймворки, конфигурационные файлы;
  • само приложение и инструкции для его запуска (entrypoint, команды).

💡 Почему это важно
Контейнерные образы обеспечивают единообразие среды: код, протестированный в одном окружении, будет работать так же в любом другом. Это исключает ситуации «у меня на локальной машине всё работало».

Как создаётся
Обычно разработчик пишет Dockerfile, где описывает шаги сборки: какую базу взять, что скопировать, какие команды выполнить. На выходе получается образ, который можно хранить в реестрах (Docker Hub, GitHub Container Registry, Harbor) и скачивать при необходимости.

Пример жизненного цикла Container Image

  1. Разработчик пишет код и Dockerfile.
  2. Система CI/CD собирает образ.
  3. Образ загружается в реестр.
  4. Kubernetes или другой оркестратор скачивает образ и разворачивает контейнеры.

Для клиента
Использование контейнерных образов упрощает развёртывание, повышает скорость выхода обновлений и улучшает безопасность — можно точно контролировать, из чего состоит рабочая среда.

Пример Dockerfile для простого Python-приложения:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]

Структура и слои (layers)

Container Image собран из слоёв — неизменяемых наборов файлов. Каждый следующий слой «накладывается» поверх предыдущего, формируя итоговую файловую систему контейнера.

  • Базовый слой: минимальная ОС или рантайм (alpine, debian-slim, distroless).
  • Слои зависимостей: установленные пакеты, библиотеки, языковые рантаймы.
  • Слои приложения: ваш код, статические ассеты, конфиги.
  • Метаданные: ENTRYPOINT, CMD, ENV, EXPOSE, LABEL — инструкции запуска и описательные поля.

Образы соответствуют формату OCI Image и хранятся как набор слоёв с контент‑адресацией: каждый слой и манифест имеют криптографический digest (sha256), что делает их идентифицируемыми и кэшируемыми.


Кэширование сборки

Docker/BuildKit кэширует слои: если инструкция в Dockerfile и её входные данные не менялись — слой берётся из кэша.

Практические приёмы:

  • Сначала зависимости, потом код COPY requirements.txt . RUN pip install -r requirements.txt COPY . . Обновление кода не ломает кэш слоя с зависимостями.
  • Игнорируйте лишнее: используйте .dockerignore, чтобы не тащить временные файлы (node_modules, .git, логи).
  • Стабильные версии: фиксируйте версии пакетов, иначе кэш будет часто инвалидироваться.
  • BuildKit: включайте DOCKER_BUILDKIT=1 — более умное кэширование и параллелизм.

Размер и производительность

  • Используйте тонкие базовые образы (alpine, -slim, distroless для продакшна).
  • Применяйте multi-stage builds: собирайте в одном этапе, копируйте артефакты в минимальный рантайм.

Пример (Go):

# stage 1: build
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o app ./cmd/app

# stage 2: runtime
FROM gcr.io/distroless/static:nonroot
WORKDIR /app
COPY --from=build /src/app .
USER nonroot:nonroot
ENTRYPOINT ["/app/app"]

Результат — минимальный образ без шелла и package‑менеджера, меньше поверхность атаки, быстрее доставка.


Теги, digest’ы и неизменяемость

  • Теги (:latest, :v1.4.2) — удобны, но переопределяемы.
  • Digest (@sha256:…) — неизменяемый идентификатор. Для воспроизводимости деплойте по digest’у:
image: registry.example.com/app@sha256:abc123...

Практика: храните и тег, и digest в манифестах (Kubernetes, Helm), чтобы сочетать читабельность и детерминизм.


Безопасность образов

  • Минимизируйте поверхность атаки: distroless/ubi‑micro, убирайте компиляторы и шелл в рантайме.
  • Не запускайте от root: USER 10001:10001, задавайте непривилегированного пользователя.
  • Подписывайте образы: Cosign/Notary v2 — верифицируйте в CI/CD и на кластере (Policy/Admission).
  • Сканируйте уязвимости: Trivy, Grype, Anchore; включите «break the build» на критических CVE.
  • SBOM (Software Bill of Materials): генерируйте CycloneDX/SPDX (Syft, Trivy) и храните рядом с образом.
  • Обновляйте базу: регулярно пересобирайте образы с новыми патчами базового слоя.

Регистры и политики

  • Хранение: Docker Hub, GHCR, Harbor, ECR/GCR/ACR.
  • Репликация между регионами — для снижения latency и отказоустойчивости.
  • Политики очистки: автоматическое удаление «висячих» тегов и старых билдов.
  • Контроль доступа: RBAC/OIDC, отдельные репозитории на окружение (dev, staging, prod).

Диагностика и дебаг

  • Добавляйте минимум инструментов в прод‑образ. Для диагностики — отдельный «debug»-вариант (с шеллом и curl).
  • Логи и метрики — вне образа: стандартные потоки и sidecar‑агенты (не прошивайте агенты внутрь).

Шаблон «хорошего» Dockerfile (Node.js)

# build stage
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --ignore-scripts
COPY . .
RUN npm run build && npm prune --production

# runtime stage
FROM gcr.io/distroless/nodejs20-debian12:nonroot
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
ENV NODE_ENV=production
USER nonroot:nonroot
EXPOSE 3000
CMD ["dist/server.js"]

Особенности:

  • npm ci — воспроизводимая установка.
  • prune --production — только runtime‑зависимости.
  • distroless — минимальный рантайм, USER — без root.

Для чего это всё клиенту

Контейнерный образ — это стандартная единица поставки ПО. Корректная структура слоёв, воспроизводимая сборка, безопасность и управление тегами уменьшают риски, ускоряют релизы и упрощают аудит. Итог — предсказуемые деплои и контролируемая стоимость (меньше трафика и времени доставки).