16.06, ООО «ВК»
16.06, ООО «ВК»
16.06, ООО «ВК»

6 советов, которые реально прокачают навыки работы с Docker

Многое можно делать в Docker на автомате. Но чтобы решать нетривиальные задачи, нужно углубляться. В статье — шесть практик, которые помогут выйти за рамки docker build и научат думать как инженер, а не как пользователь CLI.

6К открытий14К показов
6 советов, которые реально прокачают навыки работы с Docker

Docker давно стал стандартом в разработке: контейнеры запускают фронтенд, бэкенд, базы данных, пайплайны и тесты. Но большинство разработчиков использует его как ещё один способ запустить проект, не вдаваясь в детали. А ведь за docker build и docker run скрывается целая экосистема. Сегодня рассмотрим шесть практик, которые реально прокачают навыки работы с Docker.

Используете Docker?
Да
Нет

Сделайте минимальный Docker-образ, не ломая прод

Большинство Docker-образов в проектах содержат больше данных, зависимостей и инструментов, чем требуется для работы приложения. Это приводит к избыточному объёму, медленной сборке и повышенным рискам безопасности. Умение собирать минимальный образ — один из базовых навыков работы с Docker, который напрямую влияет на стабильность и скорость развёртывания.

Начать стоит с multi-stage сборки: на первом этапе — установка зависимостей и сборка, на втором — только нужные артефакты. Это позволяет исключить лишние файлы и утилиты из финального образа. В качестве базового слоя лучше использовать alpine, distroless или даже scratch, если вы точно понимаете, какие бинарники и библиотеки требуются приложению.

Хорошая практика — использовать утилиты docker history и dive для анализа того, какие файлы и слои попали в образ. Если размер превышает ожидания, стоит проверить, не остались ли во внутреннем слое временные файлы, dev-зависимости или директории кэша.

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

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

Попробуйте контейнизировать то, что изначально не предназначалось для этого

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

Попробуйте упаковать в контейнер любую графическую программу. Технически это возможно: X11 или Wayland можно пробросить через сокет, устройства передать через volume, окружение прописать вручную. Но на практике вы столкнётесь с рядом ограничений: отсутствие звука, глюки интерфейса, ошибки в драйверах, проблемы с доступом к GPU или нестабильное поведение при рендеринге.

В этом упражнении ценен не сам результат, а путь. Вы увидите, как устроена изоляция в Docker, почему графические приложения не работают «из коробки» и где находятся реальные границы контейнеризации. Контейнер — это не виртуальная машина, у него нет полноценного init, драйверов или прямого доступа к оборудованию. Многие фичи, которые работают локально, в контейнере требуют дополнительных танцев с бубном.

Такая практика особенно полезна, если вы имеете дело с devtool'ами, UI-обвязкой или тестированием в headless-средах. Понимание, что именно ломается и почему, позволяет более точно проектировать окружение и избегать архитектурных ловушек в будущем.

Соберите базовый образ с нуля

Когда разработчик пишет FROM node или FROM ubuntu, он автоматически получает десятки слоёв, библиотек и утилит, о которых, скорее всего, не задумывается. Это удобно, но не даёт понимания, как вообще работает контейнер на низком уровне. Попробуем разобраться.

Укажите в Dockerfile FROM scratch — и не увидите ни bash, ни glibc, ни стандартных каталогов. Вам придётся самостоятельно добавить всё необходимое: бинарник, зависимости, библиотеки, конфигурацию. Если пишете на Go, задача упрощается: можно собрать статически слинкованный исполняемый файл и скопировать его в образ. Для других языков, особенно тех, что зависят от динамических библиотек или рантайма (например, Python или Node.js), придётся вручную подтягивать зависимости.

Такая практика заставляет иначе взглянуть на структуру контейнера. Вы начнёте понимать, чем отличается CMD от ENTRYPOINT, зачем в некоторых образах используется sh -c, и что произойдёт, если не задать WORKDIR. Вы столкнётесь с ошибками «no such file or directory» даже тогда, когда файл вроде бы существует — потому что в контейнере не хватает нужной libc.

Отдельный повод для размышлений — Alpine. Его любят за размер и минимализм, но он использует musl вместо glibc, и не всё с ним работает корректно. В процессе сборки на практике увидите, почему иногда проще остаться на Debian Slim, чем пытаться адаптировать всё под Alpine.

Этот эксперимент не нужен для продакшена — он нужен вам, как разработчику. Прокачивает понимание, как устроен Docker, что по-настоящему важно приложению для запуска, и какие зависимости вы добавляете бессознательно.

Делайте разные варианты Docker-образов

Хороший Dockerfile — тот, который гибко адаптируется под разные сценарии: продакшен, отладку, тестирование, запуск на ARM или x86. Если вы умеете собирать только один универсальный образ — вы ещё не освоили Docker по-настоящему.

Попробуйте собрать сразу несколько версий своего образа: на Debian и на Alpine, с минимальным размером и с полным набором утилит, для amd64 и arm64. Добавьте build-аргументы (ARG) — они позволяют передавать параметры на этапе сборки: выбрать базовый образ, включить или выключить зависимости, задать переменные окружения. Используйте RUN if или шаблонизацию через Dockerfile.template, чтобы варьировать поведение без дублирования кода.

Вот типичный пример: в режиме отладки вам нужен образ с установленным curl, vim, доступом к логам и расширенной трассировкой. А в продакшене — максимально облегчённый, с удалёнными временными файлами, сжатым слоем и только необходимыми бинарниками. Один и тот же проект — два разных образа. Добавьте сюда ещё поддержку разных архитектур (multi-arch build через --platform), и вы выйдете на уровень CI/CD, где из одного пайплайна собирается три-четыре артефакта.

Такая практика решает сразу несколько задач. Во-первых, помогает лучше понять, как влияет каждый шаг сборки на финальный размер и поведение контейнера. Во-вторых, избавляет от лишних костылей, когда на проде всё работает, а на локалке — нет. И главное — прокачивает навык автоматизации. Один Dockerfile, разные образы, ноль копипасты.

Поиграйте в «А что если запустить чужой (небезопасный) код?»

Представьте задачу: вам нужно запустить код, который написал кто-то другой. Вы не уверены, что он безопасен. Это может быть скомпилированный бинарник, питоновский скрипт или даже npm-зависимость с подозрительным хуком. Где-то в коде может быть rm -rf /, попытка выйти за пределы контейнера, установить рутовый доступ или просто майнить крипту. И теперь этот код запускается на вашей машине — внутри Docker.

Кажется, контейнер защитит? Не всегда.

Docker не является полноценной песочницей. По умолчанию контейнер может обращаться к файловой системе, к ядру и к хостовым ресурсам — особенно если вы запускаете его с --privileged или без ограничения пользователя. Даже docker run -it ubuntu работает от root внутри контейнера, что уже создаёт риски.

Если вы действительно хотите запустить небезопасный код, нужно жёстко ограничить контейнер:

  • отключить права root (через --user);
  • запретить модификацию файловой системы (--read-only);
  • отобрать лишние возможности ядра (--cap-drop=ALL);
  • включить seccomp-профиль, AppArmor или SELinux;
  • отключить доступ к сети или монтированию сокетов.

Список можно продолжать. Главное — понять, что Docker по умолчанию не даёт изоляции на уровне VM. Если вы работаете с кодом, которому не доверяете, этого может быть недостаточно.

Это упражнение учит думать о безопасности как о процессе. И особенно важно пройти его, если вы когда-нибудь планируете запускать user-generated code: плагины, кастомные скрипты, пайплайны CI. Только на практике становится ясно, где заканчиваются возможности Docker и начинаются границы настоящей песочницы.

Работайте без Docker CLI

Если убрать Docker CLI — что останется? Больше, чем кажется.

Docker ― это не единый монолит. Он работает поверх набора инструментов и стандартов: buildkit, containerd, спецификация OCI. CLI просто прячет эту архитектуру за удобными командами: docker build, docker run, docker push. Но всё, что кажется магией, можно повторить вручную.

Попробуйте отказаться от docker как от инструмента. Сконфигурируйте buildctl напрямую и соберите образ без Dockerfile. Или вообще создайте его вручную: сформируйте структуру, описания слоёв, метаданные, манифест. Прочитайте спецификацию OCI Image Format и идите по ее шагам. Понадобится tar, sha256sum, немного JSON — и внимательность.

Образ собрали? Отлично. Теперь отправьте его в реестр без docker push. Используйте oras, skopeo или curl с аутентификацией и ручной отправкой слоёв по HTTP API. Узнаете много нового: как работает digest, что такое manifest list и зачем нужны media types.

Наконец, запустите контейнер без docker run. Через ctr, runc или даже напрямую с помощью systemd-nspawn.

Зачем это нужно? Чтобы воспринимать Docker глубже. Так, вы понимаете, как устроен pipeline сборки и запуск контейнера, и точнее управляете им. Особенно это важно в продакшн-среде: когда образы не собираются, push падает, реестр отвечает 403, а пайплайн горит.

Ты точно программист, если читаешь это! Больше мемов, инсайтов и боли кодеров тут.

Следите за новыми постами
Следите за новыми постами по любимым темам
6К открытий14К показов