Version v0.5.7 of the documentation is no longer actively maintained. The site that you are currently viewing is an archived snapshot. For up-to-date documentation, see the latest version.
ADR 0091: Registry login without docker
Status: Accepted Date: 2026-10-17 Relates: ADR 0080 (registry credentials from cloud CLI sessions), ADR 0073 (daemonless image builds), ADR 0015 (no Docker SDK) Issues: #1891, #1705
Context
ADR 0080 removed docker login for Artifact Registry, GCR, ECR and ACR: the
CLI reads the cloud CLI’s session. For every other registry (GHCR, Docker
Hub, Harbor) the preflight, dexaflow doctor and a refused push still said
docker login <registry>. On a machine with no container daemon, which the
daemonless paths of ADR 0073 now serve, that is a command the user does not
have, for a build that never needs docker.
GHCR and Docker Hub both accept a token as the password of a basic credential, exchanged for a registry token by their token services. GitHub offers a device flow, but only for an OAuth app registered by whoever ships the client; Docker Hub’s device flow belongs to Docker’s own client.
Decision
dexaflow registry login <registry>. It reads a token from stdin (--password-stdin, with--username), never from a flag. Forghcr.iowith no token given, it exchanges the session of the GitHub CLI (gh auth token, and the user fromgh api user), as ADR 0080 reads a cloud CLI’s session.- Checked before it is stored. The CLI speaks the registry API’s handshake with the credential (a Basic challenge, or the token exchange of a Bearer one) through the registry client’s transport, so the loopback and TLS rules of ADR 0075 hold. A credential the registry refuses is not stored, and no error quotes it.
- Stored where everything reads it. The credential goes to the docker
config (
DOCKER_CONFIG, else~/.docker/config.json), the file go-containerregistry’s keychain and every container tool read. AcredHelpersentry for the host, else acredsStore, keeps the secret in that helper through the credential helper protocol (docker-credential-<name> store, JSON on stdin), asdocker logindoes, and the file records only the host; with no helper the secret goes inauths, base64 as docker writes it. Every other field of the file is kept, the file is replaced atomically and stays readable by its owner only, and Docker Hub is filed underhttps://index.docker.io/v1/whichever of its names was given. The CLI writes the file itself:github.com/docker/clistays reachable only through the keychain (ADR 0073), and no dependency is added. - The hints name it. For a registry no cloud owns, the login command the
preflight,
doctor,promoteand a refused push print isdexaflow registry login <host>when the tool is docker. Another tool (podman, nerdctl) keeps its own login, since it keeps its own credentials.
Consequences
- A deploy to GHCR or Docker Hub needs no docker on the machine, from the first sign-in on.
- The
ghsession’s token has the scopesgh auth loginasked for, which do not includewrite:packagesby default; the command says how to add it. - A token stored in the file (no helper configured) is as readable as a
docker loginone: base64 in a file only its owner can read. - A real device flow for GHCR needs an OAuth app the project registers and operates; it can be added behind the same command later.
Alternatives considered
- Read the
ghsession on every request, as ADR 0080 does for clouds. It stores nothing, but docker, podman and nerdctl would not see it, and alocal-daemonbuild would still needdocker login. - Write
config.jsonthroughgithub.com/docker/cli’s config package. It handles the helpers too, but the lint keeps that module behind the keychain (ADR 0073), and the part needed here is one file format and one helper call.