Introduction
Flyway is a powerful open-source tool for managing database schema migrations in a versioned and reproducible way. Unlike manual SQL scripts, Flyway applies atomic, numbered changes (V1__, V2__, etc.), ensuring all environments (dev, staging, prod) stay in sync.
Why use it in 2026? Distributed teams and CI/CD deployments demand perfect traceability: Flyway tracks history in the flyway_schema_history table, supports undo for rollbacks, and integrates natively with Maven, Gradle, or Docker. Perfect for PostgreSQL, MySQL, or SQL Server, it minimizes downtime and human errors.
This intermediate tutorial guides you step by step: from CLI installation to complex migrations, validation, and baselines. By the end, you'll master a pro workflow ready for production. (128 words)
Prerequisites
- Java 17+ installed (Flyway CLI is an executable JAR)
- PostgreSQL 14+ with a test database (
flyway_demo) - SQL client like
psqlto verify changes - CLI tools:
curl,unzip(Linux/Mac) or Windows equivalents - Basic SQL and Git knowledge for versioning migrations
Download and Install Flyway CLI
curl -L https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/10.20.1/flyway-commandline-10.20.1-linux-x64.tar.gz | tar -xz
# Or for macOS
curl -L https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/10.20.1/flyway-commandline-10.20.1-macosx-x64.tar.gz | tar -xz
# Add to PATH (Linux example)
export PATH=$PWD/flyway-10.20.1:$PATH
flyway -vThis script downloads the latest stable Flyway CLI version (10.20.1 in 2026), extracts it, and verifies the installation with -v. Use the OS-specific variant; avoid outdated versions to access new features like repeatable migrations.
Configure the Database Connection
Create a config file to centralize DB parameters. Flyway supports flyway.conf (INI-like format) or YAML. Place it at your migrations project root.
Analogy: It's like a .env for your DB, but Git-versionable. Always exclude sensitive creds via .gitignore and use environment variables in production.
flyway.conf Configuration File
flyway.url=jdbc:postgresql://localhost:5432/flyway_demo
flyway.user=postgres
flyway.password=secret
flyway.schemas=public
flyway.locations=filesystem:./sql
flyway.baselineOnMigrate=true
flyway.validateOnMigrate=true
flyway.outOfOrder=falseThis config connects to local PostgreSQL, targets the public schema, and scans migrations in ./sql. baselineOnMigrate=true initializes existing DBs; validateOnMigrate checks consistency before applying. Pitfall: Forget schemas and Flyway applies changes everywhere.
First Migration: Create Users Table
-- Version: 1
-- Description: Create initial users table
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE users IS 'Users table';V1__ migration: Creates a users table with PK/UNIQUE constraints and auto timestamp. The V1__ prefix is required for ordering; the description follows the double underscores. Always test locally before committing.
Run the First Migration
Place flyway.conf and the sql/ folder at the root. Run flyway migrate: Flyway creates flyway_schema_history, applies V1, and logs everything. Verify with psql: \dt for tables, SELECT * FROM flyway_schema_history; for history.
Apply Migrations
./flyway-10.20.1/flyway -configFiles=flyway.conf migrate
# Check status
./flyway-10.20.1/flyway -configFiles=flyway.conf infomigrate applies pending migrations sequentially; info shows status (applied, pending, future). In CI/CD, chain with validate to fail fast on drift.
Second Migration: Add Role Column
-- Version: 2
-- Description: Add role column to users
ALTER TABLE users ADD COLUMN role VARCHAR(20) DEFAULT 'USER' NOT NULL;
CREATE INDEX idx_users_role ON users(role);
-- Insert sample data
INSERT INTO users (username, email, role) VALUES
('admin', 'admin@ex.com', 'ADMIN'),
('user1', 'user1@ex.com', 'USER');V2__ extends V1 without downtime: ADD COLUMN with default for compatibility. Index for query performance. Always include init data if relevant; Flyway applies idempotently via internal checks.
Repeatable Migration: Update View
-- Repeatable: R__ run on every migrate if changed
DROP VIEW IF EXISTS vue_users_actifs;
CREATE OR REPLACE VIEW vue_users_actifs AS
SELECT id, username, email, role
FROM users
WHERE created_at > NOW() - INTERVAL '30 days';R__ prefix for repeatables: Re-executed if checksum changes (ideal for views/procs). CREATE OR REPLACE ensures idempotence. Great for dynamic configs without full versioning.
Handle Rollbacks with Undo
Key intermediate feature: Flyway supports undo since v8. Create U2__ to revert V2. Run flyway undo 1 to go back to V1. Perfect for hotfixes without downtime.
Undo Migration for V2
-- Undo Version: 2
-- Revert: Remove role and index
DROP INDEX IF EXISTS idx_users_role;
ALTER TABLE users DROP COLUMN IF EXISTS role;U2__ matches V2 and reverts exactly. IF EXISTS for safety. Test undo 1: Rolls back cleanly to V1, updates flyway_schema_history. Limit: Not for destructive data changes without backups.
Maven Integration for Automated Builds
<project>
<groupId>com.example</groupId>
<artifactId>flyway-demo</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>10.20.1</version>
<configuration>
<url>jdbc:postgresql://localhost:5432/flyway_demo</url>
<user>postgres</user>
<password>secret</password>
<locations>
<location>filesystem:./sql</location>
</locations>
</configuration>
</plugin>
</plugins>
</build>
</project>Maven plugin for mvn flyway:migrate in CI. Copy CLI config into . Benefits: Auto SQL transactions, rollback on failure. Add clean for dev resets.
Best Practices
- Version everything: Commit
sql/andflyway.confto Git; ignore creds via env vars (flyway.password=${DB_PASS}). - Short migrations: Keep each < 2s for zero-downtime; split complex ALTERs.
- Automated tests: Integrate
flyway validatein pre-commit hooks. - Environments: Use
flyway.baselineVersion=1for legacy prod. - Prioritize repeatables: Use
R__for views/triggers instead ofV__.
Common Errors to Avoid
- OutOfOrder=true: Allows skips but breaks reproducibility; keep
falsein teams. - Checksum mismatch: NEVER modify applied migrations; create Vn+1__fix.
- Missing transactions: Flyway wraps automatically, but test nested TX in Postgres.
- Multiple locations: Forget
filesystem:sql, classpath:db/migrationand miss migrations.
Next Steps
Dive into the official Flyway docs for Docker/Cloud integrations. Combine with Spring Boot via @Flyway or Liquibase hybrids.
Check out our Learni DevOps and database courses to master advanced CI/CD with migrations.