Promoting environment variables
Modern apps increasingly use environment variables for configuration settings because they're supported by practically every platform, from physical machines to PaaS, to serverless functions. All platforms use environment variables in the same way – as a store of key-value pairs – so by using environment variables for configuration, you make your app highly portable.
ASP.NET apps already have the rich configuration framework in Web.config, but with some small code changes, you can take key settings and move them to environment variables. This lets you build one Docker image for your app, which you can run in different platforms, setting environment variables in containers to change configuration.
Docker lets you specify environment variables in the Dockerfile and give them initial default values. The ENV instruction sets environment variables, and you can set either one variable or many variables in each ENV instruction. The following example is from the Dockerfile for dockeronwindows/ch03-iis-environment-variables:2e:
ENV A01_KEY A01 value ENV A02_KEY="A02 value" `
A03_KEY="A03 value"
Settings added to the Dockerfile with ENV become part of the image, so every container you run from the image will have these values set. When you run a container, you can add new environment variables or replace the values of existing image variables using the --env or -e option. You can see how environment variables work with a simple Nano Server container:
> docker container run `
--env ENV_01='Hello' --env ENV_02='World' `
mcr.microsoft.com/windows/nanoserver:1809 `
cmd /s /c echo %ENV_01% %ENV_02%
Hello World
With apps hosted in IIS, there's a complication in using environment variables from Docker. When IIS starts it reads all the environment variables from the system and caches them. When Docker runs a container with environment variables set, it writes them at the process level, but that happens after IIS has cached the original values, so they don't get updated and IIS applications won't see the new value. IIS doesn't cache machine-level environment variables in the same way however, so we can promote the values set by Docker to machine-level environment variables, and IIS apps will be able to read them.
Promoting environment variables can be done by copying them from the process level to the machine level. You can use a PowerShell script in your container startup command, which does it by looping through all process-level variables and copying them to the machine level, unless the machine-level key that already exists:
foreach($key in [System.Environment]::GetEnvironmentVariables('Process').Keys) { if ([System.Environment]::GetEnvironmentVariable($key, 'Machine') -eq $null) { $value = [System.Environment]::GetEnvironmentVariable($key, 'Process') [System.Environment]::SetEnvironmentVariable($key, $value, 'Machine') } }
You don't need to do this if you're using an image based on Microsoft's IIS image, because it's done for you with a utility called ServiceMonitor.exe, which is packaged in the IIS image. ServiceMonitor does three things – it makes process-level environment variables available, it starts a background Windows Service, and then it watches the service to make sure it keeps running. This means you can use ServiceMonitor as the start process for your container, and if the IIS Windows Service fails, ServiceMonitor will exit and Docker will see that your application has stopped.
If you want to use ServiceMonitor along with your own logic to echo out IIS logs, you need to start ServiceMonitor in the background and finish your startup command in the Dockerfile with the log read. I do this in dockeronwindows/ch03-iis-environment-variables:2e, running ServiceMonitor with PowerShell's Start-Process cmdlet:
ENTRYPOINT ["powershell"]
CMD Start-Process -NoNewWindow -FilePath C:\ServiceMonitor.exe -ArgumentList w3svc; `
Invoke-WebRequest http://localhost -UseBasicParsing | Out-Null; `
netsh http flush logbuffer | Out-Null; `
Get-Content -path 'C:\iislog\W3SVC\u_extend1.log' -Tail 1 -Wait
The application in the image is a simple ASP.NET Web Forms page that lists environment variables. I can run this in a container in the usual way:
docker container run -d -P --name iis-env dockeronwindows/ch03-iis-environment-variables:2e
When the container starts, I can get the container's port and open a browser on the ASP.NET Web Forms page a some simple PowerShell script:
$port = $(docker container port iis-env).Split(':')[1] start "http://localhost:$port"
The website show the default environment variable values from the Docker image listed as process-level variables:
You can run the same image with different environment variables, overriding one of the image variables and adding a new variable:
docker container run -d -P --name iis-env2 `
-e A01_KEY='NEW VALUE!' `
-e B01_KEY='NEW KEY!' `
dockeronwindows/ch03-iis-environment-variables:2e
Browse the new container's port and you'll see the new values written out by the ASP.NET page:
I've added support for Docker's environment variable management into an IIS image now, so ASP.NET apps can use the System.Environment class to read the configuration settings. I've retained the IIS log echo in this new image, so this is a good Docker citizen and now you can configure the application and check the logs through Docker.
One last improvement I can make is to tell Docker how to monitor the application running inside the container, so Docker can determine whether the application is healthy and take action if it becomes unhealthy.