AI & Automation
Containerising Legacy Applications: A Practical Guide
31 October 2026

Containerising a legacy application means packaging its runtime, libraries and configuration into an image that runs identically on a developer laptop, a test cluster and production. It is a suitable approach when the application can be separated from machine-level state, such as local files, registry entries or hard-coded hosts, and when the organisation wants repeatable deployments without a full rewrite. When that separation is not realistic, a Windows container, a virtual machine or a staged refactoring is the more defensible choice, and the assessment described below is what tells the two situations apart.
TL;DR
- Containerisation is a rehosting or replatforming step, not a rewrite: it improves deployment consistency, but it does not remove the technical debt inside the code.
- The decisive question is state: local files, in-process sessions, registry settings and machine-bound licences determine whether an application containerises as it is or needs refactoring first.
- .NET Framework applications generally need Windows containers; mostly managed code can often move to modern .NET and Linux containers.
- Persistent data, secrets, logs and health probes must be externalised and designed before the first production deployment, not after.
- Cut over gradually with blue/green or canary releases, and treat image patching as a permanent operational duty.
Table of Contents
- What containerising a legacy application involves
- When containerisation is the right move
- Assessing runtime, dependencies, configuration and data
- Building the image
- Persistent data, secrets, networking, logging and health checks
- Testing before production
- Security and operations after go-live
- When containers are the wrong answer
- Our perspective
- How we can help
- FAQ
- Sources
What containerising a legacy application involves
A legacy application is rarely defined by its age alone. It is defined by assumptions: that it runs on one long-lived server, that configuration lives in files edited by hand, that sessions are held in process memory, that a particular version of a runtime is installed globally. A container removes the server from that picture and keeps only what the application declares explicitly. Every undeclared assumption therefore becomes a failure at the first build.
The work usually falls into five stages: assessment, image construction, externalisation of state and configuration, validation, and operational hardening. Each stage has a different owner and a different failure mode, which is why teams that treat containerisation as a single Dockerfile exercise tend to discover the hard problems in production rather than in review.
It also helps to be precise about what the technique does and does not deliver. Microsoft describes Windows containers as a way to move traditional or legacy server applications, including IIS applications, into containers, and it frames this explicitly as lift and shift. Lift and shift buys portability, density and a consistent deployment artefact. It does not buy modularity, independent scaling of components or a cleaner codebase. Those belong to a later modernisation phase, and it is sensible to say so in the business case from the outset.

When containerisation is the right move
The approach fits best where the application has a stable interface, a tolerable runtime and a deployment process that is currently the main source of pain. Typical examples include a Java EE application server hosting a handful of enterprise archives, a PHP monolith whose environment differs between staging and production, or an ASP.NET application whose installation is documented only in one engineer’s memory.
The table below summarises the decision signals we use as a first filter. It is a heuristic, not a verdict: the technical assessment in the next section is what confirms or overturns it.
| Signal | Points towards containers | Points towards another route |
|---|---|---|
| State | Data in a database or object store; sessions can be externalised | Heavy reliance on local disk, in-process sessions or machine-level state |
| Runtime | Linux-friendly stack, or .NET Framework with IIS only | COM components, heavy native dependencies, drivers or hardware access |
| Licensing | Licence is per application or per core | Licence tied to machine identity or hardware |
| Goal | Repeatable deployments, environment parity, platform standardisation | Immediate need for independent scaling of internal modules |
| Change appetite | Team can adjust configuration handling | No capacity or source access to alter anything |
Microsoft’s migration guidance makes the same distinction for .NET: if blockers such as COM, heavy native dependencies or machine-level state are difficult to change, Windows containers may be more realistic, whereas an application that is mainly managed code and can move away from machine-level dependencies is a candidate for refactoring to .NET 8 and Linux containers.
Assessing runtime, dependencies, configuration and data
The assessment determines the whole project, and it is the stage most often shortened. Google Cloud’s application modernisation team states that technical assessment plays a critical role in reducing modernisation risk, and describes inspecting the application’s architecture, binaries, configuration files and running services. That is the right scope: the inventory must come from the running system, not from outdated documentation.
The inventory to produce
For a Windows or .NET Framework workload, Microsoft’s guide lists the questions that matter: whether the application depends on IIS features, Windows authentication, native code, COM, the registry or local state, how it handles file and session state, what networking it requires, whether it needs Windows-only services, and whether its licensing permits containers at all. The same guide treats each of these as a possible blocker to containerising as it is, or as a reason to refactor first.
For Linux and Java workloads the list differs in vocabulary but not in spirit. The inventory should cover the following areas, recorded in a single document that the delivery team and the operations team both sign off:
The runtime and its exact version, including the application server and any patches applied by hand. The build chain, since a legacy build that only works on one retired workstation must be reproduced before an image can be built reliably. External dependencies, such as databases, queues, file shares, mail relays and licence servers, with their network addresses and authentication methods. Configuration, split between values that change per environment and values that are effectively constants. Data, meaning what is written at runtime, where, how large it grows and who reads it. Finally, the non-functional baseline: start-up time, memory profile and typical load, because these numbers are what the later performance tests will be compared against.
Reading the results
The output is a classification of each component into one of three groups: containerise as is, containerise after a bounded change (for example, moving session storage to a shared cache), or leave outside the container platform for now. Components in the third group are not a failure of the project; a database server with strict latency and durability requirements, for instance, is frequently better kept on managed infrastructure than inside a cluster.
Building the image
The construction of the image depends on the stack. For .NET Framework and ASP.NET, Microsoft’s guidance is to use a Windows Server Core-based ASP.NET image and a Dockerfile that installs and configures IIS, copies the application in and exposes the ports it needs, while moving secrets and application settings to secure stores or runtime configuration, as set out in the AKS migration guide. Server Core images are large compared with Linux equivalents, and this is a legitimate cost to include in the comparison with refactoring.
For Linux workloads, the principles recorded in Docker’s own language guides apply well to older applications too. Docker’s Angular guide, for example, uses lightweight base images, a multi-stage build and a non-root user in the final image. A multi-stage build is particularly valuable for legacy code, because it allows an ageing toolchain to exist in the build stage only, leaving the runtime image small and free of compilers.
Practices that save time later
Pin base image versions rather than relying on floating tags; a legacy application is sensitive to minor runtime changes, and an unexpected update is the most common cause of a build that worked yesterday and fails today. Keep one process per container wherever the application allows it. Where it does not, as with older applications that start several cooperating daemons, accept the compromise explicitly and document it. Standardise the build instructions as well: Microsoft’s assessment rules for Java migration flag problematic constructs such as apt-get upgrade in Dockerfiles, because unpinned upgrades undermine the predictability that containers are meant to provide.
Persistent data, secrets, networking, logging and health checks
A container is disposable by design, and a legacy application is not. Reconciling the two is the core engineering task, and it is where most of the effort in a real project is spent.
Data and session state
Microsoft’s guidance is to externalise persistent data to storage or a database and to move file-system and in-process session state to an external service, naming options such as Redis, SQL, blob storage or shared storage in the AKS migration guide. In development, Docker’s Node.js guide shows the same separation at small scale, using a named volume for database data and a Docker secret mounted as a file for the password. The pattern is identical: nothing the business cares about lives inside the container’s writable layer.
Configuration and secrets
Legacy applications frequently read connection strings from a file inside the installation directory. The least invasive change is to generate that file at start-up from environment variables or from a Kubernetes ConfigMap, and to inject credentials from a secret store rather than baking them into the image. This preserves the application’s code while removing the most damaging habit of the old deployment model.
Networking, logging and probes
In Kubernetes, inbound traffic is handled through a Service or an Ingress, and the application’s hard-coded host names usually need replacing with service names. Logs should be written to standard output or collected by an agent and centralised, since a container’s file system disappears with it. Liveness and readiness probes require particular care: Microsoft’s guide stresses tuning timeouts and delays for applications with slow start-up, and a legacy application that needs ninety seconds to warm up will be restarted in a loop by a default probe configuration that assumes five.
Testing before production
The test strategy has two layers. The first is functional and behavioural: the containerised application must produce the same outputs as the original for the same inputs, including edge cases that nobody remembers documenting. The second is performance and resilience: the baseline captured during assessment is compared with the new environment under realistic load, and session behaviour is verified across restarts and across multiple replicas.
Microsoft recommends running functional and performance tests, validating session and application behaviour, and cutting traffic over gradually with blue/green or canary deployment. AWS adds a point that is easy to postpone and expensive to skip: its prescriptive guidance on containerising traditional Java EE applications recommends establishing an automated build and test pipeline in the first project phase, so that every change to code or configuration is checked against existing business logic.
A practical consequence is that the pipeline should exist before the first container is deployed anywhere, and that the legacy system should keep running in parallel until the canary has carried real traffic for a full business cycle, including month-end or any other peak the application is known for.
Security and operations after go-live
The move to containers changes who is responsible for what. NIST observes that the transition shifts security responsibility significantly towards developers, who must now handle vulnerability management, patching and configuration during the build, and that containers increase the number of objects to manage and secure. For a legacy estate this is a real change: a base image carrying an outdated library is a vulnerability that no longer sits in a server team’s patch cycle but in the delivery pipeline.
NIST’s application container security publication sets out the corresponding disciplines. Image integrity should rest on cryptographic signatures and unique identifiers for image components, images should be rebuilt automatically when base images are patched, and containers should be stateless and immutable: a running container is replaced by a new one built from an updated image, never modified in place. On the orchestration side, Microsoft’s guide adds network policies, policies for container images and role-based access control. Teams defining their own build and update controls can compare them with documented security controls for container images from another engineering organisation.
Three operational routines deserve a named owner from the first day: scheduled image rebuilds and vulnerability scanning, a documented rollback procedure tested at least once, and a review of resource limits after the first full month of production data.
When containers are the wrong answer
Containerisation is not universally suitable, and an honest assessment will sometimes conclude as much. Three situations recur. The first is machine-bound licensing: Microsoft’s guidance advises checking whether application licensing is tied to machine identity or hardware, since container-based licensing may need vendor coordination and in some cases is simply unavailable. The second is deep dependence on hardware, drivers or COM components that have no container equivalent. The third is an application with no source access and no vendor support, where an unmodified virtual machine is both safer and cheaper than an experimental image.
In those cases the alternatives are a virtual machine for the short term and a planned modernisation for the long term, typically through the strangler pattern: new functionality is built as separate services while the legacy core is retired piece by piece. Containers then serve as the common runtime for both halves during the transition.
Our perspective
We approach containerisation as an engineering decision before it is a tooling decision. The most frequent error we see is the assumption that a working Dockerfile means a finished migration, when the image is merely the visible part of a larger change in how state, configuration and responsibility are handled.
Because our clients talk directly to the engineers who design and maintain the system, the assessment is a conversation about the actual application rather than a questionnaire passed through an account layer. We have no vendor bias: the recommendation may be Windows containers, a refactoring to a modern runtime on Linux, OpenShift, or leaving a component exactly where it is. Every consulting engagement ends with a written recommendation that states the options, the trade-offs and the reasons for the one we propose, so that the decision can be reviewed by people who were not in the room.
We also limit the number of simultaneous projects, since legacy work rewards attention to detail far more than it rewards speed. An undocumented cron job found in week two is cheaper than the same job discovered during a cutover.
— Pepe F.
How we can help
Our code audit and architecture services establish whether an application is a good container candidate before any build effort is committed, and our custom software development and modernisation services cover the implementation, from image construction to Kubernetes and Red Hat OpenShift deployment. For organisations that need continuing technical leadership through a migration, the CTO advisory plans start at €1,800 per month for CTO Advisory, with Fractional CTO from €4,000 per month and CTO Partner from €7,500 per month; an initial assessment is available from €3,500 as a one-off engagement.

Previous engagements, including modernisation work, are summarised in our case studies.
FAQ
Can any legacy application be containerised?
Not without conditions. Applications that depend on machine-level state, COM components, specialised hardware or machine-bound licences may need refactoring, a Windows container or a virtual machine instead. The assessment of dependencies and state is what establishes which of these applies.
Do .NET Framework applications need Windows containers?
In general, yes: ASP.NET applications on .NET Framework run in Windows Server Core-based images with IIS. If the code is mainly managed and free of machine-level dependencies, Microsoft suggests considering a move to modern .NET and Linux containers, which produces smaller images and wider platform choice.
Does containerisation reduce technical debt?
Not by itself. It standardises deployment and the runtime environment, which removes a class of operational problems, but the structure of the code is unchanged. Debt reduction comes from the refactoring and modularisation that containerisation makes safer to carry out afterwards.
How should a containerised legacy application be released to production?
Gradually. Run functional and performance tests against the baseline, validate session behaviour, and move traffic in stages with a blue/green or canary release, keeping the original system available for rollback until the new one has handled a full business cycle.
Sources
- Migrating Legacy .NET / Windows Applications to AKS, Microsoft Learn
- Lift and shift to containers, Microsoft Learn
- Shift your apps to container-based workloads on the command line, Google Cloud
- Angular language-specific guide, Docker
- Containerizing traditional Java EE applications, AWS Prescriptive Guidance
- Security Assurance Requirements for Linux Application Container Deployments, NIST
- NIST Guidance on Application Container Security, ITL Bulletin
- Assessment rules for Java migration, Microsoft Learn
This article was produced with AI assistance and reviewed for accuracy. It is provided for general information only and is not professional advice.