Production Considerations – Windows Containers

Containerization is one of the most important technologies of our time. In an earlier post, I made the business case for this technology.

Things that are taken for granted on host OSes (domain joining, RDP/SSH access, Event Logs, Simplified App Updates..) are not necessarily straightforward in a containerized world.

This post focuses on the technical challenges that one typically needs to overcome — including — Containerizing Web Servers, Base Images and Derived Images, Containerizing Databases, Domain Joining Containers and Installing Certificates on Web Servers inside Containers.

Production Docker Tip 1 — Domain Joining Containers

One of the first issues you may encounter is:

Can my container be domain joined? If not, how do I handle windows integrated authentication in my apps?

In order for integrated windows authentication to work, your host server would typically be domain joined to the AD domain.

Containers, however, CANNOT be domain joined. So what is one to do?

Option A (best practice solution for Production Docker) — global MSA (gMSA) accounts in Active Directory

A gMSA is a special account in AD — which acts as both — a ‘user object’ and a ‘computer object’. An AD ‘user’ account (type ‘user’) is tied to a specific computer (class ‘computer’)​. What this does is — it lets the Computer pretend to be a user on the domain.

Additionally, this gMSA account does not require password management, as the password is automatically managed in AD​. It works for any computer on the domain where the gMSA is created.​

  1. Create a gMSA account in the active directory that you need to domain join to.
  2. Create a JSON file called a Credential Spec, which contains all the metadata about the gMSA. Configure your app to use the gMSA
  3. Run your docker container passing in a security-opt flag — and the name of the credentialspec file (JSON file). I typically do this inside my docker-compose.yml file.
 — security-opt “credentialspec=file://mycredspec.json” 

The beauty of this solution is that your container now ‘acts and feels’ like it is domain joined! Hence, you can continue doing windows integrated authentication for your app users (or for your database users).

Microsoft’s documentation on using gMSA’s with containers is fairly comprehensive and self-explanatory.

Option B — Adding a server as a trusted host on Windows Server, using Powershell

This won’t solve your problem of domain authentication, but it provides a workaround for communicating with additional windows servers from within the container.

winrm set winrm/config/client @{TrustedHosts="RemoteComputerName"}

Production Docker Tip 2 — Base Docker Production Image and Application build mechanism

Containers are built using layers upon layers of files (images). ​ These images are of two types — Base Images and Derived Images. ​

For Base Images, always use a trusted source (e.g. https://hub.docker.com/_/microsoft-windowsfamily-windows

For Derived Images (your custom apps), you have a few options. You can either build the app from scratch, by bundling the actual build tools into the container. This approach, while ensuring that you always get the latest build, is better suited for development and staging environments.

For production environments, a second approach is recommended.

Pre-Build your app and copy the required (published) binaries into the container.

This approach is cleaner, and lighter weight (you don’t need to package any build tools etc.)

Here’s how you might package your web app into the container a simple COPY command (where the output of your build tool is copied to the current folder inside the container).

COPY ${source:-obj/Docker/publish} .​

This single line of code allows you to package any built app (even multiple apps) into a container. If you use Visual Studio to do your development, this line is part of the auto generated Dockerfiles that Studio creates for each project in your solution.

Production Docker Tip 3 — Start with the smallest stripped down Server OS — and install missing pieces as needed.

The exact OS server image that you use will always be a subset of the full blown server OS. In addition, there could be a HUGE difference between the same server OSes — different releases.

For instance, server core 2019, is one third the size of server core 2016, without compromising any of the key server components. So, unless you have a really good reason to use Server Core 2016 as your base image, you ought to really be using server core 2019.

If there are any components missing in 2019, simply install them using simple powershell commands. Here’s how you might enable Remote Access to the IIS Manager inside your container (Remote access is NOT enabled by default)

Import-Module servermanager
Add-WindowsFeature web-mgmt-service 
Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\WebManagement\Server -Name EnableRemoteManagement -Value 1
Start-Service wmsvc

You can even add features at the component level (for example add features to SQLServer or IIS Server). Here’s how you might add integrated windows authentication to your IIS server.

RUN Add-WindowsFeature Web-Windows-Auth | Out-Null

Production Tip 4 —Bundling in SSL and TLS Certificates

As you package your app into the image, consider the customizations you need on top of your app. For instance, you may need to install SSL certificates for your web tier. This is as simple as creating a web binding (in IIS) using a Powershell command, as shown below. Again, the sheer simplicity of the command — and what it actually accomplishes, is what makes containerization such a useful technology.

New-WebBinding -Name ‘Default Web Site’ -IP ‘*’ -Port 443 -Protocol https | Out-Null;​

You could also just have the certificates pre-created on the host machine, and use a simple copy command to copy them into the container.

Production Docker Tip 5— Persisting state in Containers / Database Containers

Once you are done packaging the web server and the corresponding certificates in a container, your next challenge will involve dockerizing the database used by the web app.

Packaging an entire SQL Server Express instance into a docker image is, again, trivial, as shown below.

FROM microsoft/mssql-server-windows-express

What may not seem trivial is :

  1. How do you create your schema inside the instance?
  2. How do you populate the data? How do you ensure that your database persists it’s data when the container is destroyed?
  3. And lastly, how would you access the Sql Server instance from your web tier?

The first thing to understand is that, your SQL Server WILL need access to the underlying filesytem, to access state. Whether it be backup files, log files, or even transactional state, Relational Databases are notorious for relying on an underlying filesystem.

So, you have to grant access to the host filesystem. This is accomplished using a VOLUME mapping, as shown in the YAML snippet below.

version: '3.4'
services:
db:
ports:
- "1433"
security_opt:
- credentialspec=file://sqlserverspec.json
hostname: db
volumes:
- c:\Data:c:\Data
networks:
default:
external:
name: nat

Enter Dacpacs

The simplest way to create a new database inside your SQL Server express is to use a dacpac. This is familiar to those who have ‘published’ databases from within a Visual Studio environment.

The ‘dacpac’ file is the output of the VS publish process. To deploy this dacpac file, you can use a tool called SqlPackage.

Since the c:\data host folder is mapped to the container volume, we can check this directory when the container starts up. Should it detect an EMPTY directory, it has what it needs to deploy a new database. Should it detect a populated directory, it can simply ATTACH the database files and UPGRADE the attached database using the DACPAC. To accomplish all this, we need a custom Powershell script (say CreateDatabase.ps1) which handles all these tasks. Then, you simply COPY the powershell into the container image — and invoke it using a CMD (more on CMDs and EXECs in part 2 of this series).

COPY CreateDatabase.ps1
CMD ./CreateDatabase.ps1 -sa_password password -data_path datapath

That’s it. Although Dockerfiles are terse and to the point, there is a lot of ‘behind the scenes’ action involved with each line of code.

Where does your database container reside? Does it need to sit on the same host? How will the web server ‘discover’ the database?

Docker has built-in DNS mechanism that allows containers to discover other containers on the same Docker network (named NAT in our docker-compose.yml). All that the web container needs is the hostname exposed by the database container — and it can find the database, regardless of whether it is on the same host or another host.

Summary — Docker and Dockerfile Production Tips — Part 1

There’s a lot more to creating Production Grade Dockerfiles. Some of these Dockerfile topics will be addressed in Part 2 of Docker (Containerization) Production Tips.

  1. Events Logs, Health Monitoring
  2. Entrypoints, CMDs, Shell Mode, Exec Mode
  3. Exposing ENVIRONMENT variables
  4. Leveraging Docker Compose
  5. Restart Policies
  6. Managing Containers using Docker Swarm
  7. Troubleshooting Running Containers, Server Manager / IIS Manager

Need an experienced AWS/GCP/Azure Professional to help out with your Public Cloud Strategy? Set up a time with Anuj Varma.