What is a Docker credential helper (a `docker-credential-*` binary), and why do cloud registries such as Amazon ECR effectively require one instead of a one-off `docker login`?
answer
- docker-credential-<name> on PATH
- config.json: credsStore vs credHelpers
- get / store / erase over stdin JSON
- ECR token = 12 hours, username AWS
- Ambient cloud identity, nothing on disk
basics
~20 sA credential helper is an external binary the Docker CLI calls to get credentials for a registry instead of reading them from its config file. Cloud registries issue short-lived tokens — ECR's lasts 12 hours — so a helper that mints a fresh one per call avoids expiry and stores no secret on disk.
solid answer
~50 sBy default `docker login` writes a base64-encoded username:password into `~/.docker/config.json`. A **credential helper** replaces that: you set `credsStore` (global) or `credHelpers` (per-registry host) in the config, and the CLI executes `docker-credential-<name>`, passing the registry host on stdin and reading a JSON `{Username, Secret}` from stdout. The protocol has three verbs: `get`, `store`, `erase`. Two families: - **Secret-store helpers** — `osxkeychain`, `wincred`, `pass`, `secretservice`. They keep the same static credential but out of a world-readable file. - **Identity-derived helpers** — `docker-credential-ecr-login`, `gcloud`/Artifact Registry helper, ACR helper. These do not store a registry password at all: they use the ambient cloud identity (instance role, workload identity, service account) to mint a fresh registry token on demand. ECR is the classic case: `aws ecr get-login-password` yields a token valid **12 hours**, so a one-off login breaks the next day. The helper re-mints per pull, so nodes and CI just work, and the secret never lands on disk or in an image layer.
code
json · 10 lines{
"credsStore": "osxkeychain",
"credHelpers": {
"111122223333.dkr.ecr.eu-west-1.amazonaws.com": "ecr-login",
"europe-docker.pkg.dev": "gcloud"
},
"auths": {
"111122223333.dkr.ecr.eu-west-1.amazonaws.com": {}
}
}go deeper
Know that a helper is an external program supplying credentials instead of a file entry, and that ECR tokens expire after 12 hours.
Explain the get/store/erase protocol, credsStore versus credHelpers, and the difference between keychain helpers and identity-derived ones.
Argue for eliminating long-lived registry passwords entirely on fleets, and debug helper failures by separating missing binary from missing identity.
Frame it as workload identity: the machine's identity is the credential, rotation and revocation become policy changes, and secret distribution largely disappears.
## The default and why it is weak `docker login registry.example.com` writes into `~/.docker/config.json`: ```json {"auths":{"registry.example.com":{"auth":"Y2ktYm90OnMzY3JldA=="}}} ``` That `auth` value is `base64(username:password)` — encoding, not encryption. Anyone who can read the file, or any process running as that user, recovers the password. The credential is also long-lived and identical everywhere it is copied, which is exactly what you do not want on a fleet of build agents. ## The helper protocol A credential helper is any executable named `docker-credential-<name>` on `PATH` that speaks a tiny line protocol: - `get` — reads a registry URL on stdin, writes `{"ServerURL":"...","Username":"...","Secret":"..."}` on stdout. - `store` — reads that same JSON on stdin and persists it (used by `docker login`). - `erase` — reads a URL and deletes the entry (used by `docker logout`). You wire it up in `~/.docker/config.json` two ways: ```json { "credsStore": "osxkeychain", "credHelpers": { "111122223333.dkr.ecr.eu-west-1.amazonaws.com": "ecr-login", "europe-docker.pkg.dev": "gcloud" } } ``` `credsStore` is the default for all registries; `credHelpers` overrides it per host and wins. With either in place, the `auths` entry holds no secret — often just an empty object — and the CLI shells out on every request. ## Two different problems being solved **Problem 1: secrets at rest.** `osxkeychain`, `wincred`, `pass` and `secretservice` keep the *same* credential but delegate storage to the OS keychain or a GPG-backed store. The threat model addressed is a readable dotfile, a backup, or a `COPY . .` that sweeps the config into an image layer. **Problem 2: credentials that expire and should not exist.** Cloud registries authenticate you with an *ambient identity*, not a registry password: - **Amazon ECR** — `aws ecr get-login-password` calls `ecr:GetAuthorizationToken` with your IAM identity and returns a password valid for **12 hours**, used with the literal username `AWS`. `docker-credential-ecr-login` performs that call transparently on every registry request, resolving IAM credentials from the instance/task role, environment, or profile. Nothing is stored, and there is no daily re-login cron. - **Google Artifact Registry / GCR** — `gcloud auth configure-docker` installs the `gcloud` helper, which exchanges the active service-account or workload identity for an OAuth access token per call. - **Azure Container Registry** — the ACR helper exchanges an Entra token for an ACR refresh token. The payoff: the machine's identity *is* the credential. Rotation is automatic, revocation is a policy change rather than a secret-rotation exercise, and no long-lived registry password exists to leak. ## Operational notes worth saying out loud - **The helper must be on `PATH` for the user that runs the CLI.** A helper installed for your shell but missing for the CI user produces `error getting credentials - err: exec: "docker-credential-ecr-login": executable file not found`. - **Helpers serve the CLI, not other runtimes.** The Docker daemon receives credentials passed by the CLI on the pull request. Other consumers — containerd, other build tools, orchestrators — have their own credential configuration and do not read your `config.json`. Orchestrator-side image pull credentials are a separate mechanism. - **`docker login` still works with a helper configured**; it routes to `store` instead of writing the base64 blob. With identity-derived helpers, an explicit login is simply unnecessary. - **Per-host mapping must match exactly.** The key in `credHelpers` is the registry host as it appears in image references, including the account ID and region for ECR. - **Failure mode is quiet.** If the helper cannot resolve an identity (expired SSO session, no instance role), it returns an error the CLI reports as a credential problem, which is easy to mistake for a permissions problem. Running the helper by hand — `echo <host> | docker-credential-ecr-login get` — separates the two in seconds. ## When a plain login is still fine Short-lived contexts with a scoped, disposable token — an ephemeral CI job that logs in with a job-scoped token and exits — do not need a helper. The helper earns its place on long-lived machines, on developer laptops, and anywhere the alternative is a static password copied around.
- With a credential helper configured, what is left inside `~/.docker/config.json`?Only configuration: the `credsStore`/`credHelpers` mapping and usually an empty object under `auths` for the host, which marks that credentials for it come from the helper. The secret itself lives in the OS keychain or is never persisted at all for identity-derived helpers. That is why leaking the config file is far less serious than with the default base64 `auth` entry.
- A pull works from your shell but the same helper fails inside a CI job. What do you check first?Whether `docker-credential-<name>` is on `PATH` for the user the job runs as, and whether that environment has an identity the helper can use — an instance or task role, a service account, or valid cloud CLI credentials. Running the helper by hand with `get` on stdin reproduces the failure directly and distinguishes a missing binary from a missing identity.
A stored login is a copied house key; a credential helper is a badge reader that checks who you are and issues a day pass each time you arrive.
saying these in an interview costs you the question
- Claiming the base64 `auth` value in config.json is encrypted
- Thinking a helper stores a password more securely, when identity-derived helpers store nothing
- Scheduling a cron `docker login` for ECR instead of installing the helper
- Believing the config.json helper setting also configures other container runtimes or the orchestrator's pull credentials
- Assuming `credsStore` overrides `credHelpers` — the per-registry mapping wins