Docker Compose и PostgreSQL: приложение, сеть и сохранение данных

Docker Compose и PostgreSQL: приложение, сеть и сохранение данных
Содержание статьи

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

Это локальное упражнение Mentoring.digital, а не конфигурация для публикации в интернете. Здесь нет внешних портов и рабочих секретов. Конфигурация разобрана по документации Docker и PostgreSQL; фактический запуск Docker в редакционной среде не выполнен, поэтому команды ниже сопровождаются ожидаемыми результатами, а не выданным за тест отчётом.

Создайте три файла учебного проекта

Нужен работающий Docker Engine с Compose v2 или Docker Desktop с Linux-контейнерами. Проверьте docker version и docker compose version. В новой папке создайте compose.yaml:

name: mentoring-compose-lab
services:
  db:
    image: postgres:17
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: local-study-only
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 2s
      timeout: 3s
      retries: 20
  app:
    build: .
    environment:
      PGHOST: db
      PGDATABASE: app
      PGUSER: app
      PGPASSWORD: local-study-only
    depends_on:
      db:
        condition: service_healthy
volumes:
  pgdata:

Сервис db использует PostgreSQL 17. Для этой версии официального образа каталог данных в примере /var/lib/postgresql/data. У образов PostgreSQL 18 и новее схема каталогов отличается, поэтому нельзя просто заменить номер и считать перенос данных выполненным.

Файл Dockerfile собирает небольшое Python-приложение:

FROM python:3.12-slim
WORKDIR /app
RUN pip install --no-cache-dir "psycopg[binary]==3.2.3"
COPY app.py .
CMD ["python", "app.py"]

Версия драйвера закреплена для этого учебного примера. Это не заявление о последней версии. Теги python:3.12-slim и postgres:17 могут получать обновления. Для воспроизведения точных байтов образов в командной работе дополнительно фиксируют digest и хранят зависимости в lock-файле.

Файл app.py:

import os
import psycopg

with psycopg.connect(
    host=os.environ["PGHOST"],
    dbname=os.environ["PGDATABASE"],
    user=os.environ["PGUSER"],
    password=os.environ["PGPASSWORD"],
) as conn:
    with conn.cursor() as cur:
        cur.execute("""CREATE TABLE IF NOT EXISTS visits (
            id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
            created_at timestamptz NOT NULL DEFAULT now()
        )""")
        cur.execute("INSERT INTO visits DEFAULT VALUES")
        cur.execute("SELECT count(*) FROM visits")
        print(f"Saved runs: {cur.fetchone()[0]}")

Контекст соединения фиксирует успешную транзакцию и закрывает соединение. При исключении изменения этой транзакции откатываются. Программа выполняет одну операцию и завершается: это ожидаемо, у неё нет веб-сервера и фонового цикла.

Запустите окружение и проверьте результат

docker compose config --quiet
docker compose up -d --build
docker compose ps -a
docker compose logs app
docker compose exec db psql -U app -d app -c "SELECT count(*) FROM visits;"

Проверка config обнаруживает ошибки структуры и подстановок, но не доказывает, что база доступна. После первого успешного запуска на новом томе приложение должно вывести Saved runs: 1, а SELECT вернуть 1. Если том уже содержит учебные записи, число будет больше.

Состояние Exited (0) у app здесь нормально: работа выполнена. Ненулевой код требует чтения логов. Не добавляйте бесконечный sleep только ради зелёного статуса контейнера.

Почему адрес базы db, а не localhost

Compose создаёт сеть проекта и позволяет сервисам находить друг друга по именам. Из app база доступна как db на внутреннем порту 5432. Адрес localhost внутри app указывает на сам контейнер приложения. Он не становится адресом соседнего PostgreSQL.

В примере нет ports: приложение общается с базой по внутренней сети, а проверка выполняется через compose exec. Если понадобится клиент на вашем компьютере, добавьте к db привязку 127.0.0.1:55432:5432 и подключайтесь с компьютера к 127.0.0.1:55432. В приложении адрес db:5432 при этом сохраняется.

Проверьте, что том сохраняет данные

Запустите приложение ещё раз как отдельную короткую задачу:

docker compose run --rm app
docker compose exec db psql -U app -d app -c "SELECT count(*) FROM visits;"
docker compose down
docker compose up -d
docker compose logs app

При исходном пустом томе после первого дополнительного запуска ожидается 2, после down и нового up приложение добавит третью запись. Именованный том pgdata сохраняется при обычном down и подключается к новому контейнеру базы того же Compose-проекта.

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

Разберите ошибки по слоям

СимптомПроверка
Имя db не разрешаетсяПриложение запущено в той же Compose-сети, адрес не перепутан с хостом
Connection refusedБаза запущена и принимает подключения; смотрите docker compose logs db
Password authentication failedУчётные данные совпадают с уже созданным кластером
После изменения POSTGRES_PASSWORD пароль остался прежнимПеременные инициализации не перенастраивают существующую базу автоматически
Приложение завершилосьПосмотрите код выхода и логи: одноразовая программа вправе завершаться
После пересоздания ноль записейПроверьте имя проекта, том и каталог монтирования

Условие service_healthy упорядочивает первоначальный запуск по healthcheck. Оно не заменяет обработку обрывов соединения в долго работающем приложении и не гарантирует существование всех таблиц. Для полноценного сервиса нужны миграции, повторные подключения и отдельные проверки готовности под его задачи.

Что зафиксировать в README

Запишите версии инструментов, три файла, команды запуска и ожидаемый рост счётчика. Укажите, что пароль учебный, данные хранятся в именованном томе, а удаление контейнеров и удаление данных являются разными операциями. Не переносите этот пароль и роль владельца базы в рабочую систему.

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

Похожие статьи по данной теме