I have led a couple of platform migrations, from on-premises to AWS and from one AWS account structure to another. Each time my job was to coordinate the application, database, DevOps and network teams from scoping to cutover.
This is the list I would want you to keep in mind before you start one. It is grouped into seven categories. Under each I say what to think about and, where it helps, what we did.
1. Scope: decide what changes and what waits
A migration is a chance to modernize, and it is also the easiest place to over-engineer. Start from the rules of the target environment. Ours were no static credentials, TLS on database connections, nothing public by default, autoscaling, and tags on every resource so cost splits by application.
Then list every improvement someone wants and make a call on each one.
| Area | Think about | What we did |
|---|---|---|
| Database | Move to a managed engine? Upgrade the major version in the same move? | Moved to Aurora with an engine upgrade and TLS |
| Runtime | Can VMs, ECS and EKS collapse into one? | Consolidated on EKS |
| Secrets | Use a secrets manager and take passwords out of code and config files | Secrets moved into AWS |
| Configuration | Externalize parameters so the same build deploys to every environment | Config maps |
| Logging | Send application logs to a central store instead of local disk | Central search cluster |
| Access | Use roles between services and drop long-lived keys | Role-based access |
| Pipeline | Can one pipeline deploy everything? Is GitOps in reach? | One CI server in a shared account. GitOps came later, once the runtime was standard |
| Search and loaders | Upgrade or rewrite now, or carry them over as they are? | Deferred. Both needed application code changes |
| Backups | Does going live need a new backup design? | Deferred. Snapshots were enough |
Two columns in the task tracker kept this honest: "key project goal" and "blocker for the first environment". A task with a no in both waited.
2. Discovery: get the complete picture
Get a complete application architecture diagram before anyone builds. Expect the first version to be partial, and keep asking. A platform that has run for years has connections nobody wrote down.
Build an inventory for every environment:
- Which component runs where, and why it runs there.
- Every DNS name, with its zone, load balancer, protocol, target group and the resource behind it.
- Every inbound caller and every outbound dependency.
- Every loader, job and script that reads or writes the data.
Check your assumptions two and three times, including the convenient ones. We worried that bucket names were stored in the database, which would have made renaming buckets painful. We checked, and they were in a properties file. We also assumed a transit gateway would cover traffic between accounts, and we needed VPC peering as well.
Compare the environments against each other. Production and non-production are rarely built the same way, and the differences are where the cutover steps change.
Even with all of this, three things found us late: another product's web client that read our object storage directly, a data warehouse job that read a logging table we planned to retire, and a monitoring script that counted documents in the old search cluster. Leave room in the plan for your own version of these.
3. People and calendar: one scrum, one plan
Each team has its own backlog, its own way of working and other projects competing for the same people. Someone has to keep those plans moving as one.
- Run one scrum across all teams. Ours started weekly, went to twice a week to clear blockers, and became daily near the first cutover.
- Ask every team the same three questions. What are you working on, and what is the estimate? What is blocked or likely to slip, and what is the alternative? What still has to happen for the next environment to cut over on its date?
- Keep one board. One epic, with each team's own tickets linked in.
- Spell out requirements early. When one team settles on a solution, share what it needs from the others the same day.
- Post outcomes in the shared channel. Two-person working sessions are fine, but the result belongs where every team sees it.
- Follow up instead of waiting. It never hurts to over-communicate.
- Plan around the calendar. Change freezes, public holidays and time off for key people all punch holes in a quarter. Keep a tab for who is out and when, and set each environment window around it.
Dates will still move, and ours did on the first environment. Build slack around the known gaps so one slip does not push every later window.
4. Data: the part that decides the cutover
Compute can be redeployed. Data has to be moved, kept in sync and proven correct, so it shapes the whole cutover. There is no single right method, only questions you have to answer.
Can you take downtime? Decide this first, with the product owner. A short window removes a whole class of design. We could take one.
One direction or two? Bidirectional replication buys an easy rollback and costs you conflict handling and weeks of testing. We tested replicating back from the upgraded engine to the old one and hit problems. We chose one direction: seed the new database from a shared snapshot, replicate old to new with native replication, and roll back by export and import. That rollback carries downtime, so tell the product team before they agree.
Native or heterogeneous tooling? Every engine has its own replication. Tools such as AWS DMS or Oracle GoldenGate cover moves across engines. Pick the option with the fewest steps to script and test. For us that was native.
Where else does the data live? Rarely in one place. Ours was in three, each with its own method:
| Store | How it moved | Rollback |
|---|---|---|
| Relational database | Snapshot seed, then one-way native replication | Export and import |
| Search cluster | Snapshot to a shared bucket and restore, then a remote reindex of the current day during the window | Reindex back |
| Object storage | Bidirectional bucket replication, left on for days after cutover | Files already in both accounts |
What feeds it and what reads it? List every inbound loader and every downstream reader. We stopped the loaders before the window and repointed them after. The downstream warehouse stayed on the old database for one more day.
What can testing break? Readiness tests that write to the new database while replication is running can break the replication. Plan which tests write, and when.
5. Network and edges: where it usually breaks
The application and the database get the attention. The surprises come from DNS, load balancers and allowlists.
DNS. If the domain's hosting moves too, stage it so a rollback never touches the registrar. We moved ours into Route 53 like this:
- Create the new hosted zone as an empty shell. Not live, and no records created early.
- Add temporary names in the old zone that point at the new load balancers, so the new stack can be tested before cutover.
- At cutover, repoint the old zone's records to the new load balancers.
- The next day, sync the records into the new zone.
- After 24 hours, publish the new name servers at the registrar next to the old ones.
- After another 24 hours, remove the old name servers and watch the URL monitors.
Load balancers. Decide where TLS terminates, what the listener rules are, and whether each endpoint gets its own load balancer or shares one with host and path routing. Have services call each other through a load balancer or a DNS name, not through the cluster's internal networking.
Allowlists. A new account means new outbound addresses. Every partner and internal service that allowlisted the old ones needs the new ones before cutover. Give every outbound call (email delivery, analytics, partner APIs) its own line in the inventory. Check inbound routes too, such as loaders that still run on-premises.
Object storage. If browser or mobile clients read buckets directly, the CORS rules move with the buckets. If bucket names change, find every place a name is stored.
Edge protection. Check what sits in front of production that the lower environments lack. One of our APIs was behind a web application firewall only in production, and a deployment that rewrote its DNS record would have broken it.
6. Cutover: rehearse it the same way every time
Cut over the lowest environment exactly the way you will cut over production. People will ask why QA needs this much ceremony. QA is the only rehearsal production gets.
- Use one runbook format for every environment. Ours had a preparation tab, a cutover tab, a rollback tab and a DNS tab. Every row had a date, time, duration, team, owner and status.
- Write the application test cases before the first environment. Run the same set in every environment.
- Get a sign-off from each team. Once before the window and again after it.
- Script every cutover and rollback command. Store the scripts before the night.
- Say ahead of time what a lower environment cannot prove. A production-only firewall, a registrar change and a disaster-recovery copy are typical.
- Give cleanup its own change request. We had two people present for every bucket deletion.
Our rehearsals showed that deployments had to finish before the window, that published DNS had to be checked against expected results, and that the cutover needed an application redeploy. Each of those would have been a surprise in production.
For a sense of scale, our production runbook budgeted about forty minutes from cutting traffic to sign-off.
7. Monitoring, retention and cost: decide early
Monitoring. Set up monitoring and central logging in the new environment before the cutover. Put full coverage of databases and application URLs on the production readiness list. Stay on one approved logging path. If the cluster auto-instruments applications, a new service comes up already covered.
Retention. Autoscaling without retention is a bill that grows quietly. Set retention at every layer as it is built: log indices, database logging tables, buckets and container images. We left some of these until after cutover, and I would not do that again.
Cost. The cost lessons are mostly about deciding early:
- Pick instance classes before the build, so reserved capacity can be planned.
- Run the consolidated cluster on reserved instances.
- Create temporary instances with volumes that delete on termination.
- Question any design that copies data between accounts more than it needs to.
- Ask whether every log line is needed before paying to ship and store it.
My take
A migration is mostly coverage. Know what you have, decide what changes now and what waits, move the data with a rollback you have tested, and rehearse the cutover in every environment. In my experience the hard moments come from things nobody wrote down far more often than from the technology.
It is also never one person's work. Application, database, DevOps and network engineers build it. The lead's job is to keep the scope honest, the plan visible and the teams talking to each other.
With a clear scope, a complete architecture diagram, a regular scrum and a firm timeline, and with AI tools to speed up the inventory and runbook work, three to five months is a fair plan for most platform migrations.



Top comments (0)