Containerizing a web application

In this article, we will look at different options for containerizing a web application.

We will go through the following deployment scenarios, going from traditional options to cloud services and containers:

Deploy to local machine

Deploy to an Infrastructure as a Service (IaaS) VM

Deploy to a Platform as a Service (PaaS) website

Deploy to a container running Windows Server Core

Deploy to a container running Nano Server

Our example application is the PowerShell Universal Dashboard – a web application built on ASP .NET Core, PowerShell Core, ReactJS, and a handful of JavaScript libraries.

This means it can support cross-platform deployments, running on Windows, Linux, and macOS.

That is already a very flexible range of options, but we can get even more options by using containers.

The PowerShell Universal Dashboard PowerShell module allows for creation of web-based dashboards. The client- and server-side code for the dashboard is authored completely with PowerShell. Charts, monitors, tables and grids can easily be created with the cmdlets included in the module. The module is a cross-platform module and will run anywhere PowerShell Core can run.

Deployment options

Deploy to local machine

Let us first look at how to install and setup the application locally on a client machine.

Install the UniversalDashboard module from the PowerShell Gallery using PowerShellGet:

Install-Module UniversalDashboard

When the module is installed you will have access to a number of new commands:

Start-UDDashboard is the main command for starting a new instance of a dashboard.

For the example usage of the other commands, I would encourage you to have a look at the Components section of the beforementioned demo website.

The example dashboard I am going to use for this article is one from a real-world scenario. During an onboarding process, there was a need to gather some data (an employee number) from an external company. The link was to an instance of PowerShell Universal Dashboard, which would take the input from the external user and send it as a parameter to an Azure Automation webhook. The webhook would start an Azure Automation runbook, which would register the data in the internal system.

When the UniversalDashboard PowerShell module is installed, we can simply run the dashboard.ps1 script to start the dashboard. Here is a screenshot from performing this in Visual Studio Code:

At this point, the website should be up and running. We can test by navigating to http://localhost/register/123

In this example, we are also taking input from the URL. 123 can be any number (in the example scenario, a service request ID), and will be available as a parameter variable in the New-UDInput command.

At this point, we have the application up and running on a local computer as a PowerShell module. For production use, we want to run this on a server platform.

As stated in the product documentation, the PowerShell Universal Dashboard can be hosted in Azure or IIS:

To host a dashboard in Azure or IIS, you will need to deploy the entire module to your site or WebApp. In the root module directory, place your dashboard.ps1. You need to specify the -Wait parameter on Start-UDDashboard so that the script blocks and waits for requests in Azure or IIS. Specifying the port isn’t necessary because Azure and IIS use dynamic port tunneling.

#3 was used to build the demo website for this article, but in a production environment a more appropriate method would be to leverage continuous integration to deploy files to the website based on commits from source code.

We get many benefits from leveraging a PaaS offering such as Azure App Service:

No servers to manage (no patching, monitoring, etc)

We can add custom domains and SSL certificates as part of the service

(bigger VM sizes)

Scale in and out (more VM instances)

Deploy the application using continuous delivery such as Visual Studio Team Services

There are also many other benefits such as pre-authentication, load balancing and more.

Containerization

Next, we will look at leveraging containers – a solution to the problem of how to get software to run reliably when moved from one computing environment to another.

This is a new technology with a promise to change the IT landscape the same way as virtualization did in the early 2000s.

Deploy to a container running Windows Server Core

Our first example of containerizing the PowerShell Universal Dashboard will be based on Windows Server Core, version 1709. Since we already have the application up and running on a native operating system, it should be easy in this case to transform it into a container.

dashboard.ps1 and license.lic are the same files we used when running the application in a native operating system. These will be referenced in the Dockerfile to be copied into the container image.

In this example, we are using Docker – a container management tool – to build and deploy our demos. From the documentation:

Docker can build images automatically by reading the instructions from a Dockerfile. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession.

Let us have a look at the Dockerfile which defines our Windows Server Core, version 1709 demo website:

We are using an image from Docker Hub – a central repository for Docker images – which is built by Microsoft and have IIS pre-installed. Then we do not have to think about installing and configuring the Web-Server role.

Next, we are downloading and installing the .NET Core Windows Server Hosting bundle like we did when running the application on a native operating system.

PowerShellGet is used to download the UniversalDashboard module.

The last step is to copy the dashboard.ps1 and licence.lic files as well as exposing the port the website is running on.

When the Dockerfile is ready, we can user docker.exe to build a container image (line 4-5).

When the image is successfully built, we are ready to test it by starting a container instance using our new image (line 8).

If you have made modifications to dashboard.ps1 or simply want the latest version of the UniversalDashboard module, re-run the docker build command and the image will be updated with any changes.

Deploy to a container running Nano Server

Before we try to make our demo application run on Nano Server, there are some important changes to Nano Server introduced in Windows Server 1709 to be aware of:

Starting with the new feature release of Windows Server, version 1709, Nano Server will be available only as a container base OS image. You must run it as a container in a container host, such as a Server Core installation of Windows Server. Running a container based on Nano Server in the new feature release differs from earlier releases in these ways:

Nano Server has been optimized for .NET Core applications.

Nano Server is even smaller than the Windows Server 2016 version.

PowerShell Core, .NET Core, and WMI are no longer included by default, but you can include PowerShell Core and .NET Core container packages when building your container.

There is no longer a servicing stack included in Nano Server. Microsoft publishes an updated Nano container to Docker Hub that you redeploy.

You troubleshoot the new Nano Container by using Docker.

You can now run Nano containers on IoT Core.

One more thing that is useful to know, but not mentioned in the referenced article, is that IIS is not available in Nano Server 1709.

This means we need to take a different approach to get the application running in a Nano-based container.

PowerShell Universal Dashboard is built on top of Kestrel – a cross-platform web server for ASP.NET Core. This means we can simply run Start-UDDashboard from PowerShell Core in Nano Server 1709 to get the web application up and running.

Kestrel is a .NET Web Server implementation that has been heavily optimized for throughput performance. It’s fast and functional in getting network requests into your application, but it’s ‘just’ a raw Web server. It does not include Web management services as a full featured server like IIS does. If you run on Windows you will likely want to run Kestrel behind IIS to gain infrastructure features like port 80/443 forwarding via Host Headers, process lifetime management and certificate management to name a few.

The bottom line for all of this is if you are hosting on Windows you’ll want to use IIS and the AspNetCoreModule.

Some of the limitations can be overcome by leveraging different options such as a PaaS service or custom reverse proxy to publish the application externally, as well as a container orchestration tool such as Kubernetes for scaling and managing the application.

With these limitations in mind, let us go on and see if we can get this working on the latest version of Nano Server.

As mentioned previously, PowerShell Core has been removed from Nano Server starting with the 1709 release. The first step would be to build a new container image where PowerShell Core is included.

Luckily, the PowerShell team have published the Dockerfile for the official Docker image for PowerShell Core on GitHub.

In our custom Dockerfile, we are leveraging almost all of the Dockerfile used to build PowerShell Core. We are also using the concept of multi stage builds to include ASP .NET Core by referencing FROM microsoft/aspnetcore:2.0-nanoserver-1709 in the Dockerfile.

The only code we need to add is to download the UniversalDashboard PowerShell module as well as copy the dashboard.ps1 file as we did when running on Server Core:

When using IIS, licence.lic was automatically read by the application. This is not the case when using the module directly from PowerShell, hence we have added Set-UDLicense to specify the licence inside dashboard.ps1.

Since IIS is not available, we need to add an entry point in the Dockerfile in order to launch the dashboard.ps1 file. This will launch the website by calling Start-UDDashboard.

You might notice that PowerShell Core is called by using pwsh.exe. With the release of PowerShell Core 6.0.0-beta.9 the binary for PowerShell Core was renamed from powershell.exe and powershell on Windows and Linux/Unix/macOS respectively to pwsh.exe and pwsh. Mark Kraus has written a great article with more background info about that change.

A final note about Nano Server: On the Azure Marketplace offering for Windows Server, there is an offer called Windows Server Datacenter, version 1709 with Containers. If you deploy an instance of that image, Docker will be pre-installed and you can download the latest official Microsoft image of Nano Server from Docker Hub by running docker pull microsoft/nanoserver:1709.

Summary

In this article, we have explored various options for hosting a web application in different environments, starting with a native operating system and ending up with a very small container image based on Nano Server.

Like this:

About Jan Egil Ring

Jan Egil works as a Lead Architect on the R&D Team at Crayon, Norway.
He mainly works with automation, and has a strong passion for PowerShell. Products and services he works with includes Microsoft infrastructure products such as Windows Server, System Center and Microsoft Azure.
He is a multiple-year recipient of Microsoft Most Valuable Professional Award for his contributions in the Windows PowerShell and Cloud & Datacenter Management technical communities.
He speaks regularly at user groups and conferences, such as Nordic Infrastructure Conference (NIC), PowerShell Conference Europe and PowerShell Summit.
He is a co-founder of the Norwegian Microsoft Technology User Group (MTUG), which is an association of local MTUG user groups in Norway. He is also co-organizing the MTUG Script Club which focuses mainly on PowerShell.
You can follow him on Twitter @JanEgilRing.