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](https://www.red-gate.com/products/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](https://www.deployhq.com/features/build-pipelines), 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](https://www.red-gate.com/). 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](https://documentation.red-gate.com/fd/supported-databases-and-versions-143754067.html)), 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
```

1. **You write migration files** --- plain SQL files with version numbers: `V1 __create_users_table.sql`, `V2__ add_email_column.sql`
2. **Flyway tracks state** --- a `flyway_schema_history` table in your database records every applied migration, its checksum, and when it ran
3. **Flyway applies the diff** --- on each run, it compares your migration files against the history table and applies only the pending ones, in order
4. **Checksums catch tampering** --- if an already-applied script is modified, Flyway detects the mismatch and stops, preventing silent corruption
5. **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](https://documentation.red-gate.com/flyway/reference/usage/command-line) 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](https://www.deployhq.com), you can run this as a [build pipeline](https://www.deployhq.com/features/build-pipelines) 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`](https://pypi.org/project/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](https://www.deployhq.com) [build pipeline](https://www.deployhq.com/features/build-pipelines) 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

1. **Make migrations additive** --- prefer `ADD COLUMN` over `DROP COLUMN`. Additive changes are inherently backward-compatible and don't require immediate rollback scripts
2. **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
3. **Test undo scripts** --- if you use the Teams/Enterprise `undo` command, test undo scripts in CI just as you test forward migrations
4. **Always back up before migrating production** --- no rollback strategy replaces a known-good database backup
5. **Consider [zero-downtime database migrations](https://www.deployhq.com/blog/database-migration-strategies-for-zero-downtime-deployments)** 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=true` in production configs
- **Undo migrations** --- available in the [Enterprise edition](https://www.red-gate.com/products/flyway/enterprise/). 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](https://documentation.red-gate.com/fd/supported-databases-and-versions-143754067.html)

## Best Practices

1. **One change per migration** --- each script should be a single, atomic schema change. Don't bundle unrelated changes
2. **Descriptive names** --- `V3 __add_index_on_users_email.sql` is better than `V3__ update.sql`
3. **Test before production** --- run migrations in staging first. Tools like [Testcontainers](https://testcontainers.com/) can spin up isolated databases for automated testing
4. **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
5. **Commit migrations to Git** --- store them alongside your application code so schema history tracks with your codebase. [Deploy from GitHub](https://www.deployhq.com/deploy-from-github) or GitLab and [DeployHQ](https://www.deployhq.com) picks them up automatically
6. **Automate with CI/CD** --- run migrations as a pre-deployment step, not manually. DeployHQ's [build pipeline integration](https://www.deployhq.com/features/build-pipelines) handles this natively
7. **Use minimal database privileges** --- create a dedicated migration user with only the permissions Flyway needs
8. **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:**

1. Manually inspect and fix the database state
2. Run `flyway repair` to remove the failed migration record from the history table
3. 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](https://www.deployhq.com/features) supports Flyway through its build pipeline and hook system:

- **[Build pipelines](https://www.deployhq.com/features/build-pipelines)** --- run `flyway migrate` (or Maven/Gradle commands) as a build step before files are deployed
- **[Pre-deploy validation checks](https://www.deployhq.com/features/deployment-checks)** --- run `flyway validate` as a gating check before the build starts, blocking the release when migration scripts have drifted from what's already applied
- **Pre-deployment SSH hooks** --- execute migrations directly on your server before the new code goes live
- **Environment variables** --- store `DB_HOST`, `DB_USER`, `DB_PASSWORD` securely without hardcoding credentials
- **Deployment history** --- full visibility into every deployment, including migration outcomes
- **[One-click rollback](https://www.deployhq.com/features/one-click-rollback)** --- revert your application deployment if a migration causes issues
- **Deploy from [GitHub](https://www.deployhq.com/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](https://www.deployhq.com/blog/automating-database-migrations-in-net).

## Flyway vs Alternatives

Flyway isn't the only migration tool. Here's how it compares:

| Tool | Ecosystem | Key Difference |
| --- | --- | --- |
| **[Liquibase](https://www.liquibase.com/)** | Java / cross-platform | Supports XML/YAML/JSON changelogs in addition to SQL. More enterprise-oriented |
| **[Alembic](https://alembic.sqlalchemy.org/)** | Python (SQLAlchemy) | Auto-generates migrations from model changes. Python-native |
| **[Prisma Migrate](https://www.prisma.io/docs/orm/prisma-migrate)** | Node.js / TypeScript | Schema-first approach with auto-generated SQL |
| **[Atlas](https://atlasgo.io/)** | 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](https://www.red-gate.com/products/flyway/)

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](https://documentation.red-gate.com/fd/supported-databases-and-versions-143754067.html).

**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](https://www.red-gate.com/products/flyway/) 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](https://www.deployhq.com/features/automatic-deployments), those migrations run automatically as part of every deployment.

**[Start your free](https://www.deployhq.com/signup)[DeployHQ](https://www.deployhq.com) trial** and automate your database migrations alongside your code deployments.

* * *

Questions? Reach out at [support@deployhq.com](mailto:support@deployhq.com) or [@deployhq](https://x.com/deployhq).

