Skip to main content

Deploying Migrations to Database-per-Tenant Architecture

In the previous section, we learned how to define target groups in Atlas to manage migrations for a database-per-tenant architecture. In this section, we will see how to deploy migrations to the target groups.

Setting up

For the purpose of this guide, we will use a simple example to demonstrate how to deploy migrations to target groups. To simplify things, we will be using SQLite files as our target databases and statically defining the target groups in the atlas.hcl file.

Our config file

In our project directory, let's create a file named atlas.hcl with the following content:

locals {
tenant = ["tenant_1", "tenant_2"]
}

env "prod" {
for_each = toset(local.tenant)
url = "sqlite://${each.value}.db"
migration {
dir = "file://migrations"
}
}

An initial migration

Let's create an initial migration file to bootstrap our project:

atlas migrate new --edit init

Once the local editor opens, add the following SQL statements:

CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);

Save the file and exit the editor. Observe that two new files were created in the migrations/ directory:

├── 20240721101205_init.sql
└── atlas.sum

1 directory, 2 files

Deploying the migrations

We can deploy the migrations directly to the target group using the migrate apply command with the --env flag:

atlas migrate apply --env prod

This command will apply the migrations to both tenant_1 and tenant_2 databases. Atlas will output:

Migrating to version 20240721101205 (1 migrations in total):

-- migrating version 20240721101205
-> CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
-- ok (345.458µs)

-------------------------
-- 3.400333ms
-- 1 migration
-- 1 sql statement
Migrating to version 20240721101205 (1 migrations in total):

-- migrating version 20240721101205
-> CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
-- ok (266.375µs)

-------------------------
-- 905.875µs
-- 1 migration
-- 1 sql statement

As you can see from the output, the migration was applied to both databases. Observe that two new files were created in our project directory: tenant_1.db and tenant_2.db.

Verifying our migrations were applied

We can check the current schema of our local SQLite databases using the migrate status command. Run:

atlas migrate status --url sqlite://tenant_1.db

Atlas prints:

Migration Status: OK
-- Current Version: 20240721101205
-- Next Version: Already at latest version
-- Executed Files: 1
-- Pending Files: 0

As expected, the tenant_1 database is up-to-date with the latest migration.

Checking for Drift

Additionally, we may want to verify that the schema of the tenant database is in sync with the latest migration. We can utilize the schema diff command to compare the current schema with the latest migration:

atlas schema diff \
--dev-url 'sqlite://?mode=memory' \
--from file://migrations \
--to sqlite://tenant_1.db \
--exclude 'atlas_schema_revisions'

Atlas will output:

Schemas are synced, no changes to be made.

Next steps

As you can see, deploying migrations to target groups is straightforward using the Atlas CLI, but getting visibility into the status of each tenant, is done individually. To bridge this gap, we will show how to use the Atlas Cloud control plane to gain visibility into the status of our system in the next section.