Skip to content
ocx
install

Authenticate to GHCR and ECR from CI

This page shows how to give an ocx job credentials for a private registry. It has a recipe each for GitHub Container Registry (GHCR) and Amazon ECR. The recipes need no terminal and leave nothing on a throwaway runner.

ocx reads credentials per registry. OCX_AUTH_* variables win, then the Docker configuration. Authenticate to a registry explains the layers. Here you pick the one that suits a runner.

MethodUse it when
OCX_AUTH_<REGISTRY>_* variablesThe token is in the CI secret store and the job runs only ocx. This is the default choice.
ocx login --password-stdinAnother tool in the same job reads ~/.docker/config.json too.
A Docker credential helperThe runner is long-lived and a helper can mint short-lived tokens itself.

Variables are scoped to one registry. The registry name in the variable replaces every non-alphanumeric character with _, so ghcr.io becomes ghcr_io. The reference lists the type, user and token variables.

If TYPE is omitted, ocx infers it: USER and TOKEN together mean basic, and TOKEN alone means bearer.

GHCR takes your GitHub username as the user and a token as the password. In GitHub Actions the built-in GITHUB_TOKEN is enough for packages the repository can read. Grant the job packages: read, or packages: write to push.

Set the variables on the job, not on one step. The setup-ocx action pulls the toolchain before your own steps run, and it needs the credential to do so.

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
env:
OCX_AUTH_ghcr_io_TYPE: basic
OCX_AUTH_ghcr_io_USER: ${{ github.actor }}
OCX_AUTH_ghcr_io_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
- uses: ocx-sh/setup-ocx@v1
- run: ocx exec -- my-private-tool --version

A private package must also allow the repository in its package settings. A token that works for docker login ghcr.io works here.

To share the credential with other tools, log in once instead. Map the token into the step with env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}, because the runner exports GITHUB_ACTOR but not GITHUB_TOKEN:

Terminal window
echo "$GITHUB_TOKEN" | ocx login ghcr.io --username "$GITHUB_ACTOR" --password-stdin --allow-insecure-store

--allow-insecure-store is explained below.

ECR issues a password with the AWS CLI. The user is always AWS, and the password is valid for 12 hours, so fetch it inside the job. The registry host is <account>.dkr.ecr.<region>.amazonaws.com.

Terminal window
export OCX_AUTH_123456789012_dkr_ecr_eu_west_1_amazonaws_com_TYPE=basic
export OCX_AUTH_123456789012_dkr_ecr_eu_west_1_amazonaws_com_USER=AWS
export OCX_AUTH_123456789012_dkr_ecr_eu_west_1_amazonaws_com_TOKEN="$(aws ecr get-login-password --region eu-west-1)"
ocx pull

The long names come from the host: 123456789012.dkr.ecr.eu-west-1.amazonaws.com with every dot and hyphen turned into _.

The login form writes the same credential to the Docker configuration:

Terminal window
aws ecr get-login-password --region eu-west-1 \
| ocx login 123456789012.dkr.ecr.eu-west-1.amazonaws.com --username AWS --password-stdin --allow-insecure-store

On a runner that stays up, the Amazon ECR credential helper mints tokens on demand. Name it for the registry in ~/.docker/config.json, and ocx delegates to it:

{ "credHelpers": { "123456789012.dkr.ecr.eu-west-1.amazonaws.com": "ecr-login" } }

ocx login writes to the same ~/.docker/config.json as docker login. It prefers a credential helper or a keychain. A runner has neither, so the command refuses with exit 78 unless you pass --allow-insecure-store. That flag stores the credential as plaintext base64 in the config file.

Use it only on a runner that is destroyed after the job. The OCX_AUTH_* variables take precedence over a stored credential at read time. ocx logout exits 0 when no credential is stored, but a failed revocation exits non-zero. A cleanup step that must never fail needs || true.

Never bake a token into an image with ENV or ARG. The Docker page shows the BuildKit secret form.

A refused credential exits 80 (AuthError). ocx login checks the credential against the registry first unless you pass --no-verify. Check these in order:

  1. The variable name matches the registry host after the _ substitution, including the account id and region for ECR.
  2. TYPE is basic for a user and password pair, and bearer or token for a bare token.
  3. The token has the scope for the action. Pushing needs write access, and pulling needs read.
  4. A mirror in [mirrors] needs its own credential. Auth for the mirror covers it.