Skip to main content
Redhat Developers  Logo
  • Products

    Featured

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat OpenShift AI
      Red Hat OpenShift AI
    • Red Hat Enterprise Linux AI
      Linux icon inside of a brain
    • Image mode for Red Hat Enterprise Linux
      RHEL image mode
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • Red Hat Developer Hub
      Developer Hub
    • View All Red Hat Products
    • Linux

      • Red Hat Enterprise Linux
      • Image mode for Red Hat Enterprise Linux
      • Red Hat Universal Base Images (UBI)
    • Java runtimes & frameworks

      • JBoss Enterprise Application Platform
      • Red Hat build of OpenJDK
    • Kubernetes

      • Red Hat OpenShift
      • Microsoft Azure Red Hat OpenShift
      • Red Hat OpenShift Virtualization
      • Red Hat OpenShift Lightspeed
    • Integration & App Connectivity

      • Red Hat Build of Apache Camel
      • Red Hat Service Interconnect
      • Red Hat Connectivity Link
    • AI/ML

      • Red Hat OpenShift AI
      • Red Hat Enterprise Linux AI
    • Automation

      • Red Hat Ansible Automation Platform
      • Red Hat Ansible Lightspeed
    • Developer tools

      • Red Hat Trusted Software Supply Chain
      • Podman Desktop
      • Red Hat OpenShift Dev Spaces
    • Developer Sandbox

      Developer Sandbox
      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared 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
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

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

      • Developer productivity
      • Developer Tools
      • GitOps
    • Secure Development & Architectures

      • Security
      • Secure coding
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
      • View All Technologies
    • Start exploring in the Developer Sandbox for free

      sandbox graphic
      Try Red Hat's products and technologies without setup or configuration.
    • Try at no cost
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • Java
      Java icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • API Catalog
    • Product Documentation
    • Legacy Documentation
    • Red Hat Learning

      Learning image
      Boost your technical skills to expert-level with the help of interactive lessons offered by various Red Hat Learning programs.
    • Explore Red Hat Learning
  • 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 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

The Evolution of a Linux Container

February 6, 2017
Don Schenck
Related topics:
LinuxContainers.NET
Related products:
Red Hat Enterprise Linux

Share:

    (Probably, a more accurate title would be "The Evolution of a Linux Container Developer")

    Since .NET now runs on Linux (as well as Windows and macOS), the whole world of Linux containers and microservices has opened up to .NET developers. With a large pool of developers, a long track record of success, and performance numbers that are impressive, .NET offers a great opportunity to expand the world of Linux containers to formerly Windows-centric developers.

    While it’s tempting to rush in -- and I am the first to say, “go for it” -- there are some nuances which should not be missed when running .NET code inside a Linux container. It’s far too easy to push some code into an image and be done. After all, everything happens so quickly, surely all is well. Right?

    Not necessarily. While getting your .NET code to run in a Linux container is not a trivial event, there’s an old adage to remember: “Make it work first, then make it work fast”.

    Fast, in our case, can refer to the time it takes to build the image, the time it takes to start the image, and the performance of the code inside the image. This two-part blog post will deal with the first two -- image build time and image start time. We’ll start by getting a simple .NET program to run in a container-based application and then watch the evolution of the image as it gets smaller, resulting in shorter build and load times.

    Code optimization, on the other hand, is a whole other subject about which entire books have been written.

    Lightning strikes ... and it’s alive!

    Consider a very simple example microservice that simply gives a “Hello world”-type HTTP response. That is, you point your browser to the URL and get a very simple response, including the host name. With this simple example in mind, I logged into my Linux virtual machine (VM) and downloaded the code from this repo:

    https://github.com/donschenck/dotnet_docker_msa.git

    Like any developer new to a technology -- in this case Linux containers -- I wanted to get the application up and running as soon as possible. So I hastily crafted a Dockerfile (Dockerfile.attempt1 in the repo) and built the image with the following command:

    docker build -t attempt1 -f Dockerfile.attempt1 .

    Of course, I was excited when the build successfully finished, and even more thrilled when I was able to run the image in a container using this command:

    docker run -d -p 5000:5000 --name attempt1 attempt1

    I pointed my browser to the correct URL, which is the IP address of my VM, and voila!

    The Numbers

    The first time I built the image, it took a whopping 95 seconds. Turns out it was downloading the entire Red Hat Enterprise Linux (RHEL) image with the .NET SDK installed, which weighs in at 490 MB. That also resulted in an image size of 659 MB.

    To be fair, subsequent builds would be faster, since the docker-formatted container image was already now available on my machine. I altered the source code and ran the build again; this time it took roughly 50 seconds; they resulting image size was the same at 659 MB.

    Image size matters. That's storage space being used on your machine, and while space is cheap, it's still a finite commodity. When working with containers on a regular basis, it's easy to forget about obsolete images that are just sitting around taking up space. You can fill up a disk rather quickly if you're not careful.

    So, how to make this image smaller?

    Removing Some Unneeded Parts

    Adding one simple command-line option to the dotnet restore command helped. I used dotnet restore --no-cache (see Dockerfile.attempt2) to eliminate any caching and the image size dropped to 608.6 MB -- saving 50.6 MB, a savings of over seven percent. But I wasn't satisfied -- there had to be more.

    Build the App before Building the Image

    I realized that the application was building the .NET application every time I ran an image in a container. While this took about 1.6 seconds -- not a huge time sink -- nevertheless I felt it was a waste of time. By inserting the command dotnet build between the restore, and building the application before I built the container image, it would mean a much faster container start-up. This result is in Dockerfile.attempt3. This came at the cost of storage -- the image size went up to 610.2 MB. However, the dotnet build had to be run no matter what; we might as well spend that time now and benefit every time a container is started.

    dotnet publish

    Then the lights came on since the container is a runtime environment, why not use the dotnet publish command to publish the code before putting it into the image. If I did this, I wouldn't need .NET to be already installed in my container, after all, publish allows you to build a standalone (or "self-contained") application that can run anywhere. This is what dotnet publish is all about! This has to be a huge win in terms of images size and startup time.

    I had to alter the project.json file to support the publishing; I removed the line (commented it out, actually) that told the compiler to build for a platform. You can see it in this screenshot:

    Next, I published the code by using the publish command, dotnet publish -c Release -r rheh.7.2-x64. This put all the compiled bits, including all necessary runtime bits, into a folder that I can copy into the image. It gets better: Because I don't need .NET installed, I can use a base image that is just plain RHEL without .NET on it; this will definitely save some space!

    To install the bits into the image, I used the following Dockerfile (Dockerfile.attempt4 in the repo):

    FROM registry.access.redhat.com/rhel7

    RUN yum install -y libunwind
    RUN yum install -y libicu

    ADD bin/Release/netcoreapp1.0/rhel.7.2-x64/publish/. /opt/app-root/src/

    WORKDIR /opt/app-root/src/

    EXPOSE 5000

    CMD ["/bin/bash", "-c", "/opt/app-root/src/dotnet_docker_msa"]

    Note that the two yum install commands will install some prerequisites that .NET on RHEL needs; there is no way around this. But hey, ... no big deal. I ran the docker build, and the resulting image size.

    694.6 MB! WHAT HAPPENED?

    Who Needs Cache?

    Turns out, the two yum install instructions are also building caches for future yum install commands. So if I clear out the caches immediately after each command, I should be fine. Here's the fifth iteration of my Dockerfile, Dockerfile.attempt5:

    FROM registry.access.redhat.com/rhel7

    RUN yum install -y libunwind && yum clean all
    RUN yum install -y libicu && yum clean all

    ADD bin/Release/netcoreapp1.0/rhel.7.2-x64/publish/. /opt/app-root/src/

    WORKDIR /opt/app-root/src/

    EXPOSE 5000

    CMD ["/bin/bash", "-c", "/opt/app-root/src/dotnet_docker_msa"]

    Running docker build against this Dockerfile results in an image size of... drumroll, please...

    293.7 MB. That's over 55 percent smaller than the first attempt.

    Stacking Commands Instead of Cups

    My final change, reflected in the file Dockerfile, was to stack the yum install commands, as follows:

    FROM registry.access.redhat.com/rhel7

    RUN yum install -y libunwind libicu && yum clean all
    ADD bin/Release/netcoreapp1.0/rhel.7.2-x64/publish/. /opt/app-root/src/

    WORKDIR /opt/app-root/src/

    EXPOSE 5000

    CMD ["/bin/bash", "-c", "/opt/app-root/src/dotnet_docker_msa"]

    The resulting image weighs in at 257.5 MB, down over 60 percent from my first attempt. To refresh your memory, this tells the story:

    Summary

    As we explore new technologies and patterns, we must be careful to not confuse our early results with our best efforts or practices. While early success brings excitement and encouragement, it can also blind us to progress. Be diligent, keep experimenting, and always be open to suggestions for improvement.

     

    Last updated: September 3, 2019

    Recent Posts

    • How to build a Model-as-a-Service platform

    • How Quarkus works with OpenTelemetry on OpenShift

    • Our top 10 articles of 2025 (so far)

    • The benefits of auto-merging GitHub and GitLab repositories

    • Supercharging AI isolation: microVMs with RamaLama & libkrun

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    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