Skip to main content

Automatic YugabyteDB Schema Migrations with Atlas

YugabyteDB is a distributed SQL database with a PostgreSQL-compatible YSQL API. Atlas supports YugabyteDB through the ysql:// driver, so you can inspect schemas, plan migrations, and manage schema changes as code.

Enter: Atlas

Atlas helps developers manage their database schema as code. You provide the desired state of the schema, and Atlas plans the migration needed to move the database to that state.

In this guide, we will set up Atlas with YugabyteDB and walk through both declarative and versioned migration workflows.

Prerequisites

  1. Docker
  2. Atlas installed on your machine:

To download and install the latest release of the Atlas CLI, simply run the following in your terminal:

curl -sSf https://atlasgo.sh | sh
  1. An Atlas Pro account.

YugabyteDB support is available to Pro users. To use this feature, run atlas login.

Logging in to Atlas

To use YugabyteDB with Atlas, you'll need to log in to Atlas. If it's your first time, you will be prompted to create both an account and a workspace (organization):

atlas login

Inspecting our Database

Let's start by running YugabyteDB locally:

docker run --rm -d --name atlas-yugabyte-demo \
-p 5433:5433 \
yugabytedb/yugabyte:latest \
bin/yugabyted start --background=false

YugabyteDB may take a few seconds to start. For this example, we will begin with a minimal database that contains a users table:

CREATE TABLE "users" (
"id" bigint,
"name" varchar NOT NULL,
PRIMARY KEY ("id")
);

Create the table in the local YugabyteDB container:

docker exec atlas-yugabyte-demo bash -lc \
'bin/ysqlsh -h "$(hostname -i)" -U yugabyte -d yugabyte -c "CREATE TABLE \"users\" (\"id\" bigint, \"name\" varchar NOT NULL, PRIMARY KEY (\"id\"));"'
CREATE TABLE

Atlas uses the ysql:// scheme for YugabyteDB YSQL connections. Inspect the database and write the result to schema.hcl:

atlas schema inspect \
-u "ysql://yugabyte@localhost:5433/yugabyte?search_path=public&sslmode=disable" > schema.hcl

Open schema.hcl to view the Atlas schema that describes the database:

schema.hcl
table "users" {
schema = schema.public
column "id" {
null = false
type = bigint
}
column "name" {
null = false
type = character_varying
}
primary_key {
columns = [column.id]
}
}
schema "public" {
comment = "standard public schema"
}

The table block describes the users table. The schema field references the public schema defined below it.

info

For more details on inspecting databases, see the atlas schema inspect documentation.

Declarative Migrations

The declarative workflow starts with the desired state of the schema. Atlas compares that state to the target database and plans the SQL needed to reconcile the difference.

Let's add a repos table to schema.hcl:

schema.hcl
table "users" {
schema = schema.public
column "id" {
null = false
type = bigint
}
column "name" {
null = false
type = character_varying
}
primary_key {
columns = [column.id]
}
}
table "repos" {
schema = schema.public
column "id" {
type = bigint
null = false
}
column "name" {
type = character_varying
null = false
}
column "owner_id" {
type = bigint
null = false
}
primary_key {
columns = [column.id]
}
foreign_key "fk_repo_owner" {
columns = [column.owner_id]
ref_columns = [table.users.column.id]
}
}
schema "public" {
comment = "standard public schema"
}

Now run atlas schema apply to preview the migration. The --dev-url flag points to a Dev Database that Atlas uses to plan and validate the migration without touching the target:

atlas schema apply \
-u "ysql://yugabyte@localhost:5433/yugabyte?search_path=public&sslmode=disable" \
--to file://schema.hcl \
--dev-url "docker://ysql/latest" \
--dry-run

Atlas prints the plan, runs lint checks, and executes the dry-run:

Planning migration statements (1 in total):

-- create "repos" table:
-> CREATE TABLE "repos" (
"id" bigint NOT NULL,
"name" character varying NOT NULL,
"owner_id" bigint NOT NULL,
PRIMARY KEY ("id"),
CONSTRAINT "fk_repo_owner" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION
);

-------------------------------------------

Analyzing planned statements (1 in total):

-- no diagnostics found

-------------------------
-- 1 schema change

-------------------------------------------

Running dry-run migration (1 statement in total):

-- create "repos" table
-> CREATE TABLE "repos" (
"id" bigint NOT NULL,
"name" character varying NOT NULL,
"owner_id" bigint NOT NULL,
PRIMARY KEY ("id"),
CONSTRAINT "fk_repo_owner" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION
);
-- ok

-------------------------
-- 1 migration
-- 1 sql statement

Review the plan. When you are ready to apply it, run the same command without --dry-run.

Versioned Migrations

The versioned workflow stores schema changes as SQL files in a migration directory. The files can be reviewed, committed, and applied later.

To create the first migration from schema.hcl, run the following command with these flags:

  • --to - The URL of the desired state (your schema.hcl file).
  • --dev-url - A URL to a Dev Database that Atlas uses to compute the diff.
atlas migrate diff initial \
--to file://schema.hcl \
--dev-url "docker://ysql/latest"

Atlas creates a migrations directory with a migration file and an atlas.sum file:

20260515045626_initial.sql
-- Create "users" table
CREATE TABLE "public"."users" (
"id" bigint NOT NULL,
"name" character varying NOT NULL,
PRIMARY KEY ("id")
);
-- Create "repos" table
CREATE TABLE "public"."repos" (
"id" bigint NOT NULL,
"name" character varying NOT NULL,
"owner_id" bigint NOT NULL,
PRIMARY KEY ("id"),
CONSTRAINT "fk_repo_owner" FOREIGN KEY ("owner_id") REFERENCES "public"."users" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION
);

The migration file represents the current desired state, and atlas.sum protects the integrity of the migration directory. To learn more, see migration directory integrity.

Next Steps

In this guide, we inspected a YugabyteDB schema, planned a declarative migration, and generated a versioned migration with Atlas. Here are a few good next steps: