Home β€Ί .NET on AWS β€Ί Using CodeArtifact for .NET NuGet Packages

Using CodeArtifact for .NET NuGet Packages

Hosting private NuGet packages on AWS CodeArtifact: publishing, consuming, CI/CD integration, and CDK setup.

What Is CodeArtifact?

CodeArtifact is AWS's managed artifact repository. It hosts NuGet, npm, PyPI, and Maven packages. For .NET teams, it replaces running your own NuGet server (ProGet, MyGet, Azure Artifacts) with a managed service that integrates with IAM for access control.

The main value: your private packages live in the same ecosystem as your build pipeline. No external service to manage, no separate credentials to rotate. CodeBuild, Lambda, and your developer workstations all authenticate with the same IAM roles.

Setting Up a NuGet Repository

Domain and repository structure

CodeArtifact uses a two-level hierarchy:

  • Domain: organizational boundary (usually one per org/team)
  • Repository: the actual package feed (can have multiple: dev, staging, prod)

Repositories can have upstreams. When a package isn't found locally, CodeArtifact checks the upstream. Point your repo upstream at nuget.org to cache public packages too.

Publishing Packages

From your local machine

# Get auth token (valid 12 hours)
export CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token \
  --domain my-org \
  --domain-owner 123456789012 \
  --query authorizationToken \
  --output text)

# Add source (one-time setup per machine)
dotnet nuget add source \
  "https://my-org-123456789012.d.codeartifact.us-east-1.amazonaws.com/nuget/my-packages/v3/index.json" \
  --name CodeArtifact \
  --username aws \
  --password $CODEARTIFACT_AUTH_TOKEN \
  --store-password-in-clear-text

# Pack and push
dotnet pack -c Release -o ./packages
dotnet nuget push ./packages/*.nupkg --source CodeArtifact

From CodeBuild (CI/CD)

version: 0.2

phases:
  pre_build:
    commands:
      - export CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token --domain my-org --domain-owner 123456789012 --query authorizationToken --output text)
      - export NUGET_SOURCE="https://my-org-123456789012.d.codeartifact.us-east-1.amazonaws.com/nuget/my-packages/v3/index.json"
      - dotnet nuget add source "$NUGET_SOURCE" --name CodeArtifact --username aws --password $CODEARTIFACT_AUTH_TOKEN --store-password-in-clear-text

  build:
    commands:
      - dotnet build -c Release
      - dotnet test -c Release --no-build
      - dotnet pack -c Release -o packages --no-build

  post_build:
    commands:
      - dotnet nuget push packages/*.nupkg --source CodeArtifact --skip-duplicate

The --skip-duplicate flag prevents failures when re-running a build for a version that already exists.

Consuming Packages

NuGet.Config for local development

Create a NuGet.Config at the solution root:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="CodeArtifact" value="https://my-org-123456789012.d.codeartifact.us-east-1.amazonaws.com/nuget/my-packages/v3/index.json" />
  </packageSources>
  <packageSourceCredentials>
    <CodeArtifact>
      <add key="Username" value="aws" />
      <add key="ClearTextPassword" value="%CODEARTIFACT_AUTH_TOKEN%" />
    </CodeArtifact>
  </packageSourceCredentials>
</configuration>

Login helper script

Since tokens expire after 12 hours, keep a helper script:

#!/bin/bash
# login-codeartifact.sh

TOKEN=$(aws codeartifact get-authorization-token \
  --domain my-org \
  --domain-owner 123456789012 \
  --query authorizationToken \
  --output text)

# Update the source credentials
dotnet nuget update source CodeArtifact \
  --username aws \
  --password $TOKEN \
  --store-password-in-clear-text

echo "CodeArtifact token refreshed (valid 12 hours)"

Upstream Repositories (Caching nuget.org)

Configure your CodeArtifact repository to upstream to nuget.org. This way:

  1. Public packages are cached in your CodeArtifact repo
  2. If nuget.org goes down, cached packages still resolve
  3. One NuGet source for everything (private + public)
  4. You get visibility into what public packages your org uses

Developers only need the CodeArtifact source in their NuGet.Config. It proxies nuget.org transparently.

Versioning Strategy

For internal packages used across services:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <PackageId>MyOrg.SharedModels</PackageId>
    <Version>1.0.0</Version>
    <Authors>My Org</Authors>
    <Description>Shared domain models and contracts</Description>
  </PropertyGroup>
</Project>

Semantic versioning in CI

# In buildspec, compute version from git
phases:
  build:
    commands:
      - export VERSION="1.0.$(date +%Y%m%d).$(git rev-parse --short HEAD)"
      - dotnet pack -c Release -o packages /p:Version=$VERSION

Or use a version.json file and bump on each release.

CDK Setup (C#)

using Amazon.CDK;
using Amazon.CDK.AWS.CodeArtifact;
using Amazon.CDK.AWS.IAM;

// Domain
var domain = new CfnDomain(this, "ArtifactDomain", new CfnDomainProps
{
    DomainName = "my-org",
});

// Repository with nuget.org upstream
var nugetUpstream = new CfnRepository(this, "NuGetUpstream", new CfnRepositoryProps
{
    DomainName = domain.DomainName,
    RepositoryName = "nuget-store",
    ExternalConnections = new[] { "public:nuget-org" },
});

var internalRepo = new CfnRepository(this, "InternalPackages", new CfnRepositoryProps
{
    DomainName = domain.DomainName,
    RepositoryName = "my-packages",
    Upstreams = new[] { nugetUpstream.RepositoryName },
    Description = "Internal NuGet packages with nuget.org upstream",
});

// Grant CodeBuild project access to publish
buildProject.AddToRolePolicy(new PolicyStatement(new PolicyStatementProps
{
    Actions = new[]
    {
        "codeartifact:GetAuthorizationToken",
        "codeartifact:GetRepositoryEndpoint",
        "codeartifact:ReadFromRepository",
        "codeartifact:PublishPackageVersion",
        "codeartifact:PutPackageMetadata",
    },
    Resources = new[]
    {
        $"arn:aws:codeartifact:{Aws.REGION}:{Aws.ACCOUNT_ID}:domain/my-org",
        $"arn:aws:codeartifact:{Aws.REGION}:{Aws.ACCOUNT_ID}:repository/my-org/my-packages",
    },
}));

// Grant developers read access
buildProject.AddToRolePolicy(new PolicyStatement(new PolicyStatementProps
{
    Actions = new[] { "sts:GetServiceBearerToken" },
    Resources = new[] { "*" },
    Conditions = new Dictionary<string, object>
    {
        ["StringEquals"] = new Dictionary<string, string>
        {
            ["sts:AWSServiceName"] = "codeartifact.amazonaws.com",
        },
    },
}));

Cross-Account Access

For organizations with multiple AWS accounts (dev, staging, prod), you can share a CodeArtifact domain across accounts:

// Domain policy allowing cross-account access
var domainPolicy = new PolicyDocument(new PolicyDocumentProps
{
    Statements = new[]
    {
        new PolicyStatement(new PolicyStatementProps
        {
            Effect = Effect.ALLOW,
            Principals = new[] { new AccountPrincipal("111111111111") }, // dev account
            Actions = new[]
            {
                "codeartifact:GetAuthorizationToken",
                "codeartifact:GetRepositoryEndpoint",
                "codeartifact:ReadFromRepository",
            },
            Resources = new[] { "*" },
        }),
    },
});

Tips

  • Cache public packages. Set up nuget.org as an upstream so your builds don't depend on nuget.org availability.
  • Use --skip-duplicate on publish. Without it, pushing an existing version fails the build. With it, idempotent re-runs work fine.
  • Token expiry is 12 hours by default. For local development, run the login script at the start of your day. For CI, generate a fresh token on each build (CodeBuild's IAM role handles this).
  • Don't commit tokens. Use environment variables or the credential provider pattern. Never put ClearTextPassword in a checked-in NuGet.Config. Use a .gitignored local config or environment variable substitution.
  • Scope IAM permissions narrowly. CI gets publish access to the internal repo; developer roles get read-only. No one needs write access to the nuget.org upstream cache.

Further Reading

Looking for hands-on help? View my .NET on AWS services β†’

Managing internal packages?

Drop me a message β€” I typically respond within one business day.