In this article ⏷

Run Your SSIS in Azure First, Then Stop Maintaining It

Generate The Azure-SSIS Runtime Now, Regenerate The Packages Next

October 7, 2026

If you run SSIS and need to get to Azure, there are two honest paths, and you take them in order rather than picking one. First, run the packages you already have: an Azure-SSIS integration runtime hosts them in Azure Data Factory, and Biml generates both the runtime definition and the pipelines that start each package. Second, stop hand-maintaining them: import the packages into Biml so they become code you generate from, and let one project build the .dtsx files and the Data Factory assets that run them. What you won't find here is a button that converts packages into pipelines. That's on purpose, and the reason is the first thing worth understanding.

‍

We've covered cloud data warehouse migration risks in general. This post is narrower: an SSIS estate, and the order you move it in.

‍

Why Conversions Freeze Logic

‍

The pitch for a conversion toolkit, or for a contractor rewrite, is easy to like. Point it at the packages and get pipelines or notebooks back. Some of these tools handle the standard sources, lookups and derived columns reasonably well.

‍

The trouble starts the day after. A conversion is a snapshot of the packages as they were when it ran. From that point you have two codebases: the SSIS that keeps running production while the new target gets tested, and the converted output that someone is now fixing by hand. A fix in one doesn't reach the other. Rerun the conversion to pick up an SSIS change and you overwrite the hand edits to the output. So people stop rerunning it, and the logic is frozen on conversion day. Everything after that is a manual port, usually while the business keeps asking for changes.

‍

A contractor rewrite has the same shape on a longer clock.

‍

To be fair to converters: if you're ready for a one-way cutover and willing to own every line the tool emits, one can be the right call. It's a poor fit for an estate that has to keep running and keep changing while you move it. That describes most estates.

‍

Path One: Run What You Have

‍

Azure Data Factory can host SSIS directly. The Azure-SSIS integration runtime is a managed cluster that runs packages deployed to an SSISDB catalog, and the Execute SSIS Package activity starts a package from a pipeline. The packages don't change. Where they run and what schedules them does.

‍

Clicking this together in the portal is fine for one package. For an estate, the runtime and a few hundred activities are configuration you want in source control and rebuilt the same way every time. In Biml, authored in BimlStudio, the relevant part of the file looks like this:

‍

<DataFactories> 

<DataFactory Name="df_ssis_lift"> 

<IntegrationRuntimes> 

<ManagedIntegrationRuntime Name="ir_azure_ssis"> 

<ComputeProperties Location="East US" NodeSize="Standard_D4_v3" 

NodeCount="2" MaxParallelExecutionsPerNode="4" /> 

<SsisProperties LicenseType="BasePrice" Edition="Standard"> 

<CatalogInfo Endpoint="sql-ssis.database.windows.net" 

AdminUser="ssisadmin" PricingTier="Standard" /> 

<CustomSetupScriptProperties 

BlobContainerUri="https://stssissetup.blob.core.windows.net/setup" /> 

</SsisProperties> 

</ManagedIntegrationRuntime> 

</IntegrationRuntimes> 

<Pipelines> 

<Pipeline Name="pl_nightly_sales"> 

<Activities> 

<ExecuteSsisPackage Name="Run LoadSales" LoggingLevel="Basic"> 

<PackageLocation Path="Sales/SalesETL/LoadSales.dtsx" Type="SSISDB" /> 

<ConnectVia> 

<IntegrationRuntime IntegrationRuntimeName="ir_azure_ssis" /> 

</ConnectVia> 

<ProjectParameters> 

<ProjectParameter Name="LoadWindow">Nightly</ProjectParameter> 

</ProjectParameters> 

<ProjectConnectionManagers> 

<ProjectConnectionManager Name="SalesDW"> 

<ConnectionParameters> 

<ConnectionParameter Name="ConnectionString" IsSecure="true">(set per environment)</ConnectionParameter> 

</ConnectionParameters> 

</ProjectConnectionManager> 

</ProjectConnectionManagers> 

<PropertyOverrides> 

<PropertyOverride Name="\Package.Variables[User::BatchSize].Value">5000</PropertyOverride> 

</PropertyOverrides> 

<ExecutionCredential Domain="CORP" Username="svc_ssis" 

Password="(set per environment)" /> 

</ExecuteSsisPackage> 

</Activities> 

</Pipeline> 

</Pipelines> 

</DataFactory> 

</DataFactories> 

‍

Build it and you get Data Factory JSON files or an ARM template for both the runtime and the pipeline. The runtime definition covers what an Azure-SSIS deployment needs: location, node size, node count, parallel executions per node, the SSISDB catalog endpoint and its pricing tier, license type (bring your own SQL Server license, or pay for one included), edition, and a custom setup container. That container matters more than it looks. It's how drivers and third-party components your packages depend on get installed on the runtime nodes, and if your packages use the BimlFlex SSIS components, it's how those get there too.

‍

What the Generated Activity Carries

‍

Here's the activity from that build, trimmed to its type properties:

‍

{ 

"name": "Run LoadSales", 

"type": "ExecuteSSISPackage", 

"typeProperties": { 

"loggingLevel": "Basic", 

"packageLocation": { 

"packagePath": "Sales/SalesETL/LoadSales.dtsx", 

"type": "SSISDB" 

}, 

"projectParameters": { 

"LoadWindow": { "value": "Nightly" } 

}, 

"projectConnectionManagers": { 

"SalesDW": { 

"ConnectionString": { 

"value": { "type": "SecureString", "value": "(set per environment)" } 

} 

} 

}, 

"propertyOverrides": { 

"\\Package.Variables[User::BatchSize].Value": { "value": "5000" } 

}, 

"executionCredential": { 

"domain": "CORP", 

"userName": "svc_ssis", 

"password": { "type": "SecureString", "value": "(set per environment)" } 

}, 

"connectVia": { 

"referenceName": "ir_azure_ssis", 

"type": "IntegrationRuntimeReference" 

} 

} 

} 

‍

Look at the connection managers. Each parameter sits under the connection manager's name, SalesDW.ConnectionString.value, which is the shape Data Factory expects. It's an easy nesting to get wrong by hand across a few hundred activities. Anything marked secure comes out as a SecureString, and in the ARM template build each secure value is registered as a template parameter, so connection strings and passwords live in your parameters file, not in the template. Property overrides and the execution credential cover what a SQL Agent job step used to hold for you: property values set at run time and the account the package runs as. Package parameters, package-level connection managers and a log location are available the same way when a package needs them.

‍

Now the honest limit. Path one moves hosting. It doesn't reduce maintenance at all. Every .dtsx is still edited in Visual Studio, the runtime bills for as long as its nodes are started, and the next platform decision still lands on the same packages. That's fine. It closes a server room, puts orchestration in Data Factory, and buys time without touching business logic. It's step one.

‍

Path Two: Stop Hand-Maintaining Packages

‍

Step two changes what you maintain. The SSIS importer in BimlStudio reads an existing estate from any of four places: a .dtproj project on disk, a deployed .ispac file, loose .dtsx files, or a project pulled straight out of the SSIS catalog. What comes back is Biml, the packages as readable markup that compiles back into .dtsx.

‍

That flips the maintenance loop. You stop editing packages in a designer and start editing the source that generates SSIS packages from Biml. Better still, the forty near-identical staging packages every estate seems to have can collapse into one BimlScript pattern that loops over metadata. Then the build writes forty packages.

‍

Here's where the two paths join. The same Biml project that builds the packages also builds the Data Factory runtime and pipelines from path one, in the same compile: the SSIS packages first, then the Data Factory assets. Write the Execute SSIS Package activities with the same kind of BimlScript loop, and adding a package to the pattern adds the activity that runs it. Nobody updates a pipeline by hand to match.

‍

None of this is a snapshot. You never go back to editing .dtsx, so there's nothing to reimport and nothing to drift. Every build comes from the model you actually maintain.

‍

The importer does have a boundary. Script tasks, script components and custom third-party components come across so you can read them, but a person still decides what they should become on a new platform. The importer gets them into view. It doesn't make the call.

‍

When the Target Changes

‍

Once your load logic lives in patterns over metadata instead of in individual packages, changing the target is a change to the pattern. That's a very different project from touching five hundred packages.

‍

It's also where BimlFlex comes in. BimlFlex keeps sources, targets and load patterns as metadata and generates SSIS packages or Azure Data Factory pipelines from it, with Databricks, Snowflake and Synapse on the same footing. Moving from imported Biml into BimlFlex metadata is modeling work, and it's worth doing deliberately. But the output of that step is regenerated code on your platform, and the next retarget starts from the model, not from a pile of converted files.

‍

A Sequence You Can Plan Around

‍

  1. Generate the Azure-SSIS runtime and the Execute SSIS Package pipelines, deploy your existing packages to SSISDB, and run them in Azure. No logic changes.
  2. Import the estate into Biml and rebuild the same packages from it. Compare rebuilt packages with the originals before you swap them in.
  3. Collapse repeated packages into patterns, and generate the pipelines that call them from the same patterns.
  4. Retarget from the model when the business is ready, not when a conversion deadline says so.

‍

Each step leaves you with something that runs, and each one is code you generated and own, which is the difference between building an asset and renting a liability. None of them asks you to pick a date and freeze your logic on it.