Step-by-step guides to authenticating and pushing Docker images to the Amazon ECR Public Gallery and GitHub Container Registry (GHCR). Each section covers login, tagging/pushing, and troubleshooting authorization errors that are frequently encountered.
GitHub Container Registry (GHCR)¶
A guide to authenticating to GitHub Container Registry with a personal access token (PAT) or the GitHub CLI, pushing images, and troubleshooting common authorization errors.
Note: GHCR images are namespaced under a GitHub user or organization, e.g.
ghcr.io/<owner>/myrepo. Package visibility (public/private) and access are managed per-package in GitHub, separately from the repository the code lives in.
Prerequisites¶
The following software needs to be installed.
Docker Desktop installed and running.
A GitHub account with permission to push packages to the target owner/organization.
Either:
A personal access token (classic) with the
write:packagesscope (andread:packages,delete:packagesif needed), orOptional: The GitHub CLI (
gh) installed and authenticated
Create a Personal Access Token (One-time)¶
In GitHub select: Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token.
Select these scopes:
write:packages: required to pushread:packages: required to pulldelete:packages: optional, only if you need to delete versions
Save the token in a safe location, because GitHub only shows it once.
Alternatively, skip token management entirely and use
gh auth login, thengh auth tokento retrieve a token for the login step below.
Log in to GHCR¶
Using a Personal Access Token (PAT):
export CR_PAT=<YOUR_TOKEN>
echo $CR_PAT | docker login ghcr.io -u <github_username> --password-stdinOr, using the GitHub CLI’s token:
gh auth login
gh auth token | docker login ghcr.io -u <github_username> --password-stdinA successful login prints Login Succeeded.
Note: Classic PATs don’t expire unless an expiration date is set when creating them. If an expiration date is set, a new token needs to be generated when it expires.
Tag and Push an Image¶
Rename or tag the image with the GitHub Container Registry address, GitHub username, the image name, and tag.
# Tag your local image with the full GHCR URI
docker tag my_geolab:latest ghcr.io/<github_username>/my_geolab:latestPublish or push the image to GHCR.
# Push it
docker push ghcr.io/<github_username>/my_geolab:latestReplace <github_username> with your GitHub username or organization name. The first push to a new name automatically creates the package; you can adjust its visibility (public/private) and linked repository afterward in Package settings.
Set the Repository to Public¶
Images pushed to GHCR are private by default. The image must be set to public for GeoLab to access it. These steps change a GitHub Container Registry (GHCR) package’s visibility so the image can be pulled without authentication.
Steps:
Navigate to your packages:
User-owned:
https://github.com/users/<github_username>/packagesOr go to your profile or org page and click the Packages tab.
Select the container image (package) you want to change.
On the package page, select Package settings (right-hand sidebar or the gear icon).
Scroll down to the Danger Zone section.
Select Change visibility.
In the dialog, select Public.
Type the package name to confirm, then click I understand the consequences, change package visibility.
The image is now publicly accessible without authentication.
Troubleshooting GHCR¶
A 401 Unauthorized or 403 Forbidden are the two most common errors when pushing an image to GHCR.
401 Unauthorized¶
You are not authenticated: the token is stale, expired, lacks scope, or was never provided to Docker.
Regenerate or refresh your token. Classic PATs with an expiration date need to be re-created once they lapse.
Confirm the token has
write:packagesscope. A token missing this scope will authenticate but fail on push.Re-run the login step with a fresh token:
echo $CR_PAT | docker login ghcr.io -u <GITHUB_USERNAME> --password-stdinCheck the username matches the token’s owner: a mismatched
-uvalue is a common cause of 401s.
A mid-session 401 usually means the PAT expired or was revoked. Generate a new one and log in again.
403 Forbidden¶
You are authenticated, but you lack permission, or the package’s access settings are blocking you.
Verify token scopes include
write:packages(andread:packagesfor pulling).Check package visibility and access. For an existing package, go to its Package settings → Manage Actions access / Invite teams or people and confirm your account has write access.
Confirm the namespace is correct. Pushing to
ghcr.io/<OWNER>/...where<OWNER>is an organization requires that the organization has enabled package creation for your role, and that you’re a member with sufficient permissions.If using GitHub Actions, make sure the workflow has
permissions: packages: writeset, since the defaultGITHUB_TOKENscope is otherwise read-only.
Clearing credentials and starting fresh¶
If refreshing the token doesn’t fix a 401/403, clear cached Docker credentials and log in again from scratch.
Log out of GHCR and clear stored credentials:
# Log out of the registry
docker logout ghcr.ioThis removes the entry from Docker’s credential store (~/.docker/config.json). To clear all stored Docker credentials, not just this registry:
# Inspect first, then remove
cat ~/.docker/config.json
rm ~/.docker/config.jsonRevoke and replace the token:
In GitHub, go to Settings → Developer settings → Personal access tokens and delete the old token.
Generate a new one with the correct scopes (see Step 1).
If you used
gh auth login, rungh auth logoutfirst, thengh auth loginagain.
After clearing both, re-run the full login sequence from the Log in to GHCR step:
echo $CR_PAT | docker login ghcr.io -u <GITHUB_USERNAME> --password-stdinGHCR Quick reference¶
| Error | Meaning | First thing to try |
|---|---|---|
401 Unauthorized | Not authenticated / token expired or wrong scope | Regenerate token, re-run docker login |
403 Forbidden | Authenticated but not permitted | Check token scope and package access settings |
Token lifetimes at a glance¶
Classic PAT: No expiration by default, unless one was set at creation
gh auth token: Tied to yourghCLI session; refresh withgh auth loginif it stops working
When a token expires or is revoked, re-authenticate from the relevant step above.
Amazon Web Services Public Elastic Container Registry (ECR)¶
A step-by-step guide to authenticating and granting Amazon ECR credentials to Docker for pushing images to ECR, plus troubleshooting the two common authorization errors.
ECR Public vs. private ECR: Public repositories live at
public.ecr.aws/<REGISTRY_ALIAS>and use theaws ecr-publiccommand set. Authentication must be done inus-east-1, regardless of where you or your users are located.
Prerequisites¶
AWS CLI v2 installed (
aws --version).AWS profile configured.
Docker installed and running.
A public registry alias (find it in the ECR console under Public registries, e.g.
a1b2c3d4).
Create an AWS ECR Public Repository¶
Create an ECR public repository to store your images. If you don’t have a repository, AWS will create one, but it’s better to choose your own repository name. Choose a name to replace <my-public-repo>.
aws ecr-public create-repository \
--repository-name <my-public-repo> \
--region us-east-1Getting Public ECR Credentials¶
Authenticate Docker to a public ECR registry. Use the ecr-public command, and note that the region must be us-east-1:
aws ecr-public get-login-password --region us-east-1 --profile <user-profile> \
| docker login --username AWS --password-stdin \
public.ecr.awsReplace <user-profile> with your profile. The login host for the public gallery is always public.ecr.aws; including the registry alias is unnecessary. A successful login prints Login Succeeded.
Note: The token returned by
get-login-passwordis valid for 12 hours. After it expires, re-run this step. The public login endpoint only responds inus-east-1.
Tag and Push the Image¶
To push an image to public ECR, tag the image, replacing <registry_alias> with the repository name. This tells ECR where the image is stored.
# Tag your local image with the full public ECR URI
docker tag my_geolab:latest \
public.ecr.aws/<registry_alias>/my_geolab:latestThe next step is to push the image to public ECR.
# Push it
docker push public.ecr.aws/<registry_alias>/my_geolab:latestTroubleshooting ECR¶
A 401 Unauthorized or 403 Forbidden are the two most common errors when pushing an image to ECR.
401 Unauthorized¶
You are not authenticated: the Docker auth token is stale, expired, or was never issued.
Refresh your SSO session, since it likely expired:
aws sso login --profile <user-profile>Mint a fresh Docker token by re-running the
get-login-password | docker loginstep in the “Getting Public ECR Credentials” section.Confirm your identity resolves:
aws sts get-caller-identity --profile <user-profile>Check the login region:
aws ecr-public get-login-passwordonly works with--region us-east-1, and using any other region is a common cause of failed logins.
A mid-session 401 almost always just means the 12-hour Docker token has expired. Re-login and retry.
403 Forbidden¶
You are authenticated, but you lack permission, or a policy is blocking the action.
Verify IAM permissions. Your role needs, at minimum:
ecr-public:GetAuthorizationTokenecr-public:BatchCheckLayerAvailabilityecr-public:InitiateLayerUploadecr-public:UploadLayerPartecr-public:CompleteLayerUploadecr-public:PutImage
Confirm the repository exists. Pushing to a nonexistent repo returns 403, not 404:
aws ecr-public describe-repositories --region us-east-1 --profile <user-profile> # If missing, create it: aws ecr-public create-repository --repository-name myrepo --region us-east-1 --profile <user-profile>Check the repository policy and registry permissions: a resource-based policy may be explicitly denying your role.
Make sure you assumed the right account and role. Using a profile that lacks push rights is the most common cause.
Clearing credentials and starting fresh¶
If refreshing your session doesn’t fix a 401/403, stale cached credentials are a common culprit. Clear both AWS and Docker credentials, then log in again from scratch.
Clear AWS credentials:
Clear active session variables.
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKENClear cached roles.
# Clear cached CLI credentials
rm -rf ~/.aws/cli/cacheClear Docker ECR credentials. This removes the entry from Docker’s credential store (~/.docker/config.json).
# Log out of the ECR Public registry
docker logout public.ecr.awsAfter clearing all credentials, re-run the full login sequence from the “Getting Public ECR Credentials” step:
aws ecr-public get-login-password --region us-east-1 --profile <user-profile> \
| docker login --username AWS --password-stdin public.ecr.awsQuick reference¶
| Error | Meaning | First thing to try |
|---|---|---|
401 Unauthorized | Not authenticated / token expired | aws sso login then re-run docker login |
403 Forbidden | Authenticated but not permitted | Check IAM permissions and that the repo exists |
Token lifetimes at a glance¶
SSO session: ~8 to 12 hours (configurable by your admin)
ECR Docker token: 12 hours
When either expires, re-authenticate from the relevant step above.