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 '' week-11
start "http://$ip"
This is what you’ll see:
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 '' 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 '' week-11)"
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.
Comments