Developer Advocate @docker. Microsoft MVP. Pluralsight Author.
Posts my own.

  Docker on Windows
  My Pluralsight Courses
 
 Old blog 
 Speaking
 Books
 Courses

    Windows Weekly Dockerfile #11: A Stateful ASP.NET Core App

    There are 52 Dockerfiles in the source code for my book, Docker on Windows. Perfect for a year-long blog series.

    Each week I'll look at one Dockerfile in detail, showing you what it does and how it works. This is #11 in the series, where I'll look at running a stateful ASP.NET Core web app in a Windows container.

    Stateful applications

    This week's Dockerfile is a complete - albeit simple - version of a stateful app running in Docker. It's an ASP.NET Core web app, based on version 1.1 of .NET Core. The app simply records and displays a visitor hit count, but you can run it in different ways with Docker to make the application state disposable or persistent.

    It's also a good example of a Dockerfile best practice - pinning to specific versions in your FROM instructions. The code was originally built for version 1.1 of .NET Core. The framework has moved on since this example was written, and the stable version is now 2.0 - but my Docker app will still build and run in the same way, I won't find that the tooling has moved on and broken my deployment.

    ch02-hitcount-website

    The source code for the app is stored alongside the Dockerfile. There's not much to it, it's a boilerplate ASP.NET Core web app, with the actual work done in the HomeController class. When a user browses to the site, it reads the current visitor count from a text file:

    private int GetCurrentHitCount()  
    {
        var count = 0;
        if (io.File.Exists(COUNT_FILE_NAME))
        {
            var text = io.File.ReadAllText(COUNT_FILE_NAME);
            int.TryParse(text, out count);
        }
        return count;
    }
    

    Then it increments the count, and updates the file:

    private void SaveHitCount(int count)  
    {
        io.File.WriteAllText(COUNT_FILE_NAME, count.ToString());
    }
    

    This is a simple example which obviously doesn't scale if you have multiple containers racing to update the same file. But then if you wanted to scale you wouldn't save data in a local text file.

    The path for the file is fixed in a constant:

    private const string COUNT_FILE_NAME = "app-state\\hit-count.txt";  
    

    The Dockerfile

    I use a multi-stage Dockerfile to package the app. The first stage compiles the app using Microsoft's dotnet image with the SDK installed:

    FROM microsoft/dotnet:1.1.2-sdk-nanoserver AS builder
    
    WORKDIR C:\src  
    COPY src .
    
    RUN dotnet restore; `  
        dotnet publish
    

    This copies the whole source tree into the image, restores dependencies and publishes the app.

    It's more efficient to execute the restore and publish steps separately, but I'll come to that later in the series.

    The second stage packages the published output from the builder stage into an image based on Microsoft's ASP.NET Core runtime image:

    FROM microsoft/aspnetcore:1.1.2-nanoserver
    
    WORKDIR C:\dotnetapp  
    RUN New-Item -Type Directory -Path .\app-state
    
    CMD ["dotnet", "HitCountWebApp.dll"]  
    COPY --from=builder C:\src\bin\Debug\netcoreapp1.1\publish .  
    

    In both stages, the FROM instruction uses a specific version in the image tag: 1.1.2. Right now the default latest tag for those images is 2.0.0 but because my Dockerfile pins specifically to older versions, Docker will continue to use those versions.

    Note that there's no VOLUME instruction in the Dockerfile. In Dockerfile #10 I covered Docker volumes, but you can attach a volume to a container at run-time, you don't explicitly need a volume configured in the Dockerfile.

    Usage - without volumes

    It's a straightforward docker image build and docker container run process to run the app.

    Clone the docker-on-windows repo and then build the image:

    git clone https://github.com/sixeyed/docker-on-windows.git
    
    cd docker-on-windows\ch02\ch02-hitcount-website
    
    docker image build --tag dockeronwindows/ch02-hitcount-website .
    
    docker container run --detach --publish 80 --name week-11 dockeronwindows/ch02-hitcount-website  
    

    You don't need .NET Core installed to build or run this app, the build toolchain and the runtime are all in Docker images.

    When the container's running, you can find out the IP address and connect to view the website:

    $ip = docker container inspect --format '{{ .NetworkSettings.Networks.nat.IPAddress }}' week-11
    
    start "http://$ip"  
    

    This is what you'll see:

    ASP.NET Core hitcount website running in Docker on Windows

    Looking good.

    Refresh the page lots of times and you'll see the hitcount increase. The count file is stored in the container's writeable layer, so when you replace the container the data is lost.

    Usage - with volumes

    It's simple to map the application state path to a volume, so you can update the container without losing data.

    First remove the original container:

    docker rm -f week-11  
    

    Now create a directory on the host for the volume mapping, and run a container using a volume with that path:

    mkdir C:\app-state
    
    docker container run -d -p 80 `  
     -v C:\app-state:C:\dotnetapp\app-state `
     --name week-11 `
     dockeronwindows/ch02-hitcount-website
    

    Browse to the new container and refresh to bump up the hit count:

    start "http://$(docker container inspect --format '{{ .NetworkSettings.Networks.nat.IPAddress }}' week-11)"  
    

    You can see the contents of the file from the host:

    > cat C:\app-state\hit-count.txt
    26  
    

    And now you can replace the container, and the new one will have the same state:

    docker container rm -f week-11
    
    docker container run -d -p 80 `  
     -v C:\app-state:C:\dotnetapp\app-state `
     --name week-11 `
     dockeronwindows/ch02-hitcount-website
    
    start "http://$(docker container inspect --format '{{ .NetworkSettings.Networks.nat.IPAddress }}' week-11)"  
    

    State retained

    State retained!

    Next Up

    Next week, we're going back to the future. In the book I walk through an app modernization program based on Nerd Dinner.

    That starts with ch02-nerd-dinner, which takes the 4 year old source code and packages it to run in a Docker container on Windows.


    Share this article on
    Author image
    Written by Elton Stoneman
    Developer Advocate @docker | Microsoft MVP | Pluralsight Author