This guide walks you through publishing Docker images to GitHub's Container Registry (GHCR) manually and automating the process with GitHub Actions. It includes best practices, clear examples, and troubleshooting tips.

Introduction to GitHub Container Registry

GitHub Container Registry (GHCR) is part of GitHub Packages, enabling you to:

  • Store and share Docker images alongside your code.
  • Host public or private images under your GitHub profile or repository.
  • Access images via ghcr.io for deployment or testing.

Images appear in the Packages tab of your GitHub profile or repository (https://github.com/users/YOUR_USERNAME/packages).

Prerequisites

Before starting:

  • Install Docker locally.
  • Have a GitHub account and a repository with a Dockerfile.
  • Basic familiarity with GitHub and command-line tools.

Step 1: Authenticating with GitHub Container Registry

GHCR requires a Personal Access Token (PAT) for authentication, not a standard password.

Creating a Personal Access Token

  1. Navigate to GitHub:
    • Click your profile picture (top-right) > Settings > Developer settings > Personal access tokens > Tokens (classic) > Generate new token (classic).
  2. Configure the token:
    • Name: e.g., ghcr-access.
    • Expiration: Choose 30, 60, or 90 days for security (avoid "No expiration").
    • Scopes: Select:
      • write:packages (to push images).
      • read:packages (to pull images).
      • delete:packages (optional, for deleting images).
  3. Click Generate token and copy it immediately—it won’t be shown again.

Security Tip: Store the PAT securely (e.g., in a password manager) and never commit it to version control.

Logging In to GHCR

Run the following command to authenticate:

docker login ghcr.io -u YOUR_GITHUB_USERNAME -p YOUR_PERSONAL_ACCESS_TOKEN
  • Replace YOUR_GITHUB_USERNAME with your GitHub username.
  • Replace YOUR_PERSONAL_ACCESS_TOKEN with the copied PAT.

Example:

docker login ghcr.io -u robiulhossain -p ghp_XXXXXXXXXXXXXXXXXXXXXXXXXX

Step 2: Building and Publishing a Docker Image

Follow these steps to build and push a Docker image manually.

1. Build the Docker Image

Assuming you have a Dockerfile in your project directory, build the image:

docker build -t ghcr.io/YOUR_GITHUB_USERNAME/IMAGE_NAME:TAG .
  • Tag format: ghcr.io//: (e.g., ghcr.io/robiulhossain/hello-world:latest).
  • Use . to reference the current directory containing the Dockerfile.

Example:

docker build -t ghcr.io/robiulhossain/hello-world:latest .

2. Push the Image to GHCR

Push the built image to GHCR:

docker push ghcr.io/YOUR_GITHUB_USERNAME/IMAGE_NAME:TAG

Example:

docker push ghcr.io/robiulhossain/hello-world:latest

Step 3: Verifying the Published Image

Confirm the image was published and works as expected.

1. Check Local Images

List local Docker images to verify the build:

docker image ls | grep YOUR_GITHUB_USERNAME

This shows images tagged with your username (e.g., ghcr.io/robiulhossain/hello-world).

2. Test Pulling and Running the Image

Remove the local image to test pulling from GHCR:

docker image rm -f ghcr.io/YOUR_GITHUB_USERNAME/IMAGE_NAME:TAG

Run the image to ensure it works:

docker run ghcr.io/YOUR_GITHUB_USERNAME/IMAGE_NAME:TAG

Example:

docker image rm -f ghcr.io/robiulhossain/hello-world:latest
docker run ghcr.io/robiulhossain/hello-world:latest

3. Verify on GitHub

  • Visit your GitHub profile or repository.
  • Go to the Packages tab (https://github.com/users/YOUR_USERNAME/packages).
  • Confirm the image (e.g., hello-world) is listed.

Step 4: Automating with GitHub Actions

Automate building and pushing images using a GitHub Actions workflow. This ensures images are updated with every code change.

Setting Up Repository Secrets

Store sensitive data as GitHub Secrets:

  1. Go to your repository > Settings > Secrets and variables > Actions > New repository secret.
  2. Add these secrets:
    • GHCR_USERNAME: Your GitHub username (e.g., robiulhossain).
    • GHCR_TOKEN: Your PAT with write:packages and read:packages scopes.
    • DOTENV_FILE (optional): Contents of your .env file, if needed by your app.

Creating the Workflow

Create a file at .github/workflows/publish-ghcr.yaml:

name: Build and Push to GHCR

on:
  push:
    branches: [main]

jobs:
  build-andpush:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ secrets.GHCR_USERNAME }}
          password: ${{ secrets.GHCR_TOKEN }}

      - name: Write .env file
        if: ${{ secrets.DOTENV_FILE != '' }}
        run: echo "${{ secrets.DOTENV_FILE }}" > .env

      - name: Generate image tag
        id: vars
        run: echo "tag=$(date +'%Y%m%d%H%M')" >> $GITHUB_OUTPUT

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: |
            ghcr.io/${{ secrets.GHCR_USERNAME }}/hello-world:latest
            ghcr.io/${{ secrets.GHCR_USERNAME }}/hello-world:${{ steps.vars.outputs.tag }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Workflow Explanation

  • Trigger: Runs on pushes to the main branch.
  • Steps:
    1. Checkout code: Fetches the repository code.
    2. Set up Buildx: Enables advanced Docker builds (e.g., multi-platform support).
    3. Log in to GHCR: Authenticates using secrets.
    4. Write .env file: Conditionally creates a .env file if DOTENV_FILE secret exists.
    5. Generate tag: Creates a timestamp-based tag (e.g., 202504161230).
    6. Build and push: Builds the image and pushes it with latest and timestamp tags.
    7. Caching: Uses GitHub Actions cache to speed up builds.

Testing the Workflow

  1. Commit and push the workflow file to the main branch.
  2. Go to the Actions tab in your repository to monitor the workflow.
  3. After completion, verify the new image in the Packages tab or by running:
docker run ghcr.io/YOUR_GITHUB_USERNAME/hello-world:latest

Troubleshooting Tips

  • Authentication errors: Ensure the PAT has write:packages and read:packages scopes and hasn’t expired.
  • Image not visible: Confirm the image tag matches ghcr.io//:.
  • Workflow failures:
    • Check secret names (GHCR_USERNAME, GHCR_TOKEN).
    • Verify the Dockerfile path in the workflow.
    • Review GitHub Actions logs for errors.
  • Large images: Optimize your Dockerfile with multi-stage builds or .dockerignore to reduce image size.

Best Practices

  • Use specific tags: Avoid relying solely on latest; use versioned or timestamped tags for traceability.
  • Secure secrets: Regularly rotate PATs and limit their scopes.
  • Optimize images: Minimize layers and remove unnecessary files in the Dockerfile.
  • Test locally: Always test images locally before pushing to GHCR.
  • Monitor quotas: GHCR has storage limits for free accounts; monitor usage in the Packages tab.

Conclusion

This guide covered:

  • Manually publishing Docker images to GitHub Container Registry.
  • Automating the process with GitHub Actions for seamless updates.
  • Verifying and troubleshooting the workflow.

By integrating GHCR into your development pipeline, you can streamline container management and deployment.

References