Integrating Atlas with an existing Drizzle project
This guide explains how to configure Atlas to automatically plan migrations for your existing Drizzle project and manage untracked objects using Atlas.
Installation Atlas & Drizzle
- macOS + Linux
- Homebrew
- Docker
- Windows
- CI
- Manual Installation
To download and install the latest release of the Atlas CLI, simply run the following in your terminal:
curl -sSf https://atlasgo.sh | sh
Get the latest release with Homebrew:
brew install ariga/tap/atlas
To pull the Atlas image and run it as a Docker container:
docker pull arigaio/atlas
docker run --rm arigaio/atlas --help
If the container needs access to the host network or a local directory, use the --net=host flag and mount the desired
directory:
docker run --rm --net=host \
-v $(pwd)/migrations:/migrations \
arigaio/atlas migrate apply
--url "mysql://root:pass@:3306/test"
Download the latest release and move the atlas binary to a file location on your system PATH.
GitHub Actions
Use the setup-atlas action to install Atlas in your GitHub Actions workflow:
- uses: ariga/setup-atlas@v0
with:
cloud-token: ${{ secrets.ATLAS_CLOUD_TOKEN }}
Other CI Platforms
For other CI/CD platforms, use the installation script. See the CI/CD integrations for more details.
We will use the following packages for this guide:
drizzle-orm- Drizzle ORM packagedrizzle-kit- Drizzle CLIpg- PostgreSQL client for Node.jstsx- TypeScript compiler
- npm
- yarn
- pnpm
- bun
npm i drizzle-orm pg dotenv
npm i -D drizzle-kit tsx @types/pg
yarn add drizzle-orm pg dotenv
yarn add -D drizzle-kit tsx @types/pg
pnpm add drizzle-orm pg dotenv
pnpm add -D drizzle-kit tsx @types/pg
bun add drizzle-orm pg dotenv
bun add -D drizzle-kit tsx @types/pg
If you have an existing Drizzle project with a drizzle folder already applied to the target database,
you can move your project to using Atlas with a few adjustments.
In this section, we demonstrate how to replace drizzle-kit migrate with Atlas for managing your database schema.
Create an example Drizzle project
To mimic the scenario of an existing project, let's create a new Drizzle project with PostgreSQL database,
with an existing User model in the target database.
Follow these steps to create a new Drizzle project:
- New
userstable model
Create a new file schema.ts in your project root folder:
import { integer, pgTable, varchar } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: integer().primaryKey().generatedAlwaysAsIdentity(),
name: varchar({ length: 255 }).notNull(),
age: integer().notNull(),
email: varchar({ length: 255 }).notNull().unique(),
});
- Setup Drizzle config file
Create a drizzle.config.ts file in your project root folder:
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
out: './drizzle',
schema: './schema.ts',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
- Run the
drizzle generatecommand to generate the first migration:
npx drizzle-kit generate
- Create a PostgreSQL development database with Docker:
docker run --name postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:16
- Run the
drizzle-kit migratecommand to apply the migration to the development database:
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres npx drizzle-kit migrate
After those steps, your project folder should look like this:
my-drizzle-project
├── drizzle
| ├── meta
| | ├── ...
| ├── 0000_heavy_swordsman.sql
├── schema.ts
└── drizzle.config.ts
Configuring Atlas to use Drizzle schema
Now that we have an existing Drizzle project from the previous section,
let's see how we can use Atlas to replace the drizzle-kit migrate command.
Follow these steps to configure Atlas to use the Drizzle schema:
- At the project root folder, create
atlas.hclfile:
data "external_schema" "drizzle" {
program = [
"npx",
"drizzle-kit",
"export",
]
}
env "local" {
dev = "docker://postgres/16/dev?search_path=public"
schema {
src = data.external_schema.drizzle.url
}
migration {
dir = "file://atlas/migrations"
}
exclude = ["drizzle"]
}
In the above configuration, we utilize the drizzle-kit export command to generate a full DDL.
The generated Data Definition Language (DDL) will be used as the source schema for Atlas.
This config excludes the drizzle schema from the migration plan, as it is already applied to the database.
- Generate the migration plan using
atlas migrate diff:
atlas migrate diff --env local
You should see the migration plan generated by Atlas at the atlas/migrations folder.
- Apply generated migrations to development database with
baseline
atlas migrate apply \
--env local \
--url "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable" \
--baseline 20250102081546
The --baseline flag tells Atlas to apply the migration plan starting from the specified timestamp.
- Let's run
atlas migrate statusto verify the migration status:
atlas migrate status \
--env local \
--url "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable"
You should see the migration status as up for the applied migrations.
Migration Status: OK
-- Current Version: 20250102081546
-- Next Version: Already at latest version
-- Executed Files: 1
-- Pending Files: 0
Running Atlas to plan migrations
After transitioning your existing Drizzle project to use Atlas for managing migrations, let's see how your workflow will look like when using Atlas.
- Adding a new model to the Drizzle schema
Let's add a new Post model to the Drizzle schema:
import { integer, pgTable, serial, text, varchar } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: integer().primaryKey().generatedAlwaysAsIdentity(),
name: varchar({ length: 255 }).notNull(),
age: integer().notNull(),
email: varchar({ length: 255 }).notNull().unique(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
content: text('content'),
authorId: integer('author_id'),
});
- Generate the migration plan using
atlas migrate diff:
atlas migrate diff --env local
You should see the new migration plan generated by Atlas at the atlas/migrations folder.
-- Create "posts" table
CREATE TABLE "posts" ("id" serial NOT NULL, "content" text NULL, "author_id" integer NULL, PRIMARY KEY ("id"));
- Apply the new migration plan to the development database:
atlas migrate apply \
--env local \
--url "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable"
Migrating to version 20250102090452 from 20250102081546 (1 migrations in total):
-- migrating version 20250102090452
-> CREATE TABLE "posts" ("id" serial NOT NULL, "content" text NULL, "author_id" integer NULL, PRIMARY KEY ("id"));
-- ok (11.096792ms)
-------------------------
-- 22.413167ms
-- 1 migration
-- 1 sql statement
- Verify the migration status:
atlas migrate status \
--env local \
--url "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable"
You should see the current version as 20250102090452:
Migration Status: OK
-- Current Version: 20250102090452
-- Next Version: Already at latest version
-- Executed Files: 2
-- Pending Files: 0
Managing untracked objects
In some cases, you might have untracked objects in the database that manually created inside the Drizzle migration files or created directly in the database. To manage these untracked objects, you can use Atlas to inspect them and convert them into the Atlas schema.
To demonstrate this, let's manually add a function object to the development database:
Run by docker command to connect to the development database:
docker exec -it postgres psql -U postgres -d postgres -c 'CREATE FUNCTION public.echo (text) RETURNS text LANGUAGE sql AS $$ SELECT $1; $$;'
At this point, we have a function in the database that is not managed by Drizzle.
The idea is to use atlas schema diff command to compare the target database with our external schema.
Ok, let's create an atlas/drizzle_objects.sql file to store these untracked objects:
atlas schema diff \
--env local \
--from "file://atlas/migrations" \
--to "postgresql://postgres:postgres@localhost:5432/postgres?search_path=public&sslmode=disable" \
--exclude "atlas_schema_revisions" > atlas/drizzle_objects.sql
The atlas/drizzle_objects.sql file should contain the function object:
-- Create "echo" function
CREATE FUNCTION "echo" (text) RETURNS text LANGUAGE sql AS $$ SELECT $1; $$;
After that, edit the atlas.hcl file to include the atlas/drizzle_objects.sql with composite schemas
data "external_schema" "drizzle" {
program = [
"npx",
"drizzle-kit",
"export",
]
}
data "composite_schema" "drizzle-objects" {
schema "public" {
url = data.external_schema.drizzle.url
}
schema "public" {
url = "file://atlas/drizzle_objects.sql"
}
}
env "local" {
dev = "docker://postgres/16/dev?search_path=public"
schema {
src = data.composite_schema.drizzle-objects.url
}
migration {
dir = "file://atlas/migrations"
}
exclude = ["drizzle"]
}
Run this command to plan migrations with Atlas:
atlas migrate diff --env local
You should see the echo function included in the migration plan.
-- Create "echo" function
CREATE FUNCTION "echo" (text) RETURNS text LANGUAGE sql AS $$ SELECT $1; $$;
In the last step, set the baseline version to the latest migration version, to avoid applying duplicates to the database:
atlas migrate apply \
--env local \
--url "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable" \
--baseline 20250102095509
Since the baseline version can be set only once, if you have already set the database to a different
baseline version (as described in the section above), you can use the atlas migrate set command instead:
atlas migrate set 20250102095509 \
--env local \
--url "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable"
This command sets the database to version 20241018071458. Hence, only migration files with versions
higher than this will be applied to the database. This is intended, as these objects are already in the database.
The code for this tutorial is available under providers/drizzle