Skip to main content

SQL Server to PostgreSQL: The SqlPackage Workflow with Atlas

SqlPackage deploys a DACPAC: it compares the schema in the package to a database and runs the script that brings the database to it. It works only with the SQL Server family, such as SQL Server, Azure SQL Database, and Azure SQL Managed Instance.

Atlas works the same way for PostgreSQL. You keep the desired schema as code, and Atlas plans and applies the changes that bring each database to it. Atlas also manages SQL Server, MySQL, Oracle, Snowflake, and other databases, so one tool covers both engines while you move.

How SqlPackage Maps to Atlas​

Each SqlPackage action and publish property you rely on has a counterpart in Atlas:

SqlPackageAtlas
SQL projectSchema as code in SQL, HCL, C#, or other formats
/Action:Extractatlas schema inspect --export
/Action:Script, /Action:DeployReportatlas schema apply --dry-run, atlas schema plan, or versioned migrations
/Action:Publishatlas schema apply
BlockOnPossibleDataLossreview = ERROR, which asks for approval before a change that loses data
DropObjectsNotInSourceA diff policy skip block, which leaves drops out of the plan by object kind, such as drop_table = true
Pre- and post-deployment scriptsDeclarative data management, pre and post-deployment hooks, and Data Scripts
.dacpacThe schema or a migration directory, pushed to the Atlas Registry with atlas schema push or atlas migrate push

One default differs: SqlPackage keeps objects that are missing from the .dacpac, while Atlas plans drops for them unless the diff policy skips them.

Export the Schema as Code​

A SQL project keeps one file per object. Atlas exports a database the same way, either a single schema or the whole database with all its schemas. The URL decides which: DATABASE_URL and the dev database must have the same scope. The dev database also needs every extension the schema uses: for vector, use the pgvector image, as in docker://pgvector/pg18/dev.

DATABASE_URL names the schema with search_path=public, and so does the dev database. Configure an exporter, and point the env's desired state at the files it writes:

atlas.hcl
exporter "sql" "schema" {
path = "schema"
split_by = object
}

env "postgres" {
url = getenv("DATABASE_URL")
dev = "docker://postgres/18/dev?search_path=public"
schema {
src = "file://schema/main.sql"
}
export {
schema {
inspect = exporter.sql.schema
}
}
lint {
review = ERROR
}
}

Export the schema:

atlas schema inspect --env postgres --export

Each object gets its own file, in a directory for its type. A typical layout:

schema
├── tables
│ ├── profiles.sql
│ └── users.sql
├── views
│ └── active_users.sql
├── functions
│ └── user_age.sql
├── types
└── main.sql

Procedures, triggers, and the other object types get a directory of their own the same way. Each file imports the files it depends on with atlas:import directives, and main.sql imports all of them, so the env reads the whole schema from it. For a new database, write the schema in schema/main.sql yourself. See Export Database Schema to Code for the other exporter options.

Plan and Apply Changes​

Edit the files, then review the plan and apply it:

atlas schema apply --env postgres --dry-run
atlas schema apply --env postgres

--dry-run prints the planned statements without running them. With review = ERROR in the env's lint block, schema apply applies changes that pass linting, and asks for approval when the destructive analyzer reports an error.

Dev, QA, and production

In CI, the same desired state is applied to the database of each environment. To promote to production exactly the schema version that ran in QA, see Environment Promotion.

Manage Both Engines from One Project​

Add an exporter and an env for SQL Server next to the PostgreSQL ones, so both databases deploy through Atlas during the move:

atlas.hcl
exporter "sql" "sqlserver" {
path = "sqlserver"
split_by = object
}

env "sqlserver" {
url = getenv("SQLSERVER_URL")
dev = "docker://sqlserver/2022-latest/dev?mode=schema"
schema {
src = "file://sqlserver/main.sql"
}
export {
schema {
inspect = exporter.sql.sqlserver
}
}
}

SQLSERVER_URL names one schema with mode=schema, the scope of its dev database. Run atlas schema inspect --env sqlserver --export to write the SQL Server schema to sqlserver/. See the SQL Server guide to get started.

Managed PostgreSQL​

Atlas connects to managed PostgreSQL services with a postgres:// URL. On Azure, Cloud SQL, and Amazon RDS, a data source generates a short-lived token, and the URL uses it as the password:

atlas.hcl
data "azure_db_token" "db" {}

env "azure" {
url = urluserinfo(
"postgres://myserver.postgres.database.azure.com:5432/postgres?sslmode=require",
"app@contoso.onmicrosoft.com",
data.azure_db_token.db,
)
}

The azure_db_token data source requests a Microsoft Entra token for the user.

These URLs cover the whole database. To manage a single schema, add search_path=public, as in Export the Schema as Code. AlloyDB for PostgreSQL connects through the AlloyDB Auth Proxy, which accepts standard PostgreSQL connections.

Get Started with a Coding Agent​

With a coding agent, install the atlas-onboard skill and the database skills for both engines, atlas-mssql and atlas-postgres. One command installs them, with the other Atlas skills:

Install the skills from a terminal. It installs the Atlas plugin, with all its skills, for every project on this machine.

claude plugin marketplace add ariga/atlas
claude plugin install atlas@ariga

Then start the onboarding skill in Claude Code. See Claude Code with Atlas for the other ways to use the skills.

/atlas:atlas-onboard

atlas-onboard takes your repository from a first scan to production, one stage at a time, and the database skills hold the rules of each engine.

Next Steps​