JavaRush /Курсы /Spring Data JPA /Локальный PostgreSQL в Docker Compose

Локальный PostgreSQL в Docker Compose

Spring Data JPA
4 уровень , 0 лекция
Открыта

1. Живая PostgreSQL как старт стенда

Если раньше вы учили Spring «в вакууме», могло казаться, что главное — это application.yml, аннотации и какие-то starter’ы. Но в слое данных всё начинается не с конфигурации, а с живой и доступной базы данных. Пока базы нет, обсуждать подключение приложения просто не к чему. Иначе получается ситуация «я настроил DataSource, но подключаться не к чему» — примерно как настраивать Wi‑Fi роутер в квартире, где ещё не провели электричество.

Есть и более приземлённая, но не менее важная причина: курс должен быть воспроизводимым. Когда у половины группы PostgreSQL установлен «как-то», у другой — вообще не установлен, а у третьей — другая версия и какие-то старые базы, мы тратим время не на обучение, а на археологию чужих ноутбуков. Поэтому стенд фиксируется в репозитории как часть проекта: одна команда — и у всех одна и та же БД.

К этому моменту у нас уже есть базовая картина реляционной БД: таблицы, ключи, JOIN, INSERT/UPDATE/DELETE, commit и rollback. Теперь этой картине нужна живая PostgreSQL, иначе DataSource, приложение и первые @Entity так и останутся висеть в воздухе. Сначала поднимаем реальную базу, потом строим подключение и весь остальной слой данных поверх неё.

И ещё: PostgreSQL мы выбираем как «главную правду» курса вполне сознательно. Это не значит, что другие СУБД плохие. Это значит, что нам нужна одна конкретная реальность, под которую будут написаны примеры, логи, сценарии и дальнейшие практики.

Небольшая схема, чтобы было видно, где что живёт:

flowchart TD
    %% Репозиторий содержит docker-compose.yml, который описывает стенд (Compose-«рецепт»)
    Repo["Репозиторий shop-data-jpa docker-compose.yml"] -->|docker compose up| Docker["Docker Engine"]
    %% Docker поднимает контейнер Postgres по описанию из compose-файла
    Docker --> PG["Контейнер postgres"]
    %% Данные живут во volume и переживают перезапуски/пересоздания контейнера
    PG --> Volume["Volume: shop-postgres-data данные между перезапусками"]
    %% Разработчик подключается к Postgres через проброшенный порт
    Dev["Вы (терминал / IDE)"] -->|5432| PG

2. Docker Compose как рецепт стенда

Docker Compose удобно воспринимать как небольшой, но очень практичный «рецепт»: какие сервисы нужны проекту локально, какие версии взять, какие порты открыть, какие переменные окружения задать и какие данные сохранять. Для учебного проекта это особенно ценно. Мы не играем в «пусть каждый поставит PostgreSQL как умеет», а держим ровно то, что нужно, в одном файле. И этот файл лежит в репозитории рядом с кодом, а не где-то в папке «Downloads/настройки_потом_разберусь».

Но переоценивать Compose тоже не стоит. Мы не уходим в оркестрацию контейнеров и прочую взрослую жизнь. В рамках курса Compose — это просто способ поднять локальную PostgreSQL так, чтобы любой студент мог повторить шаги без шаманства. Он даёт изоляцию (база живёт в контейнере), простую заменяемость (не понравилось — пересоздали) и повторяемость (у всех одинаково).

Три слова лучше различать с первого дня, иначе путаница начнётся сразу.

Образ (image) — это «заготовка» PostgreSQL нужной версии. Контейнер (container) — уже запущенный экземпляр этого образа, то есть живой процесс базы. А сервис (service) в Compose — это описание того, как этот контейнер должен запускаться: порты, переменные, volume и всё остальное.

3. Минимальный docker-compose.yml

docker-compose.yml выглядит страшно ровно до того момента, пока не становится ясно: это обычная декларация «хочу вот такой сервис, из такого образа, с такими портами и такими настройками». В курсе мы начинаем с минимальной версии, чтобы вы не утонули в «лишних» строчках. Если что-то понадобится позже, добавим это аккуратно и по делу.

Вот минимальный Compose-файл для PostgreSQL — стартовый baseline проекта:

services:
  postgres: # имя сервиса (будем использовать в командах docker compose ... postgres)
    image: postgres:16 # фиксируем версию, не используем latest
    ports:
      - "5432:5432" # порт хоста : порт контейнера
    environment:
      POSTGRES_DB: shop # имя БД (создаётся при первой инициализации)
      POSTGRES_USER: shop # пользователь (создаётся при первой инициализации)
      POSTGRES_PASSWORD: shop # пароль (создаётся при первой инициализации)

Теперь человеческий разбор без мистики.

Ключ services означает: «вот список сервисов, которые мне нужны». Мы заводим один сервис с именем postgres — это просто имя, по которому потом будем к нему обращаться в командах (docker compose logs postgres и т.д.).

image: postgres:16 означает: «скачай и используй официальный образ Postgres версии 16». Версию фиксируем явно, потому что latest — отличный способ получить приключения на свою голову. Иногда кажется, что «latest» означает «лучшее», но на практике это скорее «сегодня работало, завтра внезапно перестало, и теперь вы изучаете DevOps вместо JPA».

ports — это мост между контейнером и вашей машиной. Мы пробрасываем порт PostgreSQL наружу, чтобы к нему могли подключаться инструменты на хосте: Java-приложение, psql, DBeaver — что угодно.

environment задаёт параметры первичной инициализации: имя БД, пользователя и пароль. Да, пароль shop выглядит как «самая защищённая система во Вселенной». Для учебного стенда это нормально. Мы сейчас не строим боевую систему безопасности, мы строим воспроизводимость. Но к дисциплине привыкать всё равно нужно: значения должны быть явными и согласованными.

Чтобы закрепить модель, вот маленькая табличка: как читать Compose глазами разработчика.

Кусок файла Что означает в реальности Зачем нам это в курсе
services.postgres «Сервис базы данных» Один стенд на всех
image: postgres:16 «Фиксированная версия Postgres» Одинаковое поведение и логи
ports: "5432:5432" «Доступ к Postgres с хоста» Чтобы приложение могло подключиться
environment: POSTGRES_* «Инициализация базы» Имя БД и логин/пароль едины для проекта

4. Проброс портов в Compose

Проброс портов — одно из тех мест, где особенно легко запутаться. В записи "5432:5432" левое число относится к вашему компьютеру, то есть хосту, а правое — к контейнеру. По сути вы говорите Docker: «всё, что пришло на порт 5432 на моей машине, перенаправь на порт 5432 внутри контейнера». Поэтому IDE или Spring Boot подключаются к localhost:5432.

Эту запись удобно представлять как переходник: снаружи у него один разъём, внутри — другой. Частая ошибка — думать: «оба порта одинаковые, значит и понимать тут нечего». Понимать как раз нужно — хотя бы до того момента, когда на вашей машине порт 5432 уже занят каким-нибудь локально установленным Postgres.

Если порт занят, это не трагедия и не «Docker сломался». Это просто конфликт портов. В таком случае меняете левую часть — порт хоста, а правую оставляете 5432, потому что именно его слушает PostgreSQL внутри контейнера.

Пример, если на хосте вы хотите использовать порт 15432:

services:
  postgres:
    image: postgres:16
    ports:
      - "15432:5432" # 15432 на хосте -> 5432 внутри контейнера

Теперь снаружи подключение будет идти на localhost:15432, а внутри всё так же остаётся PostgreSQL на 5432. И да, это тот случай, когда «одна цифра» решает полчаса страданий.

Чтобы окончательно прибить путаницу, вот таблица:

Где вы находитесь Порт Что это означает
Хост (ваш ноутбук) 5432 Порт, на который подключается приложение
Контейнер (Postgres внутри Docker) 5432 Порт, на котором слушает Postgres

5. Volume для данных PostgreSQL

Контейнер — штука одноразовая. В этом его плюс: быстро поднял, быстро удалил. Но в этом же и риск: удалил контейнер — потерял данные. Поэтому для базы данных нужен механизм, который сохраняет данные между перезапусками, пересозданиями и вашим внезапным «ой, я нажал не ту кнопку». Этот механизм в Docker называется volume.

Volume удобно воспринимать как отдельное хранилище с данными, которое Docker держит отдельно от контейнера. Контейнер можно удалить, а volume останется. На практике это означает простую вещь: вы остановили PostgreSQL, потом снова запустили — и данные на месте. Для учебного проекта это критично: сегодня вы просто запускаете базу, завтра добавите таблицы, послезавтра будете сохранять сущности, и всё это не должно исчезать после каждого down.

Минимальный пример добавления volume:

services:
  postgres:
    image: postgres:16
    volumes:
      # named volume -> стандартная директория данных Postgres внутри контейнера
      - shop-postgres-data:/var/lib/postgresql/data

volumes:
  # объявляем named volume (Docker будет хранить его отдельно от контейнера)
  shop-postgres-data:

Обратите внимание: путь /var/lib/postgresql/data — это стандартное место, где PostgreSQL хранит свои файлы данных внутри контейнера. Мы подключаем туда named volume shop-postgres-data.

Здесь важно понять одну очень практичную вещь: переменные окружения POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD работают только при первичной инициализации данных. Если volume уже существует, PostgreSQL видит, что данные на месте, и не пересоздаёт кластер, не меняет пароль и не создаёт базу заново. Поэтому новички часто меняют пароль в compose-файле, перезапускают контейнер и удивляются, что «ничего не поменялось». Это не баг, а защита от случайного уничтожения данных.

И давайте сразу разведём команды: тут тоже много магических ожиданий.

Если вы выполняете docker compose down, контейнеры и сеть удаляются, но volume обычно остаётся. А docker compose down -v говорит Docker: «удали ещё и volume». Это уже режим «начать с чистого листа», и включать его лучше осознанно.

6. Жизненный цикл Docker Compose

Ценность Docker Compose — в простом и повторяемом жизненном цикле: поднял стенд, убедился, что он живой, посмотрел логи, остановил. Так база перестаёт быть «магической штукой где-то там» и становится понятным сервисом, которым разработчик управляет сам. В реальной работе именно это отличает спокойный проект от проекта, где база «иногда не работает, но мы не знаем почему».

Минимальный цикл команд выглядит так:

# Поднимаем сервисы в фоне (detached)
docker compose up -d
# Проверяем статус контейнеров и проброшенные порты
docker compose ps
# Смотрим логи именно сервиса postgres
docker compose logs postgres

Первая команда поднимает сервисы в фоне (-d = detached). Вторая показывает статус: запущен ли контейнер, какие порты проброшены. Третья даёт логи именно PostgreSQL-сервиса.

Отдельно важная мысль: «контейнер запущен» не всегда значит «PostgreSQL уже готова принимать соединения». Иногда Postgres несколько секунд стартует, применяет инициализацию, создаёт базу. Поэтому логи — это не «шум», а способ понять, на каком этапе старта вы сейчас находитесь. В логах обычно появляется фраза вроде database system is ready to accept connections. Как только вы её увидели, можно считать, что база готова.

Чтобы не верить логам «на слово», можно сделать самый честный smoke-check: выполнить простой SQL-запрос. Для этого удобно зайти в контейнер и запустить psql. Например так:

# Запускаем psql внутри контейнера и выполняем простой запрос (smoke-check)
docker compose exec postgres psql -U shop -d shop -c "select 1;"

Если всё хорошо, вы получите вывод с одной колонкой и значением 1. Это максимально честная проверка: база не только «живёт», она реально отвечает на запросы.

А остановка стенда зависит от того, что именно вы хотите. Если нужно просто «выключить на ночь», достаточно остановки:

# Остановить контейнеры без удаления (данные остаются, контейнеры остаются)
docker compose stop

Если вы хотите удалить контейнеры, но оставить данные в volume, используйте:

# Удалить контейнеры и сеть, но оставить volume (данные сохранятся)
docker compose down

А если хотите удалить вообще всё, включая данные, то это уже режим «я точно понимаю, что делаю»:

# Удалить контейнеры, сеть и volume (данные будут удалены)
docker compose down -v

7. Compose-файл в репозитории

На этом этапе легко сделать типичную ошибку: «это же просто локальная база, пусть compose-файл лежит где-то отдельно». В учебном проекте это кажется безобидным — ровно до момента, когда вы переносите проект на другой компьютер, делитесь им с одногруппником или возвращаетесь к коду через две недели. И тут внезапно оказывается, что рядом с кодом нет «рецепта» стенда, и вы снова вспоминаете: какой порт, какой пользователь, какой пароль, какая версия Postgres.

Поэтому правило курса простое: docker-compose.yml лежит в корне репозитория shop-data-jpa. Он коммитится вместе с кодом. Он становится частью документации в виде кода. И это не формальность: дальше вы будете наращивать проект, и любые практики (сохранение сущностей, generated SQL, транзакционные сценарии) должны работать на одном и том же стенде.

Ещё один важный момент — согласованность значений. Сегодня мы выбираем базу shop, пользователя shop, пароль shop, порт 5432. Это будет «якорь», под который дальше подстраиваются настройки приложения. Наша цель — не просто поднять PostgreSQL, а зафиксировать договорённость: какие параметры подключения считаются нормой в проекте.

8. Типичные ошибки при поднятии PostgreSQL через Docker Compose

Ошибка №1: перепутали порядок портов в "5432:5432" и потом «ничего не подключается».
В записи ports очень легко мысленно решить, что порядок неважен. На практике он критичен: слева порт хоста, справа порт контейнера. Если вы подключаетесь не туда, приложение честно отвечает connection refused, а вы начинаете подозревать Spring, JDBC и фазу луны. Лечится это просто: сверяете docker compose ps, смотрите проброшенный порт и подключаетесь именно к нему.

Ошибка №2: поменяли POSTGRES_PASSWORD в compose-файле, перезапустили и удивились, что пароль «не изменился».
PostgreSQL инициализирует пользователя и пароль при первом создании данных. Если у вас подключён volume, значит данные уже существуют, и Postgres не будет пересоздавать кластер и менять пароль автоматически. Это защищает данные, но ломает ожидания новичка. Если вы хотите применить новые значения POSTGRES_* «с нуля», нужно осознанно удалить volume через docker compose down -v, понимая, что вместе с ним исчезнут и данные.

Ошибка №3: забыли добавить volume и получили «исчезающую базу».
Без volume база живёт внутри контейнера. Удалили контейнер — удалили всё. Это особенно неприятно, когда вы уверены, что «данные где-то есть», а после down они внезапно испарились. Даже в учебном проекте лучше сразу делать правильно: named volume — минимальная страховка от случайной потери результата.

Ошибка №4: увидели Up в docker compose ps и решили, что Postgres уже готов, хотя он ещё стартует.
Контейнер может быть “Up”, но Postgres внутри всё ещё выполнять инициализацию. Потом вы запускаете приложение, ловите ошибку подключения и начинаете перебирать настройки. Более спокойный путь — посмотреть docker compose logs postgres и дождаться момента, когда база сообщает, что готова принимать соединения, либо выполнить честный select 1 через psql.

Ошибка №5: compose-файл лежит «где-то на компьютере», а не в репозитории, и команда не может воспроизвести стенд.
Это одна из тех ошибок, которые не взрываются сразу, поэтому особенно коварны. Сегодня вы всё помните, завтра — уже нет. Правильная дисциплина проста: docker-compose.yml — такой же артефакт проекта, как build.gradle.kts. Он должен жить рядом с кодом, коммититься и быть частью «как запустить проект» наравне с командой ./gradlew bootRun.

1
Задача
Spring Data JPA, 4 уровень, 0 лекция
Недоступна
Минимальный PostgreSQL-стенд в Docker Compose
Минимальный PostgreSQL-стенд в Docker Compose
1
Задача
Spring Data JPA, 4 уровень, 0 лекция
Недоступна
Внешний порт 15432 и сохранение данных в volume
Внешний порт 15432 и сохранение данных в volume
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ