Docker давно перестал быть просто инструментом для упаковки приложений в контейнеры. Сегодня это целая экосистема с множеством возможностей, о которых многие разработчики и даже DevOps специалисты догадываются лишь поверхностно.
В этой статье я расскажу о некоторых функциях Docker, которые редко попадают в учебные руководства и официальные туториалы, но которые могут облегчить жизнь разработчикам и понять Docker чуть глубже. По ходу статьи фичи будут переходить от более известных к менее известным (подобно когда-то популярному формату айсбергов).
Игнорирование файлов при сборке:
Когда вы запускаете сборку образа, Docker-клиент ищет в корне контекста файл .dockerignore
. Если он найден, все файлы и каталоги, подпадающие под его шаблоны, исключаются из контекста ещё до того, как будут переданы билдеру.
Если у вас несколько Dockerfile, можно задать для каждого свой файл игнорирования. Для этого: файл игнорирования кладётся в ту же папку, что и сам Dockerfile, и его имя начинается с названия Dockerfile. Вот пример такого подхода:
├── index.ts
├── src/
├── docker
│ ├── build.Dockerfile
│ ├── build.Dockerfile.dockerignore
│ ├── lint.Dockerfile
│ ├── lint.Dockerfile.dockerignore
│ ├── test.Dockerfile
│ └── test.Dockerfile.dockerignore
├── package.json
└── package-lock.json
С шаблонами составления .dockerignore
вы можете ознакомиться тут.
Пример составления .dockerignore
# комментарий. будет проигнорен докером
**/.git
**/node_modules/ # любые папки node_modules на любом уровне
**/LICENSE
**/.vscode
**/npm-debug.log
**/coverage
**/logs
**/.editorconfig
**/.aws
temp? # исключит файлы и папки в корне, имена которых отличаются на 1 символ ( tempa или /tempb )
*.md # все markdown в корне(но не в сабдерикториях)
!README*.md # пощадить все файлы, начинающиеся с README и формата markdown
Многоступенчатая сборка (multi-stage builds)
Многие знают про возможность использовать несколько FROM
в одном Dockerfile, но далеко не все понимают, насколько полезным инструментом это является. Представьте, что у вас есть приложение на Go или C++, которое сначала нужно скомпилировать, а затем запустить в контейнере. Если вы используете один контейнер для всего процесса, образ получается тяжёлым: там присутствуют компиляторы, библиотеки для разработки и прочее.
Multi-stage позволяет в одном Dockerfile отделить стадию сборки (с компиляторами, dev-зависимостями) от стадии рантайма. В результате финальный образ содержит только необходимые двоичные файлы и библиотеки — меньше места и быстрее старт.
Multi-stage позволяет разделять сборку и рантайм в одном Dockerfile. В первом контейнере в FROM
ставится образ с компилятором (golang:1.22.2
), во втором — минимальный рантайм (alpine
, distroless
), ==куда копируется готовый исполняемый файл==. В итоге финальный образ весит считаные мегабайты.
FROM golang:1.24 AS build
WORKDIR /app
COPY . .
RUN go build -o /app/bin/app
FROM alpine:3.20
COPY --from=build /app/bin/app /usr/local/bin/app
CMD ["app"]
Простой шаблон (Go):
FROM golang:1.24 AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/bin/app ./cmd/app
FROM alpine:3.20
RUN adduser -D appuser
COPY --from=build /app/bin/app /usr/local/bin/app
USER appuser
CMD ["/usr/local/bin/app"]
AS build
даёт именованный этап, откуда потом COPY --from=build
берёт файлы.
Используйте явные теги образов — (golang:1.24
, alpine:3.20
) гарантируют воспроизводимость; не используйте latest
в проде.
Обязательно используйте .dockerignore
, чтобы не копировать большие/лишние каталоги в контекст (node_modules, .git, tmp). Это ускоряет сборку и уменьшает шанс утечки секретов.
Копируйте только нужные файлы, не копируйте целые папки, если нужен только бинарник.
Типы Mounts
BuildKit позволяет в инструкциях RUN
использовать дополнительные монтирования (mounts), чтобы облегчить сборку образов. Типы монтирования:
Тип | Назначение |
---|---|
| погрузить директорию или файл из файловой системы хоста в контейнер, для использования в |
| кэшировать директории между сборками, ускорить установку зависимостей, компиляцию и др. |
| давать доступ к SSH-агенту или ключам для клонирования приватных репозиториев или других SSH-операций. |
| безопасно передавать секреты (API-ключи, пароли и др.), не включать их в итоговый образ. |
Подробнее по каждому типу
1. Bind (type=bind)
Назначение:
Подключает (mount) файлы или директории внутрь контейнера в момент RUN
, без копирования в образ. То есть, содержимое доступно для команды, но не сохраняется после окончания RUN
, если только команда явно не скопирует его куда-то.
Выполните следующую команду, чтобы запустить bash в контейнере ubuntu с помощью bind mount.
Пример:
# syntax=docker/dockerfile:1.3
FROM node:16
RUN --mount=type=bind,source=./tools,target=/app/tools \
./tools/setup.sh
Опции:
target
/dst
— путь внутри контейнера, куда монтируется.source
/src
— путь внутриfrom
. Значение по умолчанию — кореньfrom
.from
— Build stage, context, или имя образа для установки корняsource
.rw
/readwrite
— разрешить запись на mount. Записанные данные не сохраняются.
Пример:
docker run -it --mount type=bind,src="$(pwd)",target=/src ubuntu bash
или
docker run -it \
-v "$(pwd):/src" \
ubuntu bash
Когда вы изменяете и сохраняете исходный код на хосте, эти изменения сразу же отображаются в подключённой папке или файле в контейнере. Контейнер видит изменения, которые вы вносите в код, сразу же, как только вы сохраняете файл.
Таким образом вы можете запускать процессы в контейнере, которые следят за изменениями файловой системы и реагируют на них.
2. Cache (type=cache)
Назначение:
Дать возможность накапливать результат каких-то операций между сборками, чтобы не запускать полную работу заново. Часто используется для пакетных менеджеров, компиляторов, apt/dpkg, pip, npm, go, ccache и т. д.
Постоянный кэш помогает ускорить этапы сборки. Даже если вы перестраиваете слой, вы загружаете только новые или изменённые пакеты.
Пример:
# syntax=docker/dockerfile:1.3
FROM python:3.11
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
COPY . .
RUN python setup.py install
Опции:
target
— где внутри контейнера будет располагаться кэш.id
— идентификатор кэша, чтобы отделять разные кэши, если нужно. По умолчанию часто равен путиtarget
.sharing
— как кэш может быть использован:shared
,private
,locked
.shared
— разные билд-команды (писатели) могут одновременно писать.private
— каждый писатель получит «свой» кэш mount.locked
— второй писатель ждёт, пока первый освободит mount.
from
— Создание stage, context, или имени изображения для использования в качестве базы для монтирования кэша. По умолчанию — пустая директория.ro
/readonly
— если нужно только чтение из кэша.source
— путь внутриfrom
. Значение по умолчанию — кореньfrom
.mode
— права доступа для новой кэш-директории в восьмеричном формате. По умолчанию: 0755.uid
— идентификатор пользователя для новой кэш-директории. По умолчанию: 0.gid
— идентификатор группы для новой кэш-директории. По умолчанию: 0.
3. SSH (type=ssh)
Если учётные данные, которые вы хотите использовать в своей сборке, являются сокетом или ключом SSH, вы можете использовать mount SSH вместо secret mount. Клонирование частных репозиториев Git является распространённым вариантом использования для mount SSH.
SSH ключи / агент передаётся временно, не остаётся в образе.
Опции:
id
— идентификатор ssh.target
— куда в контейнере монтировать ssh сокет / ключ.
Пример:
# syntax=docker/dockerfile:1.4
FROM ubuntu:22.04
RUN --mount=type=ssh git clone git@github.com:myorg/private.git /src/private
4. Secret (type=secret)
Назначение:
Когда нужны конфиденциальные данные во время сборки: API-ключи, токены, credentials, приватные конфигурации. Нужно, чтобы они были доступны только во время RUN
, и не остались в конечном образе.
На стадии сборки нужно передать секрет через команду docker build
```bash
docker build --secret id=mysecret,src=/local/path/to/secret.txt .
```
В Dockerfile
```dockerfile
RUN --mount=type=secret,id=mysecret \
some-command-that-needs-secret /run/secrets/mysecret
```
Опции:
id
— имя секрета.target
— куда монтировать (если не дефолтное/run/secrets/
).required=true/false
— если секрет обязателен, и сборка должна провалиться, если его нет.mode
,uid
,gid
— права доступа к файлу.
Здоровье контейнеров: HEALTHCHECK
Иногда сервис формально запущен, но внутри него процесс повис или работает некорректно. Docker позволяет описать внутри образа команду для проверки состояния контейнера.
Эта фича помогает обнаружить такие случаи, когда веб-сервер застрял в бесконечном цикле и неспособен обрабатывать новые соединения, но серверный процесс запущен.
HEALTHCHECK
в Dockerfile даёт Docker команду для периодической проверки состояния контейнера; статусы: starting
, healthy
, unhealthy
.
HEALTHCHECK [OPTIONS] CMD command
Пример для веб-сервиса:
HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
CMD curl -f http://localhost:8080/ || exit 1
--timeout
— если ответ на запрос не приходит в течение этого времени, то запрос считается неуспешным.--start-period
— даёт службе время на старт, прежде чем начинать проверки.--interval
— время между проверками.--retries
— число неудачных попыток до перехода вunhealthy
.
Статус выхода команды указывает на состояние здоровья контейнера. Возможные значения:
0: success — контейнер работоспособен и готов к использованию.
1: unhealthy — контейнер работает некорректно.
Команда должна быть лёгкой, детерминированной и возвращать код 0 при OK. Не делайте тяжёлых end-to-end тестов внутри HEALTHCHECK
— это нагружает контейнер.
В Dockerfile может быть только одна инструкция HEALTHCHECK. Если указано несколько, действовать будет только последняя.
Теперь docker ps
показывает STATUS
и health
(если проверка добавлена).
Отдельно здоровье можно проверить так: docker inspect --format='{{json .
State.Health
}}'
Более того, оркестраторы вроде Docker Swarm или Kubernetes могут использовать эту информацию для перезапуска контейнера. Оркестраторы (Swarm, ECS, Kubernetes) и другие инструменты могут использовать этот статус для перезапуска или маршрутизации трафика.
Log drivers: управление логами
По умолчанию Docker сохраняет логи в JSON-файл. Но это далеко не всегда удобно. Для интеграции с системами мониторинга можно выбрать другие драйверы
Docker поддерживает несколько драйверов логирования (json-file по умолчанию, syslog
, journald
, fluentd
, gelf
и т. д.)
Пример запуска контейнера с syslog:
docker run --log-driver=syslog nginx:1.27
Или задать дефолт для всех контейнеров в /etc/docker/daemon.json
:
{
"log-driver": "local",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Docker поддерживает несколько встроенных драйверов:
json-file — стандартный вариант, пишет логи в локальные JSON-файлы.
local — похож на
json-file
, но использует более компактное бинарное хранение и лучше масштабируется.syslog — отправляет логи в системный журнал.
journald — интеграция с systemd и
journalctl
.fluentd — пересылает логи в Fluentd-агент.
gelf — передаёт сообщения в Graylog или Logstash через протокол GELF.
awslogs, splunk, datadog, gcplogs, logentries — отправляют логи в облачные или коммерческие системы.
Каждый драйвер имеет свои особенности, параметры и требования. Например, fluentd
требует установленного и запущенного агента Fluentd на хосте, а syslog
— доступного демона syslog.
Docker login
Когда вы вводите docker login
и видите предупреждение
WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credential-stores
— Docker честно говорит, что по умолчанию сохраняет ваши учётные данные в файл ~/.docker/config.json
в виде, который не шифруется (только кодируется base64).
Это означает, что любой base64 --decode
вернёт user:password
Что такое credential helper и зачем он нужен
Credential helper (помощник по учётным данным) — это небольшая программа, которая умеет прятать/доставать пароли в/из платформенного хранилища (macOS Keychain, Windows Credential Manager, GNOME Keyring / secretservice, pass (password-store) и т. п.)
Docker сам не шифрует, но может делегировать хранение этим помощникам: тогда в config.json
вместо auth
будет ссылка на helper, а сам пароль хранится в безопасном хранилище ОС.
Репозиторий официальных helper-ов — docker-credential-helpers
на GitHub.
Там написана подробная инструкция по установке.
Хранилище учётных данных указывается в файле $HOME/.docker/config.json
, чтобы сообщить Docker Engine о его использовании. Значением параметра credsStore
должен быть суффикс используемой программы (то есть часть, идущая после docker-credential-
).
Примеры конфигурации
macOS (Keychain). Самый простой вариант на Mac — использовать osxkeychain
. В ~/.docker/config.json
добавить:
{ "credsStore": "osxkeychain" }
После этого docker login
положит пароль в Keychain, а в config.json
не будет явных паролей.
В Linux часто используют pass.
Пример использования pass
:
# установить pass и gpg (в Debian/Ubuntu)
sudo apt-get update
sudo apt-get install -y pass gpg
# инициализировать хранилище pass (создать/указать GPG-ключ)
gpg --full-generate-key # `RSA and RSA`; 4096; 0;
gpg --list-keys
pass init "ваш pub GPG_KEY_ID"
# скачать helper docker-credential-pass (пример: релиз v0.9.3)
curl -L -o /usr/local/bin/docker-credential-pass \
https://github.com/docker/docker-credential-helpers/releases/download/v0.9.3/docker-credential-pass-v0.9.3.linux-amd64
chmod +x /usr/local/bin/docker-credential-pass
# настроить Docker:
mkdir -p ~/.docker
cat > ~/.docker/config.json <<'EOF'
{ "credsStore": "pass" }
EOF
После этого docker login
будет сохранять данные в pass
, а не в config.json
. (Релизы helper-ов см. репозиторий — текущая версия релиза на момент написания: v0.9.3)
В Windows Docker Desktop по умолчанию интегрируется с Windows Credential Manager / Docker Desktop store (credsStore
обычно = desktop
или wincred
в зависимости от сборки).
На Windows настройки доступны в GUI Docker Desktop — опция «Securely store docker logins»; либо вручную в config.json
Многие используют только базовые команды вроде docker build
и docker run
, не догадываясь, сколько возможностей скрыто внутри. Docker за 12 лет наполнился стольким количеством функций, что документация стала похожа на бесконечный океан.
Так что если вы до сих пор используете Docker «по минимуму», попробуйте копнуть чуть глубже. Эти небольшие улучшения быстро окупаются — сборки становятся быстрее, контейнеры чище, а система в целом надёжнее и понятнее.
Ваш фидбек повлияет на выпуск 2 части. Жду ваших комментариев :)
© 2025 ООО «МТ ФИНАНС»