Writing a Dockerfile for NerdDinner

I'll follow the multi-stage build approach for NerdDinner, so the Dockerfile for the dockeronwindows/ch-02-nerd-dinner:2e images starts with a builder stage:

# escape=`
FROM microsoft/dotnet-framework:4.7.2-sdk-windowsservercore-ltsc2019 AS builder

WORKDIR C:srcNerdDinner
COPY srcNerdDinnerpackages.config .
RUN nuget restore packages.config -PackagesDirectory ..packages

COPY src C:src
RUN msbuild NerdDinner.csproj /p:OutputPath=c:out /p:Configuration=Release

The stage uses microsoft/dotnet-framework as the base image for compiling the application. This is an image which Microsoft maintains on Docker Hub. It's built on top of the Windows Server Core image, and it has everything you need to compile .NET Framework applications, including NuGet and MSBuild. The build stage happens in two parts:

  1. Copy the NuGet packages.config file into the image, and then run nuget restore.
  2. Copy the rest of the source tree and run msbuild.

Separating these parts means Docker will use multiple image layers: the first layer will contain all the restored NuGet packages, and the second layer will contain the compiled web app. This means I can take advantage of Docker's layer caching. Unless I change my NuGet references, the packages will be loaded from the cached layer and Docker won't run the restore part, which is an expensive operation. The MSBuild step will run every time any source files change.

If I had a deployment guide for NerdDinner, before the move to Docker, it would look something like this:

  1. Install Windows on a clean server.
  2. Run all Windows updates.
  3. Install IIS.
  4. Install .NET.
  5. Set up ASP.NET.
  6. Copy the web app into the C drive.
  7. Create an application pool in IIS.
  8. Create the website in IIS using the application pool.
  9. Delete the default website.

This will be the basis for the second stage of the Dockerfile, but I will be able to simplify all the steps. I can use Microsoft's ASP.NET Docker image as the FROM image, which will give me a clean install of Windows with IIS and ASP.NET installed. That takes care of the first five steps in one instruction. This is the rest of the Dockerfile for dockeronwindows/ch-02-nerd-dinner:2e:

FROM mcr.microsoft.com/dotnet/framework/aspnet:4.7.2-windowsservercore-ltsc2019
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop']

ENV BING_MAPS_KEY bing_maps_key
WORKDIR C: erd-dinner

RUN Remove-Website -Name 'Default Web Site'; `
New-Website -Name 'nerd-dinner' `
-Port 80 -PhysicalPath 'c: erd-dinner' `
-ApplicationPool '.NET v4.5'

RUN & c:windowssystem32inetsrvappcmd.exe `
unlock config /section:system.webServer/handlers

COPY --from=builder C:out\_PublishedWebsitesNerdDinner C: erd-dinner
Microsoft uses both Docker Hub and MCR to store their Docker images. The .NET Framework SDK is on Docker Hub, but the ASP.NET runtime image is on MCR. You can always find where an image is hosted by checking on Docker Hub.

Using the escape directive and SHELL instruction lets me use normal Windows file paths without double backslashes, and PowerShell-style backticks to separate commands over many lines. Removing the default website and creating a new website in IIS is simple with PowerShell, and the Dockerfile clearly shows me the port the app is using and the path of the content.

I'm using the built-in .NET 4.5 application pool, which is a simplification from the original deployment process. In IIS on a VM you'd normally have a dedicated application pool for each website in order to isolate processes from each other. But in the containerized app, there will be only one website running. Any other websites will be running in other containers, so we already have isolation, and each container can use the default application pool without worrying about interference.

The final COPY instruction copies the published web application from the builder stage into the application image. It's the last line in the Dockerfile to take advantage of Docker's caching again. When I'm working on the app, the source code will be the thing I change most frequently. The Dockerfile is structured so that when I change code and run docker image build, the only instructions that run are MSBuild in the first stage and the copy in the second stage, so the build is very fast.

This could be all you need for a fully functioning Dockerized ASP.NET website, but in the case of NerdDinner there is one more instruction, which proves that you can cope with awkward, unexpected details when you containerize your application. The NerdDinner app has some custom configuration settings in the system.webServer section of its Web.config file, and by default the section is locked by IIS. I need to unlock the section, which I do with appcmd in the second RUN instruction.

Now I can build the image and run a legacy ASP.NET app in a Windows container:

docker container run -d -P dockeronwindows/ch02-nerd-dinner:2e

I can get the container's published port with docker container port, and browse to the NerdDinner home page:

That's a six-year old application running in a Docker container with no code changes. Docker is a great platform for building new apps and modernizing old apps, but it's also a great way to get your existing applications out of the data center and into the cloud, or to move them from old versions of Windows which no longer have support, like Windows Server 2003 and (soon) Windows Server 2008.

At this point the app isn't fully functional, I just have a basic version running. The Bing Maps object doesn't show a real map because I haven't provided an API key. The API key is something that will change for every environment (each developer, the test environments, and production will have different keys).

In Docker, you manage environment configuration with environment variables and config objects, which I will use for the next iteration of the Dockerfile in Chapter 3, Developing Dockerized .NET Framework and .NET Core Applications.

If you navigate around this version of NerdDinner and try to register a new user or search for a dinner, you'll see a yellow crash page telling you the database isn't available. In its original form NerdDinner uses SQL Server LocalDB as a lightweight database and stores the database file in the app directory. I could install the LocalDB runtime into the container image, but that doesn't fit with the Docker philosophy of having one application per container. Instead, I'll build a separate image for the database so I can run it in its own container.

I'll be iterating on the NerdDinner example in the next chapter, adding configuration management, running SQL Server as a separate component in its own container, and demonstrating how you can start modernizing traditional ASP.NET apps by making use of the Docker platform.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.134.130