Crossplane "cannot push package" (registry auth) in CI
crossplane xpkg push uploads an .xpkg to an OCI registry like xpkg.upbound.io or GHCR. A push fails when the runner is not logged in, the token lacks write scope, or the repository reference is wrong.
What this error means
A publish step running crossplane xpkg push fails with an "unauthorized" or "denied" error from the registry, or "cannot push package".
crossplane: error: failed to push package: PUT https://xpkg.upbound.io/v2/...:
unexpected status code 401 Unauthorized: authentication requiredCommon causes
The runner is not authenticated to the registry
No docker/registry login ran in CI, so the push is anonymous and the registry returns 401.
The token lacks write access to the repository
Authentication succeeded but the token cannot push to that repository (403/denied), often a read-only or wrong-scope token.
How to fix it
Log in with a scoped token, then push
- Log in to the registry using a CI secret with write scope.
- Push the built package to the correct repository reference.
- Confirm the token can write to that specific repository.
echo "${{ secrets.XPKG_TOKEN }}" | crossplane xpkg login \
--username=robot --password-stdin xpkg.upbound.io
crossplane xpkg push xpkg.upbound.io/org/configuration:v1.0.0 \
-f configuration.xpkgPush to a registry you can write to
For GHCR, authenticate with a token that has package write scope and push to the matching path.
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
crossplane xpkg push ghcr.io/org/configuration:v1.0.0 -f configuration.xpkgHow to prevent it
- Store a write-scoped registry token as a CI secret.
- Log in before every push step.
- Verify the token has write access to the target repository.