Legacy web apps to containers on Kubernetes or OpenShift
Cohash containerizes legacy web applications and runs them on Kubernetes, EKS, AKS or OpenShift. We pick the apps that fit. Then we externalize configuration and secrets, move session state and file storage off the server, build minimal images in CI and deploy through GitOps with health checks and autoscaling. An app that would need a rewrite to containerize gets a different plan.
- 01Fit assessment
- 02Twelve-factor clean-up
- 03Images and pipeline
- 04Platform onboarding
- 05Cut-over and VM retirement
The story
Told without the client's name or anything that could identify them.
A manufacturer's application team ran its Java, .NET and PHP web apps on servers built by hand, and a deployment meant RDP or SSH, copying files and restarting services in a maintenance window. A platform team already ran Kubernetes, but most business apps sat on VMs beside it, and OS upgrades were stuck because nobody knew what each server had installed. We scored each app for fit, moved config, secrets, sessions and uploads off the servers, and built signed, non-root images in CI. Apps that fit moved onto the cluster through GitOps with traffic shifted gradually at the load balancer, and their VMs and patching schedules were retired.
why it was urgentExtended support for Windows Server 2016 ends on 12 January 2027, and Microsoft says its container images end on the same dates. Moving an old IIS app into a 2016-based container doesn't buy you anything. The platforms keep moving too: EKS supports each Kubernetes minor for 14 months as standard, and OpenShift 4.22 came out on 9 June 2026.
- openApp servers built by hand, and the only person who can rebuild one is on holiday.
- recurringReleasing still means an RDP or SSH session, copying files across and restarting services in a maintenance window.
- blockedA platform team runs Kubernetes, but most business apps still live on VMs beside it.
- openOS upgrades are stuck because nobody is sure what each server has installed.
The estate, before and after
Before: hand-built VMs with state on the server
Engineers deployed by RDP or SSH onto servers built by hand, and the load balancer pinned users to one server for their session. Uploads, logs, config and secrets all lived on the server's own disk.
Read the diagram as text
- Engineers over RDP or SSH → Java app VM: manual deploy
- Engineers over RDP or SSH → IIS .NET app VM: manual deploy
- Load balancer → Java app VM: sticky traffic
- Load balancer → PHP app VM: sticky traffic
- Java app VM → Local disk: writes files
- IIS .NET app VM → Config files: reads secrets
- PHP app VM → In-memory sessions: session state
After: signed images deployed by GitOps, with state moved off the server
Images come out of CI signed and non-root, into a private registry. GitOps keeps the cluster matched to the repo. Secrets are read from Vault. Sessions and files live in shared services, so any pod can serve any user.
Read the diagram as text
- CI pipeline → Private registry: signed image
- Private registry → Kubernetes or OpenShift: image pull
- Argo CD or OpenShift GitOps → Kubernetes or OpenShift: sync from repo
- Ingress and probes → Kubernetes or OpenShift: traffic
- Kubernetes or OpenShift → HashiCorp Vault: secrets
- Kubernetes or OpenShift → Redis or a database: sessions
- Kubernetes or OpenShift → Object storage: file storage
How we run it, phase by phase.
5 phases take you from Web apps on hand-patched VMs to Containers on Kubernetes or OpenShift. Each one ends with something you can check before the next starts. As you scroll, the blocks show the estate moving across.
- phase 01
Fit assessment
Score each app on runtime, OS dependencies, local file writes, session handling, licensing and startup behavior. Good fits go first; poor fits get replatforming to a managed service or stay on VMs for now.
- phase 02
Twelve-factor clean-up
We move config into environment variables or config maps and secrets into Vault or the cloud's secret store. Sessions go to Redis or a database. Uploaded files go to object storage.
- phase 03
Images and pipeline
We build minimal non-root images in CI, scan dependencies and images, sign them and push to a private registry. .NET Framework apps that need Windows get Windows container images on a supported base.
- phase 04
Platform onboarding
Each app gets its own namespace, quotas, network policies, ingress and probes. Deployment runs through Argo CD or OpenShift GitOps, so what's in the cluster is what's in the repo.
- phase 05
Cut-over and VM retirement
The containerized version runs next to the VM while we move traffic over at the load balancer, a bit at a time. If errors and latency hold, the VM is retired along with its patching schedule.
What the client got
What changed for the client once the work was done. Described, not scored: we don't publish client figures.
- result 01
Each app has a recorded fit decision
Every application was scored on runtime, OS dependencies, local writes, sessions, licensing and startup, and poor fits got a managed-service or stay-on-VM plan instead of a forced container.
- result 02
Deployments are pull requests, not remote sessions
Releases go through Argo CD or OpenShift GitOps now. The cluster matches the repo, and nobody copies files onto a server anymore.
- result 03
Pods can restart without losing state
We moved config into environment variables or config maps and secrets into Vault or the cloud secret store. Sessions sit in Redis or a database now, and uploads in object storage.
- result 04
Images follow one standard from the first app
Images are minimal and run as non-root. We scan them for dependencies and vulnerabilities, sign them in CI and pull them from a private registry.
- result 05
Retired VMs left the patching schedule
Once traffic had moved and errors and latency held steady, we shut down each app's VM and took it off the patching schedule.
Risks we plan for
-
Apps that write to local disk
Anything an app writes to the server's disk, logs, uploads, caches, vanishes when a pod restarts. We find those in the assessment, not in production.
-
Licensing tied to hosts
Some commercial app servers and libraries are licensed per host or per core, and that changes on a shared cluster. We check the licences before building images.
-
Containers as a new place for old problems
An image built on an old base, running as root, with secrets baked in, isn't progress. Image standards and scanning apply from the first app.
Where AI helps, and where it needs guardrails
AI coding assistants are good at drafting Dockerfiles, Helm charts and probes from an existing app's config, and Microsoft's GitHub Copilot modernization agent handles parts of .NET upgrades. We review every generated manifest against platform policy. Agents that deploy to clusters work through GitOps pull requests, not direct cluster credentials.
forward deployed engineers →What you're left with
We write these for your setup, and they're working documents. Your team should be able to keep things running from them long after we've gone.
- report01
Container fit assessment per application
yours to keep - code02
Hardened base images and CI pipeline with scanning and signing
yours to keep - document03
Helm charts or Kustomize overlays per app
yours to keep - guide04
Namespace, quota and network policy setup per team
yours to keep - runbook05
Cut-over plan and VM retirement record
yours to keep
Platforms and services on this move
Certified engineers, wherever you need them.
Some clients want a project team, some want a couple of engineers on theirs. Either works. Onsite, nearshore or offshore, with handovers across time zones that keep things moving.
Onsite
Engineers at your sites in the US
Nearshore
Overlapping hours with US teams
Offshore
Overnight progress on your backlog
All time zones
Coverage handed over across time zones
01Kubernetes or OpenShift?
That depends on how you want support to work and what's already in place. OpenShift comes with a registry, GitOps, monitoring and Red Hat support, so teams wanting one vendor tend to pick it. Managed Kubernetes like EKS or AKS fits teams happy to assemble those parts.
02Can .NET Framework apps run in containers?
Yes, as Windows containers on Windows nodes. A lot of teams do that first and port to modern .NET later, at which point Linux containers become an option.
03How many apps should we move first?
Pick a few apps that fit well and whose owners actually care. They'll set the image standards and pipeline patterns everything after them reuses.
04Do you run the clusters too?
Either we keep running platform upgrades, add-ons and capacity, or your platform team takes over with our runbooks.
Often on the same roadmap
On-premises Active Directory to cloud-first Microsoft Entra ID
We shrink on-premises Active Directory one application at a time, moving groups, users and app authentication to Microsoft Entra ID as each app is ready.
read the case studyAD FS federation to Entra ID authentication
Converting federated domains from AD FS to Entra ID cloud authentication, moving relying parties app by app and retiring the AD FS farm.
read the case studyApache Airflow 2 to Airflow 3
Upgrading self-managed Apache Airflow 2, now past end of life, to Airflow 3, and fixing removed features, auth and scheduling changes before DAGs move.
read the case study5696a23c - 01Microsoft Lifecycle: Windows Server 2016 (containers follow the same dates)learn.microsoft.com ↗
- 02AWS: Kubernetes version lifecycle on EKSdocs.aws.amazon.com ↗
- 03Red Hat: OpenShift 4.22 release notesdocs.redhat.com ↗
- 04Microsoft Learn: .NET Upgrade Assistant overview (Copilot modernization agent)learn.microsoft.com ↗
Planning this move?
A 30-minute working session with a senior engineer. No slides.
- The platform and the version you're on
- Where you need to get to, and by when
- What's custom: rules, plugins, integrations
- Any audit finding or deadline driving it