Skip to main content
Redhat Developers  Logo
  • Products

    Platforms

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat AI
      Red Hat AI
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • See all Red Hat products

    Featured

    • Red Hat build of OpenJDK
    • Red Hat Developer Hub
    • Red Hat JBoss Enterprise Application Platform
    • Red Hat OpenShift Dev Spaces
    • Red Hat OpenShift Local
    • Red Hat Developer Sandbox

      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Red Hat OpenShift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • See all technologies
    • Programming languages & frameworks

      • Java
      • Python
      • JavaScript
    • System design & architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer experience

      • Productivity
      • Tools
      • GitOps
    • Automated data processing

      • AI/ML
      • Data science
      • Apache Kafka on Kubernetes
    • Platform engineering

      • DevOps
      • DevSecOps
      • Red Hat Ansible Automation Platform for applications and services
    • Secure development & architectures

      • Security
      • Secure coding
  • Learn

    Featured

    • Kubernetes & cloud native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • AI/ML
      AI/ML Icon
    • See all learning resources

    E-books

    • GitOps cookbook
    • Podman in action
    • Kubernetes operators
    • The path to GitOps
    • See all e-books

    Cheat sheets

    • Linux commands
    • Bash commands
    • Git
    • systemd commands
    • See all cheat sheets

    Documentation

    • Product documentation
    • API catalog
    • Legacy documentation
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore the Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

Lift and shift a .NET application to OpenShift

December 1, 2025
Kevin Chung Sam Foreman
Related topics:
.NETApplication modernizationContainers
Related products:
Red Hat OpenShift

    Ask a Red Hatter to help migrate a monolithic legacy application, and they will likely ask, "Why not move to microservices?" While modernization is often the ideal path, it frequently conflicts with tight timelines and technical constraints. Rehosting—also known as lift and shift—is a migration pattern that moves an application without significantly changing the source code. This approach works well as a first step to prove viability on a new platform, such as Red Hat OpenShift, before making a full commitment.

    There are many benefits to modernizing .NET applications as well. In this article, we'll deploy a .NET application to OpenShift using the lift-and-shift pattern and identify key considerations. I selected a sample e-commerce website that uses a services-based architecture coded in .NET Aspire 9. Check out the source code in this Git repository.

    Creating the Containerfile

    I start the containerization process by creating a Containerfile to build the application image using a multi-stage build. In this approach, I use a Software Development Kit (SDK) image, such as the Red Hat .NET 9.0 SDK image. This image contains all the tooling needed to build the container. A common practice involves compiling the application to a smaller, lightweight runtime image, like the Red Hat .NET 9.0 Runtime image. However, to adhere to the lift-and-shift philosophy of minimal code changes, I opted to both compile and run the application using the SDK image.

    Let's take a look at the Containerfile itself. First, I declare the desired starting image:

    FROM registry.access.redhat.com/ubi8/dotnet-90:9.0 AS build

    Next, I set the SSL_CERT_DIR variable to allow OpenSSL to trust my base image's certificates directory. I also set the ASPIRE_CONTAINER_RUNTIME variable to instruct .NET Aspire to use Podman instead of Docker within my image, as outlined in the Microsoft documentation:

    ENV SSL_CERT_DIR=$HOME/.aspnet/dev-certs/trust:/etc/pki/tls/certs \
    ASPIRE_CONTAINER_RUNTIME=podman

    I then set the preferred working directory to place the source code to /opt/app-root/src, as this directory is read and writable accessible by the non-root user:

    WORKDIR /opt/app-root/src

    As a security best practice, application containers running on OpenShift should run as a non-root user. My monolithic application's code runs containers. Because I wrap it inside an OpenShift container (also known as the Podman-in-Podman approach), I must ensure that the outer container runs rootless Podman and maps additional UIDs and GIDs to interact with multiple user namespaces. I also set file capabilities for newuidmap and newgidmap binaries to elevate privileges for extra users and groups:

    USER root
    # Use rootless podman to launch app processes
    RUN microdnf install openssl podman -y && \
        usermod --add-subuids 100000-165535 default && \
        usermod --add-subgids 100000-165535 default && \
        setcap cap_setuid+eip /usr/bin/newuidmap && \
        setcap cap_setgid+eip /usr/bin/newgidmap && \
        chmod -R g=u /etc/subuid /etc/subgid

    To force my application image to run as a non-root user, I switch from root to user 1001. I then copy the application code to the source directory:

    USER 1001
    COPY --chown=1001:0 / /opt/app-root/src

    Next, I need to trust the ASP.NET Core development certificate, and finally build the application:

    RUN dotnet dev-certs https --trust && \
    dotnet build src/eShop.AppHost/eShop.AppHost.csproj -c Release

    The port declaration here is unnecessary because OpenShift defines the exposed ports through the application's service. However, I set the port as a best practice to declare the author's intent for users to configure the port:

    EXPOSE 19888

    A typical multi-stage build would swap out the SDK image for a lightweight runtime image. However, upon inspecting the base .NET project file, I found nested references to packages and other project files. Because I am using with a lift-and-shift approach, I kept the nested references intact. I run the application using the project definition rather than a compiled DLL, so I decided to use the SDK image as the runtime image. My Containerfile's default executable will look as follows:

    ENTRYPOINT ["bash", "-c", "dotnet run -c Release --project src/eShop.AppHost/eShop.AppHost.csproj --no-build"]

    As a side note, when I attempted to build the Containerfile using the podman build command, I received an error stating the requested SDK version must be 9.0.200, as specified in the original application's global.json file. The Red Hat .NET SDK image version is 9.0.110, so I updated global.json for compatibility with the lower version.

    The complete Containerfile can be referenced below:

    FROM registry.access.redhat.com/ubi8/dotnet-90:9.0 AS build
    
    ENV SSL_CERT_DIR=$HOME/.aspnet/dev-certs/trust:/etc/pki/tls/certs \
        ASPIRE_CONTAINER_RUNTIME=podman
    WORKDIR /opt/app-root/src
    
    USER root
    # Use rootless podman to launch app processes
    RUN microdnf install openssl podman -y && \
        usermod --add-subuids 100000-165535 default && \
        usermod --add-subgids 100000-165535 default && \
        setcap cap_setuid+eip /usr/bin/newuidmap && \
        setcap cap_setgid+eip /usr/bin/newgidmap && \
        chmod -R g=u /etc/subuid /etc/subgid
    
    USER 1001
    COPY --chown=1001:0 / /opt/app-root/src
    
    # Trust the ASP.NET Core HTTPS dev cert and build the .NET project
    RUN dotnet dev-certs https --trust && \
        dotnet build src/eShop.AppHost/eShop.AppHost.csproj -c Release
    
    EXPOSE 19888
    
    # Building the nested project file creates several .dll binaries in respective folders. I opted for a
    # lift & shift approach to run the project file, but a preferred approach would be to refactor as microservices and
    # use a multistage build to execute a single binary with the .NET runtime image per container
    ENTRYPOINT ["bash", "-c", "dotnet run -c Release --project src/eShop.AppHost/eShop.AppHost.csproj --no-build"]

    Building and deploying the application

    After completing the Containerfile, I create a set of basic OpenShift objects to handle the build and deployment. You can find them in the manifests directory in the repository. For more details on these objects, refer to the the official OpenShift documentation.

    Note that the application provides its own self-signed certificate, so I define the route as a passthrough route. Also, the application must trust the default .NET developer certificate. Because this certificate is trusted per user, the application must run using a static user ID (declared in the image as 1001). It also requires container capabilities to run Podman within Podman. The default restricted-v2 SecurityContextConstraint (SCC) is insufficient, so I opt to use the nonroot SCC to meet these requirements.

    Finally, I need to expose the application endpoints to make them accessible from outside the container. I change the launchSettings.json configuration from localhost to 0.0.0.0 (a wildcard * would have also sufficed).

    Because I have already defined my Kubernetes objects, I simply need to deploy the contents of the manifests directory to the cluster. I can do this with two simple commands:

    `oc new-project eshop`  
    `oc apply -f manifests/`

    After the objects are created, the deployment pod will fail with a CrashLoopBackoff status until the build pod completes the image build. After a few minutes, the application will be healthy and accessible through the route.

    Conclusion

    This blog demonstrates a quick and (relatively) painless migration strategy for a .NET application onto OpenShift. Now that the application is on the platform, I can take advantage of all the benefits this move has to offer.

    While out of scope for this blog, the following next steps are worth exploring:

    • Set up a CI/CD pipeline to simplify or even automate the build and deployment process.
    • Configure the monitoring stack to track metrics and send alerts.
    • Design a network policy to limit access & reduce security risk

    Finally, as mentioned in the introduction, lift-and-shift is not meant to replace a more robust modernization effort. I recommend exploring a microservices architecture once the basic deployment is complete.

    Related Posts

    • .NET 10 is now available for RHEL and OpenShift

    • Modernize your application deployment with Lift and Shift

    • JBoss EAP 8.1: Modernizing enterprise Java applications

    • Getting started with managed clusters migration

    • .NET container troubleshooting in OpenShift 4

    • How to use RHEL 10 as a WSL Podman machine

    Recent Posts

    • How to set up Red Hat Lightspeed Model Context Protocol

    • Lift and shift a .NET application to OpenShift

    • Run Ruby applications in FIPS mode on Red Hat Enterprise Linux

    • Use NetApp to run SAP on OpenShift Virtualization with a dual boot on bare metal

    • How does cgroups v2 impact Java and Node.js in OpenShift 4?

    What’s up next?

    OpenShift for .NET Developers is a practical guide that explains techniques for building, testing, and debugging .NET applications within the Red Hat OpenShift environment.

    Get the e-book
    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Platforms

    • Red Hat AI
    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Build

    • Developer Sandbox
    • Developer tools
    • Interactive tutorials
    • API catalog

    Quicklinks

    • Learning resources
    • E-books
    • Cheat sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site status dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit
    © 2025 Red Hat

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Report a website issue