Когда я начал поднимать PostgreSQL через Docker для своих проектов, всё выглядело просто: описал сервис в docker-compose.yml, запустил контейнер - база доступна.
Проблемы начались, когда я начал запускать миграции вместе с контейнерами. Иногда миграции стартовали раньше чем PostgreSQL успевал принять подключения, и приложение падало с ошибкой подключение к базе данных.
С опытом я пришел к более удобному варианту. Сначала запускал сам контейнер с базой и уже далее делал миграции. Так проще контролировать процесс, легче отлаживать ошибки и понятнее, что именно происходит с базой. Для миграций в своих Go-проектах я использовал goose.
goose - это инструмент для работы с миграциями базы данных. Его можно использовать как CLI-утилиту из терминала или подключить как Go-пакет и запускать миграции из кода. В этой статье я покажу вариант с запуском миграций через CLI-утилиту.
Для начала установим goose как CLI-утилиту:
go install github.com/pressly/goose/v3/cmd/goose@latest
После установки можно проверить, что команда доступна:
goose --version
Далее рассмотрим такую структуру проекта и покажу где я буду хранить миграции и как создать файлы для миграций:
my-app/ ├── cmd/ │ └── app/ │ └── main.go ├── migrations/ ├── docker-compose.yml ├── Makefile ├── go.mod └── go.sum
В папке migrations будут лежать SQL-файлы миграций. Каждый такой файл описывает изменение схемы базы: создание таблицы, добавление колонки, создание индекса и так далее.
goose -dir migrations create create_users sql
Флаг -dir указывает папку, где нужно создать файл миграции. create_users — это название миграции, а sql означает, что миграция будет написана обычным SQL.
В docker-compose.yml опишем вот такой контейнер для запуска PostgreSQL:
services: postgres: image: postgres:16-alpine container_name: go_postgres environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: app_db ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data volumes: postgres_data:
Сам запуск контейнера здесь подробно разбирать не буду: для этого достаточно выполнить docker compose up -d. Основной фокус статьи — запуск миграций. Запускать свои миграции я буду через Makefile вот так он выглядит внутри:
DB_URL=postgres://postgres:postgres@localhost:5432/app_db?sslmode=disable MIGRATIONS_DIR=migrations .PHONY: migrate-up migrate-down migrate-status migrate-create migrate-up: goose -dir $(MIGRATIONS_DIR) postgres "$(DB_URL)" up migrate-down: goose -dir $(MIGRATIONS_DIR) postgres "$(DB_URL)" down migrate-status: goose -dir $(MIGRATIONS_DIR) postgres "$(DB_URL)" status migrate-create: goose -dir $(MIGRATIONS_DIR) create $(name) sql
В моём случае goose запускается с хост-машины, поэтому в DB_URL я использую localhost. Если запускать миграции из другого контейнера внутри docker-compose, вместо localhost нужно будет указать имя сервиса — postgres.
Далее после запуска нашего контейнера базы данных мы уже применить наши миграции. и мы просто должны написать:
make migrate-up
После запуска можете проверить статус:
make migrate-status
Теперь миграции запускаются отдельным шагом после старта PostgreSQL. Поэтому мы не попадаем в ситуацию, когда goose пытается подключиться к базе раньше, чем она готова принимать соединения..
dem0n3d
В compose можно сделать так. Сервис-мигратор при этом будет ожидать работоспособности базы данных и только потом стартовать.
albatomm Автор
Да, тоже хорошая практика. В целом и этот способ тоже можно было бы в статье описать.