Declarative vs Versioned Workflows
This section introduces two types of workflows that are supported by Atlas to manage database schemas: declarative (also called state-based migrations) and versioned migrations.
Declarative Migrations
The declarative approach (also known as state-based migrations) has become increasingly popular with engineers nowadays because it embodies a convenient separation of concerns between application and infrastructure engineers. Application engineers describe what (the desired state) they need to happen, and infrastructure engineers build tools that plan and execute ways to get to that state (how). This division of labor allows for great efficiencies as it abstracts away the complicated inner workings of infrastructure behind a simple, easy to understand API for the application developers and allows for specialization and development of expertise to pay off for the infra people.
With declarative migrations, the desired state of the database schema is given as input to the migration engine, which plans and executes a set of actions to change the database to its desired state.
The desired state can be defined using HCL schema files, SQL schema files, another database, ORM providers (such as GORM, Drizzle, Django, or SQLAlchemy), or a combination of these sources. For example, you can define your core schema in an ORM and augment it with SQL files that add triggers, row-level security policies, functions, or other database-specific constructs.
For example, suppose your application uses a small SQLite database to store its data.
In this database, you have a users table with this structure:
- HCL Schema
- SQL Schema
- ORM (SQLAlchemy)
schema "main" {}
table "users" {
schema = schema.main
column "id" {
type = int
}
column "greeting" {
type = text
}
}
CREATE TABLE users (
id INTEGER,
greeting TEXT
);
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
greeting = Column(Text)
Now, suppose that you want to add a default value of "shalom" to the greeting
column. Many developers are not aware that it isn't possible to modify a column's
default value in an existing table in SQLite. Instead, the common practice is to
create a new table, copy the existing rows into the new table and drop the old one
after. Using the declarative approach, developers can change the default value for
the greeting column:
- HCL Schema
- SQL Schema
- ORM (SQLAlchemy)
schema "main" {}
table "users" {
schema = schema.main
column "id" {
type = int
}
column "greeting" {
type = text
default = "shalom"
}
}
CREATE TABLE users (
id INTEGER,
greeting TEXT DEFAULT 'shalom'
);
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
greeting = Column(Text, default='shalom')
And have Atlas's engine devise a plan similar to this:
-- Planned Changes:
-- Create "new_users" table
CREATE TABLE `new_users` (`id` int NOT NULL, `greeting` text NOT NULL DEFAULT 'shalom')
-- Copy rows from old table "users" to new temporary table "new_users"
INSERT INTO `new_users` (`id`, `greeting`) SELECT `id`, IFNULL(`greeting`, 'shalom') AS `greeting` FROM `users`
-- Drop "users" table after copying rows
DROP TABLE `users`
-- Rename temporary table "new_users" to "users"
ALTER TABLE `new_users` RENAME TO `users`
Atlas provides two main commands for working with declarative migrations:
atlas schema plan- Review and approve migration plans, skipping the approval flow at apply stageatlas schema apply- Apply schema changes to a database. By default, requires approval before execution (manual approval or based on a review policy with linting diagnostics). Pre-approved plans fromatlas schema planare applied automatically
Both commands are supported by Atlas Actions for CI/CD integration (GitHub Actions, GitLab CI, Azure DevOps, etc.), enabling automated schema management in your deployment pipelines.
Versioned Migrations
As the database is one of the most critical components in any system, applying changes to its schema is rightfully considered a dangerous operation. For this reason, many teams prefer a more imperative approach where each change to the database schema is checked-in to source control and reviewed during code-review. Each such change is called a "migration", as it migrates the database schema from the previous version to the next. To support this kind of requirement, many popular database schema management tools such as Flyway, Liquibase or golang-migrate support a workflow that is commonly called "versioned migrations".
In addition to the higher level of control which is provided by versioned migrations, applications are often deployed to multiple remote environments at once. These environments, are not controlled (or even accessible) by the development team. In such cases, declarative migrations, which rely on a network connection to the target database and on human approval of migrations plans in real-time, are not a feasible strategy.
With versioned migrations (sometimes called "change-based migrations") instead of describing the desired state ("what the database should look like"), developers describe the changes themselves ("how to reach the state"). Most of the time, this is done by creating a set of SQL files containing the statements needed. Each of the files is assigned a unique version and a description of the changes. Tools like the ones mentioned earlier are then able to interpret the migration files and to apply (some of) them in the correct order to transition to the desired database structure.
Atlas provides several commands for working with versioned migrations:
atlas migrate diff- Automatically generate migration files based on the difference between the current migration directory state and the desired schemaatlas migrate lint- Analyze migration files for potential issues and policy violationsatlas migrate apply- Apply pending migration files to a database
These commands are also supported by Atlas Actions for CI/CD integration, enabling automated migration generation, linting, and deployment in your pipelines.
Migration Authoring
The downside of the versioned migration approach is, of course, that it puts the burden of planning the migration on developers. This requires a certain level of expertise that is not always available to every engineer, as we demonstrated in our example of setting a default value in a SQLite database above.
As part of the Atlas project we advocate for a third combined approach that we call "Versioned Migration Authoring". Versioned Migration Authoring is an attempt to combine the simplicity and expressiveness of the declarative approach with the control and explicitness of versioned migrations.
With versioned migration authoring, users still declare their desired state and use the Atlas engine to plan a safe migration from the existing to the new state. However, instead of coupling planning and execution, plans are instead written into normal migration files which can be checked-in to source control, fine-tuned manually and reviewed in regular code review processes.
Combining Declarative and Versioned Workflows
Atlas workflows are not mutually exclusive. Many teams mix declarative and versioned techniques to benefit from both.
During local development, engineers often work declaratively. They edit HCL or SQL schema files and run atlas schema apply
against a local database to bring it up to the desired state. This allows quick iterations without manually writing SQL,
and Atlas handles planning and execution.
When the schema change is ready to be shared, the team switches to the versioned workflow. Instead of applying the change
directly on shared environments, they run atlas migrate diff against the updated desired state. Atlas computes the difference
between the current migration history and the new schema and writes a migration file into the migrations directory. The file
is then committed to source control as part of a pull request. Standard processes such as code review, migration linting, and
CI/CD pipelines apply the migration using atlas migrate apply.
This hybrid flow keeps local development flexible and fast while ensuring that production changes are explicit, reviewable, and reproducible.
When to Prefer Versioned Migrations
While both workflows have their place, the versioned migrations approach is particularly well-suited for certain scenarios:
-
On-premise or packaged software deployments - If you're shipping software that customers install and manage themselves, you cannot review or approve migration plans during the apply stage. Versioned migrations ensure that the exact migration files created, reviewed, and merged to your main branch are executed as-is on customer databases, providing deterministic and repeatable deployments based on git history.
-
Limited database access from CI/CD - When your CI runners cannot connect to production or staging databases (due to network isolation, security policies, or compliance requirements), you cannot generate migration plans dynamically. Versioned migrations are pre-generated based on the previous state of the migration directory, and can be applied without needing to inspect the target database schema beforehand.
-
Large teams with concurrent schema changes - When many engineers are simultaneously working on schema changes, the migration directory integrity file (
atlas.sum) used by versioned migrations helps prevent conflicts. It ensures linear history through merge conflict detection, and requires rebase. -
Consistency across environments - When you need to guarantee that the same schema changes are applied identically across multiple environments, customers, or database instances, versioned migrations ensure consistency. Migration files are executed in the same order, with each file representing the exact SQL that will run, eliminating variations that might occur from regenerating plans in different contexts.
-
Strict change control and audit requirements - When you need an explicit, version-controlled record of every schema change for compliance, security audits, or change management processes, versioned migrations provide a clear paper trail. Each migration file serves as a documented artifact of what changed, when, and by whom.