Publishing to AWS CodeArtifact
AWS CodeArtifact is a managed package registry that speaks the npm protocol, integrates with IAM for access control, and can proxy the public npm registry so installs go through one controlled endpoint. For teams already on AWS it is a natural home for internal packages. Its authentication model differs from npm's, though, and that difference is the source of most day-to-day friction: tokens are short-lived (twelve hours by default) and must be fetched with the AWS CLI, which surprises teams used to long-lived npm tokens and produces 401 errors hours into a CI run or the morning after a login. This guide sets up a domain and repository, configures npm and pnpm to publish and install, handles token expiry in CI, and scopes access with IAM.
How CodeArtifact is organised
CodeArtifact has two levels: a domain, which owns storage and encryption for an organisation, and repositories inside it. Repositories can have upstreams — another CodeArtifact repository or an external connection to the public npm registry. A typical setup has one repository with an external connection that caches public packages, and one repository for internal packages that uses the first as its upstream, so consumers point at a single URL. The general concepts are covered in Private Registries and Access Control.
Creating the domain and repositories
aws codeartifact create-domain --domain acme
aws codeartifact create-repository --domain acme --repository npm-store \
--description "Proxy of the public npm registry"
aws codeartifact associate-external-connection --domain acme --repository npm-store \
--external-connection public:npmjs
aws codeartifact create-repository --domain acme --repository internal \
--upstreams repositoryName=npm-store
Authenticating npm and pnpm
The AWS CLI writes registry and token configuration for you:
aws codeartifact login --tool npm --domain acme --domain-owner 123456789012 --repository internal
That command runs npm config set for the registry URL and an auth token in your user .npmrc, valid for twelve hours by default. For a project-level setup that works with pnpm and scoped packages, fetch the token yourself and reference it through an environment variable:
export CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token \
--domain acme --domain-owner 123456789012 --query authorizationToken --output text)
# .npmrc (committed; no secrets)
@acme:registry=https://acme-123456789012.d.codeartifact.eu-west-1.amazonaws.com/npm/internal/
//acme-123456789012.d.codeartifact.eu-west-1.amazonaws.com/npm/internal/:_authToken=${CODEARTIFACT_AUTH_TOKEN}
This routes only the @acme scope to CodeArtifact. To route all installs through CodeArtifact (to benefit from caching and auditing of public packages), set registry= to the same URL instead. Scope routing is covered in Routing Scopes to Multiple Registries in .npmrc.
Configure the package to publish there:
{
"name": "@acme/sdk",
"publishConfig": {
"registry": "https://acme-123456789012.d.codeartifact.eu-west-1.amazonaws.com/npm/internal/"
}
}
Then npm publish or pnpm publish uploads to the internal repository.
Token expiry in CI and development
The most common operational problem is an expired token:
npm error code E401
npm error Unable to authenticate, your authentication token seems to be invalid.
In CI, fetch a fresh token at the start of every job, using the job's IAM role (ideally obtained through OIDC, with no stored AWS keys):
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/ci-codeartifact-publish
aws-region: eu-west-1
- name: CodeArtifact token
run: |
echo "CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token \
--domain acme --domain-owner 123456789012 --duration-seconds 3600 \
--query authorizationToken --output text)" >> "$GITHUB_ENV"
- uses: pnpm/action-setup@v4
- run: pnpm install --frozen-lockfile
- run: pnpm -r publish --no-git-checks
For developers, a small script or shell alias that refreshes the token, and a clear error message in the install instructions, save a lot of confusion. Some teams add a preinstall check that warns when CODEARTIFACT_AUTH_TOKEN is unset.
Access control with IAM
CodeArtifact permissions are IAM actions on the domain and repository, so least privilege is expressed in policies rather than registry roles:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["codeartifact:GetAuthorizationToken"],
"Resource": "arn:aws:codeartifact:eu-west-1:123456789012:domain/acme"
},
{
"Effect": "Allow",
"Action": "sts:GetServiceBearerToken",
"Resource": "*",
"Condition": { "StringEquals": { "sts:AWSServiceName": "codeartifact.amazonaws.com" } }
},
{
"Effect": "Allow",
"Action": ["codeartifact:PublishPackageVersion", "codeartifact:PutPackageMetadata", "codeartifact:ReadFromRepository", "codeartifact:GetRepositoryEndpoint"],
"Resource": [
"arn:aws:codeartifact:eu-west-1:123456789012:repository/acme/internal",
"arn:aws:codeartifact:eu-west-1:123456789012:package/acme/internal/npm/acme/*"
]
}
]
}
Give developers and most CI jobs read-only access (ReadFromRepository), and grant publish actions only to the release role. Package-level resources let you restrict publishing to specific scopes. Review these policies whenever a new team starts publishing, because it is easy to grant a broad wildcard once and forget it.
Package origin controls and upstream behaviour
When a repository has an upstream connected to the public registry, a name can in principle exist in both places — an internal @acme/sdk and a public package with the same name published by someone else. CodeArtifact's package origin controls decide, per package, whether versions may come from direct publishing, from upstreams, or both. By default, once you publish a package directly to a repository, CodeArtifact blocks new upstream versions of that package, which protects internal names from being shadowed by public packages. For packages that should only ever come from the public registry, you can block direct publishing instead.
Check and set them with aws codeartifact describe-package and put-package-origin-configuration. Reviewing origin controls for your internal scope is a cheap, high-value hardening step.
Upstream fetching has a second behaviour worth knowing: a public package version is cached in the proxy repository the first time any client requests it, and afterwards is served from the cache even if it is later removed from the public registry. That gives you resilience against unpublished or yanked packages, but also means a malicious version fetched once stays available until you delete it from the repository. Include CodeArtifact in your incident response for compromised dependencies, as described in Responding to a Compromised Dependency.
Lockfiles and resolved URLs
When installs go through CodeArtifact, lockfiles record CodeArtifact URLs as the resolved source of each package. That ties the lockfile to your domain, account and region, which is usually what you want inside an organisation — but it matters in two cases. Open-source repositories should not commit lockfiles pointing at private infrastructure, and repositories shared with contractors outside your AWS account need the same registry access or they cannot install. pnpm records tarball URLs relative to the configured registry where possible, which keeps lockfiles portable across registry URLs; npm records absolute URLs. If you migrate between registries later, regenerate the lockfile against the new registry in a dedicated pull request.
Worked example: consolidating installs through one endpoint
A company runs installs against the public npm registry directly and publishes internal packages to a self-managed Verdaccio instance that needs regular patching. They create a CodeArtifact domain with a public proxy repository and an internal repository, point every project's .npmrc at the internal repository for all packages, and switch CI to fetch tokens through OIDC roles. Internal packages are republished to CodeArtifact with their existing version history. Install logs now show one registry host, CodeArtifact's package origin controls block publishing an internal name that already exists on the public registry, and the Verdaccio server is retired.
Prevention and guardrails
- Fetch tokens per CI job through an IAM role, with a short duration.
- Reference tokens through environment variables in a committed
.npmrc; never commit a token. - Separate read and publish permissions in IAM.
- Use package origin controls to prevent dependency confusion between internal and public names, as covered in Preventing Dependency Confusion Attacks.
Frequently Asked Questions
Can I extend a token beyond twelve hours? No. Twelve hours is the maximum duration. Automate refreshing instead.
Does CodeArtifact support npm provenance and trusted publishing? Not in the same way as the public npm registry. Access control is IAM-based; for supply-chain attestations, generate and store them alongside your release process.
Can I delete a published version?
Yes, with aws codeartifact delete-package-versions or by disposing versions, subject to your IAM permissions — unlike the public registry's strict unpublish policy. Prefer deprecation-style communication anyway, since consumers may have locked the version.
How do I use CodeArtifact with Yarn Berry?
Yarn Berry ignores .npmrc. Configure npmRegistryServer (or a scoped npmScopes entry) and npmAuthToken: "${CODEARTIFACT_AUTH_TOKEN}" in .yarnrc.yml, and set npmAlwaysAuth: true so every request sends the token.
Is there a cost to proxying public packages? CodeArtifact charges for storage and requests, including cached public packages. For most teams the cost is small compared with the control and availability benefits, but monitor it if you proxy very large dependency trees across many accounts.
Can multiple AWS accounts share one domain? Yes. Domain and repository policies can grant access to other accounts in your organisation, which lets many teams use one set of repositories with central governance.
Related
- Private Registries and Access Control compares private registry options.
- Routing Scopes to Multiple Registries in .npmrc configures clients for mixed registries.
- Fixing npm 401 Unauthorized on a Private Registry handles expired-token errors in depth.
- Setting Up Verdaccio as a Private Proxy Registry is the self-hosted alternative.