Product articles Redgate Flyway Configuration, Authentication
Defining and Using Multiple Flyway…

Defining and Using Multiple Flyway Environments in TOML

This article shows how to define environments in your TOML files, use resolvers to provide secure connection details, and configure per-environment overrides and placeholders. It explains how this approach simplifies automation, makes CI/CD pipelines easier to manage, and helps teams work consistently and securely across multiple databases.

Guest post

This is a guest post from Phil Factor. Phil Factor (real name withheld to protect the guilty), aka Database Mole, has 30 years of experience with database-intensive applications.

Despite having once been shouted at by a furious Bill Gates at an exhibition in the early 1980s, he has remained resolutely anonymous throughout his career.

He is a regular contributor to Simple Talk and SQLServerCentral.


This article is part of a series on using TOML configuration, environments, and resolvers in Flyway:

  1. Getting Started with TOML Configuration in Flyway
  2. Managing Credentials Securely with Flyway Resolvers
  3. Defining and Using Multiple Flyway Environments in TOML – you are here
  4. Using Flyway Environments and Resolvers to Manage Many Databases – coming soon
  5. Double‑checking your Current Flyway Database Environment – coming soon

Flyway’s support for TOML-based configuration simplifies how development teams manage multiple database environments. When used with resolvers, we can safely define environments as project-level resources, while maintaining individual user logins. This greatly simplifies team development while keeping it secure.

Instead of juggling separate config files or repeating settings, we can now use the [environments] namespace to define all our database environments, such as ‘development’, ‘test’, and ‘staging’, each with different credentials and connection details, in a single TOML file. After that, we can then reference them in the project simply by name.

What makes environments even more powerful is the nested [environments.<name>.flyway] namespace, where you can override or extend Flyway’s default settings for each environment. This lets you tailor configuration to the database’s capabilities, the stage of development, or the specific needs of your team, all without rewriting scripts or duplicating settings.

Defining and configuring database environments

Flyway now reads its configuration from flyway.toml files, which support several namespaces. Alongside the familiar [flyway] namespace (carried over from the old .conf format), you can use the [environments] namespace to define all your environments – such as dev, test, or staging – and their settings, within a single TOML file in the user area. Previously, the old flyway.user.conf allowed only a single connection, now represented as the default environment.

By keeping configuration in one place, and letting it be overridden or extended per environment, it’s much easier to adapt Flyway’s behavior across databases and platforms

Defining multiple environments

Flyway makes it easy to define multiple environments, each representing a different stage of the workflow, simply by specifying values directly in the [environments] namespace in your flyway.toml file. You can configure distinct settings, credentials, and behaviors for each environment. This gives teams the flexibility to support local development, customer-specific testing, automated CI/CD pipelines, or production-like staging, all from a single, structured configuration file. For instance, you might have:

  • Develop: A local development database with debugging enabled.
  • Tenant2Dev: An isolated development database for a specific customer tenant or feature.
  • Test: A shared testing environment managed by automated CI/CD pipelines.
  • Staging: A near-identical replica of the production environment.

You can define environments in either your flyway.user.toml file or, for teamwork, your project-level flyway.toml.

Here’s a simple example where we define two environments, dev and test. We specify the connection details for each database environment (URL, user, password) and the schemas we want Flyway to manage. We can also override the defaults for certain connection behaviors like number of retries, but we won’t do that here.

Since we’re hardcoding credentials in this example, this configuration would need to be stored in the flyway.user.toml file in the secure user-area of the user’s workstation:

Defining environments securely

Instead of storing sensitive configuration values directly in your configuration files, as in the simple example above, it is far more secure if your TOML files instead contain resolvers that tell Flyway how to fetch from a secure store. Here is the dev environment, defined more securely using resolvers:

I use long names for each resolver, to distinguish the project (Pubs), RDBMS (SQL Server), branch (dev), server (localhost) and the actual secret I’m storing, such as the password. It is otherwise easy to get in a muddle.

flyway resolvers

Flyway will, at runtime, substitute in the actual values from the specified secrets store (in this case, Credentials Manager in Windows). Using resolvers also means database environments can be safely defined in a shared project directory, keeping the team in sync with configuration changes while each user still resolves their own credentials locally.

By segregating configurations into environment-specific namespaces, and using resolvers, sensitive data such as production database credentials can be isolated and protected within the definition of the ‘environment’. This separation also helps with compliance with your organization’s regulatory requirements. Perhaps the greatest advantage is that the actual credentials can be changed regularly in line with current security guidelines without altering the TOML configuration file.

Configuring environment-specific behaviors

Within the nested [environments.<name>.flyway] namespace, we can override or extend Flyway’s default settings for an environment, as well as establish environment-specific behaviors. For example, we can:

  • Set per-environment migration locations — perhaps to point to a different list of Migration folders for a feature branch or for a regional database variant.
  • Override general Flyway behavior — such as selectively enabling the clean command only in development environments, by setting cleanDisabled = false.
  • Define environment-specific placeholder values — for example, a ${schemaname} placeholder in your migration scripts, defined within the Flyway namespace by placeholder called schemaName, could resolve to different schemas in dev and prod.

environment overrrides

Here’s how this might look in a TOML file, for the development (dev) environment:

What you can and can’t override

Generally, Flyway settings within the [flyway] namespace can be overridden in [environments.<name>.flyway]. However, there are some exceptions. Some settings that you might want to set per-environment, like environment, cherryPick, check, email, token, licenseKey, and undoSqlMigrationPrefixare can’t be overridden because they are processed before Flyway evaluates the selected environment.

On top of the official list there are some other settings that you can’t override, either because they aren’t in the flyway workspace, or because it’s impossible within the context:

  • Logging/output settings – e.g., log level or format settings that are processed too early to be environment-specific
  • Configuration file paths – such as configFiles, workingDirectory, and configFileEncoding, which control how and where Flyway loads its config files
  • Display settings – like flyway.outputType and flyway.color, which affect how command output is shown
  • RDBMS-specific configuration blocks – like [flyway.oracle], [flyway.sqlserver], [flyway.postgresql]. These are not merged or resolved per environment
  • Secrets manager integrations – including Vault (vaultSecrets, vaultToken, vaultUrl), Dapr (daprSecrets, daprUrl), and GCP Secret Manager. These are configured globally

Making the most of Flyway environments

Flyway environments don’t just simplify secure, per-environment configuration, they also make many aspects of Flyway-based development and deployment easier to manage and scale.

Simplified CLI Commands

Having configured our environments, it becomes very simple to switch between them in a series of Flyway commands. Instead of redefining configurations for each command, we specify the desired environment using a parameter, such as:

flyway -environment=test migrate

Easy integration with CI/CD pipelines

With the [Environments] namespace, and the ability to customize Flyway’s default settings for each environment, Flyway fits naturally into a scripted development process or CI/CD pipeline.

Instead of injecting environment-specific settings via scripts or environment variables, you can define all necessary configurations (connection details, placeholder values, migration locations, etc.) for each environment in your TOML file. Then, when your pipeline runs Flyway, it simply specifies the appropriate environment name, and Flyway uses the correct settings automatically.

This is very useful when you require automated database migrations, tailored to the specific environment being deployed.

Handling rapidly changing authentication

I like to use short-term database containers for development, which means credentials can change frequently. This was once awkward for Flyway because TOML files can’t be updated automatically in place. With resolvers, Flyway can now cope easily with rapidly changing authentication: you just update the value in your secrets store manually, via the UI, or through a script that automatically updates the credentials when you create a new container. I’ve shown how to update Credential Manager values using PowerShell in Managing Many Databases with Flyway Environments and Resolvers.

Working with multiple environments

Flyway environments are flexible. You can define them once and reuse them, or even generate them on demand. Where you define them depends on your workflow:

  • User-level TOML files are handy for personal setups or cross-project automation.
  • Project-level TOML files (under source control) are ideal for shared team environments.
  • Dynamic configs are useful for ephemeral setups, like containers or one-off tasks.

Flyway loads configuration in order – system-level first, then user-level, then project-level – so you can always override settings if needed, either in the project TOML or by supplying an external config file dynamically.

User-level or project-level configuration

Before Flyway introduced resolvers, Flyway Environments that defined credentials or connection information had to be stored in the flyway.user.toml file within the user area. With resolvers, you no longer need to hardcode secrets, so you can safely store most shared environments in the project directory under source control. This way, everyone uses the same configuration, but developers still get individual logins.

Even so, I still like to define some environments entirely in my user-level TOML because it gives me a central list of all the environments I care about, across multiple projects. This makes it much easier to script cross-project automation operations like:

  • Running flyway info on every dev database
  • Backing up all Staging databases across projects
  • Verifying the schema version across all test environments

If you stored only project-level configs, you’d have to iterate through each project folder, read its TOML individually, and merge separate environment lists.

Nevertheless, some tasks remain awkward. Flyway expects commands to run from the project directory, so automation scripts must move into the right folder first. The only way to automate that cleanly is to store the project path as a placeholder in each environment definition and retrieve it programmatically. It would be useful to be able to query the list of all defined environments, but Flyway doesn’t currently offer a built-in way to do this.

Generating the config dynamically

For more ephemeral configs, such as when working with short-lived containers or for one-off tasks, you can generate TOML files on the fly in scripts and point Flyway to them with the -configFiles parameter. These ephemeral configs don’t live in source control – they’re created as needed, used once, and discarded.

Here’s an example of my own environment definitions that I generated dynamically using PowerShell. I then run flyway info against each of them to record their status (not shown here – I’ll cover that in the next article).

While Flyway expects override settings to live in [environments.<name>.flyway], as demonstrated earlier, it also supports placing certain settings directly within the [environments.<name>] block, as I’ve done here.

Overriding values at runtime

Sometimes, you find that some or all values for an environment aren’t known until runtime. Flyway can handle this in a few ways, without editing static TOML files.

You can pass or overwrite their values at the command line using conventional dotted notation (one long line)

flyway info -environment=develop -'environments.develop.url'='myURL -'environments.develop.user'=PhilFactor -'environments.develop.password'=ratherSecret

Alternatively, you can create a temporary TOML file dynamically (e.g., in a script) and pass it to Flyway using the -configfiles parameter.

flyway -configFiles=/path/to/temp.toml info

You can also pass them in the STDIN stream of Flyway. This allows you to ‘ use encrypted TOML files, files with temporary values such as passwords, and to iterate through a list of environments. This is particularly useful when you need to customize paths, connection details, or other settings per environment, without manually editing configuration files.

Adapting to the environment using placeholders

So far, we’ve discussed how environments help manage connection details and Flyway settings across multiple environments. We can take things a step further by using environment placeholders to adapt the actual behavior of migrations for each environment.

environment placeholders

Placeholders are variables Flyway replaces at runtime. By defining them differently per environment, you can write one migration script that runs safely everywhere, even when different environments have slightly different requirements.

This is useful, for example, when:

  • A newer SQL feature is supported only in later versions of your RDBMS
  • A linked-server exists in staging but not in development
  • You need to create variants, such as variants of an accounting package, for different legislative areas

For example, SQL Server 2017 and later supports STRING_AGG(), but earlier versions don’t. Instead of maintaining two separate scripts, you can define a simple canDoStringAgg placeholder that resolves to 1=1 (true) in environments that support STRING_AGG, and 1=0 (false) otherwise.

In the flyway.placeholders namespace we define a default ” of ‘false’, so the code will be a legacy XML technique that works on any SQL Server version we support. The[environment.<name>.flyway.placeholder] namespace can override this, if the environment is running SQL Server 2017 or later.

Then we can use a single migration script, with the $canDoStringAgg placeholder, to select the appropriate variant of the code of a view, procedure or function, depending on the version of SQL Server that is running in the environment. This is all done inline and is invisible to the user.

The ShouldExecute technique

I’ve demonstrated in a previous article how to do the same trick using Flyway’s ShouldExecute script configuration. Here, the legacy XML version of the code is used by default and the String_Agg version is overlaid using the ALTER syntax, if canDoStringAgg is set to true. It requires a special version file and a conf file for each new database feature, which can get cluttered. With environment placeholders, it all becomes much simpler

Conclusion

Flyway’s [environments] namespace provides a straightforward, practical way to manage configuration across multiple databases and environments. By defining connection details and settings in one place and separating out secrets, it becomes much easier to work consistently and securely as a team.

It’s a feature that requires a bit of initial discipline but where the reward is fewer errors, better automation, and a configuration setup that’s easier to understand and maintain. Whether you’re managing a single development database or a more complex enterprise system, this approach makes life simpler.

In the next article, I show how to define a canonical source of environment data, sync it with Windows Credential Manager, and run Flyway commands securely across several environments, without hardcoding anything.

Tools in this post

Redgate Flyway

Bring stability and speed to database deployments

Find out more