Cloud Migration to Kubernetes: Lessons Learned from Government
The application had run on WebLogic for years. A case-management system for electronic legal communication that courts and agencies use every day. A deployment took a day. Not because of the bits. Because of approvals, test windows and a maintenance slot that several departments had to sign off.
The goal was clear: onto Kubernetes without putting the running service at risk. Getting there was the actual project. I spent four years on my share of it, architecture, platform, cutover.
Why not a big bang
One weekend, one switch, done: nobody in a regulated shop would sign off on that. Too many dependencies, too much risk for a service courts use daily. So strangler fig instead of a cutover on one date. Every phase had to stand on its own and be accepted separately.
Four phases
First, containerize, still no Kubernetes: the application in a Docker container, behavior identical to the WebLogic deploy. Only once that held did the next change land.
Then J2EE to Quarkus: J2EE-specific APIs replaced piece by piece. Quarkus starts in seconds, not minutes, and for rolling updates and autoscaling that is the difference between possible and theoretical.
After that, onto the platform: Rancher as the Kubernetes distribution, Terraform for the clusters, Helm for deploys, Istio as the mesh, GitLab CI/CD for the pipeline. None of these were up for debate, each closed a gap the previous phase had opened.
Last, harden: Prometheus and Grafana for metrics, SonarQube for code quality, NexusIQ for dependency scans. Without this layer nobody would have let the team release weekly instead of quarterly.
The dashboards that mattered in the end were not the ones with the most panels. Latency per endpoint, error rate after a deploy, pod restarts over time. Three numbers that together show whether a release was clean before a user reports anything. Everything else mattered for proving compliance to security, not for the daily question of whether the last deploy is holding.
What Terraform and Istio actually bought
Terraform was not order for its own sake. Every infrastructure change went through review, was versioned, could be rolled back. Configuration drift that used to cost days of debugging became a diff in a merge request.
Istio delivered mTLS between services, traffic splitting for canary deploys and observability without touching a line of application code. That is the point where a service mesh stops being a buzzword: when the developers do not notice it and operations can still measure what is running.
The cutover itself
The actual move was not a switch, it was weeks of running both sides at once. WebLogic and the Quarkus version ran in parallel, and traffic moved through Istio in steps from the old side to the new one. A small percentage first, then more, with a rollback option at every stage.
The uncomfortable part was not the application, it was the state around it: sessions that lived on the application server, and a database schema that had to serve both versions at the same time while the parallel run lasted. We deliberately kept sessions short before the move and cut schema changes so they stayed backward compatible until the last WebLogic instance was off. Without that rule, every rollback would have meant a data migration running backward, and that cannot happen during a cutover.
What is different about government work
Security clearances, privacy reviews, acceptances. In a startup that takes days. Here it took weeks, sometimes months, and that was not bad work, it was the number of offices that had to sign off. If you do not plan for it, you lose the time anyway, just unplanned and in the wrong place in the schedule. For us that was roughly 30% of the calendar, not the effort hours.
The second point people underestimate: moving from WebLogic to Kubernetes is not a tooling update for a team that has worked on an application server for years. It is a different mental model for deployment, scaling and error handling. Workshops and pairing sessions absorbed that. Without them, handover would have taken longer than the migration itself.
What stood at the end
Deployment from one day to 20 minutes. Zero downtime at cutover. Release frequency from quarterly to weekly. Availability above 99.9%, carried by health checks and rolling updates instead of weekend manual work.
The numbers are not luck from good tools. They are the result of every phase being acceptable on its own, infrastructure running as code, and the organization getting the time it needed.
What I would do differently today
The order, containerize, then switch framework, then platform, I would choose again. What I would plan earlier: the session and schema question from the cutover section belongs at the start of the target architecture, not in the middle of implementation. If you only notice during the parallel run that two versions read the same schema differently, you lose weeks the schedule never accounted for.
The second point: get the internal team onto the new pipeline as early as possible, not only at handover. Knowledge that only sits with me is a risk for the client after four years, not an advantage for me.
What this means for your migration
If you run a similar legacy stack, WebLogic, old J2EE, a deployment process that needs hours instead of minutes, do not start with the tooling. Start with the question: what stays, what moves, what dies. An architecture review is the right first step, before any cutover plan.
If nobody inside holds the role for the platform migration, you need someone for a stretch who decides and hands back off: interim CTO. The platform work itself, clusters, pipelines, mesh, sits under cloud and Kubernetes platforms.
The written entry point before you commit is the Kubernetes migration assessment. Five days, target architecture, cutover strategy, risks. No migration included in the price, just the brief you need to decide internally.
The agency in this case would probably have gotten to Kubernetes eventually without this order. Just later, more expensively, and with a team that does not understand the platform at the end, because it was handed to them instead of built with them.
A call
30 minutes. If the use case does not belong in production, I'll say so.