Docker для начинающих: от основ до Python-приложений

Курс охватывает фундаментальные принципы контейнеризации, написание Dockerfile и работу с Docker Compose. Вы научитесь упаковывать и запускать Python-приложения в изолированных средах, управляя зависимостями и данными.

1. Введение в Docker: установка, архитектура и базовые команды управления контейнерами

Введение в Docker: установка, архитектура и базовые команды управления контейнерами

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

В этой статье мы разберем, что такое Docker, чем он отличается от виртуальных машин, как устроена его архитектура и научимся управлять контейнерами с помощью базовых команд.

Что такое Docker и зачем он нужен?

Docker — это открытая платформа для разработки, доставки и запуска приложений. Она позволяет отделить ваше приложение от инфраструктуры, чтобы вы могли быстро доставлять программное обеспечение. С Docker вы можете управлять инфраструктурой так же, как управляете своими приложениями.

Главная идея Docker заключается в упаковке приложения и всех его зависимостей (библиотек, конфигурационных файлов, среды исполнения) в единый объект, называемый контейнером. Это гарантирует, что приложение будет работать одинаково везде: на ноутбуке разработчика, на тестовом сервере или в облаке.

Проблема «It works on my machine»

Представьте, что вы пишете приложение на Python. У вас установлена конкретная версия Python (например, 3.9), определенные версии библиотек pandas и flask, а также специфические настройки операционной системы. Вы передаете код коллеге, у которого стоит Python 3.11 и другие версии библиотек. Приложение падает с ошибкой. Docker решает эту проблему, изолируя среду исполнения.

Контейнеры против Виртуальных машин

Чтобы понять суть Docker, важно сравнить его с виртуальными машинами (VM). Оба подхода используются для изоляции приложений, но работают они по-разному.

!Сравнение архитектуры: виртуальные машины требуют полноценную гостевую ОС для каждого экземпляра, тогда как контейнеры используют ядро хостовой системы.

  • Виртуальные машины: Эмулируют полноценный компьютер. На одном физическом сервере запускается гипервизор, поверх которого работают несколько гостевых операционных систем (Guest OS). Каждая VM имеет свое ядро, выделенную память и процессорное время. Это надежно, но ресурсоемко: каждая VM занимает гигабайты места и требует времени на загрузку.
  • Контейнеры: Используют ядро хостовой операционной системы. Они не требуют установки отдельной ОС для каждого приложения. Контейнеры легковесны (занимают мегабайты), запускаются мгновенно и потребляют меньше ресурсов.
  • Архитектура Docker

    Docker использует клиент-серверную архитектуру. Понимание этих компонентов критически важно для дальнейшей работы.

    !Клиент-серверная архитектура Docker: взаимодействие клиента, демона и реестра образов.

    Основные компоненты

  • Docker Daemon (dockerd): Это «сердце» Docker. Фоновый процесс, который работает на хост-машине. Он слушает запросы от клиента API и управляет объектами Docker (образами, контейнерами, сетями).
  • Docker Client (docker): Это то, с чем вы взаимодействуете в терминале. Когда вы пишете команду docker run, клиент отправляет запрос демону, который и выполняет работу.
  • Docker Registry: Хранилище образов. Существует публичный реестр Docker Hub, где хранятся тысячи готовых образов (например, Python, PostgreSQL, Nginx). Вы также можете создавать свои приватные реестры.
  • Ключевые понятия: Образ и Контейнер

    Новички часто путают эти два понятия. Давайте проведем аналогию с кулинарией или программированием.

    Образ (Image): Это шаблон или класс* в программировании. Это неизменяемый файл, содержащий исходный код, библиотеки, зависимости и инструменты, необходимые для запуска приложения. Образ — это то, что вы скачиваете или собираете. Контейнер (Container): Это запущенный экземпляр образа или объект* класса. Это живой процесс. Вы можете запустить множество контейнеров из одного и того же образа.

    > Образ — это рецепт торта. Контейнер — это сам испеченный торт. Вы можете испечь сколько угодно тортов по одному рецепту.

    Установка Docker

    Процесс установки зависит от вашей операционной системы. Мы не будем дублировать документацию, так как она обновляется, но дадим основные направления.

  • Windows и macOS: Рекомендуется установить Docker Desktop. Это удобное приложение, которое включает в себя Docker Engine, Docker CLI, Docker Compose и графический интерфейс. Скачать его можно на официальном сайте Docker.
  • Linux: На Linux (например, Ubuntu) обычно устанавливается Docker Engine напрямую через терминал. Это наиболее «нативный» способ работы.
  • После установки откройте терминал (или PowerShell) и введите команду для проверки версии:

    Если вы видите номер версии, значит, установка прошла успешно.

    Базовые команды управления

    Теперь перейдем к практике. Мы научимся скачивать образы, запускать контейнеры и управлять ими.

    1. Запуск первого контейнера

    Традиционно начнем с «Hello World». Введите в терминале:

    Что произошло?

  • Docker клиент связался с демоном.
  • Демон проверил, есть ли образ hello-world у вас локально.
  • Так как его не было, демон скачал (pull) его с Docker Hub.
  • Демон создал контейнер из этого образа и запустил его.
  • Контейнер вывел сообщение в терминал и завершил работу.
  • 2. Работа с образами

    Чтобы скачать образ без запуска, используется команда pull. Давайте скачаем официальный образ веб-сервера Nginx:

    Чтобы посмотреть список всех скачанных образов на вашем компьютере:

    Вы увидите таблицу с репозиторием, тегом (версией), ID образа и его размером.

    3. Запуск контейнера в фоновом режиме

    Если мы запустим Nginx просто командой docker run nginx, он захватит терминал, и мы не сможем вводить другие команды. Для серверных приложений обычно используется флаг -d (detach — отсоединить), чтобы запустить контейнер в фоне.

    Также нам нужно получить доступ к веб-серверу. Контейнеры изолированы, поэтому нам нужно «пробросить» порт из контейнера наружу. Для этого используется флаг -p.

    Команда будет выглядеть так:

    Разберем аргументы: * -d: Запуск в фоновом режиме. * -p 8080:80: Перенаправление порта. Порт 8080 на вашем компьютере (host) будет вести на порт 80 внутри контейнера. * --name my-web-server: Мы дали контейнеру понятное имя, иначе Docker придумает случайное (например, vigorous_newton). * nginx: Имя образа.

    Теперь откройте браузер и перейдите по адресу http://localhost:8080. Вы увидите приветственную страницу Nginx.

    4. Просмотр запущенных контейнеров

    Чтобы узнать, какие контейнеры работают прямо сейчас, используйте команду:

    Если вы хотите увидеть все контейнеры, включая те, что были остановлены (например, наш hello-world), добавьте флаг -a:

    5. Остановка и удаление

    Чтобы остановить работающий контейнер, используйте команду stop и имя (или ID) контейнера:

    Теперь контейнер остановлен, но он все еще существует (занимает место на диске). Чтобы удалить его окончательно, используйте rm:

    Важно: Вы не можете удалить запущенный контейнер командой rm без предварительной остановки (если не использовать флаг -f для принудительного удаления, что не рекомендуется делать без необходимости).

    Если вы хотите удалить сам образ (рецепт), используйте команду rmi (remove image):

    Жизненный цикл контейнера

    Понимание состояний контейнера поможет вам в отладке.

    !Жизненный цикл контейнера: от создания до удаления.

    * Created: Контейнер создан, но не запущен. * Running: Процесс внутри контейнера работает. * Paused: Все процессы контейнера приостановлены. * Exited (Stopped): Процесс завершился или был остановлен. * Dead: Контейнер неработоспособен (встречается редко).

    Заключение

    В этой статье мы познакомились с фундаментом Docker. Мы узнали, что контейнеры — это легковесная альтернатива виртуальным машинам, использующая ядро хостовой системы. Мы разобрали архитектуру (клиент, демон, реестр) и научились выполнять базовые операции: скачивать образы, запускать, останавливать и удалять контейнеры.

    В следующих статьях мы углубимся в создание собственных образов с помощью Dockerfile и научимся упаковывать Python-приложения, чтобы они работали идеально на любой машине.

    2. Создание образов: синтаксис Dockerfile, слои и сборка собственного окружения

    Создание образов: синтаксис Dockerfile, слои и сборка собственного окружения

    В предыдущей статье мы научились запускать готовые контейнеры, такие как Nginx или Hello-World. Это похоже на использование готовых блюд из супермаркета: быстро и удобно. Но настоящая сила Docker раскрывается, когда вы начинаете «готовить» сами.

    В этой части курса мы разберем, как упаковать ваше собственное Python-приложение в Docker-образ. Мы изучим синтаксис Dockerfile, поймем, как работают слои файловой системы, и соберем наш первый кастомный образ.

    Что такое Dockerfile?

    Dockerfile — это простой текстовый файл, содержащий набор инструкций, которые Docker читает по порядку для автоматической сборки образа. Если образ — это «слепок» системы, то Dockerfile — это рецепт его приготовления.

    В этом файле мы описываем:

  • Какую операционную систему взять за основу.
  • Какие файлы скопировать внутрь.
  • Какие команды выполнить (например, установка библиотек).
  • Какую команду запустить при старте контейнера.
  • !Процесс превращения текстового Dockerfile в бинарный образ через команду сборки.

    Основные инструкции Dockerfile

    Инструкции в Dockerfile принято писать ЗАГЛАВНЫМИ БУКВАМИ. Рассмотрим самые важные из них на примере подготовки Python-приложения.

    FROM

    Каждый Dockerfile должен начинаться с инструкции FROM. Она задает базовый образ.

    Здесь мы говорим Docker: «Возьми за основу официальный образ Python версии 3.9». Тег slim означает урезанную, более легкую версию Linux, что позволяет экономить место.

    WORKDIR

    Эта инструкция создает рабочую директорию внутри образа и делает ее текущей для всех последующих команд. Это аналог команды cd в терминале.

    Если папки /app не существовало, Docker создаст ее.

    COPY и ADD

    Эти инструкции копируют файлы с вашего компьютера (хоста) внутрь образа.

    Эта команда берет файл requirements.txt из папки проекта и копирует его в текущую рабочую директорию образа (/app, так как мы задали ее выше). Точка . означает «текущая папка».

    > Часто возникает вопрос: в чем разница между COPY и ADD? Инструкция COPY просто копирует файлы. Инструкция ADD более мощная: она умеет распаковывать архивы (tar) и даже скачивать файлы по URL. Однако в 95% случаев рекомендуется использовать COPY как более предсказуемую команду.

    RUN

    Инструкция RUN выполняет команду во время сборки образа. Результат выполнения этой команды сохраняется в образе.

    Здесь мы устанавливаем зависимости Python. Это происходит один раз — при создании образа.

    CMD и ENTRYPOINT

    Эти инструкции говорят Docker, что нужно сделать, когда контейнер запускается.

    Разница между RUN и CMD критична: * RUN выполняется на этапе сборки (Build time). Например, установка библиотек. * CMD выполняется на этапе запуска (Runtime). Например, старт вашего скрипта.

    Практика: Собираем Python-приложение

    Давайте создадим простое веб-приложение и упакуем его.

    Шаг 1: Подготовка файлов

    Создайте папку my-docker-app и внутри нее два файла.

    Файл main.py (простейший веб-сервер на Flask):

    Файл requirements.txt:

    Шаг 2: Создание Dockerfile

    В той же папке создайте файл без расширения с именем Dockerfile и добавьте в него следующий код:

    Обратите внимание на инструкцию EXPOSE 5000. Она носит информационный характер и говорит пользователю образа, что приложение внутри использует порт 5000.

    Шаг 3: Сборка образа (Build)

    Откройте терминал в папке с файлами и выполните команду:

    Разберем команду: * docker build: команда сборки. * -t my-python-app: флаг tag, задает имя нашему образу. * .: самая важная часть. Точка указывает на контекст сборки (build context). Это говорит Docker: «Ищи Dockerfile и файлы для копирования в текущей директории».

    После нажатия Enter вы увидите, как Docker выполняет шаги (Step 1/7, Step 2/7...), скачивает Python, устанавливает Flask и завершает сборку.

    Слои и кэширование

    Понимание слоев — ключ к созданию эффективных образов. Образ Docker состоит из набора слоев, доступных только для чтения. Каждая инструкция (RUN, COPY, ADD) создает новый слой поверх предыдущего.

    !Структура слоеного пирога файловой системы Docker, где каждый шаг Dockerfile добавляет новый уровень.

    Как работает кэш?

    Когда вы запускаете docker build, Docker проверяет, изменилось ли что-то на каждом шаге. Если инструкция и файлы не менялись, Docker использует кэшированный слой, вместо того чтобы выполнять команду заново. Это значительно ускоряет сборку.

    Посмотрите на наш Dockerfile еще раз:

    Почему мы сначала копируем только requirements.txt, устанавливаем библиотеки, и только потом копируем весь код (COPY . .)?

    Это оптимизация кэша:

  • Исходный код (main.py) меняется часто.
  • Зависимости (requirements.txt) меняются редко.
  • Если мы изменим main.py, Docker увидит изменения только на шаге COPY . ..
  • Предыдущие шаги (установка Flask) будут взяты из кэша. Сборка займет секунды.
  • Если бы мы написали COPY . . в самом начале, то любое изменение в коде заставляло бы Docker заново переустанавливать все библиотеки, так как кэш сбрасывается при изменении любого файла в инструкции COPY.

    Файл .dockerignore

    Перед отправкой контекста в Docker daemon (процесс сборки), CLI ищет файл .dockerignore. Он работает так же, как .gitignore.

    В него нужно добавлять файлы, которые не должны попасть в образ: * Папки виртуального окружения (venv, .env). * Папки Git (.git). * Временные файлы и кэши (__pycache__). * Файлы с секретами и паролями.

    Пример .dockerignore:

    Это уменьшает размер контекста сборки и повышает безопасность.

    Запуск нашего образа

    Теперь, когда образ my-python-app собран, давайте запустим его:

    Перейдите в браузере по адресу http://localhost:5000. Вы должны увидеть сообщение: Hello from Docker! Python version...

    Мы успешно упаковали Python-приложение, изолировав его от настроек вашего локального компьютера.

    Заключение

    В этой статье мы разобрали фундамент создания образов: * Dockerfile — это инструкция по сборке. * FROM, RUN, COPY, CMD — базовые команды, позволяющие настроить окружение. * Слои — механизм, позволяющий Docker переиспользовать данные и ускорять сборку. * Оптимизация — правильный порядок команд экономит время и ресурсы.

    Теперь вы умеете не только пользоваться чужими контейнерами, но и создавать свои. В следующей статье мы перейдем к инструменту Docker Compose, который позволит нам запускать сложные приложения, состоящие из нескольких контейнеров (например, Python-приложение + база данных) одной командой.

    3. Работа с данными и сетью: тома (Volumes) и взаимодействие между контейнерами

    Работа с данными и сетью: тома (Volumes) и взаимодействие между контейнерами

    В предыдущих статьях мы научились упаковывать Python-приложения в образы и запускать их. Мы сравнили контейнеры с «одноразовой посудой»: они легкие, удобные, но по умолчанию не предназначены для длительного хранения данных. Если вы удалите контейнер, все файлы, созданные внутри него во время работы, исчезнут навсегда.

    Но что делать, если нам нужно сохранить базу данных? Или если наше приложение должно общаться с другим сервисом, например, с Redis или PostgreSQL? В этой статье мы решим две фундаментальные задачи: сохранение данных (Persistence) и сетевое взаимодействие (Networking).

    Проблема потери данных

    По умолчанию файловая система контейнера эфемерна (временна). Когда вы запускаете контейнер, Docker создает тонкий слой для записи поверх неизменяемого образа. Все изменения происходят в этом слое.

    Представьте, что вы запустили контейнер с базой данных, наполнили его данными клиентов, а затем остановили и удалили контейнер командой docker rm. Ваши данные исчезнут вместе с контейнером. Это неприемлемо для большинства реальных задач.

    Для решения этой проблемы Docker предлагает механизм Volumes (Тома).

    Тома (Volumes) и Bind Mounts

    Существует два основных способа сохранить данные на хост-машине (вашем компьютере), чтобы они пережили удаление контейнера.

    !Сравнение Bind Mounts и Volumes: Bind Mounts используют любую папку, Volumes используют управляемое хранилище Docker.

    1. Bind Mounts (Привязка папок)

    Это самый простой способ, который мы часто используем при разработке. Вы просто говорите Docker: «Возьми вот эту папку на моем рабочем столе и отобрази её внутри контейнера».

    Это полезно, когда вы пишете код и хотите, чтобы изменения в файле .py на вашем компьютере мгновенно появлялись внутри контейнера без пересборки образа.

    Синтаксис флага -v: -v /путь/на/хосте:/путь/в/контейнере

    Пример запуска: bash docker volume create my-db-data bash docker run -d --name my-postgres -v my-db-data:/var/lib/postgresql/data postgres bash docker network create my-app-net bash docker run -d --name redis-server --network my-app-net redis bash docker run -it --rm --network my-app-net python:3.9-slim python python

    Внутри Python консоли:

    import socket

    Проверим, виден ли redis по имени

    print(socket.gethostbyname('redis-server'))

    Вывод: что-то вроде 172.18.0.2

    python import time import redis

    Подключаемся к контейнеру с именем 'redis-server'

    cache = redis.Redis(host='redis-server', port=6379)

    def get_hit_count(): retries = 5 while True: try: return cache.incr('hits') except redis.exceptions.ConnectionError as exc: if retries == 0: raise exc retries -= 1 time.sleep(0.5)

    print(f"I have been seen {get_hit_count()} times.") dockerfile FROM python:3.9-slim WORKDIR /app RUN pip install redis COPY app.py . CMD ["python", "app.py"] bash

    Собираем образ

    docker build -t my-counter .

    Убедимся, что сеть существует (если не создали ранее)

    docker network create my-app-net

    Запускаем Redis (если не запущен)

    docker run -d --name redis-server --network my-app-net redis

    Запускаем наше приложение в той же сети

    docker run --rm --network my-app-net my-counter bash docker run -d --name redis-server --network my-app-net -v redis-data:/data redis ``

    Резюме

    Мы разобрали два критически важных аспекта Docker:

  • Volumes (Тома): Позволяют данным жить дольше, чем живут контейнеры. Используйте флаг -v том:/путь для баз данных и важных файлов.
  • Networks (Сети): Позволяют контейнерам общаться друг с другом по имени. Используйте docker network create и флаг --network`.
  • Теперь вы умеете запускать отдельные компоненты и связывать их вручную. Но запускать каждый контейнер отдельной длинной командой, помнить про создание сетей и томов — это утомительно и чревато ошибками.

    В следующей статье мы познакомимся с Docker Compose — инструментом, который позволит описать всю нашу архитектуру (Python приложение + Redis + Сети + Тома) в одном файле и запустить всё одной командой.

    4. Оркестрация с Docker Compose: управление многоконтейнерными приложениями через YAML-конфигурацию

    Оркестрация с Docker Compose: управление многоконтейнерными приложениями через YAML-конфигурацию

    В предыдущей статье мы столкнулись с реальностью разработки сложных приложений: нам пришлось вручную создавать сеть, запускать базу данных Redis, затем запускать Python-приложение, указывая длинные флаги для подключения портов и томов. Если ваше приложение состоит из 5-10 сервисов (фронтенд, бэкенд, база данных, кэш, очередь задач), ручной запуск превращается в кошмар.

    Сегодня мы познакомимся с инструментом, который решает эту проблему раз и навсегда. Добро пожаловать в мир Docker Compose.

    Что такое Docker Compose?

    Docker Compose — это инструмент для определения и запуска многоконтейнерных приложений Docker. С его помощью вы используете файл YAML для настройки служб вашего приложения. Затем, с помощью одной команды, вы создаете и запускаете все службы из вашей конфигурации.

    Представьте, что docker run — это игра на одном музыкальном инструменте. Вы можете сыграть на скрипке, потом отложить её и сыграть на барабане. Но чтобы звучала симфония, вам нужен оркестр и дирижер. Docker Compose выступает в роли партитуры (нот) и дирижера одновременно.

    Зачем это нужно?

  • Инфраструктура как код (IaC): Вся конфигурация запуска (порты, сети, тома, переменные среды) хранится в одном файле, который можно положить в Git.
  • Упрощение разработки: Одной командой docker-compose up вы поднимаете всё окружение. Одной командой docker-compose down — удаляете его.
  • Изоляция сред: Compose создает изолированную сеть для каждого проекта, предотвращая конфликты портов и имен.
  • !Визуализация того, как один конфигурационный файл управляет развертыванием целого кластера сервисов.

    Формат YAML и структура файла

    Docker Compose использует формат YAML (YAML Ain't Markup Language). Это человекочитаемый формат сериализации данных. Главное правило YAML — отступы имеют значение. Обычно используются 2 пробела для каждого уровня вложенности. Использование табуляции (Tab) запрещено и приведет к ошибке.

    Файл конфигурации по умолчанию называется docker-compose.yml.

    Анатомия docker-compose.yml

    Давайте разберем структуру типичного файла:

    Основные блоки:

  • version: Указывает версию синтаксиса Compose. Рекомендуется использовать последние версии (например, 3.8).
  • services: Здесь мы перечисляем контейнеры, которые хотим запустить. Имя сервиса (например, web или db) становится сетевым именем (hostname) внутри сети Docker.
  • volumes: Здесь объявляются именованные тома для хранения данных.
  • networks: Здесь можно настроить сложные сетевые конфигурации (хотя Compose создает сеть по умолчанию автоматически).
  • Практика: Python (Flask) + Redis

    Давайте перепишем пример из прошлой статьи, используя Docker Compose. Наша цель — создать счетчик посещений, который хранит данные в Redis.

    Шаг 1: Подготовка файлов

    Создайте новую папку compose-demo. Внутри нее нам понадобятся три файла.

    1. app.py (код приложения):

    2. requirements.txt (зависимости):

    3. Dockerfile (инструкция сборки образа для Python):

    Шаг 2: Создание docker-compose.yml

    Теперь самое интересное. Создайте файл docker-compose.yml в той же папке:

    Разберем ключевые директивы: * build: . — говорит Compose: «Не ищи готовый образ, а собери его из Dockerfile в текущей папке (.)». depends_on: — указывает порядок запуска. Compose запустит redis перед запуском web. Примечание: это не гарантирует, что Redis будет полностью готов к приему соединений, только то, что контейнер запущен.* * volumes: — внизу файла мы регистрируем том redis-data, а внутри сервиса redis монтируем его в папку /data.

    Шаг 3: Запуск оркестра

    Откройте терминал в папке проекта и введите магическую команду:

    Вы увидите, как Docker:

  • Создает сеть compose-demo_default.
  • Собирает образ для сервиса web.
  • Скачивает образ redis.
  • Запускает оба контейнера и выводит их логи в один поток.
  • Откройте браузер по адресу http://localhost:5000. Обновите страницу несколько раз. Счетчик будет расти.

    Чтобы остановить приложение, нажмите Ctrl+C в терминале.

    Основные команды Docker Compose

    Управление через Compose намного удобнее, чем через чистый Docker CLI. Вот список команд, которые вы будете использовать 90% времени.

    Запуск в фоновом режиме

    Чтобы терминал не блокировался логами, используйте флаг -d (detached):

    Теперь контейнеры работают в фоне.

    Просмотр статуса

    Чтобы узнать, какие сервисы запущены в текущем проекте:

    Просмотр логов

    Если контейнеры работают в фоне, вы можете посмотреть их вывод командой:

    Флаг -f (follow) позволяет следить за логами в реальном времени.

    Остановка и удаление

    Чтобы остановить контейнеры и удалить созданные сети (но сохранить тома):

    Если вы хотите удалить еще и тома (сбросить базу данных), добавьте флаг -v:

    Пересборка

    Если вы изменили код в app.py или Dockerfile, простого перезапуска может быть недостаточно (особенно если изменились зависимости). Используйте флаг --build:

    Сетевое взаимодействие в Compose

    Одной из самых мощных функций Compose является автоматическая настройка сети.

    Когда вы запускаете docker-compose up, создается сеть (обычно с именем папкапроекта_default). Все контейнеры, описанные в docker-compose.yml, автоматически попадают в эту сеть.

    Самое главное: они видят друг друга по именам сервисов.

    В нашем примере в файле app.py мы написали:

    Здесь строка 'redis' — это не магия, это имя сервиса, которое мы задали в YAML файле:

    Вам больше не нужно заботиться об IP-адресах контейнеров. Docker имеет встроенный DNS-сервер, который автоматически разрешает имя сервиса в его внутренний IP-адрес.

    Переменные окружения

    Хорошей практикой является вынос настроек (пароли, порты, версии) в переменные окружения. Docker Compose отлично работает с файлами .env.

    Создайте файл .env:

    Теперь обновите docker-compose.yml:

    При запуске Compose автоматически подставит значения из файла .env. Это позволяет легко менять конфигурацию для разных сред (разработка, тестирование, продакшн), не меняя сам код.

    Заключение

    Docker Compose — это стандарт де-факто для локальной разработки многоконтейнерных приложений. Он превращает сложный процесс развертывания в простую, повторяемую и документированную процедуру.

    Мы научились:

  • Описывать архитектуру приложения в docker-compose.yml.
  • Связывать сервисы (Python и Redis) без ручного создания сетей.
  • Управлять жизненным циклом приложения командами up, down, ps.
  • В следующей, заключительной статье курса, мы рассмотрим лучшие практики оптимизации Docker-образов для Python, чтобы ваши контейнеры были легкими, быстрыми и безопасными.

    5. Практика: развертывание веб-сервиса на Python с базой данных через Docker Compose

    Практика: развертывание веб-сервиса на Python с базой данных через Docker Compose

    Добро пожаловать на практическое занятие курса «Docker для начинающих». В предыдущей статье мы познакомились с теоретическими основами Docker Compose и запустили простую связку Python + Redis. Теперь пришло время для задачи, с которой сталкивается каждый бэкенд-разработчик: развертывание полноценного веб-сервиса с реляционной базой данных.

    В этой статье мы создадим приложение на Flask, подключим его к базе данных PostgreSQL, настроим сохранение данных через тома и спрячем конфиденциальную информацию в переменные окружения. Мы пройдем весь путь от пустого редактора кода до работающего сервиса.

    Архитектура нашего приложения

    Прежде чем писать код, давайте визуализируем то, что мы собираемся построить. Наше приложение будет состоять из двух сервисов, работающих в изолированной сети, но доступных из внешнего мира через определенные порты.

    !Схема взаимодействия сервисов: веб-приложение общается с базой данных внутри сети Docker, а данные базы сохраняются в томе.

    Нам понадобятся:

  • Web-сервис: Python-приложение на Flask, которое принимает HTTP-запросы.
  • Database-сервис: PostgreSQL для хранения данных пользователей.
  • Volume: Хранилище для базы данных, чтобы данные не исчезали при перезапуске.
  • Шаг 1: Подготовка структуры проекта

    Создайте новую директорию для нашего проекта, например flask-postgres-docker, и перейдите в нее. Структура файлов будет выглядеть следующим образом:

    Шаг 2: Создание Python-приложения

    Начнем с зависимостей. Нам понадобится сам фреймворк Flask и драйвер для работы с PostgreSQL — psycopg2-binary.

    Создайте файл requirements.txt:

    Теперь напишем код приложения в app.py. Наше приложение будет простым: при заходе на главную страницу оно будет добавлять запись о визите в базу данных и показывать общее количество визитов.

    Важный момент: При запуске через Docker Compose база данных может стартовать медленнее, чем веб-приложение. Если Python попытается подключиться к БД до того, как она будет готова, приложение упадет с ошибкой. Поэтому мы добавим простую логику повторных попыток подключения (retry logic).

    Содержимое app.py:

    Шаг 3: Написание Dockerfile

    Здесь все стандартно, как мы изучали в уроке про образы. Мы берем легкий образ Python, устанавливаем зависимости и копируем код.

    Файл Dockerfile:

    Шаг 4: Конфигурация Docker Compose

    Теперь самое интересное — объединение сервисов. Нам нужно описать два сервиса: web (наш код) и db (PostgreSQL).

    Создайте файл docker-compose.yml:

    Обратите внимание на синтаксис ${VARIABLE}. Это указание Docker Compose взять значение из файла .env или переменных окружения системы.

    Шаг 5: Работа с секретами (.env)

    Никогда не храните пароли в docker-compose.yml или Dockerfile, так как эти файлы обычно попадают в систему контроля версий (Git). Для этого существует файл .env.

    Создайте файл .env:

    Docker Compose автоматически прочитает этот файл при запуске.

    Шаг 6: Запуск и проверка

    Теперь, когда все файлы готовы, запустим нашу систему. Откройте терминал в папке проекта и выполните:

    Флаг --build заставляет Docker пересобрать образ web, если вы вносили изменения в код или Dockerfile.

    Вы увидите логи обоих контейнеров. Сначала инициализируется база данных, затем запустится Python-приложение. Если вы увидите сообщение Running on http://0.0.0.0:5000, значит, все прошло успешно.

    Перейдите в браузере по адресу http://localhost:5000. Вы увидите: > Hello! This page has been visited 1 times.

    Обновите страницу несколько раз. Счетчик будет расти: 2, 3, 4...

    Шаг 7: Проверка персистентности (сохранности) данных

    Давайте проверим, работает ли наш том postgres_data. Остановим контейнеры и удалим их:

    Эта команда останавливает и удаляет контейнеры и сеть, но не удаляет тома по умолчанию. Теперь запустим приложение снова:

    Снова откройте http://localhost:5000. Если счетчик продолжил отсчет (например, показал 5, а не 1), поздравляю! Вы успешно настроили сохранение данных. Ваша база данных пережила уничтожение контейнера.

    Как это работает под капотом?

  • Сетевое имя: В файле docker-compose.yml мы назвали сервис базы данных db. В файле app.py мы используем это имя как хост: DB_HOST = os.environ.get('DB_HOST', 'db'). Docker DNS автоматически преобразует имя db в IP-адрес контейнера.
  • Переменные окружения: Мы задали пароль один раз в .env, и он был передан и в контейнер базы данных (чтобы она знала, какой пароль ожидать), и в контейнер приложения (чтобы оно знало, какой пароль отправлять).
  • Depends_on: Эта директива гарантирует, что Docker отправит команду на запуск контейнера db раньше, чем web. Однако она не гарантирует, что Postgres готов принимать соединения. Именно поэтому мы написали цикл while в Python-коде.
  • Полезные команды для отладки

    Иногда что-то идет не так. Вот как можно «заглянуть» внутрь.

    Если вы хотите подключиться к базе данных напрямую из терминала, пока контейнер работает:

    Разберем команду: * exec: выполнить команду внутри запущенного контейнера. * db: имя сервиса из compose-файла. * psql ...: консольная утилита PostgreSQL.

    Внутри открывшейся консоли SQL вы можете выполнить SELECT * FROM visits; и увидеть ваши данные.

    Заключение

    В этой статье мы реализовали классический сценарий развертывания веб-приложения. Мы научились: * Связывать Python и PostgreSQL через Docker Compose. * Использовать файл .env для управления конфигурацией и секретами. * Реализовывать устойчивое подключение к БД с повторными попытками. * Проверять сохранность данных при перезапуске контейнеров.

    Теперь у вас есть шаблон, который можно использовать как основу для более сложных проектов, добавляя туда Nginx, Redis, Celery и другие сервисы. В следующей, завершающей части курса, мы поговорим об оптимизации: как сделать ваши образы меньше, а сборку — быстрее.