Python у Docker: створення легкого dev-середовища

Коли ти працюєш над кількома Python-проєктами одночасно, то рано чи пізно зіткнешся з класичною проблемою: один проєкт вимагає Python 3.9 з Django 3.2, а інший — Python 3.11 з FastAPI. Встановлювати все це на хостову систему? Ні, дякую. Саме тут на сцену виходить Docker, який дозволяє створити ізольоване, легке та портативне середовище розробки. Розповім, як я налаштовую своє dev-середовище Python у Docker і чому це справді зручно.

Навіщо взагалі використовувати Docker для Python

Перш ніж заглибитися в технічні деталі, варто зрозуміти базову мотивацію. Docker — це не просто модний інструмент, а реальне рішення кількох проблем. По-перше, ти отримуєш повну ізоляцію середовища. Кожен проєкт живе у своєму контейнері з власними залежностями, версіями бібліотек та конфігураціями. По-друге, портативність: твій Dockerfile працюватиме однаково на macOS, Linux чи Windows. По-третє, легкість масштабування — від локальної розробки до production-deployment зміни мінімальні.

Звісно, є й складнощі. Docker додає додатковий шар абстракції, який треба вивчити. Іноді налагодження помилок усередині контейнера складніше, ніж на хості. Та й продуктивність на Windows з WSL2 може бути не ідеальною. Але, на мою думку, переваги значно переважують недоліки, особливо коли працюєш у команді.

Базовий Dockerfile для Python-проєкту

Почнемо з простого, але робочого прикладу. Створюємо файл Dockerfile у корені проєкту. Ось як виглядає мій стандартний шаблон для Flask-додатку:

# Використовуємо офіційний образ Python як базовий
FROM python:3.11-slim

# Встановлюємо робочу директорію
WORKDIR /app

# Копіюємо файл залежностей
COPY requirements.txt .

# Встановлюємо залежності
RUN pip install --no-cache-dir -r requirements.txt

# Копіюємо весь проєкт
COPY . .

# Відкриваємо порт
EXPOSE 5000

# Команда запуску
CMD ["python", "app.py"]Code language: PHP (php)

Що тут відбувається? Ми беремо офіційний образ Python 3.11 у slim-версії (вона легша за повну). Потім встановлюємо робочу директорію /app, куди копіюємо спершу тільки requirements.txt. Це важливий трюк: Docker кешує шари, тому якщо залежності не змінилися, він не буде їх переустановлювати при кожній зборці. Лише після цього копіюємо решту файлів проєкту.

Оптимізація розміру образу Docker для Python

Якщо дотримуватися базового підходу, образ може вийти досить об’ємним. Ось кілька порад, які допоможуть його зменшити. Використовуй python:3.11-alpine замість slim, якщо проєкт не залежить від специфічних системних бібліотек. Alpine — це мінімалістичний Linux-дистрибутив, який дає образи на 50-70% менші. Правда, іноді виникають проблеми з компіляцією деяких Python-пакетів, тому перевіряй сумісність.

Ще один важливий момент — використання .dockerignore. Створи цей файл поруч з Dockerfile і додай туди все, що не повинно потрапити в образ:

__pycache__
*.pyc
*.pyo
*.pyd
.Python
env/
venv/
.git
.gitignore
.vscode
.idea
*.md
tests/
.pytest_cache

Це прискорить збірку та зменшить розмір фінального образу. Я колись забув додати .git у .dockerignore, і образ роздувся на додаткових 300 МБ — неприємний сюрприз.

Схема роботи Python у Docker контейнера з Python
Візуалізація шарів Docker-образу для Python

Docker Compose для зручнішої роботи з Python

Одного Dockerfile для Python часто недостатньо, особливо коли проєкт вимагає бази даних, Redis чи інших сервісів. Тут на допомогу приходить Docker Compose. Створюємо файл docker-compose.yml:

version: '3.8'

services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/app
    environment:
      - FLASK_ENV=development
      - DATABASE_URL=postgresql://user:password@db:5432/mydb
    depends_on:
      - db
    command: python app.py

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:Code language: PHP (php)

Такий конфіг запускає два сервіси: Flask-додаток та PostgreSQL. Зверни увагу на секцію volumes — вона монтує поточну директорію всередину контейнера. Це означає, що зміни в коді відразу відображаються в контейнері без пересборки образу. Це надзвичайно зручно під час розробки. Для запуску всієї системи досить однієї команди: docker-compose up.

Робота з віртуальними середовищами всередині Docker

Цікаве питання: чи потрібне віртуальне середовище (venv) всередині Docker-контейнера? Технічно — ні, адже контейнер уже є ізольованим середовищем. Але я все одно часто використовую venv усередині контейнера з кількох причин. По-перше, це узгоджує процес розробки, якщо хтось із команди працює без Docker. По-друге, деякі інструменти (наприклад, Poetry) краще працюють з явним віртуальним середовищем.

Ось модифікований Dockerfile з venv:

FROM python:3.11-slim

WORKDIR /app

# Створюємо віртуальне середовище
RUN python -m venv /opt/venv

# Активуємо його (додаємо до PATH)
ENV PATH="/opt/venv/bin:$PATH"

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

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]Code language: PHP (php)

Тепер усі пакети встановлюються у /opt/venv, а не глобально. Це робить середовище чистішим і ближчим до того, як ми працюємо локально.

Налагодження Python-коду в Docker

Одна з найбільших претензій до Docker у розробці — складність налагодження. Але насправді все не так погано, якщо правильно налаштувати інструменти. Для VS Code є чудове розширення Remote – Containers, яке дозволяє підключитися до контейнера і працювати так, ніби код виконується локально. Ти можеш ставити breakpoint’и, дивитися змінні, виконувати код крок за кроком.

Альтернативний підхід — використання pdb або ipdb. Додаємо в код:

import ipdb; ipdb.set_trace()Code language: JavaScript (javascript)

Потім запускаємо контейнер в інтерактивному режимі:

docker-compose run --service-ports web

Коли виконання дійде до set_trace(), ти потрапиш у інтерактивний debugger прямо в терміналі. Не так зручно, як візуальний дебагер, але працює скрізь і не вимагає додаткових налаштувань.

Багатоетапні збірки для production

Коли переходиш від розробки до production, важливо мінімізувати розмір образу та виключити все зайве. Багатоетапні збірки (multi-stage builds for Python у Docker) — саме те, що потрібно. Ось приклад:

# Етап 1: Збірка залежностей
FROM python:3.11-slim as builder

WORKDIR /app

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

# Етап 2: Фінальний образ
FROM python:3.11-slim

WORKDIR /app

# Копіюємо встановлені пакети з першого етапу
COPY --from=builder /root/.local /root/.local
COPY . .

ENV PATH=/root/.local/bin:$PATH

EXPOSE 5000

CMD ["python", "app.py"]Code language: PHP (php)

У першому етапі ми встановлюємо всі залежності, включаючи build-tools, які можуть бути потрібні для компіляції деяких пакетів. У другому етапі копіюємо тільки результат — встановлені Python-пакети, без компіляторів та іншого баласту. Це може зменшити фінальний образ на 30-50%.

Безпека Python у Docker

Коли працюєш з Docker, не можна ігнорувати питання безпеки. Кілька простих правил: ніколи не запускай процеси від root-користувача всередині контейнера. Створи окремого користувача:

FROM python:3.11-slim

# Створюємо непривілейованого користувача
RUN useradd -m -u 1000 appuser

WORKDIR /app

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

COPY . .

# Змінюємо власника файлів
RUN chown -R appuser:appuser /app

# Перемикаємося на нового користувача
USER appuser

EXPOSE 5000

CMD ["python", "app.py"]Code language: PHP (php)

Також регулярно оновлюй базові образи. Офіційні образи Python на Docker Hub постійно отримують патчі безпеки. Використовуй інструменти на кшталт docker scan або trivy для сканування вразливостей у своїх образах.

Практичні поради з досвіду

За роки роботи з Docker і Python я накопичив чимало практичних порад, які можуть зекономити твій час. Завжди використовуй конкретні версії у requirements.txt. Замість flask пиши flask==2.3.2. Це гарантує відтворюваність збірок. Якщо проєкт великий, розглянь використання pip-tools або Poetry для керування залежностями — вони створюють lock-файли, які фіксують точні версії всіх транзитивних залежностей.

Ще один лайфхак — використання docker-compose.override.yml для локальних налаштувань. Основний docker-compose.yml комітиш у репозиторій, а в override-файлі зберігаєш особисті налаштування (наприклад, інші порти або додаткові volume). Docker Compose автоматично поєднує обидва файли.

Не забувай про логування. У контейнерах виводь логи в stdout/stderr, а не в файли. Docker збирає ці потоки, і ти зможеш переглядати їх через docker logs або інтегрувати з системами централізованого логування на кшталт ELK.

Коли Docker може бути зайвим для Python-розробки

Попри всі переваги, Docker не завжди найкращий вибір. Для невеликих скриптів або особистих проєктів, де не потрібна складна інфраструктура, достатньо звичайного venv. Якщо працюєш виключно на Linux і не плануєш ділитися проєктом, то ізоляція Docker може бути надлишковою. Також на слабких машинах Docker може споживати чимало ресурсів, особливо якщо запущено кілька контейнерів одночасно.

Іноді альтернативи на кшталт pyenv + virtualenv або навіть conda можуть виявитися простішими та швидшими у налаштуванні. Важливо оцінити конкретні потреби проєкту, а не просто слідувати моді на контейнеризацію.

Інтеграція з CI/CD

Якщо вже використовуєш Docker для розробки, логічно продовжити його використання в CI/CD-процесах. У GitHub Actions, наприклад, можна додати workflow, який збирає Docker-образ і запускає тести всередині контейнера:

name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Build Docker image
        run: docker build -t myapp:test .
      
      - name: Run tests
        run: docker run myapp:test pytest tests/Code language: HTTP (http)

Це гарантує, що тести виконуються в тому ж середовищі, що й локально. Плюс, легко додати додаткові кроки: лінтинг, перевірку безпеки, деплоймент. Усе це в межах одного Docker-образу, який потім можна розгорнути в production без змін.

Підсумки

Docker справді змінює підхід до розробки на Python. Ти отримуєш ізольоване, відтворюване середовище, яке працює однаково на будь-якій платформі. Так, є крива навчання, і іноді виникають нюанси з продуктивністю або налагодженням. Але коли робиш все правильно — використовуєш легкі базові образи, оптимізуєш шари, грамотно організовуєш Dockerfile для Python і docker-compose.yml — то отримуєш потужний інструмент, який прискорює розробку та спрощує життя всій команді.

Я рекомендую почати з простого проєкту: спробуй обгорнути свій Flask або FastAPI додаток у Docker, додай базу даних через docker-compose. Побалуйся з налаштуваннями, подивись, як воно працює. Після кількох днів експериментів ти зрозумієш, чи підходить тобі такий workflow. Для багатьох розробників (включно зі мною) Docker у Python-розробці став стандартом де-факто, і повертатися назад просто не хочеться.

Рекомендуємо прочитати

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *