This is the next post in a series on building ASP.NET Core apps in Docker. In this post, I discuss how you can create NuGet packages when you build your app in Docker using the .NET Core CLI.
There's nothing particularly different about doing this in Docker compared to another system, but there are a couple of gotchas with versioning you can run into if you're not careful.
Previous posts in this series:
- Exploring the .NET Core Docker files: dotnet vs aspnetcore vs aspnetcore-build
- Building ASP.NET Core apps using Cake in Docker
- Optimising ASP.NET Core apps in Docker - avoiding manually copying csproj files
- Optimising ASP.NET Core apps in Docker - avoiding manually copying csproj files (Part 2)
- Creating a generalised Docker image for building ASP.NET Core apps using ONBUILD
Creating NuGet packages with the .NET CLI
The .NET Core SDK and new "SDK style" .csproj format makes it easy to create NuGet packages from your projects, without having to use NuGet.exe, or mess around with .nuspec files. You can use the dotnet pack
command to create a NuGet package by providing the path to a project file.
For example, imagine you have a library in your solution you want to package:
You can pack this project by running the following command from the solution directory - the .csproj file is found and a NuGet package is created. I've used the -c
switch to ensure we're building in Release
mode:
dotnet pack ./src/AspNetCoreInDocker -c Release
By default, this command runs dotnet restore
and dotnet build
before producing the final NuGet package, in the bin folder of your project:
If you've been following along with my previous posts, you'll know that when you build apps in Docker, you should think carefully about the layers that are created in your image. In previous posts I described how to structure your projects so as to take advantage of this layer caching. In particular, you should ensure the dotnet restore
happens early in the Docker layers, so that is is cached for subsequent builds.
You will typically run dotnet pack
at the end of a build process, after you've confirmed all the tests for the solution pass. At that point, you will have already run dotnet restore
and dotnet build
so, running it again is unnecessary. Luckily, dotnet pack
includes switches to do just this:
dotnet pack ./src/AspNetCoreInDocker -c Release --no-build --no-restore
If your project has multiple projects that you want to package, you can pass in the path to a solution file, or just call dotnet pack
in the solution directory:
dotnet pack -c Release --no-build --no-restore
This will attempt to package all projects in your solution. If you don't want to package a particular project, you can add <IsPackable>false</IsPackable>
to the project's .csproj file. For example:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
</Project>
That's pretty much all there is to it. You can add this command to the end of your Dockerfile, and NuGet packages will be created for all your packable projects. There's one major point I've left out with regard to creating packages - setting the version number.
Setting the version number for your NuGet packages
Version numbers seem to be a continual bugbear of .NET; ASP.NET Core has gone through so many numbering iterations and mis-aligned versions that it can be hard for newcomers to figure out what's going on.
Sadly, the same is almost true when it comes to versioning of your .NET Project dlls. There are no less than seven different version properties you can apply to your project. Each of these has slightly different rules, and meaning, as I discussed in a previous post.
Luckily, you can typically get away with only worrying about one: Version
.
As I discussed in my previous post, the MSBuild Version
property is used as the default value for the various version numbers that are embedded in your assembly: AssemblyVersion
, FileVersion
, and InformationalVersion
, as well as the NuGet PackageVersion
when you pack your library. When you're building NuGet packages to share with other applications, you will probably want to ensure that these values are all updated.
There's two primary ways you can set the Version
property for your project
- Set it in your .csproj file
- Provide it at the command line when you
dotnet build
your app.
Which you choose is somewhat a matter of preference - if in your .csproj, then the build number is checked into source code and will picked up automatically by the .NET CLI. However, be aware that if you're building in Docker (and have been following my optimisation series), then updating the .csproj will break your layer cache, so you'll get a slower build immediately after bumping the version number.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Version>0.1.0</Version>
</PropertyGroup>
</Project>
One reason to provide the Version
number on the command line is if your app version comes from a CI build. If you create a NuGet package in AppVeyor/Travis/Jenkins with every checkin, then you might want your version numbers to be provided by the CI system. In that case, the easiest approach is to set the version at runtime.
In principle, setting the Version
just requires passing the correct argument to set the MSBuild property when you call dotnet
:
RUN dotnet build /p:Version=0.1.0 -c Release --no-restore
RUN dotnet pack /p:Version=0.1.0 -c Release --no-restore --no-build
However, if you're using a CI system to build your NuGet packages, you need some way of updating the version number in the Dockerfile dynamically. There's several ways you could do this, but one way is to use a Docker build argument.
Build arguments are values passed in when you call docker build
. For example, I could pass in a build argument called Version
when building my Dockerfile using:
docker build --build-arg Version="0.1.0" .
Note that as you're providing the version number on the command line when you call
docker build
you can pass in a dynamic value, for example an Environment Variable set by your CI system.
In order for your Dockerfile to use the provided build argument, you need to declare it using the ARG
instruction:
ARG Version
To put that into context, the following is a very basic Dockerfile that uses a version provided in --build-args
when building the app
FROM microsoft/dotnet:2.0.3-sdk AS builder
ARG Version
WORKDIR /sln
COPY . .
RUN dotnet restore -c Release
RUN dotnet build /p:Version=$Version -c Release --no-restore
RUN dotnet pack /p:Version=$Version -c Release --no-restore --no-build
Warning: This Dockerfile is VERY basic - don't use it for anything other than as an example of using
ARG
!
After building this Dockerfile you'll have an image that contains the NuGet packages for your application. It's then just a case of using dotnet nuget push
to publish your package to a NuGet server. I won't go into details on how to do that in this post, so check the documentation for details.
Summary
Building NuGet packages in Docker is much like building them anywhere else with dotnet pack
. The main things you need to take into account are optimising your Dockerfile to take advantage of layer caching, and how to set the version number for the generated packages. In this post I described how to use the --build-args
argument to update the Version
property at build time, to give the smallest possible effect on your build cache.