Anthony Chu Contact Me

ASP.NET Web.config Transforms in Docker Containers

Wednesday, June 29, 2016

Update - December 23, 2016

Article and samples have been updated to work with the latest microsoft/iis image, Docker, and Windows Server 2016 updates.

Update - November 12, 2017

Check out this better approach for applying config transforms in your ASP.NET apps in Windows containers.

In the last article, we walked through how to run an ASP.NET 4.6 application in a Windows Server container. In that example, we didn't have a way of applying environment-specific transformations to the Web.config. Today we'll look into how to do that.

Environment Variables in Docker

If the exact same Docker image is deployed to different environments, how do we manage environment-specific settings in each running container? The answer to this is environment variables. When we first start up a Docker container (e.g., using docker run), we can pass one or more environment variables to the container. These variables are available to the application or script that is launched as the entrypoint of the container.

In ASP.NET 4.6 and earlier, it's common to use configuration transformations to change values in the Web.config to the correct settings for the environment. When we start a container for the first time, we can pass an environment variable into the container to signal which config transformation to apply.

Web.config Transformations Inside a Container

To apply config transforms inside a container, we first have to install a command line tool that'll do the work. We can add this line to the Dockerfile to install the WebConfigTransformRunner utility inside the container image:

nuget install WebConfigTransformRunner -Version 1.0.0.1

The next thing we need to do is to create a PowerShell script that we'll execute when a container first starts up that'll read an environment variable called ASPNET_ENVIRONMENT and apply the transformation if needed. Here's a file we'll call InitializeContainer.ps1. And of course we'll also need to create Web.config transformations that are properly named. For example, Web.DEV.config and Web.PROD.config.

If (Test-Path Env:\ASPNET_ENVIRONMENT)
{
    \WebConfigTransformRunner.1.0.0.1\Tools\WebConfigTransformRunner.exe \inetpub\wwwroot\Web.config "\inetpub\wwwroot\Web.$env:ASPNET_ENVIRONMENT.config" \inetpub\wwwroot\Web.config
}
Write-Host "IIS Started..."
while ($true) { Start-Sleep -Seconds 3600 }

We've also added an infinite loop at the end of the file to keep the container from exiting.

Next we modify the Dockerfile to run the InitializeContainer.ps1 script as the entrypoint. The entire Dockerfile looks like this:

FROM microsoft/iis

# Install Chocolatey
RUN @powershell -NoProfile -ExecutionPolicy Bypass -Command "$env:ChocolateyUseWindowsCompression='false'; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"

# Install build tools
RUN powershell add-windowsfeature web-asp-net45 \
    && choco install microsoft-build-tools -y --allow-empty-checksums -version 14.0.23107.10 \
    && choco install dotnet4.6-targetpack --allow-empty-checksums -y \
    && choco install nuget.commandline --allow-empty-checksums -y \
    && nuget install MSBuild.Microsoft.VisualStudio.Web.targets -Version 14.0.0.3 \
    && nuget install WebConfigTransformRunner -Version 1.0.0.1

RUN powershell remove-item C:\inetpub\wwwroot\iisstart.*

# Copy files
RUN md c:\build
WORKDIR c:/build
COPY . c:/build

# Restore packages, build, copy
RUN nuget restore \
    && "c:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe" /p:Platform="Any CPU" /p:VisualStudioVersion=12.0 /p:VSToolsPath=c:\MSBuild.Microsoft.VisualStudio.Web.targets.14.0.0.3\tools\VSToolsPath DockerDemo.sln \
    && xcopy c:\build\DockerDemo\* c:\inetpub\wwwroot /s

ENTRYPOINT powershell .\InitializeContainer

Starting the Containers

Now when we build the new image and start a container, we can supply an environment variable called ASPNET_ENVIRONMENT:

docker run -d -p 80:80 -e ASPNET_ENVIRONMENT=PROD aspnet4x

If we browse to the container host at port 80, we'll see that the production transformation did, in fact, apply:

Production

And we can start another container for the DEV environment on another port:

docker run -d -p 81:80 -e ASPNET_ENVIRONMENT=DEV aspnet4x

Production

Source Code

Here's the GitHub repo from the previous article updated with these changes: https://github.com/anthonychu/aspnet-4.x-docker-windows-container