Skip to main content

Що таке Docker image (образ)?

Docker image це незмінний read-only шаблон, що пакує застосунок разом з усім потрібним для запуску: код, рантайм, системні бібліотеки, environment-змінні і дефолтну конфігурацію.

Теорія

TL;DR

  • Image це креслення, а не щось запущене. Container це запущені екземпляри image.
  • Складається з трьох частин: шарів (файлова система), manifest (які шари і config використати) і config blob (env, команда, working dir, тощо).
  • Ідентифікується через tag на кшталт nginx:1.27-alpine (мінливий, може бути перенесений) або digest на кшталт sha256:4f06b3e2... (незмінний, content-addressed).
  • Ти або тягнеш з registry (docker pull), або збираєш свій з Dockerfile (docker build).
  • Стандартизовано OCI Image Spec, тому Docker, Podman, containerd, Kubernetes читають той самий формат.

Швидкий приклад

bash
# Тягнемо конкретну версію nginx $ docker pull nginx:1.27-alpine 1.27-alpine: Pulling from library/nginx 9824c27679d3: Pull complete 8e1015e74a85: Pull complete Digest: sha256:4f06b3e2c0c1e8e6a9d2c8f3e8d7a6b5c4... Status: Downloaded newer image for nginx:1.27-alpine # Бачимо у локальному кеші $ docker images REPOSITORY TAG IMAGE ID SIZE nginx 1.27-alpine 4f06b3e2c0c1 54.9MB

Image тепер на твоєму диску. Поки нічого не робить. Щоб використати, стартуєш container з нього: docker run nginx:1.27-alpine.

Що насправді всередині image

Три частини, всі адресовані по hash контенту:

  1. Шари (layers) - вміст файлової системи, розділений на впорядковані tarball'и. Кожна Dockerfile-інструкція зазвичай дає один шар. Шари дедуплікуються між image: якщо node:22-alpine і python:3.13-alpine обидва на тій самій Alpine-основі, ця базова частина лежить на диску один раз.
  2. Config blob - JSON, що описує як запускати container з цього image: working directory, дефолтна команда, environment-змінні, експоновані порти, користувач, entrypoint.
  3. Manifest - JSON, що зв'язує вищезгадане: список digest'ів шарів, digest config'у, платформа (linux/amd64, linux/arm64). Сам manifest має digest, і цей digest це справжня ідентичність твого image.

Коли ти робиш pull, daemon тягне спочатку manifest, потім ті шари і config, яких ще немає локально.

Tag vs digest

Це підставляє майже всіх з першого разу.

  • Tag це ім'я, що вказує на manifest. Воно мінливе. Сьогодні nginx:latest вказує на manifest A; завтра Docker Inc пушить новий latest, і nginx:latest вже вказує на manifest B. Те саме ім'я, інший image.
  • Digest це SHA256 hash контенту manifest'у. За визначенням незмінний. nginx@sha256:4f06b3e2... завжди означає рівно ті самі байти, назавжди.
bash
# Відтворювано: pull по digest $ docker pull nginx@sha256:4f06b3e2c0c1e8e6... # Не відтворювано: tag міг помінятися з моменту тестування $ docker pull nginx:latest

Для проду пінься на digest або хоча б на конкретну версію типу nginx:1.27.4. :latest тебе колись здивує.

Build vs pull

Два шляхи отримати image у локальний кеш:

Pull - завантажити готовий image з registry (Docker Hub, ECR, GHCR, свій власний).

bash
$ docker pull postgres:16-alpine

Build - зібрати свій з Dockerfile, шар за шаром.

bash
$ docker build -t myapp:0.1 .

У реальному workflow твій CI збирає image з Dockerfile, тегує його і пушить у registry. Прод-хости потім тягнуть.

Іменування image

Повна форма: [REGISTRY[:PORT]/]NAMESPACE/REPOSITORY[:TAG|@DIGEST]. Кілька прикладів:

  • nginx:1.27-alpine - shorthand. Дефолтний registry Docker Hub, namespace library.
  • myorg/myapp:v2.3 - приватне репо на Docker Hub.
  • ghcr.io/myorg/myapp:v2.3 - GitHub Container Registry.
  • 123456789.dkr.ecr.eu-west-1.amazonaws.com/myapp:v2.3 - AWS ECR.

Якщо tag не вказати, Docker припускає :latest. Зручність, що кусає у проді.

Типові помилки

Плутати image з container

bash
$ docker images # список IMAGES (шаблони) $ docker ps -a # список CONTAINERS (екземпляри) $ docker rmi <id> # видалити image $ docker rm <id> # видалити container

Якщо docker rmi nginx скаржиться «image is being used by stopped container», image це шаблон, і якийсь container ще навколо і його використовує. Спочатку видали container, потім image.

Довіряти :latest у проді

yaml
# НЕПРАВИЛЬНО: це може помінятися за ніч image: nginx:latest # ПРАВИЛЬНО: пін на конкретну версію image: nginx:1.27.4 # КРАЩЕ: пін на digest image: nginx@sha256:4f06b3e2c0c1...

Новий :latest може принести breaking change між двома деплоями того самого коду. Digest ніколи не бреше.

Слати весь репо як build context

dockerfile
# Без .dockerignore це шле все на daemon: COPY . /app
# .dockerignore - тримай build context маленьким node_modules .git *.log dist/

Docker заливає build context (твою поточну директорію) на daemon перед збіркою. Без .dockerignore ти шиппиш node_modules, .git і гігабайти dev-артефактів на daemon на кожен білд.

Мутувати image після збірки

Не можна. Якщо ти заходиш у container і apt-get install щось, image не змінився, ці пакети живуть у writable-шарі того одного container і зникають при рестарті. Щоб запекти, редагуй Dockerfile і пересобирай.

Реальне застосування

  • Docker Hub - публічний registry, що хостить nginx, postgres, redis, node і мільйони community-image. Дефолтний registry, коли робиш docker pull без вказівки.
  • AWS ECR / Google Artifact Registry / GitHub Container Registry - приватні registry, що використовують більшість команд, які шиппять прод. Той самий формат image, інший контроль доступу.
  • Multi-architecture image - один tag типу nginx:1.27 насправді вказує на manifest list («image index»), що містить manifest'и для linux/amd64, linux/arm64, linux/arm/v7. Твій клієнт сам обирає правильний для твого CPU.
  • Cosign / Sigstore - криптографічне підписування digest'ів image. Використовується у supply-chain-обережних сетапах, щоб прод деплоїв тільки image, підписані довіреним CI.

Питання для поглиблення

Q: Де image реально лежать на моїй машині?


A: У зоні зберігання Docker, типово /var/lib/docker/overlay2/ на Linux. Кожен шар це окрема директорія. Шлях приватний для daemon, ти не взаємодієш з ним напряму, ти використовуєш docker images і docker rmi.

Q: Docker image і OCI image це одне й те саме?


A: По суті так. OCI Image Specification було витягнуто з формату Docker і тепер є відкритим стандартом. Docker, Podman, containerd і Kubernetes читають OCI image. «Docker image» і «OCI image» на практиці взаємозамінні.

Q: Чому два image з однаковим контентом іноді мають різні digest?


A: Бо метадані у config blob (build timestamps, build args) міняють байти, навіть коли шари файлової системи однакові. Щоб отримати reproducible digest, збирай з --build-arg SOURCE_DATE_EPOCH=... та іншими reproducibility-флагами, і уникай вшитих timestamp'ів.

Q: Яка різниця між image і manifest list?


A: Image manifest описує один image для однієї платформи (скажімо linux/amd64). Manifest list (OCI кличе це «image index») вказує на кілька manifest'ів для різних платформ. Коли ти робиш docker pull nginx:1.27, daemon тягне manifest list, обирає manifest для твого CPU, потім тягне ті шари. Той самий tag, інші байти на різних платформах.

Q: (Senior) Як гарантувати, що image, протестований у CI, це рівно той image, що задеплоєний у проді?


A: Пінься на digest, не на tag. Твій CI ловить digest після збірки (docker buildx imagetools inspect або вивід docker push), і твій deployment manifest посилається на image@sha256:.... Це повністю обходить проблему мінливості tag. Для supply-chain гарантії підписуй digest через Cosign і верифікуй підпис на etapi admission. Tag-based деплої зручні, але такої гарантії дати не можуть.

Приклади

Інспекція image

bash
$ docker inspect nginx:1.27-alpine | head -30 [ { "Id": "sha256:4f06b3e2c0c1...", "RepoTags": ["nginx:1.27-alpine"], "RepoDigests": ["nginx@sha256:abcd1234..."], "Architecture": "amd64", "Os": "linux", "Size": 54923456, "Config": { "Cmd": ["nginx", "-g", "daemon off;"], "ExposedPorts": { "80/tcp": {} }, "WorkingDir": "", "Env": ["PATH=/usr/local/sbin:/usr/local/bin..."] } } ]

Це виплюне manifest, config і метадані image. Секція Config це те, що визначає дефолти container: коли ти робиш docker run nginx:1.27-alpine без overrides, ти отримуєш рівно ці значення.

Збірка крихітного image

dockerfile
# Dockerfile FROM alpine:3.21 RUN apk add --no-cache curl CMD ["curl", "--version"]
bash
$ docker build -t curl-tool:0.1 . [+] Building 4.2s (6/6) FINISHED => [1/2] FROM alpine:3.21 => [2/2] RUN apk add --no-cache curl => exporting to image $ docker run --rm curl-tool:0.1 curl 8.10.1 (x86_64-alpine-linux-musl)

Три шари видно: Alpine-основа, apk add, метадані image. Загальний розмір під 10 MB. Той самий image, без install, без залишків dev-тулзів.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Коментарі

Ще немає коментарів