Skip to main content

Command Palette

Search for a command to run...

The Modern Way to Use GitHub Container Registry (GHCR)

Updated
3 min read
The Modern Way to Use GitHub Container Registry (GHCR)

Let’s set it up properly.

Install Docker Desktop

If you don’t already have Docker:

For Mac

brew install --cask docker

For Linux

https://docs.docker.com/engine/install/

Start Docker Desktop from Applications.

Verify:

docker --version
docker info

Create a Secure GitHub Token (Fine-Grained Recommended)

Modern best practice: Use a Fine-Grained Personal Access Token (PAT) instead of classic tokens.

Create Token

  1. Go to GitHub → Settings

  2. Click Developer settings

  3. Click Personal access tokens

  4. Choose Fine-grained tokens

  5. Click Generate new token

Configure:

  • Repository access → Select only required repos (or all if needed)

  • Permissions:

    • Packages: Read & Write

    • Contents: Read (if linking to repo)

Set expiration (recommended: 30–90 days).

Copy the token.

Login to GHCR Securely (Best Practice Method)

Instead of typing your password directly, use stdin:

echo YOUR_GITHUB_TOKEN | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin

If successful:

Login Succeeded

Create a Proper Production Docker Image

Create a sample project:

mkdir ghcr-demo && cd ghcr-demo

Create a Dockerfile:

FROM alpine:3.19
LABEL org.opencontainers.image.source="https://github.com/YOUR_USERNAME/YOUR_REPO"
CMD ["echo", "Hello from modern GHCR setup"]

Why add LABEL?

It links your image to your GitHub repository — this is modern OCI best practice.

Build the image:

docker build -t myapp:1.0.0 .

Tag Image Correctly (Important)

Modern naming format:

ghcr.io/<owner>/<image-name>:<version>

Example:

docker tag myapp:1.0.0 ghcr.io/YOUR_USERNAME/myapp:1.0.0

For production projects, use semantic versioning:

  • 1.0.0

  • 1.0.1

  • 2.0.0

  • latest (optional)


☁️ — Push to GHCR

docker push ghcr.io/YOUR_USERNAME/myapp:1.0.0

You’ll see layers uploading.

Done 🎉

Set Image to Private

  1. Go to GitHub profile

  2. Click Packages

  3. Select your image

  4. Go to Package settings

  5. Ensure visibility = Private

By default, it usually inherits repository visibility.


Pull Image from Another Machine

Login first:

docker login ghcr.io -u YOUR_GITHUB_USERNAME

Pull:

docker pull ghcr.io/YOUR_USERNAME/myapp:1.0.0

Run:

docker run ghcr.io/YOUR_USERNAME/myapp:1.0.0

🧠 Modern Best Practices (Important Section)

1️⃣ Always Version Your Images

Avoid relying only on latest.

Good:

1.0.0
1.0.1
2.0.0

Best format if linked to a repo:

docker tag myapp:1.0.0 ghcr.io/YOUR_USERNAME/YOUR_REPOSITORY/myapp:1.0.0

This:

  • Connects image to repo

  • Enables GitHub Actions automation

  • Improves security visibility

3️⃣ Automate with GitHub Actions (Modern DevOps Way)

Instead of pushing manually, use CI/CD.

Example workflow:

.github/workflows/docker.yml

name: Build and Push to GHCR

on:
  push:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4
      - name: Login to GHCR
        run: echo "\({{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u \){{ github.actor }} --password-stdin
      - name: Build image
        run: docker build -t ghcr.io/${{ github.repository }}/myapp:latest .
      - name: Push image
        run: docker push ghcr.io/${{ github.repository }}/myapp:latest

Now every push builds and uploads automatically.

🏁 Final Architecture (Modern Setup)

Your flow becomes:

PC → Docker Build → GHCR (Private)
              ↑
       GitHub Actions (Auto CI/CD)

This is production-ready and scalable.

🎯 Why GHCR Is Better Than Docker Hub (Free Tier)

  • Better private limits

  • Better CI integration

  • No aggressive rate limiting

  • More control over permissions