Application code gets version control, automated tests, and one-click deployments. Database schemas? Often they're still managed with hand-run SQL scripts and whispered tribal knowledge. That gap between code deployment and database deployment is where things break.
Flyway is a database migration tool that brings version control principles to your schema. You write numbered SQL files, Flyway tracks what's been applied, and every environment stays in sync. Combined with DeployHQ's build pipeline integration, you can automate database migrations as a pre-deployment step --- so your schema and code always ship together.
This guide covers what Flyway is, how it works, how to use it with JVM, Python, and Node.js projects, and how to integrate it into your deployment workflow.
What Is Flyway?
Flyway is an open-source database migration tool developed by Redgate. It applies versioned SQL scripts to your database in a deterministic order, tracking which migrations have already run so they are never re-applied. Think of it as Git for your database schema --- every change is an immutable, ordered script that moves the schema from one known state to the next.
Under the hood, Flyway maintains a metadata table (flyway_schema_history) inside your database. Each row records a migration's version number, description, checksum, and execution timestamp. When you run flyway migrate, it compares the scripts on disk against this table and applies only the pending ones. If someone edits an already-applied script, the checksum mismatch halts execution immediately --- preventing silent schema corruption that manual SQL workflows can't catch.
Flyway supports 50+ databases (PostgreSQL, MySQL, Oracle, SQL Server, SQLite, MariaDB, CockroachDB, Snowflake, and many more), works with any programming language via its CLI, and integrates natively with JVM build tools like Maven and Gradle.
The Database Migration Problem
When database changes are managed manually, teams run into predictable problems:
- Schema drift --- development, staging, and production databases diverge, causing
works on my machine
failures - Error-prone deployments --- running SQL scripts by hand is tedious and one wrong statement can cause downtime
- No audit trail --- no record of who changed what, when, or why
- Difficult rollbacks --- reverting a schema change is far harder than reverting a code commit
Migration tools solve this by treating schema changes as versioned, sequential scripts that are applied automatically.
How Flyway Works
Flyway operates on a straightforward principle: versioned migration scripts.
flowchart LR
A["Write SQL\nmigration"] --> B["flyway validate"]
B --> C["flyway migrate"]
C --> D["Verify\nschema state"]
D --> E["Deploy\napplication"]
E --> F{"Issues?"}
F -- No --> G["Done"]
F -- Yes --> H["Rollback\nmigration"]
H --> A
- You write migration files --- plain SQL files with version numbers:
V1__create_users_table.sql,V2__add_email_column.sql - Flyway tracks state --- a
flyway_schema_historytable in your database records every applied migration, its checksum, and when it ran - Flyway applies the diff --- on each run, it compares your migration files against the history table and applies only the pending ones, in order
- Checksums catch tampering --- if an already-applied script is modified, Flyway detects the mismatch and stops, preventing silent corruption
- Every environment stays in sync --- the same migrations run in dev, staging, and production, eliminating drift
Flyway for JVM Projects
Flyway originated in the Java ecosystem and integrates deeply with JVM build tools.
Java API
Run migrations programmatically --- useful for applying them on application startup:
import org.flywaydb.core.Flyway;
public class DatabaseMigrator {
public static void main(String[] args) {
Flyway flyway = Flyway.configure()
.dataSource("jdbc:postgresql://localhost:5432/mydb", "user", "password")
.locations("classpath:db/migration")
.load();
flyway.migrate();
System.out.println("Database migration completed successfully!");
}
}
Maven Plugin
Add Flyway to your build lifecycle:
<build>
<plugins>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>11.8.0</version>
<configuration>
<url>jdbc:postgresql://localhost:5432/mydb</url>
<user>user</user>
<password>password</password>
<locations>
<location>classpath:db/migration</location>
</locations>
</configuration>
</plugin>
</plugins>
</build>
Run with: mvn flyway:migrate
Gradle Plugin
plugins {
id "org.flywaydb.flyway" version "11.8.0"
}
flyway {
url = 'jdbc:postgresql://localhost:5432/mydb'
user = 'user'
password = 'password'
locations = ['classpath:db/migration']
}
Run with: gradle flywayMigrate
Spring Boot Integration
Spring Boot 3.x auto-configures Flyway when you add the flyway-core dependency --- migrations run automatically on startup from src/main/resources/db/migration.
Note: Spring Boot 4.x changed this --- you now need spring-boot-starter-flyway instead of just flyway-core.
Transactional DDL
For databases that support it (PostgreSQL, SQL Server), Flyway wraps each migration in a transaction. If any statement fails, the entire script rolls back --- no partial schema changes.
Flyway for Non-JVM Projects
Flyway's command-line interface works with any language or framework. The CLI download bundles a JRE, so you don't need Java installed separately.
1. Configure your connection
Create a flyway.conf file:
flyway.url=jdbc:mysql://localhost:3306/mydb
flyway.user=root
flyway.password=password
flyway.locations=filesystem:sql/migrations
2. Organise your migration scripts
sql/migrations/
V1__create_products_table.sql
V2__add_price_column.sql
V3__create_orders_table.sql
Version numbers can use dots or underscores as separators (V2.1__ and V2_1__ are both valid).
3. Run migrations
flyway -configFiles=flyway.conf migrate
4. Integrate with CI/CD
Add Flyway as a pre-deployment step in your pipeline. Here's a shell script example:
#!/bin/bash
export FLYWAY_URL="jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME}"
export FLYWAY_USER="${DB_USER}"
export FLYWAY_PASSWORD="${DB_PASSWORD}"
export FLYWAY_LOCATIONS="filesystem:./db/migrations"
echo "Running Flyway migrations..."
./flyway migrate
if [ $? -eq 0 ]; then
echo "Flyway migrations completed successfully!"
else
echo "Flyway migrations failed!"
exit 1
fi
In DeployHQ, you can run this as a build pipeline command or SSH pre-deployment hook, with database credentials stored securely as environment variables.
Flyway with Python Projects
Python projects commonly use Alembic or Django migrations, but Flyway is a strong choice when your team manages multiple services across different languages and wants a single migration tool for all of them. The Flyway CLI runs SQL migrations regardless of your application language.
Using the Flyway CLI from Python
The most reliable approach is calling the Flyway CLI directly from your Python build or deployment scripts:
import subprocess
import sys
import os
def run_flyway_migrate():
"""Run Flyway migrations as part of the deployment pipeline."""
env = os.environ.copy()
env.update({
"FLYWAY_URL": f"jdbc:postgresql://{os.getenv('DB_HOST', 'localhost')}:5432/{os.getenv('DB_NAME', 'mydb')}",
"FLYWAY_USER": os.getenv("DB_USER", "postgres"),
"FLYWAY_PASSWORD": os.getenv("DB_PASSWORD", ""),
"FLYWAY_LOCATIONS": "filesystem:./db/migrations",
})
result = subprocess.run(
["flyway", "migrate"],
env=env,
capture_output=True,
text=True,
)
if result.returncode != 0:
print(f"Migration failed:\n{result.stderr}", file=sys.stderr)
sys.exit(1)
print(result.stdout)
print("Flyway migrations applied successfully.")
if __name__ == "__main__":
run_flyway_migrate()
Using the flyway-python wrapper
The flyway PyPI package provides a thin wrapper around the Flyway CLI, downloading and managing the binary automatically:
pip install flyway
# flyway-python exposes the CLI as a Python callable
from flyway import Flyway
flyway = Flyway(
url="jdbc:postgresql://localhost:5432/mydb",
user="postgres",
password="secret",
locations=["filesystem:./db/migrations"],
)
flyway.migrate()
This is convenient for local development, but for production CI/CD pipelines most teams install the Flyway CLI directly --- it avoids adding a Python dependency to your migration path.
Project structure
A typical Python project using Flyway:
my-python-app/
app/
__init__.py
main.py
db/
migrations/
V1__create_users.sql
V2__add_sessions_table.sql
flyway.conf
requirements.txt
deploy.py # calls flyway migrate
Flyway with Node.js Projects
Node.js projects typically use tools like Prisma Migrate or Knex migrations, but Flyway works well when you need a language-agnostic migration tool across a polyglot stack --- for example, when your Node.js API shares a database with Java microservices.
Using the Flyway CLI from Node.js
Call the Flyway CLI as a child process in your deployment scripts:
// scripts/migrate.js
const { execSync } = require("child_process");
const env = {
...process.env,
FLYWAY_URL: `jdbc:postgresql://${process.env.DB_HOST || "localhost"}:5432/${process.env.DB_NAME || "mydb"}`,
FLYWAY_USER: process.env.DB_USER || "postgres",
FLYWAY_PASSWORD: process.env.DB_PASSWORD || "",
FLYWAY_LOCATIONS: "filesystem:./db/migrations",
};
try {
const output = execSync("flyway migrate", { env, encoding: "utf-8" });
console.log(output);
console.log("Flyway migrations applied successfully.");
} catch (error) {
console.error("Migration failed:", error.stderr || error.message);
process.exit(1);
}
npm scripts integration
Add migration commands to your package.json:
{
"scripts": {
"db:migrate": "flyway -configFiles=flyway.conf migrate",
"db:validate": "flyway -configFiles=flyway.conf validate",
"db:info": "flyway -configFiles=flyway.conf info",
"predeploy": "npm run db:migrate"
}
}
This lets you run npm run db:migrate locally and hook it into your DeployHQ build pipeline as a build step.
Project structure
my-node-app/
src/
index.ts
routes/
db/
migrations/
V1__create_accounts.sql
V2__add_api_keys_table.sql
flyway.conf
package.json
scripts/
migrate.js
Rollback Strategies
Rolling back database migrations is harder than rolling back application code --- you can't simply revert a SQL file that has already altered your schema. Flyway provides several approaches depending on your edition and requirements.
Undo migrations (Teams/Enterprise edition)
Flyway's Teams and Enterprise editions include the flyway undo command. For each versioned migration, you write a corresponding undo script:
db/migrations/
V1__create_users.sql
U1__drop_users.sql
V2__add_email_column.sql
U2__remove_email_column.sql
Running flyway undo reverses the most recently applied migration. You can run it multiple times to step back through several versions.
Compensation migrations (Community edition)
The free Community edition does not include undo. Instead, the standard pattern is to write a new forward migration that reverses the effect of the problematic one:
-- V3__rollback_add_email_column.sql
-- Compensation migration: reverses V2__add_email_column.sql
ALTER TABLE users DROP COLUMN IF EXISTS email;
This keeps your migration history moving forward --- every change is recorded and auditable, even rollbacks.
Best practices for rollback-safe migrations
- Make migrations additive --- prefer
ADD COLUMNoverDROP COLUMN. Additive changes are inherently backward-compatible and don't require immediate rollback scripts - Use phased removals --- to drop a column, first deploy code that stops reading it, then add a migration to remove it in a later release
- Test undo scripts --- if you use the Teams/Enterprise
undocommand, test undo scripts in CI just as you test forward migrations - Always back up before migrating production --- no rollback strategy replaces a known-good database backup
- Consider zero-downtime database migrations for high-availability systems where rollback must be instant
Key Features
- Plain SQL migrations --- write standard SQL, no proprietary syntax required
- Checksum validation --- prevents silent modifications to already-applied scripts
- Baseline support --- bring an existing database under Flyway control without replaying history
- Validation --- detect inconsistencies between your scripts and database state with
flyway validate - Clean command --- wipe schemas for dev/test environments. Warning: this drops ALL objects in configured schemas. Never use in production. Set
flyway.cleanDisabled=truein production configs - Undo migrations --- available in the Enterprise edition. The free Community edition requires compensation migrations (see Rollback Strategies above)
- 50+ databases supported --- MySQL, PostgreSQL, Oracle, SQL Server, SQLite, MariaDB, CockroachDB, Snowflake, and many more
Best Practices
- One change per migration --- each script should be a single, atomic schema change. Don't bundle unrelated changes
- Descriptive names ---
V3__add_index_on_users_email.sqlis better thanV3__update.sql - Test before production --- run migrations in staging first. Tools like Testcontainers can spin up isolated databases for automated testing
- Design for backward compatibility --- avoid destructive changes (dropping columns) if the current app version depends on them. Use a phased approach: deprecate first, remove in a later migration
- Commit migrations to Git --- store them alongside your application code so schema history tracks with your codebase. Deploy from GitHub or GitLab and DeployHQ picks them up automatically
- Automate with CI/CD --- run migrations as a pre-deployment step, not manually. DeployHQ's build pipeline integration handles this natively
- Use minimal database privileges --- create a dedicated migration user with only the permissions Flyway needs
- Always back up before migrating production --- Flyway is reliable, but backups are non-negotiable
Troubleshooting Common Flyway Errors
Checksum mismatch
ERROR: Migration checksum mismatch for migration version 3
-> Applied to database : 1234567890
-> Resolved locally : 9876543210
Cause: Someone edited a migration file after it was already applied to the database. Flyway refuses to continue because the script on disk no longer matches what was executed.
Fix: If the edit was intentional (e.g., fixing a comment), run flyway repair to update the stored checksum. If the edit changed actual SQL, write a new migration instead --- never modify applied scripts.
Out-of-order migrations
ERROR: Detected resolved migration not applied to database: 2.1
Cause: A migration with a lower version number was added after a higher version was already applied. This happens when two developers create migrations in parallel.
Fix: Enable out-of-order execution in your config:
flyway.outOfOrder=true
This tells Flyway to apply any pending migration regardless of version ordering. Use this cautiously --- it works well for additive changes but can cause issues if migrations have interdependencies.
Failed migration leaves database in dirty state
ERROR: Found non-empty schema(s) "public" but no schema history table.
Or after a partial failure:
ERROR: Detected failed migration to version 5 (add_orders_table)
Cause: A migration failed partway through. On databases without transactional DDL (like MySQL for DDL statements), the partial changes remain applied but Flyway marks the migration as failed.
Fix:
- Manually inspect and fix the database state
- Run
flyway repairto remove the failed migration record from the history table - Fix the migration script and re-run
flyway migrate
Connection refused or authentication failures
ERROR: Unable to obtain connection from database
Cause: Wrong JDBC URL, incorrect credentials, or the database server is unreachable.
Fix: Verify your connection details. Test with a direct database client first:
# PostgreSQL
psql -h localhost -p 5432 -U user -d mydb
# MySQL
mysql -h localhost -P 3306 -u root -p mydb
Then confirm the JDBC URL format matches your database. Common mistakes include using mysql:// instead of jdbc:mysql:// or forgetting the port number.
How Flyway Works with DeployHQ
DeployHQ supports Flyway through its build pipeline and hook system:
- Build pipelines --- run
flyway migrate(or Maven/Gradle commands) as a build step before files are deployed - Pre-deployment SSH hooks --- execute migrations directly on your server before the new code goes live
- Environment variables --- store
DB_HOST,DB_USER,DB_PASSWORDsecurely without hardcoding credentials - Deployment history --- full visibility into every deployment, including migration outcomes
- One-click rollback --- revert your application deployment if a migration causes issues
- Deploy from GitHub or GitLab --- your migration scripts deploy alongside your code from any provider
For a broader look at how other frameworks handle database migrations during deployments, see our guide on .NET database migrations with DbUp.
Flyway vs Alternatives
Flyway isn't the only migration tool. Here's how it compares:
| Tool | Ecosystem | Key Difference |
|---|---|---|
| Liquibase | Java / cross-platform | Supports XML/YAML/JSON changelogs in addition to SQL. More enterprise-oriented |
| Alembic | Python (SQLAlchemy) | Auto-generates migrations from model changes. Python-native |
| Prisma Migrate | Node.js / TypeScript | Schema-first approach with auto-generated SQL |
| Atlas | Go / cross-platform | Declarative schema-as-code with HCL syntax |
| Django Migrations | Python (Django) | Built into the framework, auto-detects model changes |
Flyway's strength is its simplicity: plain SQL files, no proprietary format, and it works with any language via the CLI.
Licensing
Flyway offers two editions:
- Community (free, open source) --- core migration features, checksum validation, CLI + Maven + Gradle support
- Enterprise (paid) --- adds undo migrations, dry runs, Oracle SQL*Plus support, and priority support from Redgate
The free Community edition covers the majority of use cases.
FAQ
Q: Does Flyway work with MySQL? A: Yes. Flyway supports 50+ databases including MySQL, PostgreSQL, Oracle, SQL Server, SQLite, MariaDB, and CockroachDB. See the full list.
Q: Do I need Java installed to use Flyway? A: Not if you use the CLI --- it bundles a JRE. For JVM projects using Maven/Gradle plugins, you'll already have Java in your build environment.
Q: Can I use Flyway with a Node.js or Python project? A: Yes. Use the Flyway CLI to run migrations from any language. See the dedicated Python and Node.js sections above for integration examples.
Q: Is Flyway free? A: The Community edition is free and open source. The Enterprise edition adds advanced features like undo migrations. See Redgate's pricing for details.
Q: What happens if I accidentally modify an already-applied migration?
A: Flyway detects the checksum mismatch and halts, preventing further migrations. You can use flyway repair to update the stored checksum if the change was intentional. See the Troubleshooting section above for details.
Q: How do I roll back a Flyway migration?
A: The Enterprise edition provides the flyway undo command. With the free Community edition, write a compensation migration --- a new forward migration that reverses the previous change. See Rollback Strategies above for full details.
Database migrations don't have to be a manual, error-prone process. Flyway gives you version-controlled, repeatable schema changes --- and when paired with DeployHQ's automated deployments, those migrations run automatically as part of every deployment.
Start your free DeployHQ trial and automate your database migrations alongside your code deployments.
Questions? Reach out at support@deployhq.com or @deployhq.