Skip to main content

How to Publish a Docker Container Image

In this article, I'm going to show you how to publish a Docker container image to the GitHub Container Registry using GitHub Actions.

The instructions were written for a Mac, but can be easily ported to other operating systems.

Step 1. Setup

To use these steps you first need a Docker project on GitHub. If you don't have one, see my previous article: How to Build a Docker Container

Step 2. Create a GitHub workflows folder

GitHub looks for Action scripts in a particular workflow folder.  To create that folder, go to the root of your Docker project and make the directory and subdirectory:

mkdir -p .github/workflows

Step 3. Create a workflow

GitHub Actions will run any workflow file that it finds in the folder that you just created.

Create a file to publish to a docker registry:

touch .github/workflows/publish.yml

Open that file, copy the text below into it, and save it:

name: publish
on:
push:
tags:
- 'v*'

jobs:
publish-docker-image:
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build the Docker image
run: |
tag=${GITHUB_REF#refs/tags/}
tag=${tag:1}
image=ghcr.io/${GITHUB_REPOSITORY}
docker build . -t ${image}:${tag} -t ${image}:latest
docker push ${image} --all-tags

The code above does the following:

  • Triggers when you push a tag that begins with a v (like v1.2.3)
  • Logs in to the GitHub Container Registry (ghcr.io)
  • Creates a tag variable that removes the v from the front of the GitHub tag (v1.2.3 becomes just 1.2.3)
  • Creates an image path variable starting with ghcr.io/ - the root of the GitHub Container Registry - and ending with the name of the repo (which includes the GitHub username in the path)
  • Builds, tags, and publishes your container image to the GitHub Container Registry

You may notice that it pushes two tags:

  • one based on the tag (minus the v at the front)
  • another is called latest - so that people won't need to know the exact version number and just get the latest

Once the file is saved, commit and push the code to your GitHub repo.

The latest tag

There is some debate about whether using the latest tag is a good thing or if it can lead to problems.  If you don't want to use that tag you can edit the workflow to only use the version tag.  To do that remove -t ${image}:latest from the docker build command.

Note that if you do not include the latest tag, commands like docker pull will require a version tag.  If the user doesn't include a tag then it will default to the latest tag and then fail.

This is not necessarily a bad thing.  It's inconvenient. But it does have the advantage of making the user of your container image define exactly which version they want to use.  That will protect them against sudden failures if the latest version changes functionality.

Step 4: Generate a token

To access the GitHub Container Registry you are going to need to generate a token and log in with it. You can do that by doing the following:

  • In GitHub open your Profile Settings
  • Scroll to the bottom of the left menu and click on Developer settings
  • On the left menu select Personal access tokens / Tokens (classic)
  • Click Generate new token / Generate new token (classic)
  • For Note, you can say something like "Container Registry"
  • For Expiration, some say you should never choose the No expiration option - it's up to you
  • Check repo
  • Check write:packages
  • Click Generate token
  • Copy the result to the clipboard (never save the result in a repo or doc!)
  • Open up terminal window and paste the token into this command (paste over YOUR_TOKEN here):
export CR_PAT=YOUR_TOKEN
  • Login using the credential:
echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin

Step 5. Set the Action permission

If you try to run the workflow as is, you will get an error like this from the build step:

denied: installation not allowed to Write organization package

To make sure that your action has permission to create a package, do the following:

  • in your repo, click on Settings
  • on the left, expand Actions
  • under Actions, select General (not to be confused with General at the top of the menu)
  • In the main window scroll down to the Workflow permissions section
  • Select Read and write permissions
  • Click Save

Step 6. Trigger the build

The workflow is triggered when you push a tag or create a release. To trigger a build using a tag, do the following:

git checkout main
git tag v1.0.0
git push origin --tags
note

I used git tag to make this example agnostic.

If you are using npm or yarn: You could subsitute npm version patch or yarn version patch, followed by a git push to increment the version number automatically and push tags and the updated package.json file.

Now go to your GitHub repo and click on the Actions tab.

If the run comes up green, then you should have successfully published the repo.

Remember that every time you issue a tag command you should increment the number (v1.0.0, v1.0.1, v1.0.2, ...).

If you prefer to use the GitHub site to generate a release, I'll include a link to their instructions at the end of this article.

Step 7. Test the published image

When an image is stored on GitHub it is considered a "package".  There are a few ways to find packages on GitHub:

  1. On the Home Page of your GitHub Profile, click the Packages tab
  2. In your repo, you should see a Packages section in the right column

If your repo was private, then the package visibility will default to private. Otherwise, it will be public.

When you click on a container image package you will see a generated docker pull command that you can copy to the clipboard.

  • Copy the docker pull command to the clipboard
  • Open up a terminal window, paste in the pull command, and run it

If the pull command succeeded then congratulations!  You've published a container image to the GitHub Container Registry!

Note that if this is your first time - and you are using a latest tag - the docker pull command that is generated will use latest as the tag.  But if you tag a new version and have multiple versions of your package listed, it will use the version tag instead of latest in the generated command.

Conclusion

In this article you learned how to:

  • Create a GitHub Actions workflow
  • How to write a workflow to publish a docker container image
  • How to find the published package and test it

References

  • Understanding GitHub Actions - [1]
  • Working with the Container registry - [2]
  • What's Wrong With The Docker :latest Tag? - [3]
  • About billing for GitHub Packages - [4]
  • Managing releases in a repository - [5]