Docker — основы контейнеризации

Docker — платформа контейнеризации, которая решает одну из самых старых проблем в разработке: «у меня на машине работает». Она упаковывает приложение вместе со всеми его зависимостями — системными библиотеками, средой исполнения, конфигурациями — в изолированный контейнер. Этот контейнер ведёт себя одинаково на ноутбуке разработчика, CI-сервере и продакшн-кластере.

Для Go-разработчика Docker — это не абстрактный инструмент из мира DevOps, а повседневная реальность. Вы будете собирать Go-бинарники в Docker, поднимать базы и зависимости через Compose, описывать деплой в Kubernetes. Поэтому начнём с фундамента: что на самом деле происходит, когда вы запускаете docker run.


Что такое Docker и зачем он нужен

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

Проблема окружения. Приложение зависит не только от своего кода. Ему нужна конкретная версия Go (или другого рантайма), определённые системные библиотеки, переменные окружения, файлы конфигурации. На одной машине стоит PostgreSQL 14, на другой — 15. На одной — Ubuntu, на другой — Alpine. Docker фиксирует всё окружение целиком.

Проблема изоляции. На одном сервере могут работать несколько приложений, и они не должны мешать друг другу. Один сервис слушает порт 8080, другому тоже нужен 8080. Один требует определённую версию glibc, другой — другую. Docker изолирует каждое приложение в собственном пространстве.

Проблема масштабирования. Когда нагрузка растёт, нужно быстро поднимать новые экземпляры сервиса. Docker-контейнеры запускаются за секунды, в отличие от виртуальных машин, которым нужны минуты на загрузку полноценной ОС.

Проблема CI/CD. Сборка, тестирование и деплой должны происходить в одинаковом окружении. Docker-образ — это артефакт, который один раз собрали, протестировали и развернули в продакшн без изменений.


Контейнеры vs виртуальные машины

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

Виртуальные машины

VM виртуализирует железо. Гипервизор (VMware, KVM, Hyper-V) создаёт виртуальное оборудование — процессор, память, диск, сетевой адаптер — и внутри него запускается полноценная гостевая ОС со своим ядром:

text
┌──────────────────────────────────────────────────┐ │ Хостовая ОС / Гипервизор │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ Гостевая ОС │ │ Гостевая ОС │ │ │ │ (ядро Linux) │ │ (ядро Windows)│ │ │ │ ┌──────────┐│ │ ┌──────────┐│ │ │ │ │ App A ││ │ │ App B ││ │ │ │ └──────────┘│ │ └──────────┘│ │ │ └──────────────┘ └──────────────┘ │ └──────────────────────────────────────────────────┘

Каждая VM — это гигабайты данных (ОС + приложение) и минуты на старт.

Контейнеры

Контейнер виртуализирует операционную систему. Все контейнеры используют одно и то же ядро хоста, но каждый видит собственное изолированное пространство процессов, сети и файловой системы:

text
┌──────────────────────────────────────────────────┐ │ Хостовая ОС (Linux kernel) │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ Контейнер A │ │ Контейнер B │ │ │ │ (userspace) │ │ (userspace) │ │ │ │ ┌──────────┐│ │ ┌──────────┐│ │ │ │ │ App A ││ │ │ App B ││ │ │ │ └──────────┘│ │ └──────────┘│ │ │ └──────────────┘ └──────────────┘ │ └──────────────────────────────────────────────────┘

Сравнение

ХарактеристикаКонтейнерВиртуальная машина
Уровень виртуализацииОС (userspace)Железо (hardware)
ЯдроОбщее с хостомСобственное
РазмерМегабайтыГигабайты
Время стартаСекундыМинуты
Накладные расходыМинимальныеЗначительные
ИзоляцияПроцессная (namespaces)Полная (гипервизор)
Разные ОС на одном хостеНетДа

Контейнеры выигрывают в скорости и эффективности, а виртуальные машины — в безопасности и гибкости. Но это не значит, что одно вытесняет другое — они часто работают вместе. Kubernetes-кластер, например, обычно работает на виртуальных машинах, внутри которых бегут контейнеры.


Linux under the hood: namespaces, cgroups, overlay fs

Docker — это не магия. Под капотом он использует три механизма ядра Linux, которые существовали задолго до Docker. Docker просто сделал их удобными.

Namespaces — изоляция

Namespaces создают иллюзию того, что контейнер — отдельная машина. Каждый namespace изолирует определённый аспект системы:

NamespaceЧто изолируетЭффект
PIDПроцессыКонтейнер видит только свои процессы, PID 1 — его init
NETСетьСобственный сетевой стек, IP-адрес, порты
MNTФайловая системаСобственная корневая ФС, точки монтирования
UTSHostnameСобственное имя хоста
IPCМежпроцессное общениеИзолированные очереди сообщений, shared memory
USERПользователиМаппинг UID/GID — root в контейнере != root на хосте

Когда вы запускаете docker run, Docker создаёт набор namespaces для контейнера. Процесс внутри контейнера думает, что он единственный в системе — у него PID 1, свой IP-адрес, своя файловая система. Но на самом деле это обычный процесс хоста, просто с ограниченной видимостью.

Cgroups — лимиты ресурсов

Namespaces изолируют видимость, но не ограничивают потребление. Если контейнер начнёт есть всю память хоста — другие контейнеры упадут. Здесь вступают cgroups (control groups).

Cgroups позволяют задать лимиты:

  • CPU — сколько процессорного времени может использовать контейнер
  • Memory — максимальный объём оперативной памяти
  • Disk I/O — скорость чтения/записи на диск
  • Network — пропускная способность сети
bash
# Запуск контейнера с ограничением: максимум 512 МБ RAM и 1 ядро CPU docker run --memory=512m --cpus=1.0 my-app

Если контейнер превышает лимит памяти, ядро Linux отправляет ему OOM-сигнал (Out of Memory) и завершает процесс. Это поведение критично для Go-приложений: рантайм Go должен знать о лимитах cgroups, иначе GC будет работать неэффективно. Начиная с Go 1.19, рантайм автоматически читает лимиты cgroups через GOMEMLIMIT.

Overlay FS — слоистая файловая система

Overlay FS — это механизм, который позволяет «наложить» несколько файловых систем друг на друга. Docker использует его для создания слоёв образа:

  • Нижние слои — read-only (базовый образ, зависимости)
  • Верхний слой — writable (изменения контейнера)

Если контейнер хочет изменить файл из нижнего слоя, overlay fs создаёт копию файла в верхнем слое (copy-on-write). Оригинал остаётся нетронутым. Это позволяет множеству контейнеров делить одни и те же базовые слои, экономя место на диске.


Docker на Mac и Windows — почему нужна VM

Это один из самых частых вопросов: «если контейнер использует ядро хоста, а на Mac нет Linux-ядра — как Docker вообще работает?»

Ответ прост: на Mac и Windows Docker запускает скрытую Linux VM.

text
Linux-хост: Приложение --> контейнер --> Linux kernel хоста (напрямую) Mac/Windows: Приложение --> контейнер --> Linux kernel --> LinuxKit VM --> хост ОС

Docker Desktop устанавливает легковесную виртуальную машину LinuxKit, внутри которой работает настоящее Linux-ядро. Все контейнеры бегут внутри этой VM. На Mac для виртуализации используется встроенный фреймворк Apple Hypervisor (или ранее xhyve), на Windows — WSL 2 или Hyper-V.

Из-за этого на Mac/Windows есть дополнительные накладные расходы по сравнению с нативным Linux:

  • Потребление памяти (VM забирает фиксированный объём)
  • Скорость файловой системы (bind mounts работают через виртуальный FS)
  • Networking (порты пробрасываются через VM)

Почему VM не вымерли

Раз контейнеры такие лёгкие и удобные, зачем нужны VM? Потому что у контейнеров есть фундаментальное ограничение — они разделяют ядро хоста:

СценарийКонтейнерVM
Разные ОС на одном хостеНетДа
Полная изоляция ядра (безопасность)НетДа
Мультитенантность (чужой код)РискованноДа
Legacy-приложения (Windows Server)НетДа
Микросервисы, CI/CD, dev-окружениеДаИзбыточно

Если эксплойт в ядре Linux — все контейнеры на хосте уязвимы. VM обеспечивает полную изоляцию на уровне гипервизора. Поэтому в облаках (AWS, GCP) каждый клиент получает свои VM, а внутри VM уже бегут контейнеры.

Также существуют Windows-контейнеры — они работают только на Windows-хосте. Linux-контейнер на Windows запускается через VM (WSL 2). А вот Windows-контейнер на Linux — невозможно в принципе, потому что ему нужно Windows-ядро.


Docker Image — неизменяемый шаблон

Docker Image (образ) — это read-only шаблон, который содержит всё необходимое для запуска приложения:

  • Легковесную операционную систему (обычно урезанный Linux)
  • Среду исполнения (если нужна)
  • Код приложения
  • Зависимости
  • Конфигурации
  • Инструкции по запуску

Ключевые характеристики образа

Неизменяемость (immutability). После сборки образ невозможно изменить. Любое изменение — это новый образ с новым хешем. Это фундаментальное свойство, которое обеспечивает воспроизводимость: если образ работает на staging — он будет работать точно так же на production.

Легковесность. Образ содержит только минимальный набор компонентов. Go-приложение в multi-stage build может уместиться в 10-20 МБ (alpine + бинарник), в отличие от VM-образа на несколько гигабайт.

Портативность. Один раз собранный образ будет работать одинаково на любом сервере, где установлен Docker. Не важно — Ubuntu, CentOS, или Amazon Linux.

Основные команды для работы с образами

bash
# Сборка образа из Dockerfile (точка — контекст сборки, текущая директория) docker build -t my-backend-app:v1.0 . # Получение готового образа из реестра (Docker Hub, GitLab Registry, etc.) docker pull postgres:15-alpine # Список всех образов на локальной машине docker images # Удаление образа docker rmi my-backend-app:v1.0 # Просмотр истории слоёв образа docker history my-backend-app:v1.0

Теги и реестры

Каждый образ имеет имя и тег: postgres:15-alpine, golang:1.26-bookworm, my-app:v2.3.1. Тег latest используется по умолчанию, если тег не указан явно — но полагаться на latest в production опасно, потому что он указывает на разные версии в разное время.

Версии базовых образов в примерах нужно регулярно ревизовать: Go поддерживает только две последние major/minor ветки, а Alpine release branches имеют ограниченный срок security support. Старый фиксированный тег лучше latest, но он всё равно устаревает.

Образы хранятся в реестрах (registries). Docker Hub — публичный реестр по умолчанию. В компаниях обычно используют приватные реестры: GitLab Container Registry, AWS ECR, Google Artifact Registry.


Слои Docker — механизм кэширования

Каждая инструкция в Dockerfile (RUN, COPY, ADD) создаёт новый read-only слой. Слои — это то, что делает Docker быстрым и экономным.

Как устроены слои

text
Образ (read-only слои): ┌─────────────────────┐ │ ENTRYPOINT ["/app"] │ слой 4 ├─────────────────────┤ │ RUN go build │ слой 3 ├─────────────────────┤ │ COPY . . │ слой 2 ├─────────────────────┤ │ FROM golang:1.26 │ слой 1 (базовый) └─────────────────────┘ Контейнер: ┌─────────────────────┐ │ writable layer │ <-- логи, tmp файлы (удаляется с контейнером) ├─────────────────────┤ │ образ (read-only) │ └─────────────────────┘

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

Кэширование слоёв

Docker кэширует каждый слой по его содержимому. Если инструкция и её входные данные не изменились — слой берётся из кэша. Это критически важно для скорости сборки.

Но есть правило: если слой изменился — все последующие слои пересобираются. Именно поэтому порядок инструкций в Dockerfile имеет огромное значение.

dockerfile
# Плохо: любое изменение кода инвалидирует кэш зависимостей COPY . . RUN go mod download RUN go build # Хорошо: зависимости кэшируются отдельно COPY go.mod go.sum ./ RUN go mod download # кэшируется, пока go.mod не изменился COPY . . RUN go build

В первом варианте каждое изменение в любом .go файле инвалидирует слой COPY . ., а значит — и go mod download будет выполняться заново. Во втором варианте зависимости скачиваются только когда изменился go.mod или go.sum.

Группировка команд RUN

Каждый RUN создаёт отдельный слой. Связанные команды стоит объединять, чтобы уменьшить количество слоёв и размер образа:

dockerfile
# Плохо: 3 слоя, промежуточные данные (apt cache) остаются в слоях RUN apt-get update RUN apt-get install -y curl RUN rm -rf /var/lib/apt/lists/* # Хорошо: 1 слой, чище и меньше по размеру RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

Важно понимать: удаление файла в следующем слое не уменьшает размер образа. Файл всё ещё существует в предыдущем слое (слои неизменяемы). Поэтому создание и удаление временных файлов нужно делать в одном RUN.

Переиспользование слоёв между образами

Если два образа используют один и тот же базовый образ (например, alpine:3.23), Docker хранит его слои только один раз. Десять сервисов на базе alpine:3.23 не занимают 10x места — базовые слои разделяются. Это справедливо и для docker pull — скачиваются только те слои, которых ещё нет локально.


Docker Container — запущенный экземпляр образа

Docker Container (контейнер) — это запущенный и работающий экземпляр образа. Если образ — это пассивный шаблон, «архив» с файлами и инструкциями, то контейнер — это активный процесс, изолированный от хостовой ОС и других контейнеров.

Ключевые особенности контейнера

Изоляция. Контейнер работает внутри собственного набора namespaces. У него свой PID-namespace (процессы), NET-namespace (сетевой стек), MNT-namespace (файловая система). Он не видит процессы других контейнеров и не конфликтует с ними.

Эффективность. В отличие от VM, контейнеру не нужна полная гостевая ОС. Он использует ядро хоста и содержит только минимальный userspace. Типичный контейнер с Go-приложением весит десятки мегабайт и запускается за секунды.

Контроль ресурсов. При запуске можно явно ограничить, сколько CPU и памяти может использовать контейнер. Это защищает другие контейнеры и хост от «прожорливых» процессов.

Эфемерность. Контейнеры проектируются как временные сущности. Их можно остановить, удалить, пересоздать из того же образа — и получить идентичный результат. Данные, которые должны пережить контейнер, хранятся в volumes (о них ниже).

Жизненный цикл контейнера

text
docker create --> Created docker start --> Running docker pause --> Paused docker unpause --> Running docker stop --> Stopped (Exited) docker rm --> Removed

Когда контейнер останавливается, его writable layer сохраняется до явного удаления (docker rm). Это позволяет посмотреть логи или скопировать файлы из остановленного контейнера.

Основные команды для работы с контейнерами

bash
# Запуск контейнера из образа с пробросом порта docker run -p 8088:8088 my-backend-app # Запуск в фоновом режиме (detached) docker run -d -p 8088:8088 --name my-app my-backend-app # Список запущенных контейнеров docker ps # Список всех контейнеров (включая остановленные) docker ps -a # Просмотр логов контейнера docker logs my-app docker logs -f my-app # в режиме follow (аналог tail -f) # Выполнение команды внутри работающего контейнера docker exec -it my-app /bin/sh # Остановка контейнера (SIGTERM, затем SIGKILL через 10 секунд) docker stop my-app # Удаление остановленного контейнера docker rm my-app

Флаг -it в docker exec — это комбинация -i (interactive, stdin остаётся открытым) и -t (tty, выделяется псевдотерминал). Без них вы не сможете интерактивно работать в shell контейнера.


Docker Volumes — персистентное хранилище

Контейнеры по своей природе эфемерны — когда контейнер удаляется, его writable layer исчезает вместе со всеми данными. Но базе данных нужно хранить файлы между перезапусками. Volumes решают эту проблему.

Существует три способа прокинуть данные в контейнер:

Bind mount

Bind mount монтирует конкретную папку хоста внутрь контейнера. Контейнер видит содержимое этой папки как своё собственное:

bash
# Через командную строку docker run -v /host/path:/container/path myapp # Монтирование конфига и данных docker run -v ./config:/app/config -v ./data:/var/lib/postgresql myapp

Bind mount привязывает контейнер к конкретной структуре файловой системы хоста. Это удобно для разработки — можно монтировать каталог с исходниками и видеть изменения в реальном времени (hot reload). Но на production это анти-паттерн: пути на разных серверах могут отличаться.

Named volume

Named volume — Docker сам управляет хранилищем. Данные хранятся в специальной директории Docker (/var/lib/docker/volumes/) и не зависят от файловой системы хоста:

bash
# Создание именованного тома docker volume create pgdata # Использование при запуске контейнера docker run -v pgdata:/var/lib/postgresql/data postgres:15-alpine # Список томов docker volume ls # Удаление тома docker volume rm pgdata

Named volumes — предпочтительный способ для production. Они портативны, Docker управляет их жизненным циклом, и они могут быть подключены к разным контейнерам.

tmpfs

tmpfs — данные хранятся в оперативной памяти и исчезают при остановке контейнера:

bash
docker run --tmpfs /tmp myapp

Подходит для временных файлов и секретов, которые не должны попадать на диск.

Сравнение типов

ТипПерсистентностьКто управляетКогда использовать
Bind mountДа, на хостеРазработчикDev: конфиги, код, hot reload
Named volumeДа, в DockerDockerProd: данные БД, файлы
tmpfsНет--Секреты, временные файлы

Пример: PostgreSQL с named volume

yaml
# docker-compose.yaml services: postgres: image: postgres:15-alpine environment: POSTGRES_PASSWORD: secret volumes: - pgdata:/var/lib/postgresql/data ports: - "5432:5432" volumes: pgdata: # Docker создаёт и хранит в /var/lib/docker/volumes/

Даже если контейнер postgres удалить и создать заново — данные в pgdata сохранятся. Это фундаментальный паттерн для stateful-сервисов в Docker.


Основные команды Docker

Сводная таблица команд, которые вы будете использовать ежедневно:

Образы

bash
# Сборка образа из Dockerfile в текущей директории docker build -t my-app:v1.0 . # Сборка с указанием конкретного Dockerfile docker build -f Dockerfile.prod -t my-app:prod . # Скачать образ из реестра docker pull postgres:15-alpine # Список локальных образов docker images # Удалить образ docker rmi my-app:v1.0 # Удалить неиспользуемые образы docker image prune

Контейнеры

bash
# Запуск с пробросом порта и именем docker run -d -p 8080:8080 --name api my-app:v1.0 # Запуск с переменными окружения docker run -d -e DB_HOST=localhost -e DB_PORT=5432 my-app:v1.0 # Запуск с ограничением ресурсов docker run -d --memory=256m --cpus=0.5 my-app:v1.0 # Список запущенных контейнеров docker ps # Логи docker logs -f api # Зайти внутрь контейнера docker exec -it api /bin/sh # Остановить и удалить docker stop api && docker rm api # Остановить все контейнеры docker stop $(docker ps -q)

Volumes

bash
# Создать том docker volume create mydata # Список томов docker volume ls # Информация о томе docker volume inspect mydata # Удалить том docker volume rm mydata # Удалить неиспользуемые тома docker volume prune

Общая очистка

bash
# Удалить все остановленные контейнеры, неиспользуемые сети и образы docker system prune # То же + volumes (осторожно!) docker system prune --volumes # Показать использование дискового пространства docker system df

Как всё связано вместе

Подведём итог, чтобы собрать полную картину:

  1. Dockerfile описывает шаги сборки образа
  2. docker build выполняет эти шаги и создаёт Image — набор read-only слоёв
  3. docker run берёт Image, добавляет writable layer и создаёт Container — изолированный процесс
  4. Контейнер изолирован через namespaces, ограничен через cgroups, его файловая система построена на overlay fs
  5. Данные, которые должны пережить контейнер, хранятся в Volumes
  6. На Mac/Windows всё это работает внутри скрытой Linux VM (LinuxKit)
text
Dockerfile --build--> Image (read-only слои) | run | v Container (image + writable layer) | ┌------┼------┐ | | | namespaces cgroups overlay fs | | | └------┼------┘ | Linux Kernel

Вопросы на собеседовании

1. Чем контейнер отличается от виртуальной машины?

Контейнер виртуализирует ОС (userspace), VM виртуализирует железо. Контейнер использует ядро хоста через namespaces и cgroups, а VM имеет собственное ядро. Контейнеры легче (мегабайты vs гигабайты), быстрее запускаются (секунды vs минуты), но обеспечивают менее строгую изоляцию — уязвимость в ядре затрагивает все контейнеры.

2. Что такое namespaces и cgroups? Какую роль они играют в Docker?

Namespaces обеспечивают изоляцию: PID (процессы), NET (сеть), MNT (файловая система), UTS (hostname), IPC (межпроцессное общение), USER (пользователи). Cgroups ограничивают потребление ресурсов: CPU, память, disk I/O. Docker использует оба механизма: namespaces создают иллюзию отдельной машины, cgroups не дают контейнеру «съесть» все ресурсы хоста.

3. Что произойдёт с данными при удалении контейнера?

Writable layer контейнера удаляется вместе с ним — все данные, записанные внутри контейнера, теряются. Чтобы данные сохранялись, нужно использовать volumes: bind mount (монтирование папки хоста), named volume (управляемый Docker), или tmpfs (в памяти, для временных данных).

4. Чем Docker Image отличается от Docker Container?

Image — неизменяемый (immutable) шаблон, набор read-only слоёв. Container — запущенный экземпляр образа с добавленным writable layer. Из одного образа можно создать множество контейнеров. Образ нельзя изменить после сборки, контейнер — можно (но изменения живут только в его writable layer).

5. Как Docker работает на macOS?

На macOS нет Linux-ядра, а контейнерам оно необходимо. Docker Desktop запускает легковесную Linux VM (LinuxKit) через Apple Hypervisor framework. Все контейнеры работают внутри этой VM. Из-за этого есть дополнительные накладные расходы: медленнее файловая система (bind mounts через виртуальный FS), больше потребление памяти (VM занимает фиксированный объём).

6. Как работает кэширование слоёв Docker? Почему порядок инструкций в Dockerfile важен?

Каждая инструкция (RUN, COPY, ADD) создаёт слой. Docker кэширует слои по содержимому. Если инструкция и входные данные не изменились — слой берётся из кэша. Но если один слой инвалидируется — все последующие слои тоже пересобираются. Поэтому редко меняющиеся инструкции (установка зависимостей) ставят раньше, а часто меняющиеся (копирование кода) — позже.

7. В чём разница между bind mount и named volume?

Bind mount привязывает конкретную директорию хоста к контейнеру — разработчик сам управляет путями и содержимым. Named volume управляется Docker, хранится в /var/lib/docker/volumes/. Bind mount удобен для разработки (монтирование исходников, hot reload), named volume — для production (портативность, управление жизненным циклом).


Задачи

Задача 1: Запуск PostgreSQL с персистентными данными

Запустите контейнер PostgreSQL 15 (образ postgres:15-alpine) со следующими требованиями:

  • Пароль mysecret
  • Порт проброшен на хост 5432:5432
  • Данные хранятся в named volume pg-data
  • Контейнер называется my-postgres
  • Ограничение памяти: 512 МБ

Убедитесь, что после остановки и удаления контейнера, а затем повторного запуска с тем же volume — данные сохраняются.

Задача 2: Исследование слоёв образа

Возьмите любой Docker-образ (например, golang:1.26-alpine3.23) и выполните:

  1. Посмотрите его слои через docker history
  2. Определите, какой слой занимает больше всего места
  3. Сравните размер golang:1.26-alpine3.23 и alpine:3.23 через docker images
  4. Объясните, почему разница именно такая

Задача 3: Namespaces на практике

Запустите контейнер с alpine в интерактивном режиме (docker run -it alpine /bin/sh) и выполните внутри:

  1. ps aux — какие процессы видит контейнер? Какой PID у вашего shell?
  2. hostname — какое имя хоста у контейнера?
  3. ip addr — какой IP-адрес у контейнера?

Сравните результаты с выводом тех же команд на хосте. Объясните, какие namespaces обеспечивают каждое из наблюдаемых отличий.


Интерактивная практика

Quiz+10 XP

Что точнее всего описывает разницу между Docker image и container?

  • Image — это запущенный процесс, container — архив с файловой системой
  • Image — неизменяемый шаблон, container — запущенный экземпляр с собственным состоянием
  • Image хранит только переменные окружения, container хранит только сетевые правила
  • Между ними нет практической разницы
Predict+15 XP

Что выведет этот код?

go
package main import "fmt" func dockerObject(running bool) string { if running { return "container" } return "image" } func main() { fmt.Println(dockerObject(false)) fmt.Println(dockerObject(true)) }
Задача+20 XP

Реализуй StorageKind: для host path нужен bind, для постоянного managed-хранилища — volume, для временного in-memory хранилища — tmpfs.