Docker и Python: как упаковать приложение в контейнер

Программирование 04 октябрь 2026 admin

Docker и Python: как упаковать приложение в контейнер

Docker запускает Python-приложение вместе с нужной версией интерпретатора и библиотеками, поэтому среда на сервере совпадает с локальной. Для этого достаточно Dockerfile на slim-образе: скопировать requirements.txt, поставить зависимости и указать команду запуска. Ниже — рабочий Dockerfile, команды сборки и запуска, compose для связки с базой и разбор типичных ошибок.

Что Docker даёт Python-проекту

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

Читайте также: Docker: как удалить контейнер — все способы и команды, Docker остановить контейнер: stop, kill и graceful shutdown.

Второй эффект — фиксация версии интерпретатора. Указав в Dockerfile python:3.12-slim, вы получаете 3.12 независимо от того, какой Python установлен на сервере. virtualenv так не умеет: он изолирует пакеты, но берёт интерпретатор с хоста.

Ограничения тоже реальны. Контейнер добавляет слой абстракции: сеть, логи, права пользователя, тома настраиваются отдельно. Для скрипта, который запускается раз в неделю локально, это лишняя работа — хватит venv. Не подходит Docker и там, где нужен прямой доступ к GPU или специфичному оборудованию, хотя nvidia-container-toolkit закрывает часть таких задач. На некоторых shared-хостингах контейнеры вообще запрещены.

Dockerfile для Python-приложения

Минимальный рабочий вариант для веб-сервиса выглядит так:

FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 
    PYTHONUNBUFFERED=1

WORKDIR /code

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

RUN useradd -m appuser
USER appuser

EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Разберём основные строки. PYTHONUNBUFFERED=1 отключает буферизацию stdout, иначе логи появляются в docker logs с задержкой или теряются при падении процесса. PYTHONDONTWRITEBYTECODE=1 не даёт создавать .pyc внутри образа. WORKDIR задаёт рабочую директорию, а копирование requirements.txt до остального кода — приём для кэша, о нём ниже.

Запуск через CMD в виде JSON-массива выполняется без shell: процесс получает SIGTERM напрямую, и docker stop завершает приложение корректно, а не убивает его по таймауту. Для скриптов вместо сервера подойдёт CMD ["python", "main.py"].

Какой базовый образ выбрать

ОбразРазмерОсобенности
python:3.12порядка гигабайтаполный Debian с компиляторами, сборка проходит почти всегда
python:3.12-slimв несколько раз меньшеготовые колёса PyPI работают, компилятор придётся доустанавливать
python:3.12-alpineсамый компактныйmusl вместо glibc: manylinux-колёса не подходят

Slim — разумный выбор по умолчанию: размер заметно меньше полного образа, а совместимость с бинарными колёсами сохраняется. Alpine выигрывает в размере, но для numpy, cryptography или psycopg2 pip начнёт сборку из исходников, и в образ придётся добавить build-base, musl-dev и заголовочные файлы. Часто это съедает всю экономию. Полный образ оправдан, когда нужны системные библиотеки «из коробки» и разбираться с их установкой не хочется.

Цифры у разных версий отличаются, поэтому проверяйте свой тег командой docker images после сборки.

Сборка, запуск и кэширование слоёв

Собрать и запустить образ:

docker build -t myapp .
docker run --rm -p 8000:8000 --env-file .env myapp

Флаг -p 8000:8000 публикует порт контейнера на хост — без него приложение доступно только изнутри сети Docker. --rm удаляет контейнер после остановки, --env-file подставляет переменные окружения из файла. Секреты внутрь образа не копируйте: они останутся в слое и будут видны через docker history.

Порядок команд влияет на скорость пересборки. Docker кэширует каждый слой и переиспользует его, пока не изменилось содержимое. Если сначала скопировать весь код (COPY . .), а затем ставить зависимости, то любая правка в одном .py-файле обнулит кэш и запустит pip install заново. Поэтому requirements.txt копируется отдельно и раньше.

Рядом с Dockerfile нужен .dockerignore. Без него в образ попадут .venv, __pycache__, .git и .env — минимум это лишние десятки мегабайт и медленный COPY, максимум утечка секретов:

.venv
__pycache__
*.pyc
.git
.env

Compose и режим разработки

Когда приложению нужна база или очередь, окружение удобнее описать в compose-файле:

services:
  app:
    build: .
    ports:
      - "8000:8000"
    env_file: .env
    volumes:
      - ./app:/code/app
    depends_on:
      - db
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example

Запуск — docker compose up. Внутри сети приложение обращается к базе по имени сервиса, то есть по хосту db, а не localhost. Это частая ошибка при переносе кода: локально драйвер подключается к 127.0.0.1, а в контейнере такой адрес указывает на сам контейнер. Параметры подключения выносите в переменные окружения, а не в код.

Volume ./app:/code/app подменяет код в контейнере файлами с хоста — правки видны без пересборки. Для продакшена этот приём не годится: образ перестаёт соответствовать тому, что реально выполняется. Там код должен лежать внутри образа, а состояние (данные БД, загруженные файлы) — в отдельных томах. Для одноразовых задач вместо compose достаточно docker run --rm myapp python script.py.

Частые вопросы

Нужен ли virtualenv внутри контейнера? Нет. Контейнер уже изолирует зависимости, а pip ставит пакеты в системный site-packages образа; виртуальное окружение добавит лишний слой и усложнит пути. Исключение — multi-stage сборка: пакеты ставят в отдельный префикс командой pip install --prefix=/install -r requirements.txt, а во втором этапе подключают через COPY --from=builder /install /usr/local. Так компиляторы не попадают в финальный образ.

Чем заменить Docker, если он не подходит? Для локальной разработки — venv или conda: они легче и не требуют демона. Для воспроизводимости без контейнеров — lock-файлы с точными версиями (requirements.txt, poetry.lock, uv.lock) плюс фиксация версии Python. Для изоляции с более жёсткими границами — виртуальные машины или systemd-юниты под отдельным пользователем.

Как уменьшить образ? Возьмите slim-базовый образ, ставьте пакеты с --no-cache-dir, добавьте .dockerignore, объединяйте связанные RUN в один слой и используйте multi-stage сборку. Каждый установленный системный пакет становится слоем, поэтому чистить кэш apt нужно в той же команде RUN, где шла установка.

Почему приложение не видно с хоста? Проверьте три вещи: сервер слушает 0.0.0.0, а не 127.0.0.1; порт опубликован через -p; в команде запуска нет опечатки в номере порта. Внутри контейнера localhost — это сам контейнер, поэтому bind на 127.0.0.1 делает сервис недоступным снаружи.

Начните с одного сервиса: соберите Dockerfile на slim-образе, добавьте .dockerignore и запустите приложение через docker run с публикацией порта. Когда появятся база данных и другие зависимости, перенесите описание в compose. Держите версии Python и пакетов зафиксированными в файлах — тогда образ будет собираться и вести себя одинаково на любой машине.

Редакция «ИТ Фишки»
Качественные материалы о софте, технологиях и гаджетах
Ссылка на материал
Поделиться:
Похожие публикации
Комментарии (0)

© Copyright © 2026. Все права защищены.