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
Go to GitHub → Settings
Click Developer settings
Click Personal access tokens
Choose Fine-grained tokens
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.01.0.12.0.0latest(optional)
☁️ — Push to GHCR
docker push ghcr.io/YOUR_USERNAME/myapp:1.0.0
You’ll see layers uploading.
Done 🎉
Set Image to Private
Go to GitHub profile
Click Packages
Select your image
Go to Package settings
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
2️⃣ Link Image to Repository (Professional Setup)
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



