Skip to content
Latchkey LogoLatchkey home

docker/login-action Username and password required error

The docker/login-action Username and password required error is thrown before any network call, by a three branch check that has two other messages available to it. It chose this one because both values were empty, which is a different problem from one misnamed secret.

The three branch credential check in login-action and the message each branch throws
Read in order from src/docker.ts. The first branch requires both values to be falsy, so "Username and password required" is the message that means neither arrived.

What this error means

The step fails in under a second, with no registry named and no connection attempted. Steps before it are green, and the build step after it is skipped. The wording almost never changes, which is why it is easy to miss that it is one of three possible sentences: the action also has Username required and Password required, and the one you got rules the other two out.

Actions log, the message loginStandard throws when both inputs are empty
Run docker/login-action@v3
Error: Username and password required
##[error]Username and password required

Three messages, and the one you got

The check lives in loginStandard in src/docker.ts and runs before anything is sent anywhere. It is a ladder, and the order matters, because the first branch that matches wins and the later ones are never reached.

docker/login-action src/docker.ts, loginStandard
if (!username && !password) {
  throw new Error('Username and password required');
}
if (!username) {
  throw new Error('Username required');
}
if (!password) {
  throw new Error('Password required');
}

Common causes

The job never had the secrets, so both interpolations became empty

The common shape, and the one the wording points at. A pull request from a fork gets no repository secrets, a job in a reusable workflow that was called without secrets: inherit gets none either, and both cases empty the whole with: block at once rather than one line of it.

The with: block is misindented and its values are not inputs

YAML will accept a username key that sits one level too deep or beside uses instead of under with, and the action then receives nothing for either name. Both values are empty for the same reason, which is what distinguishes this from a spelling mistake.

Secrets exist at a scope this repository cannot read

An organization secret that was never shared with this repository, or an environment secret in a job that does not name the environment, resolves to an empty string with no warning. When one set of credentials is stored together this way, both halves disappear together.

The login step runs on an event that carries no secrets

Less common but persistent: a workflow that is correct on push and empty on pull_request from a fork. The step is unchanged between the two runs, which is why the file gets read repeatedly and looks fine each time.

How to fix it

Use the sentence to pick the search

  1. If the message names both values, stop checking secret names and check whether this job receives secrets at all.
  2. Open the job's event: a fork pull request and an uninherited reusable workflow both empty everything.
  3. If the message names one value, the other arrived, and a single name or scope is wrong.

Guard the step rather than letting it fail the job

When a job legitimately runs both with and without credentials, test the value through env rather than through the secrets context, which is not available in an if at either job or step level. Copy the secret into env first, then branch on that.

.github/workflows/publish.yml (illustrative)
      - name: Log in to the registry
        if: env.REGISTRY_USER != ''
        env:
          REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ secrets.REGISTRY_USER }}
          password: ${{ secrets.REGISTRY_TOKEN }}

Name the registry explicitly

Set registry even when you think the default is right. It costs one line and it removes the most confusing symptom of the both-empty case, which is a failure that talks about Docker Hub in a workflow that has nothing to do with Docker Hub.

For ECR, expect a different failure entirely

If the registry is ECR, or ecr: true is set, the three sentences cannot be produced. An empty credential there surfaces through the AWS SDK instead. Do not spend time looking for this message in a job that takes that path.

What the wording rules out

Because the branches are exclusive, the sentence you were given is a measurement rather than a generic complaint. The table is the whole of it.

Sentence thrownUsername valuePassword valueWhere to look first
Username and password requiredEmptyEmptyThe with: block itself: indentation, a step that lost its inputs, or a job that never had the secrets
Username requiredEmptyPresentThe username secret or variable alone
Password requiredPresentEmptyThe password secret alone, which is the misnamed-secret case

The branch only runs for some registries

The ladder sits inside loginStandard, and login decides between that and loginECR before any of it happens. The ECR path is taken when the ecr input is true, or when it is auto and the registry host looks like ECR. On that path the three sentences are unreachable, and an empty credential produces an AWS SDK failure instead, naming a region or a profile.

The other thing login does first is worth knowing for the both-empty case: when the registry input is also empty, there is no registry to name, and the action logs in to Docker Hub by default. A job that meant to authenticate to a private registry, and whose whole with: block failed to resolve, therefore fails while talking about Docker Hub, which it was never configured to use.

Why there is no recorded run on this page

The throw happens before a socket is opened, from two values that were empty in your repository's secret store rather than on any machine. A run of ours would have to deliberately leave our own secrets unset, and the log it produced would be the same single line, carrying no information about which of your names, scopes or triggers was responsible. The sentence above is the one this branch throws, read in the action's source, and the table is that source's own ordering rather than a reproduction.

How to prevent it

  • Put the login step in a job that also declares which environment or which secrets it needs, so an empty value is a visible configuration change rather than a silent one.
  • Prefer ${{ github.token }} and packages: write over a stored credential when the registry is ghcr.io, since there is then nothing to leave unset.
  • When a workflow must run on fork pull requests, guard the login step rather than letting it take the job red.
  • Set registry on every login step, so the failure names the host you meant.

Frequently asked questions

Why does it say Docker Hub when I am pushing to a private registry?
Because the registry input was empty too. The action defaults to Docker Hub when no registry is given, so a with: block that failed to resolve as a whole takes the default along with everything else. The mention of Docker Hub is a symptom of the same emptiness, not a separate misconfiguration.
I only misnamed the password secret, so why does it name both?
It would not. A present username with an empty password reaches the third branch and throws Password required. If the message names both, both values were empty when the step started, which points at the block, the event or the secret scope rather than at one name.
Can I check whether the secret exists before the login step?
Not through the secrets context in an if, because that context is not available in a job-level or step-level if condition. Assign the secret to env on the step or the job and test env.NAME instead. The value is still masked in the log; only its emptiness is observable.
Does the message differ between login-action versions?
The three sentences and their order have been stable in loginStandard across the v2 and v3 majors. What has moved around them is the dispatch above: which registries are treated as ECR, and the addition of a Docker Hub OIDC path that replaces the username and password before the ladder sees them.

Related guides

References

The login costs a second. A cold rebuild costs minutes. Latchkey keeps the layer cache on the runner. Start free → 30-day trial · No credit card