Remove a permanent Docker Hub token from GitHub Actions without breaking builds

Docker HubGitHub ActionsCI security

A staged OIDC migration plan: find dependencies on the old secret, restrict trust by repository and branch, test push and pull, and only then revoke the PAT or OAT

The visible risk: the secret works outside CI

A Docker Hub token was once added to GitHub Secrets, and the workflow started working. A PAT or OAT—a personal or organization access token—may remain valid until it expires or is revoked. Someone who obtains its value may be able to use its granted permissions outside GitHub Actions.

That is why migration should not start by deleting the secret. First find every consumer, configure OIDC for the intended run, and separately prove that images can be pulled and pushed without the old token.

What OIDC changes

OpenID Connect, or OIDC, lets GitHub Actions prove the identity of a particular run without storing a permanent key. GitHub issues a signed JWT, which is a token containing information about the run. Docker checks the required fields, called claims, against a trust rule and returns a short-lived token.

The difference is the lifetime and the conditions for issuance. A static PAT/OAT can be used while it remains valid. An OIDC token is issued only after the run is checked and expires quickly. Its value still needs protection: if it leaks, someone may be able to use it until it expires.

A narrow trust rule matters as much as the short lifetime. It should accept only the intended repository and ref—a reference to the required branch or tag—instead of every workflow in the organization.

Check prerequisites and find dependencies

Before changing anything, confirm three points:

  • OIDC connections are available for your Docker Org;
  • you have administrative access to that organization’s settings;
  • you can edit the required GitHub Actions workflow.

Next, search for the old secret name in .github/workflows, reusable workflows, and environment settings. Check other repositories and external jobs that may receive the same token. If it is a GitHub organization-level secret, do not limit the search to one repository.

Record the target registry, image name, required operations, allowed branches or tags, and the last successful run. Do not copy the secret value into documentation, tickets, or chat.

Migrate without a hard cutover

1. Define the expected trust scope first

Write down exactly which GitHub repository needs access, which ref it runs from, and whether it requires pull, push, or both. This gives you a test criterion before you change the configuration.

Create the OIDC connection in the Docker Org by following Docker’s current instructions. Start with the specific repository and required branch or tag. Do not broaden the rule to the entire GitHub organization merely to make the first test pass.

Check sub separately. It is the JWT claim that describes the subject of the run. Branch, pull request, and tag runs may produce different values. If a rule expects the default branch, another context should not automatically receive access.

2. Let the workflow request an OIDC token

GitHub Actions needs the id-token: write permission to request the token. A common fragment for a job that also reads repository contents looks like this:

permissions:
  contents: read
  id-token: write

contents: read is needed only when the job’s steps actually read repository contents. It is not part of the OIDC exchange itself. Review the remaining permissions and keep only those required by the particular workflow.

id-token: write does not grant permission to push images to Docker Hub. It only allows the workflow to request proof of its identity. Docker separately checks whether that proof matches the connection and whether the requested operation is allowed.

3. Test the new path without the old secret

For the first check, use a separate job or temporary workflow that does not reference the old secret at all. Otherwise, an active old login step may produce a misleading successful result.

Progress from the least risky check to operations that modify the registry:

  1. Perform the OIDC login exactly as described in Docker’s current instructions and confirm that the exchange succeeds.
  2. If the workflow requires pull, retrieve a known, permitted test image.
  3. If it requires push, use a designated non-production repository and a unique tag.
  4. Inspect the digest—the immutable identifier of the image content—and pull the same reference.
  5. Confirm that the job did not access the old secret at any step.

A green status without checking the resulting image proves only that the job finished, not that the new authentication path worked correctly.

If the exchange fails

Start with the simplest checks:

  1. Is id-token: write present for the intended workflow or job?
  2. Did the intended job actually run?
  3. Was the old login path completely removed from the test?
  4. Are the repository, branch, or tag values correct?
  5. Does the run context match the expected sub?

Do not print the complete JWT or short-lived token in logs, and do not send them to a public decoder. If diagnosis requires sub, inspect only the necessary data in a trusted environment and compare it with GitHub and Docker documentation.

If the apparent fix requires a pattern covering every repository or branch, stop. Correcting a precise rule or the wrong run context is safer than hiding the error behind overly broad trust.

Cut over, revoke, and prepare rollback

After successful pull and push checks, inspect every discovered workflow and external job again. Remove references to the old secret and confirm that the normal run uses only OIDC. Only then revoke the PAT/OAT in Docker Hub and delete the secret entry from GitHub.

Prepare rollback before revocation: keep a reference to the previous commit, name the decision owner, and define the condition for stopping the migration. A controlled short rollback is still possible while the old token remains valid.

After revocation, do not attempt to restore the old value. If the emergency procedure permits temporary static credentials, create a new, separately approved token with only the required permissions and lifetime. Revoke it as well after OIDC service is restored.

Unsafe shortcuts

Avoid these patterns:

  • revoking the PAT/OAT before independently testing the new login;
  • leaving the old secret in the same test job;
  • broadening trust to the entire organization because of one sub mismatch;
  • printing a JWT or the resulting Docker token in logs;
  • assuming that the migration automatically covers local clients or other CI systems.

Where AI can help—and where it cannot

AI can inspect a sanitized YAML fragment, locate authentication steps, and flag potentially excessive permissions. Provide a concrete task, trigger type, required operations, and placeholders instead of private names. Do not provide secrets, a complete JWT, or raw logs.

A model cannot see the actual Docker Hub rule, hidden workflows, or the exchange result. Verify every suggestion against the Docker Org settings, a real workflow run, the expected sub, the resulting image, and Docker’s documentation.

Limits and conclusion

OIDC reduces GitHub Actions’ dependence on a long-lived secret, but it does not make leakage of a short-lived token harmless. It also does not automatically configure local Docker clients, other CI systems, or external jobs. Each additional consumer needs a separate review and a supported authentication method.

The safe order is straightforward: find every consumer of the old token, create a narrow trust rule, add id-token: write, independently test pull and a test push, and then remove and revoke the PAT/OAT. Do not accelerate the migration by weakening the rule that is supposed to protect it.

Sources

Quick checklist

  • find every reference to the old secret
  • create a narrowly scoped trust rule
  • add `id-token: write`
  • test push and pull separately
  • revoke the PAT/OAT only after testing

Review a Docker Hub OIDC migration plan

Help me review a GitHub Actions migration from a static Docker Hub token to OIDC. Inputs: - a sanitized workflow YAML fragment; - trigger type: branch, tag, or environment; - required operations: push, pull; - expected repository and ref as placeholders; - current permissions without secret values. Do not request tokens, a complete JWT, private repository names, or logs containing credentials. Return Markdown with these sections: Authentication Points, Minimum Permissions, Trust Rule Check, Test Plan, Risks, Rollback, Manual Verification. Do not invent the current Docker Hub configuration.