Автоматизация схемы базы данных: миграции PostgreSQL в CI/CD пайплайне с Flyway и GitHub Actions
Введение: почему ручные миграции опасны в 2026 году
Ещё несколько лет назад «запустить скрипт вручную перед деплоем» было нормой для многих команд. Сегодня в 2026 году это — источник инцидентов. Команды работают быстрее, деплои происходят по несколько раз в день, инфраструктура воспроизводится автоматически. Ручное применение SQL-скриптов к базе данных в такой среде означает:
- Риск человеческой ошибки: применён не тот скрипт, не к той базе, не в том порядке.
- Отсутствие аудита: непонятно, кто и когда изменил схему.
- Рассинхронизация между окружениями: staging и production живут по-разному.
- Невозможность автоматического rollback при падении деплоя.
- Проблемы при горизонтальном масштабировании и работе нескольких команд над одной базой.
Решение — интегрировать управление схемой PostgreSQL непосредственно в CI/CD пайплайн. Инструмент Flyway в связке с GitHub Actions позволяет сделать миграции воспроизводимыми, версионированными и безопасными. В этой статье разберём полный цикл: от установки до production-ready конфигурации.
Flyway vs Liquibase vs встроенные миграции фреймворков
Прежде чем погружаться в Flyway, стоит понять, почему именно он, а не альтернативы.
Flyway
- Простая модель: SQL-файлы с именами по конвенции, таблица истории миграций
flyway_schema_history. - Поддержка PostgreSQL, MySQL, Oracle и других СУБД из коробки.
- Минимальная конфигурация для старта, богатые возможности для enterprise.
- Интегрируется в Maven, Gradle, Docker, CLI и GitHub Actions.
- Платная Flyway Teams/Enterprise добавляет dry-run, undo-миграции и больше функций отката.
Liquibase
- Более гибкий формат: XML, YAML, JSON или SQL для описания изменений.
- Поддерживает генерацию diff между схемами.
- Сложнее в освоении, больше конфигурации.
- Хорошо подходит для команд, которым нужна база-нейтральная абстракция.
Встроенные миграции фреймворков
- Django migrations, Laravel migrations, Alembic (SQLAlchemy) — удобны внутри своей экосистемы.
- Привязаны к языку и фреймворку, сложнее использовать в polyglot-системах.
- Нет прямой интеграции с CI/CD без дополнительных оберток.
Для команд, работающих с PostgreSQL в production и имеющих CI/CD на GitHub Actions, Flyway — оптимальный выбор: минимум зависимостей, предсказуемое поведение, нативная поддержка Docker.
Установка и базовая настройка Flyway для PostgreSQL
Flyway можно запустить несколькими способами. Наиболее универсальный для CI/CD — официальный Docker-образ flyway/flyway.
Структура проекта
Рекомендуемая структура директорий в репозитории:
project-root/
├── db/
│ ├── migrations/
│ │ ├── V1__init_schema.sql
│ │ ├── V2__add_users_table.sql
│ │ └── V3__add_index_on_email.sql
│ └── flyway.conf
├── docker-compose.yml
└── .github/
└── workflows/
└── deploy.yml
Файл конфигурации flyway.conf
Базовый конфиг для подключения к PostgreSQL:
flyway.url=jdbc:postgresql://localhost:5432/mydb
flyway.user=${DB_USER}
flyway.password=${DB_PASSWORD}
flyway.schemas=public
flyway.locations=filesystem:./db/migrations
flyway.baselineOnMigrate=false
flyway.validateOnMigrate=true
flyway.outOfOrder=false
Значения ${DB_USER} и ${DB_PASSWORD} Flyway подставляет из переменных окружения — это критично для безопасности в CI.
Написание версионированных SQL-миграций: naming convention и best practices
Соглашение об именовании файлов
Flyway строго следует паттерну имён файлов:
V{version}__{description}.sql— версионированные миграции (применяются один раз).R__{description}.sql— повторяемые миграции (views, stored procedures, triggers).U{version}__{description}.sql— undo-миграции (только в платной версии).
Примеры корректных имён:
V1__init_schema.sql
V2__add_users_table.sql
V2_1__add_users_email_index.sql
V3__add_orders_table.sql
R__refresh_analytics_view.sql
Пример реальных миграций
Инициализация схемы (V1__init_schema.sql):
-- V1__init_schema.sql
CREATE TABLE IF NOT EXISTS schema_meta (
id SERIAL PRIMARY KEY,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Создание таблицы пользователей (V2__add_users_table.sql):
-- V2__add_users_table.sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL,
username VARCHAR(100) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX idx_users_email ON users(email);
Добавление внешнего ключа (V3__add_orders_table.sql):
-- V3__add_orders_table.sql
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
total NUMERIC(12, 2) NOT NULL DEFAULT 0,
status VARCHAR(50) NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
Best practices написания миграций
- Каждая миграция должна быть атомарной: одно логическое изменение на файл.
- Используйте
IF NOT EXISTS/IF EXISTSдля идемпотентности там, где это применимо. - Никогда не изменяйте уже применённые миграционные файлы — Flyway проверяет контрольные суммы.
- Избегайте
DROP TABLEбез резервной копии или отдельного этапа деплоя. - Для изменения структуры больших таблиц используйте
ALTER TABLE ... ADD COLUMNс дефолтными значениями вместо пересоздания. - Документируйте назначение каждой миграции в комментарии в начале файла.
Интеграция Flyway в GitHub Actions: запуск миграций перед деплоем
Ключевой принцип: миграции должны выполняться до деплоя нового кода приложения. Это гарантирует, что новый код всегда видит актуальную схему.
Полный workflow GitHub Actions
# .github/workflows/deploy.yml
name: Deploy with DB Migrations
on:
push:
branches:
- main
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
migrate:
name: Run PostgreSQL Migrations
runs-on: ubuntu-22.04
environment: production
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Run Flyway migrations
uses: docker://flyway/flyway:10-alpine
with:
args: migrate
env:
FLYWAY_URL: jdbc:postgresql://${{ secrets.DB_HOST }}:5432/${{ secrets.DB_NAME }}
FLYWAY_USER: ${{ secrets.DB_USER }}
FLYWAY_PASSWORD: ${{ secrets.DB_PASSWORD }}
FLYWAY_LOCATIONS: filesystem:/github/workspace/db/migrations
FLYWAY_VALIDATE_ON_MIGRATE: "true"
FLYWAY_OUT_OF_ORDER: "false"
FLYWAY_BASELINE_ON_MIGRATE: "false"
- name: Verify migration status
uses: docker://flyway/flyway:10-alpine
with:
args: info
env:
FLYWAY_URL: jdbc:postgresql://${{ secrets.DB_HOST }}:5432/${{ secrets.DB_NAME }}
FLYWAY_USER: ${{ secrets.DB_USER }}
FLYWAY_PASSWORD: ${{ secrets.DB_PASSWORD }}
FLYWAY_LOCATIONS: filesystem:/github/workspace/db/migrations
deploy:
name: Deploy Application
runs-on: ubuntu-22.04
needs: migrate
environment: production
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Deploy to production
run: |
echo "Deploying application after successful migrations..."
# Ваш деплой-скрипт: kubectl apply, docker stack deploy и т.д.
Запуск тестов миграций в PR
Для pull request пайплайна полезно запускать миграции против временной тестовой базы:
# .github/workflows/pr-migration-test.yml
name: Test Migrations on PR
on:
pull_request:
paths:
- 'db/migrations/**'
jobs:
test-migrations:
runs-on: ubuntu-22.04
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpassword
ports:
- 5432:5432
options: >
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Run Flyway migrate on test DB
uses: docker://flyway/flyway:10-alpine
with:
args: migrate
env:
FLYWAY_URL: jdbc:postgresql://localhost:5432/testdb
FLYWAY_USER: testuser
FLYWAY_PASSWORD: testpassword
FLYWAY_LOCATIONS: filesystem:/github/workspace/db/migrations
- name: Validate migration info
uses: docker://flyway/flyway:10-alpine
with:
args: validate
env:
FLYWAY_URL: jdbc:postgresql://localhost:5432/testdb
FLYWAY_USER: testuser
FLYWAY_PASSWORD: testpassword
FLYWAY_LOCATIONS: filesystem:/github/workspace/db/migrations
Стратегии отката (rollback) и baseline для существующих баз
Проблема отката в Flyway
Flyway Community Edition не поддерживает автоматические undo-миграции. Это осознанное дизайнерское решение: откат схемы базы данных — опасная операция, особенно если данные уже были записаны по новой схеме.
Стратегия «forward-only»
Рекомендуемый подход для большинства production-систем: никогда не откатывать схему назад, а исправлять ошибки новой миграцией вперёд.
- Если
V5добавила столбец с ошибкой — пишемV6, которая исправляет это. - Если
V5удалила нужный столбец —V6воссоздаёт его.
Blue-green деплой как стратегия безопасности
При blue-green деплое миграция выполняется до переключения трафика. Если миграция упала — переключение не происходит, старая версия продолжает работать с нетронутой схемой.
Baseline для существующей базы данных
Если вы подключаете Flyway к уже существующей production-базе, используйте команду baseline:
# Установить baseline на версию 1 для существующей БД
flyway -url=jdbc:postgresql://localhost:5432/mydb \
-user=myuser \
-password=mypassword \
-baselineVersion=1 \
-baselineDescription="Initial baseline" \
baseline
После этого Flyway пометит текущее состояние как версию 1 и начнёт применять только новые миграции, начиная с V2. В конфиге установите flyway.baselineOnMigrate=false — baseline нужно выполнить только один раз вручную.
Работа с миграциями в Docker-окружении
Docker Compose для локальной разработки
Следующий docker-compose.yml поднимает PostgreSQL и автоматически запускает Flyway при старте:
version: '3.9'
services:
postgres:
image: postgres:16-alpine
container_name: app_postgres
restart: unless-stopped
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: appsecret
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 10s
timeout: 5s
retries: 5
flyway:
image: flyway/flyway:10-alpine
container_name: app_flyway
command: migrate
depends_on:
postgres:
condition: service_healthy
environment:
FLYWAY_URL: jdbc:postgresql://postgres:5432/appdb
FLYWAY_USER: appuser
FLYWAY_PASSWORD: appsecret
FLYWAY_LOCATIONS: filesystem:/flyway/sql
FLYWAY_VALIDATE_ON_MIGRATE: "true"
volumes:
- ./db/migrations:/flyway/sql
restart: on-failure
volumes:
postgres_data:
Запуск: docker compose up flyway — применит все pending-миграции к локальной базе.
Flyway в Dockerfile для production
Альтернативный подход — включить Flyway в init-контейнер Kubernetes или отдельный Job:
FROM flyway/flyway:10-alpine
COPY db/migrations /flyway/sql
# CMD задаётся при запуске контейнера через args
Безопасность: управление credentials и секретами в пайплайне
Безопасность credentials базы данных в CI/CD — критический аспект. Утечка пароля от production PostgreSQL может привести к катастрофическим последствиям.
GitHub Actions Secrets
Никогда не храните учётные данные в репозитории или в переменных окружения без шифрования. Используйте GitHub Secrets:
- Перейдите в Settings → Secrets and variables → Actions в вашем репозитории.
- Создайте секреты:
DB_HOST,DB_NAME,DB_USER,DB_PASSWORD. - Используйте
environmentв workflow для разделения секретов по окружениям (staging, production).
Использование HashiCorp Vault или AWS Secrets Manager
Для enterprise-окружений рекомендуется динамическое получение credentials из Vault:
# Шаг получения credentials из Vault
- name: Import secrets from Vault
uses: hashicorp/vault-action@v3
with:
url: ${{ secrets.VAULT_ADDR }}
token: ${{ secrets.VAULT_TOKEN }}
secrets: |
secret/data/production/postgres username | DB_USER ;
secret/data/production/postgres password | DB_PASSWORD
- name: Run Flyway migrations
uses: docker://flyway/flyway:10-alpine
with:
args: migrate
env:
FLYWAY_URL: jdbc:postgresql://${{ secrets.DB_HOST }}:5432/${{ secrets.DB_NAME }}
FLYWAY_USER: ${{ env.DB_USER }}
FLYWAY_PASSWORD: ${{ env.DB_PASSWORD }}
FLYWAY_LOCATIONS: filesystem:/github/workspace/db/migrations
Принцип минимальных привилегий
Создайте отдельного пользователя PostgreSQL для Flyway с минимально необходимыми правами:
-- Создание пользователя для миграций
CREATE USER flyway_runner WITH PASSWORD 'strong_random_password';
-- Права только на нужную базу и схему
GRANT CONNECT ON DATABASE appdb TO flyway_runner;
GRANT USAGE, CREATE ON SCHEMA public TO flyway_runner;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO flyway_runner;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO flyway_runner;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON TABLES TO flyway_runner;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON SEQUENCES TO flyway_runner;
Ротация паролей и аудит
- Регулярно ротируйте пароли сервисных аккаунтов (минимум раз в квартал).
- Включите логирование подключений к PostgreSQL через
log_connections = on. - Мониторьте таблицу
flyway_schema_historyна предмет неожиданных изменений. - Используйте SSL-соединение: добавьте
?sslmode=requireк JDBC URL.
Заключение и чеклист
Автоматизация миграций PostgreSQL через Flyway в CI/CD пайплайне на GitHub Actions — это не усложнение, а фундамент надёжного управления схемой базы данных. Правильно настроенный пайплайн гарантирует, что схема всегда синхронизирована с кодом, изменения версионированы и аудитируемы, а человеческий фактор исключён из критического процесса.
Ваша база данных — это не конфигурационный файл. Она требует такого же версионного контроля и автоматизации, как и исходный код приложения.
Чеклист для внедрения
- Создана директория
db/migrations/с версионированными SQL-файлами. - Настроен
flyway.confбез жёстко заданных credentials. - Создан workflow GitHub Actions: миграции выполняются в job
migrateперед jobdeploy. - PR-пайплайн тестирует миграции против ephemeral PostgreSQL-сервиса.
- Все credentials хранятся в GitHub Secrets с разделением по окружениям.
- Создан отдельный PostgreSQL-пользователь для Flyway с минимальными правами.
- JDBC URL использует
sslmode=requireдля production. - Настроен Docker Compose для локального воспроизведения пайплайна миграций.
- Зафиксирована стратегия forward-only для изменений схемы.
- Команда ознакомлена с naming convention для миграционных файлов.
Такой подход масштабируется от небольшого стартапа до высоконагруженных production-систем, обеспечивая предсказуемость и безопасность на каждом этапе жизненного цикла приложения.
Технологии
Теги
Руслан Исмаилов
Senior Web / Backend разработчик. Senior web/backend разработчик с 9-летним опытом. Стек: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, микросервисы, CI/CD. Подробнее обо мне →