TL;DR
You can use Docker to push packages to a NuGet feed. This blog post shows how to release a NuGet package to Amazon CodeArtifact via Docker. Source code can be found at https://github.com/NikiforovAll/docker-release-container-sample.
General
The idea behind having a release container is pretty straightforward - you can bundle artifacts and tools so the release mechanism is portable and unified because of Docker. Also, another advantage of building NuGet packages in Docker is that you don't need any dependencies installed on the build-server itself. I invite you to read Andrew's Lock post to get more details about the use case (https://andrewlock.net/pushing-nuget-packages-built-in-docker-by-running-the-container/). This blog post is focused on the practical side, let's dive into it by reviewing the Dockerfile:
- Base layer is used for publishing. It contains
aws-cli
and credential provider (AWS.CodeArtifact.NuGet.CredentialProvider
) so we can deploy to private NuGet feed as described in here. Please see the excellent guide on how to work with Docker and NuGet feeds https://github.com/dotnet/dotnet-docker/blob/main/documentation/scenarios/nuget-credentials.md. - Build layer is used for building and packing.
- Entrypoint defines custom publishing script, essentially,
dotnet nuget push
is called. Note that, you can specify additional arguments. (e.g: override--source
or provide--api-key
).
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS base
RUN apt-get update && apt install unzip && apt-get install -y curl
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
RUN unzip awscliv2.zip && ./aws/install
WORKDIR /artifacts
RUN dotnet new tool-manifest --name manifest
RUN dotnet tool install --ignore-failed-sources AWS.CodeArtifact.NuGet.CredentialProvider
RUN dotnet codeartifact-creds install
FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build
ARG Configuration="Release"
ENV DOTNET_CLI_TELEMETRY_OPTOUT=true \
DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true
WORKDIR /src
COPY ["src/ReleaseContainerSample/ReleaseContainerSample.csproj", "src/ReleaseContainerSample/"]
COPY ["tests/ReleaseContainerSample.Tests/ReleaseContainerSample.Tests.csproj", "tests/ReleaseContainerSample.Tests/"]
RUN dotnet restore "src/ReleaseContainerSample/ReleaseContainerSample.csproj"
COPY . .
RUN dotnet build "src/ReleaseContainerSample" \
--configuration $Configuration
# --no-restore
RUN dotnet test "tests/ReleaseContainerSample.Tests" \
--configuration $Configuration \
--no-build
FROM build AS publish
ARG Configuration="Release"
ARG Version=1.0.0
RUN dotnet pack "src/ReleaseContainerSample"\
-p:Version=$Version \
--configuration $Configuration \
--output /artifacts \
--include-symbols
FROM base AS final
WORKDIR /artifacts
COPY --from=publish /artifacts .
COPY ./build/publish-nuget.sh ./publish-nuget.sh
LABEL org.opencontainers.image.title="ReleaseContainerSample" \
org.opencontainers.image.description="" \
org.opencontainers.image.documentation="https://github.com/NikiforovAll/docker-release-container-sample" \
org.opencontainers.image.source="https://github.com/NikiforovAll/docker-release-container-sample.git" \
org.opencontainers.image.url="https://github.com/NikiforovAll/docker-release-container-sample" \
org.opencontainers.image.vendor=""
ENTRYPOINT ["./publish-nuget.sh"]
CMD ["--source", "https://api.nuget.org/v3/index.json"]
Before we produce artifact, we need to specify version. Let's use GitVersion
to get the build version.
$ Version=`docker run --rm -v "$(pwd):/repo" gittools/gitversion:5.6.6 /repo \
| tr { '\n' | tr , '\n' | tr } '\n' \
| grep "NuGetVersion" \
| awk -F'"' '{print $4}' | head -n1` && echo $Version
# out
1.0.1
After that, we are ready to build the release container (image)
$ docker build -f ./src/ReleaseContainerSample/Dockerfile \
--build-arg Version="$Version" \
-t release-container-example .
# check the result
$ docker image list
REPOSITORY TAG IMAGE ID CREATED SIZE
release-container-example latest 7ca4acd3845b 43 seconds ago 1.12GB
You can peek inside release container by running:
$ docker run --rm --entrypoint '/bin/ls' --name release-container-sample release-container-example
# out
ReleaseContainerSample.1.0.1.nupkg
ReleaseContainerSample.1.0.1.symbols.nupkg
publish-nuget.sh
🚀 Publish.
docker run --rm \
-e AWS_ACCESS_KEY_ID="" \
-e AWS_SECRET_ACCESS_KEY="" \
-e AWS_DEFAULT_REGION="eu-central-1" \
--name release-container-sample release-container-example \
--source "https://codeartifact.eu-central-1.amazonaws.com/nuget/codeartifact-repository/v3/index.json"
# Alternatively, you can use public NuGet repository.
docker run --rm \
--name release-container-sample release-container-example \
--source "https://api.nuget.org/v3/index.json" --api-key "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
Summary
In this blog post, I showed how you can build NuGet packages via Docker, and push them to your NuGet feed when you run the container.
Pros:
- Easy to release. The solution is portable. It's our goal after all.
- Extendable approach. You are in charge of how to build NuGet package and can install all required tools and dependencies when you need it.
Cons:
- Images can be quite sizable. Additional space is required to release containers, so a retention policy should be applied.
- Adds unnecessary complexity if you already use dotnet toolchain and you have all dependencies installed on build server.