GitHub action to publish .NET packages to NuGet

Photo by Jakob Owens on Unsplash

GitHub action to publish .NET packages to NuGet


3 min read

I have been a co-maintainer of .NET OSS library Marten and largely involved in taking care of the DevOps stuff pertaining to CI pipeline, build and docs system. In the early days, the build was driven using a RAKE build script written by Jeremy Miller, our lead dev and creator of Marten. NuGet packages were published from the local dev machine after confirming that we had bumped up the versions of the packages in preparation for the release. Apparently, this being a manual step, we publish the NuGet packages but sometimes tend to forget tagging and creation of release in GitHub (you know how an OSS author life is around managing day job and doing open source work).

We were also using AppVeyor for CI during the early periods of the library and I managed to add a step at the end of the CI pipeline to do a NuGet publish, when a tag was created in GitHub. This completely removed any human error or any inadvertent omissions due to lack of time etc. This worked well for us other than the occasional NuGet publish failures due to expired API key. We had to jump into AppVeyor dashboard to see what was going on and fix things. Eventually, we migrated all the CI builds to use GitHub actions so that we can lookup things all in one-place without logging into different systems. This is a huge convenience and time saver for us. We settled on doing a manual GitHub action trigger (user invoked) especially for publishing NuGet packages rather than keep it automated so that we can inspect and keep an eye on the NuGet publish as it happens after we tag and add a release in GitHub.

Following is a sample of the GitHub action YAML script for publishing package to NuGet with detailed comments adapted from the one we use with Marten :

name: NuGet Manual Publish

# workflow_dispatch is used to manually invoke the GH action
on: [workflow_dispatch]

  # Setting the required env flags
  config: Release
  dotnet_core_version: 6.0.x

    # CI running on linux
    runs-on: ubuntu-latest

      # This step clones the source code to the CI build machine
      - name: Checkout code
        uses: actions/checkout@v2

      # This step installs the .NET SDK
      - name: Install .NET 6
        uses: actions/setup-dotnet@v1
          dotnet-version: ${{ env.dotnet_core_version }}

      # Run dotnet pack to create the nupkg file for the project and store in artifacts folder
      - name: Run Pack
        run: dotnet pack src/proj/proj.csproj -o ./artifacts --configuration ${{ env.config }}
        shell: bash

      # Find all the created nupkg files and publish it to NuGet, we use the wonderful swiss-army knife capability `find -exec` to find and then execute an action on it.
      # NUGET_DEPLOY_KEY is the NuGet API key stored in the repo GH action secrets
      # If you also publish symbol packages, find the snupkg files and publish it to NuGet
      - name: Publish to NuGet
        run: |
          find . -name '*.nupkg' -exec dotnet nuget push "{}" -s -k ${{ secrets.NUGET_DEPLOY_KEY }} --skip-duplicate \;
          find . -name '*.snupkg' -exec dotnet nuget push "{}" -s -k ${{ secrets.NUGET_DEPLOY_KEY }} \;
        shell: bash

You can take look at the current version of GH action used for publishing NuGet packages in Marten here.

Stay tuned, I will be writing more posts detailing the build scripts, CI build systems (GitHub Actions, Azure Pipelines, AppVeyor), matrix builds which we use to run integration tests on various versions of Postgres based on my experiences building and maintaining these for Marten. Also will ramble on various other topics on Marten, Postgres, jsonb, Python, building desktop apps with Electron/Tauri, node.js, Tailwind CSS, alpine.js etc.

Good day :-)