4 min read

Docker: NuGet Server Windows Server Core

Docker: NuGet Server Windows Server Core

I’ve set up a NuGet Server on a Dockerized Windows Server Core IIS image. This is a multi-stage build that includes an MSBuild stage to compile NuGet Server (ASP.NET) and copy that into the final image. The Dockerfile installs useful utilities like Chocolatey and Vim.

Getting Started

Make sure you are running Windows Containers.

git clone git@github.com:mrjamiebowman-blog/Docker-NuGet-Server-Windows-Core.git
cd Docker-NuGet-Server-Windows-Core
cd nugetserver

You can access the container by running this command.

docker exec -it nugetserver powershell

MSBuild Image

There are 2 Dockerfiles in this solution. The first Dockerfile in the msbuild folder was used to figure out how to create a build server that could compile ASP.NET 4.5 since NuGet Server is older code.

Some key takeaways here are, I had to use and create a FolderProfile.pubxml file to get this to publish to the correct path, and use a PowerShell script to wrap the msbuild command.

When the msbuild command is ran it causes an enormous amount of output to the screen which returns a non zero response. This causes the Docker build to fail. I was able to over come this by wrapping that process in a PowerShell script.

FROM mcr.microsoft.com/dotnet/framework/sdk:4.8 as msbuild
LABEL maintainer="@mrjamiebowman"

SHELL ["powershell"]

# install choco
RUN Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
RUN choco install git -y
RUN choco install vim -y

# set up dirs
RUN New-Item -Path C:\source -ItemType Directory -Force
RUN New-Item -Path C:\published -ItemType Directory -Force
WORKDIR /source

# clone and run msbuild
RUN git clone https://github.com/NuGet/NuGet.Server.git .
RUN nuget restore

# wrapping the msbuild command in a powershell scripts returns 0 and does not fail...
COPY scripts/msbuild.ps1 .
RUN ./msbuild.ps1
COPY scripts/FolderProfile.pubxml /source/src/NuGet.Server/Properties/PublishProfiles/FolderProfile.pubxml
COPY scripts/release.ps1 .
RUN ./release.ps1

ENTRYPOINT ["powershell"]

Publishing with MSBuild

At first, I tried several things like running the msbuild command with parameters. This didn’t go so well. Then I tried creating an msbuild script which ultimately was difficult when it came time to compile and publish the ASP.NET code. The easiest way for me was to create a FolderProfile.pubxml file.

MSBuild Command

msbuild NuGet.Server.sln /t:Rebuild /p:Configuration=Release /v:minimal
msbuild NuGet.Server.sln /p:DeployOnBuild=true /p:PublishProfile=FolderProfile


<?xml version="1.0" encoding="utf-8"?>
This file is used by the publish/package process of your Web project. You can customize the behavior of this process
by editing this MSBuild file. In order to learn more about this please visit https://go.microsoft.com/fwlink/?LinkID=208121. 
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <LastUsedPlatform>Any CPU</LastUsedPlatform>
    <SiteUrlToLaunchAfterPublish />

NuGet Server Image

Everything above is included in the Dockerfile that builds the NuGet Server.

Issues & Challenges

I ran into a few odd issues getting this to work.


ASP.NET 4.5 is not installed by default on the IIS image. However, DotNet core is installed. I was able to get past that by running the command below.

# install asp.net 4.5
RUN Add-WindowsFeature Web-Asp-Net45
RUN Add-WindowsFeature NET-Framework-45-ASPNET

Web.config Issues

I ran into a lot of issues with IIS and the web.config file. There were several sections of the web.config that were locked. To unlock them I had to run commands against the appcmd.exe.

RUN & $env:windir\system32\inetsrv\appcmd.exe unlock config -section:system.webServer/handlers
RUN & $env:windir\system32\inetsrv\appcmd.exe unlock config -section:system.webServer/modules

Enabling Directory Browsing

This was useful for debugging and you might need this at some point.

RUN & $env:windir\system32\inetsrv\appcmd set config /section:directoryBrowse /enabled:true

Viewing Errors

IIS will not display errors to the user. There are several ways to overcome this but I found this to be the easiest. While accessing the box and invoking a web request I was able to see the errors.

Invoke-WebRequest 'http://localhost/'


# build server image
FROM mcr.microsoft.com/windows/servercore/iis as nugetserver

SHELL ["powershell"]

# todo: create user for least priviliged
# todo: volume for packages

# install choco, vim
RUN Set-ExecutionPolicy Bpass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
RUN choco install vim -y

# install
RUN Add-WindowsFeature Web-Asp-Net45
RUN Add-WindowsFeature NET-Framework-45-ASPNET

# dirs
RUN New-Item -Path C:\setup -ItemType Directory -Force
RUN New-Item -Path C:\inetpub\wwwroot\Packages -ItemType Directory -Force

# clean iis folder
RUN -NoProfile -Command Remove-Item -Recurse C:\inetpub\wwwroot\*

# unlock sections in web.config
RUN & $env:windir\system32\inetsrv\appcmd.exe unlock config -section:system.webServer/handlers
RUN & $env:windir\system32\inetsrv\appcmd.exe unlock config -section:system.webServer/modules

# enable directory browsing (useful for debugging)
#RUN & $env:windir\system32\inetsrv\appcmd set config /section:directoryBrowse /enabled:true

# copy files
WORKDIR /inetpub/wwwroot
COPY --from=msbuild /published/ .

Configuring the NuGet Server

API Keys

It’s important to protect your NuGet Server. I would recommend putting this on a private network behind a firewall of some sort but even then someone could push malicious code to the repository. The web.config has 2 keys that can be modified to enforce an API key policy: “requireApiKey“, and “apiKey“.

Docker Volumes

Being that this is a stateless image it’s important to map in a volume to manage the package data. There is a configuration key in the web.config for changing the package path; search for “packagesPath”.

Restarting IIS Site

IIS by default will reload the web.config once all connections drop but if you can’t wait run this command below.

Stop-IISSite -Name 'Default Web Site'
Start-IISSite -Name 'Default Web Site'