Справочник · Терминал и система
Docker: команды, Dockerfile и Compose
Образы и контейнеры, тома, журналы, хороший Dockerfile с многоэтапной сборкой и docker compose для локальной разработки. Плюс уборка места на диске.
Docker упаковывает приложение вместе со всем окружением в образ, а из образа запускает изолированные контейнеры. Ниже команды и приёмы, которые нужны в обычной работе, без углубления во внутренности.
Образ и контейнер
Образ — неизменяемый шаблон: файловая система и настройки запуска. Контейнер — запущенный экземпляр образа со своим слоем для записи. Из одного образа можно запустить сколько угодно контейнеров. Удаление контейнера стирает всё, что было записано внутри, если это не лежало в томе.
Контейнеры
docker run --rm -it ubuntu:24.04 bash # временный контейнер с оболочкой
docker run -d --name web -p 8080:80 nginx:1.27 # в фоне, порт 8080 хоста -> 80 контейнера
docker ps # запущенные
docker ps -a # все, включая остановленные
docker logs -f --tail 100 web # журнал, последние 100 строк и дальше
docker exec -it web sh # войти в работающий контейнер
docker stop web && docker rm web
docker restart web
docker inspect web # все настройки в JSON
docker stats # процессор и память по контейнерам
docker cp web:/etc/nginx/nginx.conf . # скопировать файл из контейнераКлюч run | Смысл |
|---|---|
-d | запустить в фоне |
-it | интерактивный терминал |
--rm | удалить контейнер после остановки |
--name | своё имя вместо случайного |
-p 8080:80 | опубликовать порт: хост слева, контейнер справа |
-p 127.0.0.1:8080:80 | опубликовать только на локальном адресе |
-e KEY=value | переменная окружения |
--env-file .env | переменные из файла |
-v data:/var/lib/data | именованный том |
-v "$PWD":/app | смонтировать папку хоста |
-w /app | рабочий каталог внутри |
--restart unless-stopped | поднимать после перезапуска Docker и сбоев |
-u 1000:1000 | от какого пользователя запускать процесс |
docker inspect выдаёт большой JSON. Отдельное поле удобно достать шаблоном, например docker inspect -f '{{.State.Status}}' web, а весь вывод разобрать в JSON-форматтере.
Образы
docker images # локальные образы
docker pull postgres:16 # скачать
docker build -t myapp:1.4.0 . # собрать из Dockerfile в текущей папке
docker build -t myapp:dev -f docker/Dockerfile.dev .
docker tag myapp:1.4.0 registry.local/myapp:1.4.0
docker push registry.local/myapp:1.4.0
docker history myapp:1.4.0 # из каких слоёв состоит и сколько весит каждый
docker rmi myapp:oldХороший Dockerfile
Пример для приложения на Python с многоэтапной сборкой:
# этап сборки: компиляторы и заголовки нужны только здесь
FROM python:3.12-slim AS build
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# итоговый образ: только то, что нужно для работы
FROM python:3.12-slim
WORKDIR /app
RUN useradd --create-home --uid 1000 app
COPY --from=build /install /usr/local
COPY --chown=app:app . .
USER app
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s CMD python -c "import socket; socket.create_connection(('127.0.0.1', 8000), 2)"
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]На что обратить внимание:
- Порядок слоёв. Docker кеширует каждый шаг. Сначала копируйте файл зависимостей и ставьте их, и только потом копируйте код. Тогда правка кода не будет заново скачивать все пакеты.
- Многоэтапная сборка. Компиляторы, исходники и кеши остаются в первом этапе, итоговый образ получается заметно меньше.
- Не root. Инструкция
USERзапускает процесс от обычного пользователя. Если приложение взломают, у атакующего будет меньше прав внутри контейнера. - Форма CMD. Запись в виде JSON-массива запускает процесс напрямую, и он получает сигнал остановки. В строковой форме процесс запускается через
sh -c, иdocker stopбудет ждать 10 секунд, прежде чем убить его. - Один процесс на контейнер. Веб-приложение и база в разных контейнерах проще обновлять и масштабировать.
Рядом с Dockerfile положите .dockerignore, чтобы в контекст сборки не попадало лишнее:
.git
node_modules
__pycache__
*.log
.envCMD и ENTRYPOINT
| Инструкция | Роль | Как переопределить |
|---|---|---|
ENTRYPOINT | программа, которая всегда запускается | docker run --entrypoint |
CMD | аргументы по умолчанию или команда целиком | аргументами после имени образа |
Если задан ENTRYPOINT ["python", "manage.py"] и CMD ["runserver"], то docker run app migrate выполнит python manage.py migrate.
Тома и данные
docker volume create pgdata
docker run -d --name db -v pgdata:/var/lib/postgresql/data -e POSTGRES_PASSWORD=dev postgres:16
docker volume ls
docker volume inspect pgdata| Вид | Запись | Когда использовать |
|---|---|---|
| именованный том | -v pgdata:/data | данные баз и сервисов, Docker сам управляет местом |
| папка хоста | -v "$PWD"/src:/app/src | разработка: правите код на хосте, контейнер видит сразу |
| только чтение | -v "$PWD"/conf:/etc/app:ro | конфиги, которые контейнер не должен менять |
| tmpfs | --tmpfs /tmp | временные файлы в памяти |
Резервная копия тома через временный контейнер:
docker run --rm -v pgdata:/data -v "$PWD":/backup alpine tar -czf /backup/pgdata.tar.gz -C /data .Для баз данных надёжнее делать дамп средствами самой базы, например docker exec db pg_dump -U postgres app > app.sql, чем копировать файлы работающей базы.
docker compose
Compose описывает несколько контейнеров одним файлом compose.yaml. Это удобнее, чем помнить длинные команды run.
services:
app:
build: .
ports:
- "127.0.0.1:8000:8000"
environment:
DB_HOST: db
DB_PORT: "5432"
DB_USER: app
DB_PASSWORD: dev
depends_on:
db:
condition: service_healthy
volumes:
- ./src:/app/src
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: dev
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
retries: 10
volumes:
pgdata:Сервисы одного файла видят друг друга по имени: приложение подключается к базе по адресу db:5432.
docker compose up -d # поднять всё в фоне
docker compose up -d --build # пересобрать образы и поднять
docker compose ps
docker compose logs -f app
docker compose exec db psql -U app
docker compose run --rm app pytest # одноразовая команда
docker compose down # остановить и удалить контейнеры
docker compose down -v # то же и удалить тома, то есть данные
docker compose config # итоговый файл со всеми подстановкамиCompose подставляет переменные из файла .env рядом с compose.yaml: в YAML пишут ${POSTGRES_PASSWORD}. Отступы в YAML коварны; если файл не читается, его удобно проверить конвертером YAML в JSON, который сразу покажет структуру.
Уборка места
Docker быстро съедает диск: старые образы, остановленные контейнеры, кеш сборки.
docker system df # сколько занимает каждая категория
docker container prune # удалить остановленные контейнеры
docker image prune # удалить образы без тегов
docker image prune -a # удалить все образы, которые не используются контейнерами
docker builder prune # очистить кеш сборки
docker system prune # всё перечисленное, кроме томов
docker volume prune # неиспользуемые тома, вместе с даннымиПеред docker volume prune стоит посмотреть docker volume ls: том остановленной, но нужной базы тоже считается неиспользуемым, если контейнер удалён.
Отладка
- Контейнер сразу завершается:
docker logs имяпокажет последнее, что он вывел. Код выхода виден вdocker ps -a. - Нужно посмотреть, что внутри образа, у которого не запускается основная команда:
docker run --rm -it --entrypoint sh myapp:1.4.0. - Приложение не открывается с хоста: проверьте, что оно слушает
0.0.0.0, а не127.0.0.1внутри контейнера, и что порт опубликован (docker port имя). - Изменения в коде не видны: образ собран раньше, пересоберите с
--buildили смонтируйте папку с кодом томом.