Complete Guide: Getting Started with GitHub Container Registry (GHCR)

🚀 Why GitHub Container Registry Matters

As our company’s project portfolio grew, each project’s environment requirements became increasingly diverse. We decided to transform our Docker-built Android Jenkins Server into a more flexible architecture: a main Jenkins Server (Master) paired with multiple Android Build Environment (Slave) instances, the latter created through Docker for clean environments. This article documents this transformation process, serving both as a personal learning review and a helpful resource for other developers.

Key Benefits of GitHub Container Registry:

  • 🆓 Free for public packages - No cost for open-source projects
  • 🔒 Integrated security - Built-in vulnerability scanning
  • 🔄 GitHub Actions integration - Seamless CI/CD workflows
  • 📦 Package management - Centralized container image storage
  • 🛡️ Access control - Fine-grained permissions management

📋 Article Overview

This comprehensive guide will walk beginners and developers who want to deeply understand how to integrate GitHub’s new tools into their CI/CD workflows. Through clear instructions and practical tips, you’ll learn how to push container images to GitHub Container Registry. I’ll demonstrate step-by-step how to set up GitHub Actions to automate the build and deployment process, making your development work more efficient.

What You’ll Learn:

  • 🐳 Docker image creation and management
  • 🔧 GitHub Container Registry setup and configuration
  • GitHub Actions automation for container builds
  • 🔐 Security best practices for container images
  • 📊 CI/CD pipeline integration strategies

🛠️ Prerequisites and Setup

Before diving into the main topic, let’s quickly set up a simple Node.js application using the Express framework as our example project.

Step 1: Create Project Directory

mkdir node_sample
cd node_sample

Step 2: Initialize Node.js Project

npm init -y
npm install express

Step 3: Create Application File

vim app.js

Application Code:

const express = require("express");
const app = express();
const port = 3000;

app.get("/", (req, res) => {
  res.send("Hello, World!");
});

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`);
});

Step 4: Create Gitignore File

💡 Pro Tip: Visit gitignore.io to generate a .gitignore file, select Node.js type for optimal configuration.

Step 5: Test the Application

node app.js

Open localhost:3000 in your browser to see the output:


🐳 Creating a Dockerfile

Now let’s containerize our application:

vim Dockerfile

Dockerfile Content:

FROM node:latest
WORKDIR /usr/src/app
COPY package*.json app.js ./
RUN npm install
EXPOSE 3000
CMD ["node", "app.js"]

Dockerfile Explanation:

  • FROM node:latest - Uses the latest Node.js base image
  • WORKDIR /usr/src/app - Sets the working directory
  • COPY package*.json app.js ./ - Copies dependency files and application
  • RUN npm install - Installs dependencies
  • EXPOSE 3000 - Exposes port 3000
  • CMD [“node”, “app.js”] - Runs the application

📤 Uploading Docker Images to GitHub Container Registry

After creating the Dockerfile, there are two ways to upload images to GitHub Container Registry:

  1. Manual upload using command line
  2. Automated upload using GitHub Actions

Let’s explore both methods:


🔧 Method 1: Manual Command Line Upload

Step 1: Build Docker Image

docker build -t node_sample .

Step 2: Verify Image Creation

docker images

Step 3: Tag Image for GitHub Container Registry

docker tag node_sample:latest ghcr.io/{NAMESPACE}/node_sample:latest

⚠️ Important: Replace {NAMESPACE} with your GitHub username.

Step 4: Generate Personal Access Token

Before logging into GitHub Container Registry, you need to generate a GITHUB_TOKEN:

  1. Go to GitHubProfile (top right) → Settings
  2. Navigate to Developer settingsPersonal access tokensTokens (classic)
  3. Click Generate new token

Required Permissions:

  • write:packages - Upload packages
  • read:packages - Download packages
  • delete:packages - Delete packages (optional)

Step 5: Login to GitHub Container Registry

export CR_PAT=YOUR_TOKEN
echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin

Expected Output: Login Succeeded 🎉

Step 6: Push Image to Registry

docker push ghcr.io/{NAMESPACE}/node_sample:latest

Method 2: GitHub Actions Automation

Using GitHub Actions is more efficient and automated. Create the following file in your node_sample directory:

Create GitHub Actions Workflow

mkdir -p .github/workflows
vim .github/workflows/deploy-image.yml

Workflow Configuration:

name: Build and Push Docker Image

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: $

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

    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    - name: Log in to Container Registry
      uses: docker/login-action@v3
      with:
        registry: $
        username: $
        password: $

    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v5
      with:
        images: $/$

    - name: Build and push Docker image
      uses: docker/build-push-action@v5
      with:
        context: .
        push: true
        tags: $
        labels: $

🔍 Verifying Your Container Registry

View Packages in GitHub

  1. Go to your GitHub profile
  2. Click on Packages tab
  3. You’ll see your uploaded container images

Pull and Run Your Image

# Pull the image
docker pull ghcr.io/{NAMESPACE}/node_sample:latest

# Run the container
docker run -p 3000:3000 ghcr.io/{NAMESPACE}/node_sample:latest

📊 GitHub Container Registry vs Docker Hub

Feature GitHub Container Registry Docker Hub
Free Public Packages ✅ Unlimited ✅ Unlimited
Free Private Packages ✅ 500MB/month ❌ Paid only
GitHub Integration ✅ Native ⚠️ Limited
Vulnerability Scanning ✅ Built-in ✅ Available
Access Control ✅ Fine-grained ⚠️ Basic
CI/CD Integration ✅ GitHub Actions ⚠️ Third-party

🛡️ Security Best Practices

1. Use Specific Base Images

# ❌ Avoid
FROM node:latest

# ✅ Prefer
FROM node:18-alpine

2. Implement Multi-stage Builds

# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# Production stage
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["node", "app.js"]

3. Scan for Vulnerabilities

- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: $/$:$
    format: 'sarif'
    output: 'trivy-results.sarif'

🚨 Common Issues and Solutions

Issue: Authentication Failed

Error: unauthorized: authentication required

Solution:

# Ensure token has correct permissions
# Check username and token are correct
echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin

Issue: Permission Denied

Error: denied: permission_denied

Solution:

  • Verify package visibility settings
  • Check repository permissions
  • Ensure GitHub token has required scopes

Issue: Image Not Found

Error: manifest for ghcr.io/user/image:tag not found

Solution:

  • Verify image name and tag
  • Check if image was successfully pushed
  • Ensure correct namespace

📈 Advanced Usage Scenarios

1. Multi-Architecture Images

- name: Build and push multi-arch image
  uses: docker/build-push-action@v5
  with:
    context: .
    platforms: linux/amd64,linux/arm64
    push: true
    tags: $

2. Automated Versioning

- name: Generate version tag
  id: version
  run: echo "::set-output name=version::$(date +'%Y%m%d')-$(git rev-parse --short HEAD)"

3. Conditional Publishing

- name: Push to Registry
  if: github.ref == 'refs/heads/main'
  run: docker push $/$:$


Conclusion

GitHub Container Registry provides a powerful, integrated solution for managing container images within the GitHub ecosystem. By following this guide, you’ve learned how to:

Key Achievements:

  • 🐳 Create and manage Docker images effectively
  • 🔧 Set up automated workflows with GitHub Actions
  • 🔐 Implement security best practices for container images
  • 📦 Integrate container management into your CI/CD pipeline

Next Steps:

  1. Explore advanced features like vulnerability scanning
  2. Implement multi-architecture builds for broader compatibility
  3. Set up automated testing for your container images
  4. Consider implementing container image signing for enhanced security

💡 Pro Tip: Use GitHub Container Registry’s built-in vulnerability scanning to automatically detect security issues in your container images.

🔔 Stay Updated: Follow our DevOps series for more container and CI/CD insights!


📚 Additional Resources:




    Enjoy Reading This Article?

    Here are some more articles you might like to read next:

  • How to Use Multiple GitHub Accounts on One Computer: Complete SSH Setup Guide
  • Excalidraw AI: Create Professional Diagrams with Text Commands - Complete Guide
  • Complete macOS Development Environment Setup Guide for 2024
  • Design Pattern 28: Interpreter Pattern - Complete Guide with Examples
  • Design Pattern 27: Visitor Pattern - Complete Guide with Real-World IoT Examples