Azure Migration

Migrate to Azure SQL with Database Migration Service: Online vs Offline Cutover Walkthrough

You have a SQL Server database — say 80 GB, doing real transactions all day — and a deadline to land it on Azure SQL with the shortest possible outage. The naive plan is “back it up, copy the .bak to Azure, restore it.” Then you discover Azure SQL Database has no RESTORE DATABASE you can drive, the copy takes two hours over your link, and your business owner says the app cannot be read-only for two hours. This is the exact gap the Azure Database Migration Service (DMS) plus the Azure SQL Migration extension for Azure Data Studio is built to close: a managed, repeatable migration engine that moves your schema and data, and — when you choose the online mode — keeps syncing the source’s changes until you decide to cut over, collapsing the outage to a few minutes.

The single decision that shapes everything else is offline versus online. An offline migration copies the database once: from the moment it starts, writes to the source are not carried across, so you take the source offline (or accept that anything written after the snapshot is lost) until it finishes and you point the app at Azure. An online migration does the same initial copy but then continuously replicates ongoing changes using log backups, so the source stays live; you flip the application to Azure during a brief, planned cutover when ready. Offline is simpler and cheaper but trades a longer outage; online buys a near-zero-downtime cutover at the cost of more setup, a continuously running sync, and stricter prerequisites. Pick wrong and you get either an outage the business won’t accept, or weeks of online-migration complexity you didn’t need for a database that could have gone offline at 2 a.m. on a Sunday.

This is the implementation walkthrough. We define DMS and the migration extension, lay out the offline-vs-online trade-off as a decision you can defend, then do the migration end to end — prerequisites, numbered steps in both the portal and az CLI, a Bicep definition, expected output, validation queries, and teardown — for SQL Server → Azure SQL Managed Instance (the path that supports both modes cleanly), with notes throughout for the Azure SQL Database target. Every option that matters is a table you can scan mid-project, because at cutover you don’t want to be reading paragraphs.

What problem this solves

Moving a production database is the part of a cloud migration that keeps people up at night, because the database is stateful and the business cannot lose a row. The compute tier you can redeploy; the data you get exactly one chance to move correctly. Doing it by hand — backup, copy, restore, then somehow reconcile every transaction that happened during the copy — is error-prone and, for Azure SQL Database, not even possible the obvious way (you cannot restore an arbitrary .bak into a logical Azure SQL Database; the restore surface differs from a box you own).

What breaks without a managed tool: teams write bespoke scripts that copy tables with bcp or SSIS, miss a foreign-key ordering, drop an index, or — the expensive one — schedule a “quick” maintenance window that turns into six hours when the copy is slower than the test run and there is no way to resume. Or they hand-build transactional replication and discover at 1 a.m. that a schema mismatch silently stopped the distributor. DMS replaces all of that with a managed service that handles the schema, the data copy, the continuous sync (online mode), retry and resumability, and a guided cutover, with progress you can watch.

Who hits this: anyone retiring on-premises or VM-hosted SQL Server in favour of Azure SQL — lift-and-shift to Azure SQL Managed Instance (closest to box SQL Server: SQL Agent, cross-database queries, CLR), or modernisation to Azure SQL Database (the managed single-database/elastic-pool PaaS). It bites hardest on databases too large to copy inside an acceptable window, ones that must stay writable until the second of cutover, and teams who have never run a controlled cutover. If you’re still choosing which migration tool, Choosing Your Migration Tool: Azure Migrate vs Site Recovery vs Database Migration Service is the upstream decision; this article assumes you’ve landed on DMS for the database tier.

Here is the whole field in one frame — the two modes, what each costs you, and when to reach for it:

Dimension Offline migration Online migration
What it does One-time copy of schema + data One-time copy, then continuous change sync
Source during migration Effectively read-only (changes after start are lost) Stays live and writable
Downtime Whole copy duration (minutes to hours) A short, planned cutover (minutes)
Setup complexity Lower Higher (log backups, network share, cutover step)
Cost DMS compute for the run only DMS compute for the whole sync window
Best for DBs that tolerate a maintenance window DBs that cannot afford a long outage
Risk if you misjudge Outage longer than the business accepts Weeks of complexity you didn’t need

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should know the basics of SQL Server (databases, full and transaction-log backups, the recovery model) and of Azure SQL — that Azure SQL Database is a single logical database (or elastic pool) with no OS or instance you manage, while Azure SQL Managed Instance (MI) is a near-100%-compatible SQL Server instance as a managed service. If either of those is fuzzy, Your First Azure SQL Database: Create, Configure Firewall Rules and Connect Securely covers the Database side and its connectivity model. You should be comfortable running az in Cloud Shell, reading JSON output, and have Azure Data Studio installed locally (the migration extension is the control surface for the actual migrations).

This sits in the Migration track, downstream of the tool-choice decision and of discovery. If you haven’t assessed the source databases for compatibility and right-sizing yet, Azure Migrate Discovery & Assessment: From Appliance Deployment to Your First Right-Sizing Report is the step before this one — the same migration extension runs the readiness/SKU-recommendation assessment that tells you which target SKU to provision. Once the data is in Azure SQL, day-two connectivity is covered by Troubleshooting Azure SQL Database: Connectivity, Timeouts, Throttling & Blocking, and choosing the compute tier by Azure SQL Database Purchasing Models: DTU vs vCore and How to Pick Without Overpaying.

A quick map of who owns which piece during a migration, so you call the right person fast:

Layer What lives here Who usually owns it What it can break
Source SQL Server Database, backups, recovery model DBA / app team Backups not full-recovery; no log chain (online)
Backup location Network share (SMB) or Azure Blob + SAS DBA / infra DMS can’t read backups → migration stalls
Self-hosted IR The agent that reads on-prem backups Whoever runs DMS Offline IR → “integration runtime unavailable”
DMS instance The managed migration engine Migration lead Wrong region/SKU → slow or failed runs
Network path On-prem ↔ Azure (VPN/ER), firewall Network team Blocked ports → no connectivity to source/target
Target Azure SQL MI or Database, sizing, auth DBA / platform Undersized → throttled copy; auth → login failed

Core concepts

A few mental models make every later step obvious.

DMS is the engine; the extension is the cockpit. The Azure Database Migration Service is a managed Azure resource — a fleet of migration workers Microsoft runs for you. You almost never click around inside DMS itself; you drive migrations through the Azure SQL Migration extension for Azure Data Studio, which assesses the source, recommends a target SKU, kicks off the migration against your DMS instance, and shows live progress. An older “classic” DMS experience exists in the portal, but the current, supported path for SQL Server is DMS + the migration extension, and that is what this walkthrough uses.

The migration always starts with a backup. DMS does not stream rows out of your running database with a magic pipe. For SQL Server sources it works from native backups — a full backup for the initial seed and, for online mode, the transaction-log backups that carry ongoing changes. You point DMS at where those backups live (an on-premises SMB network share, or Azure Blob Storage with a SAS token); it restores the full backup into the target and — online — keeps restoring log backups to stay caught up. This is why recovery model and backup hygiene matter so much: online migration requires the FULL recovery model and an unbroken log chain.

Offline copies once; online keeps replaying the log. In offline mode: restore the full backup (plus any differential/log backups up to the start) into the target, finish, done — anything written to the source after that point does not come across, so you stop writing before or at the moment you start. In online mode: restore the full backup, then continuously restore each new log backup as it appears, holding the target a short time behind the source, until you trigger cutover, which applies the final log backup and completes the migration so the target is fully consistent and you can repoint the app.

The self-hosted integration runtime bridges on-prem to Azure. When the backups live on an on-prem SMB share, DMS cannot reach them directly — it runs in Azure. The self-hosted integration runtime (SHIR) is a small agent you install on a Windows machine inside your network that DMS uses to read those backups and ship them up. If your backups live in Azure Blob instead, you don’t need a SHIR — DMS reads the blob directly. The SHIR is the most common “why is nothing happening” culprit in on-prem migrations.

Target choice changes what’s possible. Azure SQL Managed Instance supports both offline and online migrations and accepts native backups directly, making it the smooth lift-and-shift target. Azure SQL Database is supported too, but the data-movement mechanics differ (schema + data movement rather than native backup restore), the online/offline constraints are tighter, and some instance-level features (SQL Agent jobs, cross-database queries, linked servers) simply don’t exist on Database and must be re-platformed. Pick MI for minimal application change; pick Database when modernising and you can adapt — itself a sizing decision, of which Azure SQL Serverless Explained: Auto-Pause, Per-Second Billing and Right-Sizing Compute covers one Database compute option you might land on.

The vocabulary in one table

Pin down every moving part before the deep sections; the glossary repeats these for lookup.

Term One-line definition Where it lives Why it matters
DMS Managed Azure migration engine Azure resource (region) Does the actual data movement
Migration extension Azure Data Studio plug-in driving DMS Your laptop / jump box The cockpit; assess + start + monitor
Offline migration One-time copy, no ongoing sync DMS run Simpler; outage = copy duration
Online migration Copy + continuous log replay DMS run Near-zero-downtime cutover
Cutover The step that finalises an online migration Triggered by you Applies last log; target goes consistent
Full backup Complete DB backup (initial seed) Share / Blob Required to start any migration
Log backup Transaction-log backup (changes) Share / Blob Carries ongoing changes (online)
Recovery model FULL / BULK_LOGGED / SIMPLE Source DB property Online needs FULL + unbroken chain
Self-hosted IR Agent reading on-prem backups A Windows box on-prem Bridges on-prem ↔ DMS
Target Azure SQL MI or Database Azure Determines mode support + features
SAS token Scoped, time-bound Blob access Storage account Lets DMS read backups in Blob

Offline vs online: making the call

This is the decision the rest of the project hangs on. Both modes do the same initial copy; the difference is whether the source keeps serving writes while you migrate. Don’t reach for online reflexively — it is more moving parts, a longer-running (and more expensive) DMS engagement, and stricter prerequisites. Reach for it only when the offline outage is genuinely unacceptable.

The honest way to decide is to estimate the offline outage first: roughly the time to copy the full backup into the target plus validation, which scales with database size and path throughput. If that fits a maintenance window the business will grant, offline wins on simplicity. If it doesn’t — large database, modest link, or a business that cannot go read-only that long — online earns its complexity by shrinking the outage to just the cutover.

Decision factor Lean offline when… Lean online when…
Outage tolerance A maintenance window (e.g. a weekend night) is acceptable The app must stay writable until the second of cutover
Database size Small/medium; full copy fits the window Large enough that the copy exceeds the window
Change rate Low; little lost if you freeze writes High; freezing writes for the copy is unacceptable
Network throughput Fast enough that the copy is short Modest link makes the initial copy long
Target MI or Database; both fine offline MI (cleanest online); check Database constraints
Team experience First migration; keep it simple Comfortable running a controlled cutover
Number of DBs A handful you can window together Many, staggered, can’t all freeze at once

A few traps to avoid:

Trap Why it bites The right framing
“Online means zero downtime” Cutover is still a brief, real outage Online means minimal downtime — plan the cutover window
“Offline is always faster overall” The run is, but the outage may be huge Optimise for outage, not total wall-clock
“We’ll decide at cutover” Online needs FULL recovery + log chain from the start Decide before you take the first backup
“Bigger DMS SKU fixes a slow copy” Often the bottleneck is the network or source IO Profile the bottleneck before upsizing
“Online has no upper time limit” Long sync windows cost money and accumulate risk Treat the sync window as short and deliberate

The practical rule: default to offline; choose online only when the offline outage estimate exceeds what the business will accept. Most single-database migrations under a few hundred GB on a decent link go offline at 2 a.m. and nobody notices. Online is for the database where that math fails.

Prerequisites for both modes

Get these right before you touch the migration UI; a missed prerequisite is the number-one cause of a migration that won’t start or stalls at 0%. The list differs slightly by mode and by where backups live.

Source SQL Server prerequisites

Requirement Offline Online How to check / set
Supported source version Yes Yes SELECT @@VERSION; — confirm a supported SQL Server build
Recovery model Any (FULL recommended) FULL (mandatory) SELECT name, recovery_model_desc FROM sys.databases;
Unbroken log chain Not required Required Don’t take out-of-band COPY_ONLY=false log backups elsewhere
A recent full backup Required Required BACKUP DATABASE ... TO DISK = N'...'
sysadmin (or equivalent) on source Yes Yes For the account DMS/SHIR uses to read backups/metadata
Backups not encrypted with an unknown key Yes Yes TDE-encrypted backups need the cert on the target

Set FULL recovery and take a fresh full backup before an online migration:

-- On the source SQL Server: ensure FULL recovery, then take a full backup
ALTER DATABASE [SalesDB] SET RECOVERY FULL;
BACKUP DATABASE [SalesDB]
  TO DISK = N'\\fileserver\dmsbackups\SalesDB_full.bak'
  WITH INIT, COMPRESSION, FORMAT, STATS = 5;

Backup-location prerequisites

DMS reads backups from one of two places — choose one and meet its requirements:

Backup location When to use What DMS needs Common failure
On-prem SMB network share Source is on-prem; backups already land on a share A self-hosted IR with read access to the share (network credentials) Share permissions / SHIR offline
Azure Blob Storage container Source can write backups to Blob, or you’re already in Azure A container + SAS token (read/list) scoped to it Expired/over-scoped SAS; wrong container

For an on-prem share, grant the account the SHIR runs under read access to the folder; for Blob, generate a least-privilege SAS:

# Generate a read+list SAS on the backups container (valid 7 days)
EXPIRY=$(date -u -d "+7 days" '+%Y-%m-%dT%H:%MZ' 2>/dev/null || date -u -v+7d '+%Y-%m-%dT%H:%MZ')
az storage container generate-sas \
  --account-name stdmsbackups --name dmsbackups \
  --permissions rl --expiry "$EXPIRY" --https-only -o tsv

Networking prerequisites

Path Requirement Why
DMS → target (MI/Database) Network line of sight (VNet for MI; firewall rule for Database) DMS must connect to restore/write
DMS / SHIR → source backups Read access to the share or Blob To pull the full + log backups
On-prem ↔ Azure (if on-prem source) VPN or ExpressRoute, and DNS resolution Private connectivity for the data path
Outbound from SHIR machine HTTPS (443) to Azure endpoints SHIR registers and ships data over 443

For MI the DMS instance is typically deployed into (or peered with) the MI’s VNet; for Database you ensure the server firewall permits the DMS service. Connectivity to the target is exactly the model covered in Cannot Connect to Azure SQL: Fixing Login Failed, Firewall Blocks and Auth Mode Mismatches — the same firewall and auth rules that trip an app trip the migration.

Permissions & target prerequisites

Requirement Detail Verify
Source login for DMS sysadmin (reads backups/metadata) Test the login can RESTORE HEADERONLY the backup
Target login for DMS Sufficient rights to create/restore the DB on MI, or write schema+data on Database Test it can create objects on the target
Target provisioned & sized MI or Database created at a SKU that holds the data and sustains the copy Compare DB size to target storage; check vCores
Azure RBAC to run DMS Contributor (or a custom role) on the DMS resource group az role assignment list for your principal
No name collision on target Target DB name not already present (MI) SELECT name FROM sys.databases; on the target

Right-size the target before migrating — a too-small target throttles the copy and may fail it. The extension’s assessment + SKU recommendation (the same one in Azure Migrate Discovery & Assessment: From Appliance Deployment to Your First Right-Sizing Report) gives a defensible starting SKU from the source’s actual resource use rather than a guess.

Architecture at a glance

Walk the path the data takes left to right. Your source SQL Server writes native backups — a full backup to seed, plus, for online migrations, a stream of transaction-log backups — into a backup location, either an on-premises SMB share or an Azure Blob container fronted by a SAS token. The Azure Database Migration Service is the managed engine in Azure that does the work; when the backups are on-premises it reaches them through a self-hosted integration runtime installed inside your network (when they’re in Blob, DMS reads the container directly and no IR is needed). You don’t operate DMS by hand — the Azure SQL Migration extension in Azure Data Studio is the cockpit that assesses the source, recommends a target SKU, starts the migration against DMS, and shows live progress.

From the backup location DMS restores the full backup into the targetAzure SQL Managed Instance for a clean lift-and-shift, or Azure SQL Database when modernising. In offline mode that single restore is the migration: it finishes and you repoint the app. In online mode DMS keeps restoring each new log backup to hold the target a short time behind the source, until you trigger cutover, which applies the final log backup and makes the target fully consistent so you can flip the application over with a near-zero outage. The numbered badges below mark the four places this most commonly goes wrong: the source recovery model / log chain, the backup location’s permissions, the integration runtime’s availability, and the cutover step itself.

Left-to-right Azure Database Migration Service architecture: a source SQL Server emits full and transaction-log native backups into a backup location (on-prem SMB share or Azure Blob with a SAS token); a self-hosted integration runtime bridges on-prem backups to the Azure Database Migration Service engine, which is driven by the Azure SQL Migration extension in Azure Data Studio; DMS restores the full backup and, for online migrations, replays log backups into the target Azure SQL Managed Instance or Azure SQL Database until a cutover finalises the migration. Numbered badges mark the four common failure points: source recovery model and log chain, backup-location permissions, integration-runtime availability, and the cutover step.

Real-world scenario

Northwind Retail runs its order-management system on a single on-premises SQL Server 2019 instance hosting SalesDB — 140 GB, FULL recovery, around 1,200 transactions per minute during business hours, used by a warehouse floor that runs three shifts. The mandate from the migration steering group was blunt: land SalesDB on Azure SQL Managed Instance (the app uses SQL Agent jobs and a couple of cross-database queries, so Database was off the table), and the floor cannot be read-only for more than ten minutes, ever, including at night — there is no “quiet window” with three shifts.

The team’s first instinct was offline at 3 a.m. They estimated the copy: 140 GB across a 200 Mbps site-to-site VPN, restored into a freshly provisioned MI, plus validation — well over an hour of read-only. That blew the ten-minute ceiling immediately, so offline was out. They went online.

Setup took a day. They confirmed SalesDB was in FULL recovery with a clean log chain — a rogue weekly COPY_ONLY=false log backup job on a monitoring server had to be disabled first, as it would have broken the chain DMS depends on. They provisioned MI at a General Purpose, 8 vCore tier sized from the migration extension’s SKU recommendation, deployed a self-hosted integration runtime on a Windows jump box with read access to the existing \\fileserver\dmsbackups share, and created a DMS instance in the MI’s region. The pre-migration assessment flagged two deprecated sp_ calls that MI handles fine — no blockers.

They started the online migration on a Tuesday afternoon while the floor kept working. DMS restored the 140 GB full backup over about ninety minutes, then settled into continuously replaying the log backups the source’s existing 15-minute schedule produced. For three days the migration sat in “Ready to cut over”, the target trailing by under a log-backup interval, costing only the running DMS engine. The cutover was scheduled for Saturday 2 a.m. — not from downtime fear (the cutover itself is minutes) but to give the floor a calm change window. At cutover they stopped the app, let DMS apply the final log backup (a few seconds of change), confirmed consistency, repointed the connection string to the MI, and ran smoke tests. Total floor downtime: under seven minutes.

What saved them: midway through the sync window, the SHIR machine was rebooted by an unrelated patch job and the migration paused with “integration runtime unavailable.” Because they were watching progress and had an alert on the SHIR service, they caught it in minutes; the SHIR auto-reconnected on boot and DMS resumed where it left off — no re-seed. Offline, that reboot mid-copy would have meant restarting the whole 140 GB copy. Online’s resumability turned a scary moment into a footnote. They kept the source read-only for a week as a rollback net, then decommissioned it.

Advantages and disadvantages

DMS-driven migration is the right default for SQL Server → Azure SQL, but it is not free of trade-offs. The two-column view first, then where each line actually matters:

Advantages Disadvantages
Managed engine — no bespoke copy scripts to maintain Still requires correct backup hygiene and networking
Online mode gives near-zero-downtime cutover Online adds real setup complexity and a running cost
Resumable — survives transient network/IR blips On-prem path needs a self-hosted IR you must keep healthy
Handles schema + data + (online) continuous sync Some MI/Database feature gaps still need re-platforming
Guided assessment + SKU recommendation built in Target must be pre-provisioned and correctly sized
Repeatable across many databases Large DBs still bound by network/source IO throughput
Free service tier covers most one-off migrations Long online sync windows accrue cost and risk

The advantages that matter most are resumability and the online cutover. Resumability makes a real-world migration survivable: networks blip, machines reboot, and a tool that re-seeds 140 GB on every hiccup is unusable for anything large. The online cutover is the entire reason the mode exists — but if the business can grant a maintenance window, you may never need it and offline’s simplicity wins.

The disadvantages that bite hardest are the self-hosted IR (a stateful agent you must keep running, patched and connected — the most common operational failure in on-prem migrations, easy to forget until it goes offline at the wrong moment) and feature gaps on the target. The latter is a planning problem: if your source uses SQL Agent, Service Broker, cross-database queries or linked servers and you target Azure SQL Database, those don’t exist and the migration tool won’t invent them — you re-platform or target MI. Plan that on the whiteboard, not at cutover.

Hands-on lab

This is the centrepiece. We will migrate a database to Azure SQL Managed Instance end to end: provision the target and DMS, run an offline migration first (simpler, proves the pipeline), then convert to an online migration with a controlled cutover. Each step gives the portal action, the equivalent az CLI, the expected output, and a validation check; a Bicep definition for the DMS resource lets you codify it. Teardown is at the end — do it to stop billing.

Cost note: the DMS free SKU covers most one-off migrations, but the target Managed Instance bills from the moment it exists. Provision the MI as late as you can and delete it in teardown if this is only a drill.

Lab prerequisites checklist

# Prerequisite Verify with
1 A source SQL Server with a test DB in FULL recovery SELECT recovery_model_desc FROM sys.databases WHERE name='SalesDB';
2 A full backup of that DB on a reachable location (share or Blob) RESTORE HEADERONLY FROM DISK = N'...' returns one row
3 Azure CLI logged in, correct subscription selected az account show -o table
4 Azure Data Studio + Azure SQL Migration extension installed Extensions pane shows “Azure SQL Migration”
5 Resource group + region chosen (target and DMS co-located) az group show -n rg-dbmig
6 (On-prem source) a Windows host for the self-hosted IR Host can reach the share and HTTPS-out to Azure

Step 1 — Set variables and create the resource group

Define everything once so the rest is copy-paste. Pick a region close to your source.

# Names and location for the whole lab
RG=rg-dbmig
LOC=centralindia
DMS_NAME=dms-salesdb-mig
MI_NAME=mi-sales-prod          # Managed Instance name (must be globally unique)
SRC_DB=SalesDB
TGT_DB=SalesDB

# Create the resource group
az group create --name $RG --location $LOC -o table

Expected output: a table row showing rg-dbmig with ProvisioningState = Succeeded.

Validation: az group show -n $RG --query properties.provisioningState -o tsv returns Succeeded.

Step 2 — Provision the target (Azure SQL Managed Instance)

The target must exist and be sized before DMS can restore into it; MI takes a while to deploy, so start it early. Portal: Create a resource → Azure SQL → Managed Instance, choose General Purpose, set vCores/storage from your assessment, place it in a dedicated subnet of a VNet, set the admin login. CLI (MI requires a delegated subnet; this assumes the VNet/subnet exist — see Azure Private Endpoint vs Service Endpoint: Secure PaaS Access for the VNet model):

# Provision a General Purpose Managed Instance (deployment can take a while)
az sql mi create \
  --name $MI_NAME --resource-group $RG --location $LOC \
  --admin-user miadmin --admin-password '<StrongP@ssw0rd!>' \
  --subnet $(az network vnet subnet show -g $RG --vnet-name vnet-dbmig \
             --name snet-mi --query id -o tsv) \
  --capacity 8 --edition GeneralPurpose --family Gen5 \
  --storage 256GB --license-type LicenseIncluded -o table

Expected output: a long-running operation; on completion a table row for mi-sales-prod with State = Ready. MI provisioning commonly takes a substantial amount of time — do not block the lab on it; proceed to set up DMS in parallel.

Validation: az sql mi show -n $MI_NAME -g $RG --query state -o tsv returns Ready.

Targeting Azure SQL Database instead? Create a logical server and an empty database, and ensure the server firewall allows Azure services / the DMS path. The migration extension targets the empty database; DMS moves schema + data into it rather than restoring a native backup.

Step 3 — Create the Database Migration Service instance

DMS is the engine. Portal: Create a resource → Azure Database Migration Service → create, pick the resource group, region (same as the target), a name, and the pricing tier (free suits one-off migrations). CLI — the modern SQL flow uses the SQL-specific service kind:

# Register the provider once per subscription
az provider register --namespace Microsoft.DataMigration

# Create a DMS instance for the SQL migration flow
az datamigration sql-service create \
  --resource-group $RG \
  --sql-migration-service-name $DMS_NAME \
  --location $LOC -o table

Expected output: a JSON/table result showing the service dms-salesdb-mig with provisioningState = Succeeded.

Validation:

az datamigration sql-service show \
  --resource-group $RG --sql-migration-service-name $DMS_NAME \
  --query provisioningState -o tsv   # → Succeeded

Step 4 — (On-prem source only) install and register the self-hosted IR

If your backups are on an on-prem SMB share, DMS reaches them through a self-hosted integration runtime. Skip this step entirely if your backups are in Azure Blob. Portal/extension: the wizard offers to set up the IR and shows the authentication keys; download the IR installer onto your Windows host, install it, and paste a key to register it against this DMS. CLI — retrieve the IR auth keys:

# Get the integration-runtime authentication keys for this DMS
az datamigration sql-service list-auth-key \
  --resource-group $RG --sql-migration-service-name $DMS_NAME -o json

On the Windows host, install the Microsoft Integration Runtime, launch the configuration manager, and register it with authKey1 from the output. Grant the IR’s service account read on the backup share.

Expected output: the IR configuration manager shows “Connected” to the cloud service; the extension lists the node as online.

Validation:

# Monitor the registered IR node health
az datamigration sql-service list-integration-runtime-metric \
  --resource-group $RG --sql-migration-service-name $DMS_NAME -o table

A healthy node reports as available; an empty or unavailable result means the IR isn’t registered or is offline (see troubleshooting).

Step 5 — Assess the source and confirm readiness (in the extension)

Open Azure Data Studio, connect to the source, right-click the database → Azure SQL Migration → Migrate to Azure SQL. The extension runs an assessment: it flags feature/compatibility issues for your target (MI vs Database) and produces a SKU recommendation from the source’s actual CPU/memory/IO. Read the readiness report before going further — fix or accept each finding.

Assessment result Meaning What to do
Ready No blocking issues for the target Proceed
Ready with conditions Minor issues (deprecated syntax, warnings) Note them; usually safe on MI
Not ready A blocking incompatibility for this target Re-platform the feature, or choose MI over Database

Expected output: a readiness summary per database and a recommended target SKU.

Validation: every database you intend to migrate shows Ready or Ready with conditions — never start with Not ready.

Step 6 — Choose the mode and point DMS at the backups (OFFLINE first)

In the wizard, select the target MI, choose Offline mode, and tell DMS where the backups are: for an on-prem share, the share path plus the Windows credential and the self-hosted IR node; for Blob, the storage account/container and the SAS token. Provide the source connection (sysadmin) and the target connection (the MI admin).

The mode-vs-source-backup decision in one place:

You chose… Backups in… DMS needs Note
Offline On-prem SMB share SHIR + share path + Windows cred Restores the latest full (+ diffs/logs to start)
Offline Azure Blob Container + SAS (rl) No SHIR required
Online On-prem SMB share SHIR + share path + Windows cred Restores full, then keeps replaying logs
Online Azure Blob Container + SAS (rl) DMS reads new log backups as they land

CLI — start an offline migration to MI from Blob-hosted backups (the extension wraps this; the CLI shows the contract):

az datamigration sql-managed-instance create \
  --resource-group $RG \
  --managed-instance-name $MI_NAME \
  --target-db-name $TGT_DB \
  --migration-service "/subscriptions/<sub-id>/resourceGroups/$RG/providers/Microsoft.DataMigration/sqlMigrationServices/$DMS_NAME" \
  --source-location '{"AzureBlob":{"storageAccountResourceId":"<storage-id>","accountKey":"<key>","blobContainerName":"dmsbackups"}}' \
  --offline-configuration '{"offline":true}' \
  --source-sql-connection '{"dataSource":"sqlsrv-onprem","authentication":"SqlAuthentication","userName":"dmsuser","password":"<pwd>"}' \
  --scope "/subscriptions/<sub-id>/resourceGroups/$RG/providers/Microsoft.Sql/managedInstances/$MI_NAME" \
  -o table

Expected output: a migration object is created and its status moves into provisioning/restoring.

Validation:

az datamigration sql-managed-instance show \
  --resource-group $RG --managed-instance-name $MI_NAME \
  --target-db-name $TGT_DB \
  --query "properties.migrationStatus" -o tsv

Step 7 — Run the offline migration and watch progress

With offline selected, DMS restores the full backup (and any differential/log backups up to the start) into the MI, then completes. Watch progress in the extension’s monitoring view, or poll via CLI.

Migration status What it means Your action
Creating / Provisioning DMS is spinning up the migration Wait
InProgress (Restoring) Restoring the full backup into the target Wait; watch % / bytes
Succeeded (offline) Restore complete; DB is on the target Validate, then cut the app over
Failed An error stopped the migration Read the error; see troubleshooting
Canceled You (or the system) canceled it Restart after fixing the cause

Expected output: status progresses to Succeeded for an offline run once the restore finishes.

Validation — confirm the database is really there and complete. Connect to the MI and run:

-- On the target MI: confirm the DB exists, is online, and row counts match
SELECT name, state_desc, recovery_model_desc FROM sys.databases WHERE name = N'SalesDB';

-- Spot-check a known table's row count against the source
SELECT COUNT(*) AS order_rows FROM SalesDB.dbo.Orders;

Compare against the same query on the source at the migration start point. For offline, anything written to the source after the migration started won’t be present — expected, and exactly why you freeze writes before starting.

Step 8 — Convert to ONLINE: run continuous sync

Now the online path — identical through Step 5; at Step 6 you choose Online instead of Offline. DMS restores the full backup, then continuously restores each new transaction-log backup the source produces, holding the target a short time behind. It does not finish on its own — it sits in “Ready to cut over” until you act.

# Start an ONLINE migration (offline:false) — DMS keeps replaying log backups
az datamigration sql-managed-instance create \
  --resource-group $RG \
  --managed-instance-name $MI_NAME \
  --target-db-name ${TGT_DB}_online \
  --migration-service "/subscriptions/<sub-id>/resourceGroups/$RG/providers/Microsoft.DataMigration/sqlMigrationServices/$DMS_NAME" \
  --source-location '{"AzureBlob":{"storageAccountResourceId":"<storage-id>","accountKey":"<key>","blobContainerName":"dmsbackups"}}' \
  --offline-configuration '{"offline":false}' \
  --source-sql-connection '{"dataSource":"sqlsrv-onprem","authentication":"SqlAuthentication","userName":"dmsuser","password":"<pwd>"}' \
  --scope "/subscriptions/<sub-id>/resourceGroups/$RG/providers/Microsoft.Sql/managedInstances/$MI_NAME" \
  -o table

Expected output: the migration reaches a steady state and reports it is ready to cut over while continuing to apply logs.

Validation: the status reports ReadyForCutover (or the extension shows “Ready to cut over”) and the “logs restored / pending” indicator stays low. Keep the source’s log-backup job running on its normal schedule — DMS feeds on those backups.

Online sync indicator Healthy Unhealthy → action
Status ReadyForCutover InProgress stuck → check IR / backups
Pending log backups Low / draining Growing → DMS not keeping up; check throughput
Last restored log time Close to “now” Falling behind → investigate source IO / network
IR node Available Unavailable → restart SHIR (online resumes)

Step 9 — Perform the cutover

When the business window arrives and the target is caught up, cut over — a deliberate sequence you control. Extension: click “Start cutover”; it shows how many log backups are pending and waits for you to confirm. CLI:

# Trigger cutover: DMS applies the final log backup and finalises the target
az datamigration sql-managed-instance cutover \
  --resource-group $RG \
  --managed-instance-name $MI_NAME \
  --target-db-name ${TGT_DB}_online \
  --migration-operation-id "<operationId-from-show>"

The cutover runbook — do these in order:

# Cutover step Why Confirm
1 Stop the application’s writes to the source Freeze the source so no change is missed App in maintenance mode
2 Let the source’s final log backup be taken Captures the last transactions Log-backup job completed
3 Trigger cutover in DMS Applies the final log; finalises target Status → Succeeded
4 Validate the target (row counts, key tables) Prove consistency before repointing Counts match source
5 Repoint the app’s connection string to the MI Send traffic to Azure App connects to MI
6 Smoke-test the app Confirm real workflows work Critical paths pass
7 Keep source read-only as rollback for a window Safety net before decommission Source untouched

Expected output: after cutover, migration status is Succeeded and the target database is fully consistent and writable.

Validation: repeat the row-count and state_desc checks from Step 7; this time the counts include everything up to the cutover moment, because the final log backup carried the tail of the transaction log.

Step 10 — Provision DMS as code (Bicep)

For repeatable, reviewable infrastructure, define the DMS instance in Bicep. The SQL migration service is its own resource type:

@description('Location for the migration service')
param location string = resourceGroup().location

@description('Name of the Database Migration Service (SQL flow)')
param dmsName string = 'dms-salesdb-mig'

resource sqlMigrationService 'Microsoft.DataMigration/sqlMigrationServices@2023-07-15-preview' = {
  name: dmsName
  location: location
  properties: {}
}

output dmsResourceId string = sqlMigrationService.id

Deploy and confirm:

az deployment group create \
  --resource-group $RG \
  --template-file dms.bicep \
  --parameters dmsName=$DMS_NAME -o table

Expected output: provisioningState = Succeeded and the dmsResourceId output populated.

Validation: az datamigration sql-service show -g $RG --sql-migration-service-name $DMS_NAME --query provisioningState -o tsv returns Succeeded. (The migrations themselves — connections, backup locations, cutover — run through the extension or the data-plane CLI above; Bicep provisions only the engine.)

Step 11 — Teardown (stop billing)

Delete what you no longer need. The Managed Instance is the expensive item — remove it first if this was a drill; tidy DMS too.

# 1) Delete the migration(s) (data-plane), if still present
az datamigration sql-managed-instance delete \
  --resource-group $RG --managed-instance-name $MI_NAME \
  --target-db-name $TGT_DB --yes

# 2) Delete the DMS instance
az datamigration sql-service delete \
  --resource-group $RG --sql-migration-service-name $DMS_NAME --yes

# 3) Delete the Managed Instance (the costly resource)
az sql mi delete --name $MI_NAME --resource-group $RG --yes

# 4) If the whole RG was for this lab, delete it last
az group delete --name $RG --yes --no-wait

Expected output: each delete returns without error; az group delete runs asynchronously.

Validation: az resource list -g $RG -o table returns empty (or the RG is gone). Also unregister and uninstall the self-hosted IR on the on-prem host if you set one up — it keeps running otherwise.

Common mistakes & troubleshooting

The failure modes below actually stall real migrations. Each gives the symptom, root cause, the exact place to confirm it, and the fix.

# Symptom Root cause Confirm Fix
1 Online migration won’t start; “FULL recovery required” Source DB is in SIMPLE/BULK_LOGGED SELECT recovery_model_desc FROM sys.databases ALTER DATABASE SET RECOVERY FULL; then take a fresh full backup
2 Migration stuck at 0% / “integration runtime unavailable” SHIR offline, not registered, or no network out IR config manager / list-integration-runtime-metric Restart the IR service; re-register with auth key; open 443 outbound
3 “Access denied” reading the backup share IR service account lacks read on the share Try the path as that account from the IR host Grant read NTFS+share perms; supply correct Windows credential
4 DMS can’t read Blob backups SAS expired, wrong scope, or wrong container Re-test the SAS URL in a browser/az storage blob list Re-issue a read+list SAS scoped to the right container
5 Restore fails: “backup chain broken” (online) An out-of-band log backup broke the chain Audit log-backup jobs on the source Disable rogue COPY_ONLY=false log backups; re-seed from a new full
6 Restore fails: TDE-encrypted backup Target lacks the certificate to decrypt Backup is from a TDE database Restore/import the TDE cert on the target first
7 Migration fails: “database already exists” on target Target DB name collides SELECT name FROM sys.databases on target Drop/rename the target DB, or migrate to a new name
8 Copy is painfully slow / online falls behind Network or source IO is the bottleneck (not DMS SKU) Compare pending-logs trend vs link throughput Improve the path (ExpressRoute), throttle source load, or accept a longer window
9 Connectivity to target fails: “login failed” / timeout Firewall or auth on MI/Database blocks DMS Target’s firewall + auth blade Allow the DMS path; fix the login (see the SQL login article)
10 Cutover done, but app still hitting old server Connection string never repointed Check the app config / DNS Update the connection string to the MI; flush DNS/connection pools
11 Row counts don’t match after offline Writes continued on source after start Compare counts at the start LSN Expected for offline — freeze source writes before starting
12 SQL Agent jobs / linked servers missing on target Targeted Azure SQL Database (no instance features) Source uses Agent/cross-db/linked servers Re-platform those, or migrate to MI which has them

Two decision tables to triage faster mid-incident:

If you see… It’s probably… Do this
Nothing progressing, on-prem source SHIR offline Restart/re-register the IR; check 443 outbound
“FULL recovery required” Wrong recovery model Set FULL, take a fresh full backup, restart
“Access denied” on backups Share or SAS permissions Fix NTFS/share creds or re-issue the SAS
Online “falling behind” Network/source IO ceiling Improve the path or extend the window
Post-cutover, app still on source Connection string Repoint the app; recycle connection pools
Distinction The trap How to tell them apart
Offline “data loss” vs a bug Panic that DMS lost rows Offline never carries post-start writes — that’s by design, not a fault
DMS 502/timeout vs target firewall Hours in the wrong logs If the target rejects any connection (test with sqlcmd), it’s firewall/auth, not DMS
IR “registered” vs “connected” Installed ≠ working The IR can be registered yet show offline — check the cloud-connection status, not just install

Best practices

Security notes

The migration path touches your most sensitive asset — production data — so treat it with least privilege and short-lived access throughout.

Cost & sizing

Three things drive the bill: the DMS service tier, the target (MI or Database) which runs continuously once created, and — for online — the time the migration runs. DMS itself has a free tier that covers the typical one-off migration; the dominant cost is almost always the target Managed Instance, which bills from creation regardless of whether a migration is in flight.

Rough monthly figures (order-of-magnitude in INR; always confirm against the Azure pricing calculator for your region):

Cost driver Rough monthly cost (INR) How to control it
DMS service tier ~₹0 (free tier) Use the free tier for one-off migrations
Target Managed Instance (GP, 8 vCore) tens of thousands to ~₹1–1.5 lakh+/mo The real cost — provision late, right-size, decommission
Target Azure SQL Database (mid vCore) a few thousand to tens of thousands/mo Far cheaper than MI; pick DTU vs vCore well
Online sync window runs while it runs Keep it short; cut over promptly
Network egress / link per-GB over VPN/ExpressRoute Larger one-offs may justify ExpressRoute
Self-hosted IR ~₹0 (your own host) Cost is the host you already own
Blob storage for backups low (per-GB) Tier/clean up backups after cutover

The sizing rule: size the target for steady-state load, not the migration. The copy may briefly want more IO, but you don’t keep an oversized MI forever to make one restore faster. Use the assessment’s recommendation as the floor, and remember MI is dramatically more expensive than a single Azure SQL Database — if you don’t need instance-level features, the Database tiers in Azure SQL Database Purchasing Models: DTU vs vCore and How to Pick Without Overpaying save you a great deal monthly.

Interview & exam questions

Q: What is the difference between an offline and an online migration in DMS? Offline does a one-time copy of schema and data; changes written to the source after the start aren’t carried over, so the source is effectively read-only until you cut over. Online does the same initial copy but then continuously replays the source’s transaction-log backups to keep the target nearly current, letting the source stay live until a brief, planned cutover.

Q: Which Azure SQL target supports both modes most cleanly, and why? Azure SQL Managed Instance — it accepts native SQL Server backups directly and supports both offline and online migrations, making it the natural lift-and-shift target. Azure SQL Database is supported but with tighter constraints and different data-movement mechanics, and it lacks instance-level features like SQL Agent and cross-database queries.

Q: What recovery model does an online migration require, and why? FULL recovery, with an unbroken transaction-log backup chain. Online carries ongoing changes by continuously restoring the source’s log backups; that is only possible if the source is in FULL recovery and no out-of-band backup has broken the chain.

Q: What is the self-hosted integration runtime’s role, and when do you not need it? It’s an agent inside your network that lets DMS (which runs in Azure) read backups stored on an on-premises SMB share and ship them up. You don’t need it when backups live in Azure Blob Storage — DMS reads the container directly via a SAS token.

Q: A migration is stuck at 0% with “integration runtime unavailable.” What do you check? The SHIR: is the service running, is it registered against the right DMS with a valid auth key, and can the host reach Azure over HTTPS (443) outbound. A reboot or lost network commonly pauses on-prem migrations; once the IR reconnects, online migrations resume where they left off.

Q: How do you minimise downtime when the database is too large to copy inside a maintenance window? Use an online migration: DMS seeds the full backup while the source stays live, replays log backups to stay caught up, and you cut over during a short planned window where only the final log backup (a few seconds of change) is applied — turning hours of outage into minutes.

Q: What is “cutover” and what does it actually do? The operator-triggered step that finalises an online migration: you stop writes on the source, the final log backup is applied to the target, and DMS completes the migration so the target is fully consistent and writable. You then repoint the application.

Q: Why might row counts differ between source and target after an offline migration, and is that a bug? Not a bug — by design. Offline does not carry writes that happen on the source after the start, so rows added post-start won’t be on the target. The remedy is to freeze writes on the source before starting an offline migration.

Q: Your backups are TDE-encrypted. What must you do before restoring to the target? Make the TDE certificate available on the target before the restore, or the encrypted backup can’t be decrypted and the restore fails. Plan the certificate move as part of the migration; don’t disable TDE just to simplify the copy.

Q: Where does DMS sit relative to Azure Migrate? Azure Migrate is the broader hub for discovering, assessing and migrating servers and workloads; DMS is the specialist engine for the database tier. You assess databases (compatibility, SKU) first, then DMS performs the actual move — the tool-choice comparison covers when each applies.

Q (exam mapping): These map to DP-300 (Azure Database Administrator Associate) and the migration content in AZ-305 (Azure Solutions Architect) — migrating SQL workloads to Azure SQL, choosing between MI and Database, and minimising downtime with online migrations.

Quick check

  1. You can grant a two-hour read-only maintenance window and the database is 30 GB on a fast link. Offline or online?
  2. What recovery model and chain property does an online migration require on the source?
  3. Your backups live in an on-premises SMB share. What component must DMS use to reach them, and what if they were in Azure Blob instead?
  4. After an online migration sits in “Ready to cut over,” what does triggering cutover actually do?
  5. The migration is stuck at 0% with an “integration runtime unavailable” warning. Name the first three things to check.

Answers

  1. Offline. A 30 GB database on a fast link copies well inside a two-hour window, so the simpler offline mode wins — online’s complexity buys nothing here.
  2. FULL recovery model with an unbroken transaction-log backup chain. Online replays log backups to carry changes, which requires both.
  3. A self-hosted integration runtime installed inside your network reads the on-prem share and ships backups to DMS. If the backups were in Azure Blob, you’d need no IR — DMS reads the container directly with a SAS token.
  4. It applies the final transaction-log backup to the target and finalises the migration so the target is fully consistent and writable; you then repoint the application. You stop source writes first so nothing is missed.
  5. (a) Is the SHIR service running; (b) is it registered against the right DMS with a valid auth key; © can the host reach Azure over HTTPS (443) outbound. A reboot or network loss is the usual cause; online resumes once it reconnects.

Glossary

Next steps

AzureDatabase Migration ServiceAzure SQLSQL ServerOnline MigrationOffline MigrationCutoverMigration
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading