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.

GitHUb: Docker NuGet Server Windows Core

Also, if you’re looking for a more modern NuGet server that runs in .NET Core on Linux… Check out BaGet.

GitHub: BaGet

Get Started

Make sure you are running Windows Containers.

git clone [email protected]: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 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(''))
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 .
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 
<Project ToolsVersion="4.0" xmlns="">
    <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 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 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(''))
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'