Key takeaways
- Containerize first: a Linux container image built from the current code is the unit you test, ship to ECR and run on ECS Fargate.
- The database decides your cutover plan; for Azure SQL Database, plan a full load plus a short write freeze rather than counting on continuous replication.
- Route 53 weighted records (weights 0 to 255) let you move traffic in steps and send it back by changing one number, as long as DNS TTLs are short.
- Umbraco on several containers needs shared media storage, shared Data Protection keys, a distributed cache for session and a clear scheduling server role.
- Keep the Azure stack running and unchanged until the AWS stack has carried full traffic through a normal busy period.
- Compare costs only after rightsizing both sides; licensing, data transfer and engineering time often matter more than compute prices.
An Azure to AWS migration without downtime works by building the AWS environment next to the running Azure one, keeping data in sync, and moving traffic over in steps you can reverse. For a .NET or Umbraco site that means: package the app as a container, run it on Amazon ECS with Fargate behind an Application Load Balancer, move the SQL Server database to Amazon RDS, copy media to S3, then use Route 53 weighted DNS records to shift traffic gradually. The Azure stack stays live until the AWS stack has proven itself under real load.
The hard parts are not the containers. They are the database cutover, shared state between instances (media, session, cache, encryption keys) and having a rollback you have actually tested. This guide walks through each step, marks where we are giving general guidance, and uses Ben-Gurion University’s main website as a worked example. Our cloud and DevOps team moved that site from Azure to AWS while it stayed online.
The worked example: Ben-Gurion University
The Ben-Gurion University case study describes the university’s main Umbraco site, which holds about 80,000 pages, is edited by around 1,000 people and sees traffic spikes of around 300 requests per second. It also hosts an Organizational Units subsystem where each unit runs its own content, plus daily imports of 15,000+ staff records from university systems.
The requirement was that the live public site move from Azure to AWS without any interruption. What the case records about the implementation:
- The application was containerized and moved to Amazon ECS on Fargate, with Amazon RDS, Amazon S3, Amazon ECR and a load balancer.
- A Windows EC2 bastion runs the import services. The case lists a daily staff API import and a CRIS research publications import.
- Traffic shifted to the new setup gradually.
- The migration finished with zero interruption to the public site.
Everything else below is general guidance for this kind of move, not a description of the university’s specific configuration.
Target architecture on AWS
| Concern | On Azure (typical) | On AWS |
|---|---|---|
| Compute | App Service plan | ECS service on Fargate tasks |
| Images | Built during deploy, or Azure Container Registry | Amazon ECR |
| Traffic entry | App Service front end | Application Load Balancer |
| Database | Azure SQL Database or SQL Server | Amazon RDS for SQL Server |
| Media and files | App Service file system or Azure Blob Storage | Amazon S3 |
| DNS | Any provider | Route 53 (weighted records) |
| Background jobs and imports | WebJobs or a separate app | Separate ECS service or EC2 (BGU used a Windows EC2 bastion) |
Fargate means you run containers without managing the servers under them. Plan at least two tasks across two Availability Zones for a public site, and put a separate, small service or instance in charge of scheduled work.
Step 1: Containerize the application
App Service can run code without a container, so many .NET sites have never been packaged as one. Do this first, while still on Azure, because the image is what you will test, scan and promote.
- On modern .NET, the SDK can build an image without a Dockerfile:
dotnet publish --os linux --arch x64 /t:PublishContainer(requires the .NET 8 SDK or later). A Dockerfile is still reasonable if you need extra OS packages. - Remove anything that assumes App Service: local file writes for media,
WEBSITE_*environment variables, App Service authentication, in-process session. - Move configuration to environment variables and a secrets store (AWS Secrets Manager or SSM Parameter Store).
- Add a health endpoint that checks the app is ready, not just alive. The ALB target group and ECS use it to decide when a task can take traffic.
- Push images to ECR from CI with immutable tags (a commit SHA, not
latest).
If the site still runs on .NET Framework, the container route needs Windows containers or a runtime migration first. See our guide on moving .NET Framework apps to .NET 10.
Step 2: Make the app safe to run on several containers
This is where most Umbraco and ASP.NET Core sites need code or configuration changes. Umbraco’s load balancing documentation lists the requirements:
- Server roles. One server acts as
SchedulingPublisher(scheduled publishing and background tasks), the others asSubscriber. Umbraco recommends setting the roles explicitly rather than relying on automatic election. A common pattern is one backoffice service and a scaled public front-end service. - Data Protection keys. All instances must share the same keys, or you get validation and encryption errors and users get signed out. Microsoft documents key storage providers for Redis and EF Core (a database table), among others; on AWS, the EF Core provider pointing at RDS is a simple choice.
- Session and TempData. Umbraco’s docs require a distributed cache such as
DistributedSqlServerCachewhen load balancing. Do not rely on ALB sticky sessions as the only fix; containers get replaced during deploys and scaling. - Examine and Lucene indexes. Umbraco’s file system replication guidance explains that Lucene has problems on remote file shares and offers
TempFileSystemDirectoryFactoryandSyncedTempFileSystemDirectoryFactoryfor keeping indexes on local storage. On Fargate, each task builds and keeps its own index on local ephemeral storage. Measure how long a cold index rebuild takes for your content volume, because it affects how fast new tasks become ready. - Media. Containers are disposable, so media cannot live on the container file system (see Step 4).
Search and caching deserve extra attention on large sites. On the BGU site, Lucene powers global search plus more than 10 dedicated search pages, and a custom page-level cache records which pages depend on which content so only affected pages are invalidated. Behavior like that has to be checked on the new infrastructure under load, not assumed.
Step 3: Choose a database migration path
The database is what decides your cutover plan. Pick the path based on what your source is.
Option A: Native backup and restore (source is SQL Server you control)
Amazon RDS supports restoring full .bak files from S3, and AWS calls this “usually the fastest way” to move a SQL Server database. Limits worth knowing: the S3 bucket must be in the same Region, files are limited to 5 TB each (use multifile backups beyond that), you can’t restore a database with a FILESTREAM filegroup, and on Multi-AZ instances only databases in the full recovery model can be restored natively. AWS’s own recommendation is to use native restore if the database can be offline during backup, copy and restore, and AWS DMS if it can’t.
Option B: BACPAC export (source is Azure SQL Database)
Azure SQL Database does not give you .bak files. Microsoft’s supported export format is a BACPAC, which can be imported into a SQL Server instance. For a consistent export, there must be no writes during the export, or you export from a database copy. Blob storage exports are capped at 200 GB, and Microsoft recommends SqlPackage for larger databases. In practice this means a write freeze for the duration of export plus import, so test the timing.
Option C: AWS DMS
AWS DMS lists Azure SQL Database as a supported source and can do a full load followed by ongoing replication (CDC) for many SQL Server sources. Two documented details matter for Azure:
- The DMS SQL Server limitations say it “doesn’t support replication from databases with accelerated database recovery (ADR) turned on.”
- Microsoft states that ADR is always enabled in Azure SQL Database and Azure SQL Managed Instance.
Read together, we would not plan a cutover that depends on continuous CDC from Azure SQL without proving it first in a proof of concept. Also note that DMS does not migrate the identity property of columns, does not capture TRUNCATE, and does not support temporal tables. The usual pattern is to create the schema on RDS first (from a schema script or BACPAC) and use DMS for data only.
What we recommend in general
For a CMS-driven site, content changes come from editors, not from the public. That makes a short editorial freeze the simplest safe cutover: public traffic keeps flowing, editors pause for the final sync window, and the backoffice comes back up on AWS. Rehearse the full sequence on a copy at least once, and time it.
Step 4: Move media to S3
Copy existing media to S3 early, then run a final sync during the freeze. Umbraco supports replacing the media file system with a custom IFileSystem implementation; Umbraco’s own maintained provider in the Umbraco.StorageProviders project targets Azure Blob Storage. For S3 you either use a community provider that supports your Umbraco version or write a small implementation. Umbraco’s docs note that a custom media file system must also provide an IFileProviderFactory so media is served and image processing still works.
Two details to test: image cropping and resizing on media from S3, and the cache headers on media responses. Put a CDN in front of S3 or the site if you serve heavy images.
Step 5: Shift traffic in stages with Route 53
Route 53 weighted routing lets you create several records with the same name and type and assign each a weight from 0 to 255. Each record gets traffic in proportion to its weight divided by the total. Setting a weight to 0 stops traffic to that resource. The example AWS gives is weights of 1 and 255, which sends 1/256 of traffic to the first resource.
Illustrative plan (adapt it to your traffic and risk):
| Stage | Azure weight | AWS weight | Approx. share to AWS | Hold for |
|---|---|---|---|---|
| Smoke | 255 | 1 | ~0.4% | Until errors and latency are clean |
| Canary | 230 | 25 | ~10% | One busy period |
| Half | 128 | 128 | 50% | One to two days |
| Most | 25 | 230 | ~90% | One busy period |
| Full | 0 | 255 | 100% | Until you decommission Azure |
Practical notes from the Route 53 docs and general experience:
- Lower TTLs days in advance. AWS suggests a short TTL such as 300 seconds when changing records in use, and 60 seconds or less when health checks are attached. All records in a weighted group share one TTL, and if alias records point to a load balancer, AWS says to use 60 seconds for the non-alias records too.
- DNS is per resolver, not per user. Resolvers cache answers, so actual traffic shares will not match the weights exactly. Watch the real request counts on both sides.
- Health checks. If you attach health checks to all records in the group, Route 53 stops returning an unhealthy endpoint, which gives you automatic fallback.
- Single writer. While both stacks serve public traffic, both must read from the same source of truth. Either point both at one database temporarily, or keep the AWS stack read-only until the database cutover. Do not let both sides write to different databases.
Step 6: Load test before the first real user
Load test the AWS stack with production-like data before any weight goes above zero:
- Replay a realistic mix of URLs from your logs, not just the home page.
- Test at and above your known peak. For the BGU site the case records spikes of around 300 requests per second.
- Include cold starts: scale out during the test and watch how long new tasks take to become healthy, including index builds and cache warm-up.
- Watch RDS CPU, IOPS and connection counts, not just response times.
- Configure ECS service auto scaling and confirm it triggers before latency degrades.
Step 7: Write the rollback plan before you need it
A rollback you have not rehearsed is a hope. The basics:
- Keep Azure running and unchanged until AWS has carried full traffic through a normal busy period.
- Rollback for public traffic is a weight change: set AWS to 0 and Azure back to 255. With short TTLs, most traffic returns within minutes.
- Rollback after the database cutover is harder, because edits made in AWS exist only in RDS. Decide in advance: either export those changes back, or accept a short freeze while you move the data back. Keep the cutover-to-decision window short.
- Define triggers in advance: error rate, p95 latency, failed health checks, failed editor workflows. Agree who can call a rollback.
- After go-live, use ECS blue/green deployments for application releases. AWS documents a bake time during which the old revision keeps running and can take traffic back.
Cost comparison caveats
Cost is a common reason to move and a common source of disappointment. General points:
- SQL Server licensing is often the largest line. RDS for SQL Server offers License Included pricing for Enterprise, Standard, Web and Express editions, and AWS describes a Bring Your Own Media option that relies on License Mobility through Software Assurance. Compare editions and sizes, not just instance prices.
- Data transfer between Availability Zones, out to the internet and between clouds during the migration all cost money.
- Fixed costs such as load balancers, NAT gateways and a bastion host add up on small sites.
- Rightsize both sides before comparing. An oversized App Service plan compared to a tuned Fargate service tells you nothing.
- Count the engineering time for the migration and for running the new platform.
When not to move
A migration is a large change with real risk. It may not be worth it when:
- The main driver is a discount that a renegotiated Azure agreement could match.
- The site depends on Azure-specific services (Azure AD B2C flows, Service Bus, Functions) that would each need rework.
- Nobody on the team will own AWS operations after the move.
- The application still needs deeper work (an unsupported runtime or CMS version). Fix that first; moving clouds and runtimes at once multiplies risk.
Migration checklist
- Container image builds in CI and runs locally with production-like config
- Health endpoint, structured logs and error tracking in place
- Server roles, Data Protection keys, distributed session and index settings configured
- Media copied to S3, image processing verified
- Database path chosen and rehearsed with timings
- Load test passed above known peak, auto scaling verified
- DNS TTLs lowered days in advance, weighted records created at weight 0
- Rollback triggers, owners and steps written down
- Editor freeze window agreed and communicated
- Azure kept warm until sign-off
The same AWS foundation can support more than one site. The university’s login-only student and staff portal, described in the BGU portal case study, also runs on AWS Fargate with Amazon RDS, S3 and a load balancer. If you are planning a similar move for an Umbraco site, our Umbraco development and .NET development teams can help with the application-side changes.
- ~80,000pages on the main Umbraco site
- ~1,000editors
- ~300 req/straffic spikes
Figures from the Ben-Gurion University case study.
- Route 53 → Weighted records send a share of traffic Load balancer
- Load balancer → At least two tasks across two zones ECS Fargate
- Amazon ECR → Container images ECS Fargate
- ECS Fargate ⇄ One database for all tasks RDS SQL Server
- ECS Fargate ⇄ Media off the container file system Amazon S3
- EC2 bastion → Staff API and CRIS imports RDS SQL Server
- 01 Smoke ~0.4%
Azure 255, AWS 1. Hold until errors and latency are clean.
- 02 Canary ~10%
Azure 230, AWS 25. Hold one busy period.
- 03 Half 50%
128 each. Hold one to two days.
- 04 Most ~90%
Azure 25, AWS 230. Hold one busy period.
- 05 Full 100%
Azure 0, AWS 255 until you decommission Azure.
Sources
- Importing and exporting SQL Server databases using native backup and restore (Amazon RDS)
- Using a Microsoft SQL Server database as a source for AWS DMS
- Weighted routing (Amazon Route 53)
- Amazon ECS blue/green deployments
- Key storage providers in ASP.NET Core (Microsoft Learn)
- Umbraco load balancing: file system replication


