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:
| SqlPackage | Atlas |
|---|---|
| SQL project | Schema as code in SQL, HCL, C#, or other formats |
/Action:Extract | atlas schema inspect --export |
/Action:Script, /Action:DeployReport | atlas schema apply --dry-run, atlas schema plan, or versioned migrations |
/Action:Publish | atlas schema apply |
BlockOnPossibleDataLoss | review = ERROR, which asks for approval before a change that loses data |
DropObjectsNotInSource | A diff policy skip block, which leaves drops out of the plan by object kind, such as drop_table = true |
| Pre- and post-deployment scripts | Declarative data management, pre and post-deployment hooks, and Data Scripts |
.dacpac | The 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.
- Single Schema
- Database (Multiple Schemas)
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:
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
DATABASE_URL names no schema, so Atlas covers every schema in the database, and the dev database names
none either. Configure an exporter, and point the env's desired state at the files it writes:
exporter "sql" "schema" {
path = "schema"
split_by = object
}
env "postgres" {
url = getenv("DATABASE_URL")
dev = "docker://postgres/18/dev"
schema {
src = "file://schema/main.sql"
}
export {
schema {
inspect = exporter.sql.schema
}
}
lint {
review = ERROR
}
}
Export the database:
atlas schema inspect --env postgres --export
Each schema gets its own directory, with a directory for each object type, and database objects such as extensions get a directory next to them. A typical layout:
schema
├── extensions
│ └── citext.sql
├── schemas
│ ├── auth
│ │ ├── auth.sql
│ │ └── tables
│ │ └── sessions.sql
│ └── public
│ ├── public.sql
│ ├── tables
│ │ ├── profiles.sql
│ │ └── users.sql
│ └── functions
│ └── user_age.sql
└── 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.
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:
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:
- Azure Database for PostgreSQL
- Cloud SQL for PostgreSQL
- Amazon RDS
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.
data "gcp_cloudsql_token" "db" {}
env "cloudsql" {
url = "postgres://iamuser:${urlescape(data.gcp_cloudsql_token.db)}@10.0.0.1:5432/mydb?sslmode=require"
}
The gcp_cloudsql_token data source generates a
token for IAM database authentication.
locals {
user = "iamuser"
endpoint = "hostname-of-db.example9y7k.us-east-1.rds.amazonaws.com:5432"
}
data "aws_rds_token" "db" {
region = "us-east-1"
endpoint = local.endpoint
username = local.user
}
env "rds" {
url = "postgres://${local.user}:${urlescape(data.aws_rds_token.db)}@${local.endpoint}/postgres"
}
The aws_rds_token data source generates a token for
IAM database authentication.
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:
- Claude Code
- OpenAI Codex
- Cursor
- GitHub Copilot
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
Install the skills from a terminal. Run it from the repository root. It installs every Atlas skill in .agents/skills/.
npx skills add ariga/atlas -a codex -y
Then start the onboarding skill in OpenAI Codex. See OpenAI Codex with Atlas for the other ways to use the skills.
$atlas-onboard
Install the skills from a terminal. Run it from the repository root. It installs every Atlas skill in .agents/skills/.
npx skills add ariga/atlas -a cursor -y
Then start the onboarding skill in Cursor. See Cursor with Atlas for the other ways to use the skills.
/atlas-onboard
Install the skills from a terminal. Run it from the repository root. It installs every Atlas skill in .agents/skills/.
npx skills add ariga/atlas -a github-copilot -y
Then start the onboarding skill in GitHub Copilot. See GitHub Copilot with Atlas for the other ways to use the skills.
/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.