Pipeline Identities, Part 2: Docker Hub and a Stored Token

In this part you'll make the pipeline push a Docker image to Docker Hub. The call you'll cover:

  • call 3, job → Docker Hub: the job pushes the image with an access token you stored in GitHub.

It needs a Docker Hub account. It is the one call in the lab with a credential stored in GitHub, so it is the one a thief could copy.

Add the image's files

The image is a web page served by nginx, stamped with the commit it was built from.

  1. Create app/Dockerfile:

    1FROM nginx:1.29-alpine
    2ARG COMMIT=unknown
    3COPY index.html /usr/share/nginx/html/index.html
    4RUN sed -i "s/COMMIT/${COMMIT}/" /usr/share/nginx/html/index.html
    
  2. Create app/index.html:

    1<!doctype html>
    2<html>
    3<head><meta charset="utf-8"><title>pipeline-identities</title></head>
    4<body>
    5  <h1>Hello from the pipeline</h1>
    6  <p>Built from commit COMMIT.</p>
    7</body>
    8</html>
    

Add the Docker Hub steps

In .github/workflows/pipeline.yml, add two steps after the checkout:

 1      - name: Log in to Docker Hub with the stored token
 2        uses: docker/login-action@v4
 3        with:
 4          username: ${{ vars.DOCKERHUB_USERNAME }}
 5          password: ${{ secrets.DOCKERHUB_TOKEN }}
 6
 7      - name: Build and push the image
 8        run: |
 9          IMAGE="docker.io/${{ vars.DOCKERHUB_USERNAME }}/pipeline-identities:${GITHUB_SHA}"
10          docker build --build-arg COMMIT="$GITHUB_SHA" -t "$IMAGE" app
11          docker push "$IMAGE"
12          echo "IMAGE=$IMAGE" >> "$GITHUB_ENV"
  • docker/login-action runs docker login with your Docker ID and the token, and logs out when the job ends.
    • vars. reads a repository variable, secrets. a repository secret.
  • The image tag is the commit's SHA, so every push makes a new image.
  • $GITHUB_ENV keeps IMAGE for the later steps; Part 5 deploys it.

Push, and read the refusal

  1. Commit and push.

  2. Open the run. Log in to Docker Hub fails:

    1Error: Username and password required
    
    • The repository has no variable DOCKERHUB_USERNAME and no secret DOCKERHUB_TOKEN yet, so both are empty.
    • The job has nothing to show Docker Hub, and the step stops before asking.

Create the Docker Hub repository

On Docker Hub, open Repositories → Create a repository:

  • Name: pipeline-identities.
  • Visibility: Private.
    • Anyone may pull a public image, with no credential, which would leave call 7 nothing to show.

Create the access token

  1. Open Account settings → Personal access tokens → Generate new token.
  2. Fill it in:
    • Access token description: pipeline-identities push;
    • Expiration date: 30 days;
    • Access permissions: Repo Read & Write, enough to push.
  3. Generate, then copy the token: Docker Hub shows it once.

A personal access token has one permission level for every repository your account can access: it cannot be limited to pipeline-identities.

Store the token in GitHub

In your repository, open Settings → Secrets and variables → Actions:

  1. Secrets → New repository secret: name DOCKERHUB_TOKEN, value the token.
    • GitHub never shows a secret again, and masks it as *** in every log.
  2. Variables → New repository variable: name DOCKERHUB_USERNAME, value your Docker ID.
    • A user name is not a secret, so it is a variable, readable in the log.

Run the pipeline again

  1. Open the failed run, and Re-run all jobs: nothing in the repository changed, and a run reads the secrets and variables when it starts.
  2. Read the log:
    • Log in to Docker Hub ends with Login Succeeded!;
    • Build and push ends with the image's digest.
  3. On Docker Hub, check the repository: it holds a tag named after the commit.

Use the token outside the pipeline

Nothing ties the token to GitHub: whoever holds it can use it, from anywhere. That is a bearer token.

On your own machine, log in with the token as the password, and pull the image:

1$ docker login -u <your-docker-id>
2Password:
3Login Succeeded
4$ docker pull docker.io/<your-docker-id>/pipeline-identities:<commit-sha>
5$ docker logout

Deactivate the token

Docker Hub refuses a deactivated token at once, wherever it is stored.

  1. On Docker Hub, open Personal access tokens, and deactivate pipeline-identities push.
  2. On GitHub, open the last run, and Re-run all jobs.
  3. Read the log: Log in to Docker Hub fails, with an unauthorized error. This time Docker Hub refuses.
  4. Activate the token again, and re-run: it passes.