GitHub Container Registry (GHCR) is surprisingly flexible, allowing you to use it for much more than just storing your application images.

Let’s see it in action. Imagine you have a Python application that needs a specific version of a data science library, say pandas==1.3.5. Instead of managing this dependency in your application’s requirements.txt and hoping it’s available in the base image or installing it at runtime, you can bake it directly into a custom container image and push that to GHCR.

Here’s how you might build that image locally:

# Dockerfile.pandas
FROM python:3.9-slim

RUN pip install pandas==1.3.5

Then, build and tag it for GHCR. You’ll need your GitHub username and a Personal Access Token (PAT) with write:packages and read:packages scopes.

# Build the image
docker build -t ghcr.io/your-username/pandas-dep:1.3.5 -f Dockerfile.pandas .

# Log in to GHCR
echo "YOUR_GITHUB_PAT" | docker login ghcr.io -u your-username --password-stdin

# Push the image
docker push ghcr.io/your-username/pandas-dep:1.3.5

Now, in your application’s Dockerfile, you can use this custom image as a base:

# Dockerfile.myapp
FROM ghcr.io/your-username/pandas-dep:1.3.5

COPY . /app
WORKDIR /app

RUN pip install -r requirements.txt # This might only install other dependencies now

CMD ["python", "your_app.py"]

Building and running your application from this Dockerfile.myapp guarantees that pandas==1.3.5 is present, isolated, and ready to go.

The core problem GHCR solves here is dependency management and environment reproducibility at the image level. Instead of relying on external package managers at build or runtime, you control the exact environment by building it into your container images, which are then versioned and stored in GHCR. This eliminates "it works on my machine" scenarios related to specific library versions.

Internally, GHCR is built on top of GitHub’s infrastructure. When you push an image, Docker client communicates with the GHCR API, uploading image layers. When you pull, it reverses the process. Each image is identified by a unique tag (like 1.3.5 in our example) associated with a repository under your GitHub username or organization. The authentication is handled via your GitHub credentials, typically a PAT or GitHub ActionsGITHUB_TOKEN. The levers you control are the repository names, image tags, and the content of the Dockerfiles you use to build these images. You can also manage access permissions to these packages directly within GitHub’s settings.

A common pattern is to use GHCR for storing base images that include pre-installed development tools or common libraries. For instance, you might have an image ghcr.io/your-username/python-build-env:3.9 that has Python, pip, build essentials, and perhaps git and make pre-installed. This avoids repeatedly installing these in every CI/CD job that needs them, speeding up builds and ensuring consistency. Developers can then pull this specialized base image, add their application code, and build the final runtime image.

Beyond just application images, you can store any OCI-compliant artifact in GHCR. This means it’s not limited to Docker images; you could technically store Helm charts, WebAssembly runtimes, or other containerized components. You just need to use the right tools (like oras for generic OCI artifacts) to push and pull them.

The next concept to explore is how to integrate GHCR seamlessly into your CI/CD pipelines using GitHub Actions.

Want structured learning?

Take the full Github course →