DEV Community

Sebastián Gómez
Sebastián Gómez

Posted on

Windows Containers (personal) cheat sheet

I’ve been struggling to get a web app (ASP.NET 4) with some ReST services in a Windows Container. It was actually three different apps, two of them are web applications and the other is just a big batch process which must be called via MSBuild.

More than a cheat sheet, this is just a note for my future self… and maybe it’ll help someone along the way.

Image source https://blog.newrelic.com/culture/dockercon-sf/

Here’s my dockerfile, I’ll go thru each line below 👇

Here we go:

# escape=`

The escape char is used to span multiple lines. The default char is the backslash \ but since that’s also Windows directory separator it would get messy. That line can only be at the beginning, and the backtick `is the only possible value (other than the default). Read more about the escape char.

FROM microsoft/dotnet-framework:4.7.2-runtime

I’ll start from that image since it’s the needed runtime for my applications.

LABEL MAINTAINER="Seba Gómez <sgomez@genexus.com>"

Just me

# GeneXus
COPY GeneXus/ c:/GeneXus
RUN C:/GeneXus/Genexus.com /install && `
    powershell -Command "Copy-Item C:/Users/ContainerAdministrator/AppData/Roaming/GeneXus C:/Windows/SysWOW64/config/systemprofile/AppData/Roaming -Recurse"

COPY Artech.* c:/GeneXus/

# GeneXus M Service
COPY GXM_Services/ C:/GXM_Services
COPY LocalSettings.json C:/GXM_Services/GXM_Services/Services/Settings.json

# GeneXus Server
COPY GeneXusServer/ C:/GeneXusServer

This is just the copy from my host into the container. Those are the different applications that will be eventually running there. I do want to take a minute on this line:

RUN powershell -Command "Copy-Item C:/Users/ContainerAdministrator/AppData/Roaming/GeneXus C:/Windows/SysWOW64/config/systemprofile/AppData/Roaming -Recurse"

It doesn’t matter what I’m copying there but let me tell why I’m doing that. There’s a command I ran before that copies some files into the %AppData% directory. But the %AppData% directory is related to the current user… so I ran that at build time (docker build) and one of my applications tried to look for those at execution time and could not find them. Why? Because whoever copied the files is not the same user that is looking for those files. I have to copy them (manually) the where the running application will be looking for them, and that is the LocalSystem account %AppData% folder. (Stack Overlow FTW!)

#ENABLE IIS
RUN powershell -Command `
    Add-WindowsFeature Web-Server; `
    Add-WindowsFeature NET-Framework-45-ASPNET; `
    Add-WindowsFeature Web-Asp-Net45; `
    Add-WindowsFeature NET-WCF-TCP-Activation45; `
    Add-WindowsFeature NET-WCF-HTTP-Activation45; `
    Add-WindowsFeature Web-WebSockets; `
    New-WebAppPool GeneXusServerAppPool; `
    New-WebApplication -Name GeneXusServer -Site 'Default Web Site' -PhysicalPath C:\GeneXusServer\VDir -ApplicationPool GeneXusServerAppPool;

I then need to enable the IIS, I could have used one of those asp.net images… honestly?, I remember trying tons of stuff and this is the one that finally worked for me. So, I enable the Web-Server, and all those features you see there. I need all of them, maybe you don’t. This RUN command follows the best practices of keeping a bunch or RUN instructions in one single line. This is where we see the escape character, see it at the end of (almost) every line?

# Change AppPool properties
RUN c:\windows\system32\inetsrv\appcmd.exe set app /app.name:"Default Web Site/" /enabledProtocols:"http,net.tcp" & `
    c:\windows\system32\inetsrv\appcmd set apppool /apppool.name:GeneXusServerAppPool /enable32BitAppOnWin64:true & `
    c:\windows\system32\inetsrv\appcmd set apppool /apppool.name:GeneXusServerAppPool /processModel.identityType:"LocalSystem" & `
    c:\windows\system32\inetsrv\appcmd set apppool /apppool.name:GeneXusServerAppPool /managedRuntimeVersion:"v4.0"

I also discovered an IIS tool called AppCMD. There’s probably a better (PowerShell?) way of doing what I needed but I couldn’t find it. So after long hours, I figured out how to change everything I needed. Again, this is dead simple with the IIS tools, enable protocols, change the identity and set the runtime of the application pool is just a click away with GUI, not with just a command line.

# Enable https
COPY tools/enable_https.ps1 C:/tools/
RUN powershell -File .\tools\enable_https.ps1

This one is kind of embarrassing, cause I didn’t know how to do it in one or many lines in the dockerfile. So I found a little PowerShell script online and used that one to create a certificate and install it in my IIS for https support.

This is all the script does 👇

I would like to know how to do this straight in the dockerfile, if you know how to do it, please let me know.

#Install Chocolatey
RUN powershell -Command Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))

Install Chocolatey, I can't recommend using chocolatey enough. Chocolatey is the package manager for Windows, is like Ubuntu’s apt-get or whatever you use on your Mac. It’s awesome, you need to start using it in your daily basis, and it’s a must when working with Windows Containers (you’ll see why next).

# Install Microsoft Visual C++ Redistributable for Visual Studio 2017, URLRewrite Module and SQL Server CMD Utilities (BCP)
RUN choco install vcredist140 -y & `
    choco install urlrewrite -y & `
    choco install sqlserver-cmdlineutils -y

I need to install VC++ redistributable package, the URL rewrite module for IIS and the SQL Server command-line utilities because I need the bcp utility . All that is magically done by chocolatey. Seriously, go check chocolatey out.

# Install the Java SDK and Tomcat 8
RUN choco install jdk8 -y & `
    choco install tomcat -y  -params "unzipLocation=C:\\Tomcat8"

I’ll also be running some Java web applications in that container, so I need to install the Java JDK and Tomcat 8.5

# Set the needed Tomcat registry values
RUN powershell -Command `
    New-Item -Path 'HKLM:\SOFTWARE' -Name 'Apache Software Foundation'; `
    New-Item -Path 'HKLM:\SOFTWARE\Apache Software Foundation' -Name 'Tomcat'; `
    New-Item -Path 'HKLM:\SOFTWARE\Apache Software Foundation\Tomcat' -Name '8.5'; `
    New-Item -Path 'HKLM:\SOFTWARE\Apache Software Foundation\Tomcat\8.5' -Name 'Tomcat8'; `
    Set-ItemProperty -Path 'HKLM:\SOFTWARE\Apache Software Foundation\Tomcat\8.5\Tomcat8' -Name 'InstallPath' -Value 'c:\Tomcat8\apache-tomcat-8.5.12'

Also, my application tries to read the registry to know where the Tomcat is installed, unfortunately (for me) installing Tomcat via chocolatey is just like unzipping the files and does not write anything in the registry. So I had to write the needed entries myself. That’s another time when PowerShell came in handy.

# Install Tomcat as Windows Service (and set its StartType to Automatic)
RUN C:/Tomcat8/apache-tomcat-8.5.12/bin/service.bat install & `
    powershell Set-Service -Name Tomcat8 -StartupType Automatic

After that I realized that Tomcat was not starting as a service, so I had to install it and update its StartType as Automatic so it would start right away with my container.

# Install the Microsoft .NET Core SDK and Windows Server Hosting
RUN choco install dotnetcore-sdk -y & `
    choco install dotnetcore-windowshosting -y

ENV DOTNET_CLI_TELEMETRY_OPTOUT 1

I also need to run .NET Core applications, so I need to install the .NET Core SDK, because I’ll be building a project, and the Windows Server Hosting because the application will run on IIS. The last line sets the DOTNET_CLI_TELEMETRY_OPTOUT environment variable to 1 so I don’t see an ugly output, but it has not worked, I still see it and don’t know why.

EXPOSE 80 80/udp 8001 8001/udp 8080

ENTRYPOINT ["powershell", "Get-Content", "C:\\GXM_Services\\GXM_Services\\GXMBLServices.log", "-Wait", "-Tail 1"]

Finally, I need to expose the needed ports and set the ENTRYPOINT. The ENTRYPOINT is also something I fixed with PowerShell. The container had nothing exposed to the standard output, so when you started the container or tried to see the logs via docker logs nothing was there. So again, PowerShell came very handy with the command Get-Content. So what I’m doing there is printing out the last line of a log file and wait for newer lines to show up.

Looks simple now but it took me a while to configure everything I needed, especially the changes to the Application Pool.

Someday this will all be .NET Core, but right now it’s plain .NET Framework 4.7, thus, the need for a Windows Container.

When doing this I faced many tasks that are very easy to solve using Windows GUI, but that’s something that’s just not there in containers. So you’ll have to use simple CMD or PowerShell commands. I’m not a PowerShell wiz, I had to learn a lot of stuff but it’s totally worth it, if you’re not familiar with PowerShell yet, you should definitely get a big cup of coffee (or mate) and start playing with it.

Know how to improve this Dockerfile? please let me know in the comments and I’ll update it accordingly.

Latest comments (0)