Skip to main content

Policy as Code for Database Migrations

· 8 min read
Noa Rogoszinski
Noa Rogoszinski
DevRel Engineer

As AI agents become staples in modern development workflows, automating everything from code reviews to complex schema migrations, the industry is hitting a critical inflection point. While the productivity gains are undeniable, the risks have become equally prominent. We are now seeing a recurring cycle of horror stories where unchecked agents inadvertently wipe production databases or trigger catastrophic outages.

The momentum of AI integration isn't slowing down, but the margin for error has vanished. To keep pace without compromising integrity, engineering teams must move beyond blind trust and implement robust safeguards to defend their infrastructure against "rogue" AI behavior.

To truly neutralize the risk of rogue AI, teams must pivot to Policy as Code. By codifying your governance directly into CI/CD pipelines, you shift from hoping for compliance to guaranteeing it, ensuring that every schema change is programmatically validated before it ever touches production.

X posts about destructive changes made by AI agents

The Limits of Manual Governance

In a traditional schema change, the safety of your data hinges on a manual review process: a developer reads the migration, mentally scans an informal checklist, and hopes to catch that one risky ALTER or stray DROP before it hits production. This requires engineers to either memorize shifting standards or hunt down documentation that may already be out of date.

While this may suffice for small teams, it fails to scale for two primary reasons:

  1. Complexity Creep: As infrastructure grows, rule sets expand faster than developers can internalize them. For a new hire, the sheer volume of unwritten rules can be an overwhelming barrier to shipping code safely.

  2. The Friction Cycle: Manually auditing every rule against every schema change is inherently tedious. When a safety checklist feels like a bottleneck, it creates friction. Friction invites shortcuts, and shortcuts inevitably invite incidents.

When correctness depends on humans repeating the same manual steps under the pressure of a release cycle, variance is the only certainty. Policy as Code transforms that human variance into programmatic consistency.

What is Policy as Code?

When you add guidelines to a cursorrules file or an AI agent’s skills list, you are providing plaintext instructions that an agent may or may not follow. This practice is a suggestion, not a guardrail. Developers have seen time and time again how AI agents actively ignore direct instructions and cause major incidents.

True Policy as Code means defining your standards in a machine-readable format that a deterministic tool consumes and enforces. Unlike an AI agent, a CI pipeline cannot be distracted. By codifying your governance, you move from a trust-based model to a programmatic guarantee: if a change violates the code, it simply cannot pass.

Automating Database Governance

Atlas provides a comprehensive suite of built-in analyzers designed to validate your schema definitions, migration files, and migration plans both on local machines and within CI/CD pipelines. These checks specifically target the usual suspects behind production failures, including destructive changes and backward-incompatible changes.

Destructive Changes

Certain migrations are inherently irreversible. Dropping a column, for example, erases data instantly with no option of an "undo" button. While these actions are sometimes necessary for deprecating legacy features or post-migration cleanup, they should never occur by accident.

Effective policy is about making these high-stakes operations visible and gateable. In more rigorous environments, Atlas can be configured to enforce destructive change checks even if a nolint directive is present, ensuring no one bypasses safety protocols.

atlas.hcl
lint {
destructive {
force = true
}
}

Conversely, for teams that use a soft-delete strategy, such as renaming objects to a "pending delete" namespace, Atlas allows you to define allowlisted patterns. This ensures that intentional, staged deletions can proceed smoothly without compromising your safeguards.

atlas.hcl
lint {
destructive {
// Allow dropping tables when the name starts with "drop_"
allow_table {
match = "drop_.+"
}
}
}

By leveraging diff policy, you can instruct Atlas to automatically skip destructive changes, enforce concurrent index creation, and mandate specific execution patterns. Defining your policy as code allows you to build a governance framework tailored to your team’s specific requirements.

Backward Incompatible Changes

Even a seemingly trivial change, such as renaming a column from email_address to email, can trigger a massive outage if active services are still running code that references the old identifier. The moment the migration executes, production traffic hits queries that can no longer resolve the column. The schema and the application have diverged, turning a routine deployment into a high-stakes failure.

By encoding this risk into your policy, you ensure that these shifts are flagged the moment the contract is violated and long before a customer encounters an error.

atlas.hcl
lint {
incompatible {
error = true
}
}

Naming Conventions

Over time, informal naming turns into a museum of styles: UserId in one table, user_id in another, indexes called idx1 next to indexes that follow a strict prefix rule. Even with agreed-upon naming conventions, it's not unreasonable or uncommon for a developer to miss a spot.

Beyond the built-in safety checks, Atlas allows you to codify your internal standards into custom rules. By defining specific patterns for indexes, foreign keys, tables, and columns, you can ensure that every new schema change aligns with your team’s naming standards. These rules are automatically enforced by Atlas’s naming analyzer, transforming your style guide from a static document into a living, programmatic gatekeeper.

atlas.hcl
lint {
naming {
match = "^[a-z]+$"
message = "must be lowercase"
index {
match = "^[a-z]+_idx$"
message = "must be lowercase and end with _idx"
}
// schema, table, column, foreign_key and checks are also supported
}
}

Approval Policy

Policy as Code isn't just for defining schema changes, it can also be used to govern the transition from development to production.

In a declarative workflow, Atlas pre-plans your migrations during the CI phase. Atlas generates a concrete execution plan, stores it in the Atlas Registry, and comments it directly on your Pull Request. This makes the underlying SQL transparent and reviewable. Once the PR is merged, the change is marked "approved" and applied automatically.

However, real-world environments aren't always perfect. Databases can "drift" due to manual hotfixes, or an environment might miss a release, leaving the schema in an unexpected state. In these scenarios, the pre-approved plan in the registry may no longer match the reality of the database.

This is where apply-time policy acts as your final safety net. By default, atlas schema apply waits for a human to verify the SQL before execution. For teams pursuing full automation, Atlas can delegate this decision to a review policy:

  • Error-Only: Automated application proceeds unless the analyzers report errors or high-risk events.
  • Warning-Sensitive: If any warnings or risks are detected, the process pauses and mandates explicit approval.
  • Strict: Every change requires a manual sign-off unless it exactly matches a pre-planned and approved CI artifact.
atlas.hcl
lint {
review = ERROR // ERROR | WARNING | ALWAYS
destructive {
error = false
}
}

Beyond the Schema

With the recent introduction of Database Security as Code, Atlas extends its governance capabilities to users, roles, and permissions. By moving access control into declarative schema files, security configurations are transformed into reviewable artifacts that are gated and validated within your CI/CD pipeline.

Database Ownership Policy

For GitHub users, Atlas allows you to enforce ownership policies, bringing repository-style authorization directly to your database objects.

While traditional file-level controls like CODEOWNERS on GitHub protect source code, they are blind to the nuances of a database schema. Ownership policy closes this gap by treating the schema objects themselves as the units of authorization.

By defining allow and deny rules for specific resources, you can map database permissions directly to GitHub users or teams. When Atlas plans or lints a change, it programmatically verifies if the author is permitted to modify every affected object. If the change violates these boundaries, the CI pipeline fails immediately.

In practice, this functions as CODEOWNERS for your database. This is an essential safeguard for modern workflows where a helper script or AI assistant might generate a schema diff that accidentally touches a sensitive core table.

Ownership policy violation reported as a GitHub PR comment

Final Thoughts

Governance tooling is only useful when it integrates seamlessly with your team's actual delivery cycle. Adopting Policy as Code for your schema is a practical way to make safe and consistent schema changes operational rather than aspirational.

As your product and data infrastructure scale, you will find yourself wanting to enforce more and more policies. Atlas ensures that you can do so without adding more friction to the migration review process. You don’t have to choose between moving fast and remaining secure. With the right automated guardrails, you can finally do both.

Get Started

Schedule a demo to learn more about how you can use Atlas to automate database governance at scale with Policy as Code.

Further Reading