Cracking the Code: Using GitHub NuGet Packages in ACR Docker Builds
Azure Container Registry (ACR) Tasks offer a powerful and serverless way to build Docker images directly in the cloud. When paired with Azure Container Apps, this setup forms the backbone of a lightweight GitOps workflow — clean, efficient, and scalable.
Recently, however, I encountered a challenge when integrating private NuGet packages hosted on GitHub into this flow. My local development environment had no issues restoring these packages, but the CI/CD pipeline built on ACR Tasks was failing during the dotnet restore step.
Holy smokes! What’s going on?
The Problem: dotnet restore Fails in ACR Tasks
In your local setup, tools like the .net CLI can seamlessly authenticate with GitHub to restore packages from private NuGet feeds, especially if you’ve already configured your credentials. But inside an ACR Task, which runs in a stateless, isolated context, nothing is preconfigured.
So, when the build task hits a line like dotnet restore, it fails because it can’t authenticate with GitHub Packages. The solution? We need to inject the right credentials into the build environment securely.
Step 1: Create a GitHub Personal Access Token (PAT)
Start by generating a Personal Access Token in GitHub. Note that it must be a “Classic” PAT, because that’s currently the only token type GitHub Packages accepts.

When creating the PAT, ensure it has only the following scope:
read:packages
Keep this token secure — you’ll be injecting it into your CI/CD pipeline via Azure Key Vault, not hardcoding it anywhere.
Step 2: Store the PAT in Azure Key Vault
Once you’ve created your token, store it in Azure Key Vault so it can be securely retrieved at build time. If you’re already using Terraform to manage your infrastructure (as I am), you can retrieve this secret using a data source:
data "azurerm_key_vault_secret" "nuget_pat" {
name = "nuget-pat"
key_vault_id = var.keyvault.id
}
You’ll now be able to reference the NuGet PAT securely during provisioning of your ACR Task.
Step 3: Configure Your ACR Task in Terraform
The next step is to inject this token into the ACR Task. This requires adding it as a secret argument to the task definition. At the same time, we also configure a second PAT that allows ACR to clone the source GitHub repo.
Here’s how the Terraform resource for the ACR Task might look:
resource "azurerm_container_registry_task" "foo" {
name = "foo"
container_registry_id = var.container_registry.id
platform {
os = "Linux"
}
docker_step {
dockerfile_path = "${var.dockerfile_path}/Dockerfile"
context_path = local.full_repo_path
context_access_token = data.azurerm_key_vault_secret.github_pat.value
image_names = ["foo:"]
arguments = {
GITHUB_USER = var.github_user
GITHUB_PACKAGE_SOURCE = var.github_package_source
}
secret_arguments = {
GITHUB_PAT = data.azurerm_key_vault_secret.nuget_pat.value
}
}
source_trigger {
name = "main"
events = ["commit"]
repository_url = local.full_repo
source_type = "Github"
authentication {
token = data.azurerm_key_vault_secret.github_pat.value
token_type = "PAT"
}
branch = "main"
enabled = true
}
}
Notice how this Terraform resource is using two GitHub Personal Access Tokens (PATs). One that the ACR Task uses to clone the private repository with the code and another that it passes into the Docker build command in order to allow us to run dotnet nuget add source to our private Nuget repository. Hence one PAT is called the GitHub PAT and another is called the NuGet PAT.
This configuration uses two separate PATs:
-
github_pat: Used by the ACR Task to clone the private repository. -
nuget_pat: Passed as a secret to the Docker build for authenticating with GitHub’s NuGet feed.
This separation of concerns makes the build process more secure and easier to reason about.
Step 4: Update Your Dockerfile to Accept Build Arguments
Next, you need to modify your Dockerfile to accept the injected secrets and arguments:
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
ARG GITHUB_PAT
ARG GITHUB_USER
ARG GITHUB_PACKAGE_SOURCE
Right before you run dotnet restore, you need to configure the NuGet source using the passed-in credentials. Here’s how you do that inside the Dockerfile:
RUN dotnet nuget add source $GITHUB_PACKAGE_SOURCE \
--name RevOptix-GitHub \
--username $GITHUB_USER \
--password $GITHUB_PAT \
--store-password-in-clear-text
Once this is in place, your Docker build will be able to authenticate with GitHub Packages and retrieve your private NuGet packages just like your local dev environment does.
Conclusion
Integrating GitHub-hosted NuGet packages with ACR Tasks requires a bit of work, especially when it comes to securely handling authentication. By splitting responsibilities across two PATs — one for cloning the source code and another for restoring packages — you keep your build process both secure and reliable.
This setup lets you retain the lightweight, serverless benefits of ACR Tasks while still tapping into the power of private NuGet feeds. It’s a clean approach that bridges the gap between cloud-native build pipelines and the reality of private dependencies.
No more broken dotnet restore. Crisis averted.
