Практические советы по работе с docker
Docker — маст-хэв в наборе инструментов разработчика: он упрощает деплой, делает окружение воспроизводимым и снижает эффект «а у меня не работает». В одной из предыдущих статей мы рассказали, как уменьшить время сборки и размер docker-образов. А сегодня обсудим другие фишки при работе с docker, полезные на практике.
📌 Аргументы, переменные и секреты
В docker для параметризации сборки образов, запуска контейнеров и передачи чувствительных данных разработаны специальные механизмы.
🔹 При сборке docker image аргументы передаются через ARG. Они используются только при сборке и недоступны в runtime. Через ARG удобно параметризировать базовый образ или его версию. Например:
ARG BASE_IMAGE=python:3.12
FROM ${BASE_IMAGE}
По умолчанию используется базовый образ python:3.12, но его можно поменять через --build-arg:
docker build --build-arg BASE_IMAGE=python:3.11 -t my-image .
⚠️ Важно: ARG, объявленный до FROM, доступен только для выбора базового образа.
🔹 При запуске docker container аргументы передаются через ENV / .env. Так можно передавать настройки приложения, или, например, гиперпараметры.
FROM python:3.12
WORKDIR /app
COPY app.py .
ENV LOG_LEVEL=INFO #теперь переменная окружения доступна в контейнере
...
Сборка образа и запуск контейнера:
docker build -t myapp .
docker run -e LOG_LEVEL=DEBUG myapp # переопределили значение LOG_LEVEL
🔹 Токены, пароли и ключи передаются через docker secrets. Хранить такую информацию в Dockerfile или переменных окружения небезопасно, так как эти данные могут попасть в историю git или слои образа, следовательно, их можно посмотреть через команду docker inspect.
Secrets позволяет использовать секреты в процессе сборки, не включая их в слои образа.
Пример использования токена из token.txt при сборке:
FROM python:3.12
...
RUN --mount=type=secret,id=mysecret \ # получаем доступ к секрету
TOKEN=$(cat /run/secrets/mysecret) && \ # считываем его в переменную
pip install --index-url <https://$TOKEN@pypi.company.com/simple/my-package> # устанавливаем пакет из приватного репозитория
DOCKER_BUILDKIT=1 docker build --secret id=mysecret,src=token.txt .
📌 Docker volumes
По умолчанию данные внутри контейнера теряются при его удалении. Если необходимо их сохранить — используйте docker volumes. Они здесь сохранятся, даже если контейнер удалён, и их можно переиспользовать в других контейнерах.
Как правило, в volumes пишутся файлы баз данных, логи. Также на этапе разработки удобно монтировать исходный код через volume и не пересобирать контейнер при его изменениях, что позволяет ускорить разработку.
Docker volume может задавать права при монтировании: чтение + запись (rw) используется по умолчанию, а ещё есть только чтение (ro).
В примере ниже монтируется папка app в контейнер в «app» с правами «чтение и запись» (rw) и папка «config в /config с» с правами «только чтение» (ro). Контейнер может использовать настройки, но не изменять их:
docker run -d -v $(pwd)/app:/app:rw -v $(pwd)/config:/config:ro my-image
📌 Использование диска
Часто для работы с данными может понадобиться много свободного места на диске. Если его мало, один из вариантов для увеличения — почистить docker-артефакты.
Команда docker system df показывает, сколько места занимают артефакты docker на диске. Так можно увидеть, что именно занимает место и сколько можно освободить (колонка RECLAIMABLE).
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 17 3 119GB 95.23GB (80%)
Containers 3 2 3.924GB 0B (0%)
Local Volumes 3 0 284MB 284MB (100%)
Build Cache 327 0 63.28GB 63.28GB
Команда docker system prune -a позволит удалить всё из RECLAIMABLE, кроме неиспользуемых Volume. Если нужно удалить ещё и их, то добавляется флаг --volumes. Важно отметить: в RECLAIMABLE попадает всё, что не используется в данный момент, но может понадобиться в будущем. Также стоит следить за build cache: при частых сборках и изменениях проекта кэш может накапливать временные или лишние файлы. Его стоит периодически чистить с помощью следующей команды (но скорость сборки может упасть):
docker builder prune
📌 Docker save / load
Порой сборка занимает много времени: базовый образ медленно качается, или же установка зависимостей съедает кучу времени. Если нужный образ уже собран у коллеги — его можно сохранить в архив с помощью команды docker save, передать по локальной сети и загрузить на своей машине через docker load:
docker save -o myapp.tar myapp:latest
# transfer archive by local net
docker load -i myapp.tar
📌 Ограничение ресурсов
При запуске на одной машине нескольких задач, каждой в своём docker container, нам не хочется их конкуренции за ресурсы и сбою остальных запущенных задач. Docker позволяет ограничивать потребление CPU и RAM, выделять конкретные CPU и GPU для контейнера.
В примере ниже для контейнера выставляются ограничения:
- использование RAM — один гигабайт;
- конкретная GPU — нулевая;
- использование процессора половиной одного ядра — процессор будет применять ядро не более 50% от всего времени:
docker run --cpus=0.5 --memory=1g --gpus "device=0" myapp:latest
Также можно выделить контейнеру конкретные ядра процессора:
docker run --cpuset-cpus="0,1" --memory=1g --gpus "device=0" myapp:latest
📌 Root-права и безопасность
Контейнеры по умолчанию запускаются от root, что увеличивает риски безопасности при наличии уязвимостей в приложении. В продакшн-окружении рекомендуется запускать контейнеры от непривилегированного пользователя. Это снижает возможный ущерб и считается хорошей практикой безопасности.
Например:
FROM python:3.12
...
# Создается пользователь с конкретным UID и домашней папкой
RUN useradd -m -u 10001 appuser
# Создается рабочая папка и выдаются права пользователю
RUN mkdir /app && chmod 750 /app && chown -R appuser:appuser /app
WORKDIR /app
# Переключение на непривилегированного пользователя
USER appuser
# Копирование приложения, которое будет принадлежать appuser
COPY --chown=appuser:appuser app.py .
ENTRYPOINT ["python", "app.py"]
Запуск контейнера:
docker run -d --rm\
-u 10001:10001 \ # Прокидываем UID пользователя и GID
--security-opt=no-new-privileges \ #запрещаем escalation привилегий внутри контейнера
myapp
⚠️ Важно: UID и GID не должны пересекаться с уже существующими, так как это создаёт уязвимость.
📌 Отладка
Часто контейнер, запущенный с опцией --rm, падает сразу после старта. Отладить ошибку можно, запустив контейнер с ENTRYPOINT /bin/bash и введя команду руками.
Флаг --entrypoint позволяет переопределить ENTRYPOINT, не пересобирая образ:
docker run -it --rm container_name --entrypoint /bin/bash
Иногда полезно подключиться к запущенному контейнеру и выполнить какие-то команды внутри (например, посмотреть версию какого-то пакета в окружении) — с этим поможет docker exec:
docker exec -it my_container /bin/bash


