Complete GitHub Container Registry Guide: Container Image Management and CI/CD Deployment Tutorial

Why I’m Writing This Article

As the number of company projects increases, the environment requirements for each project have become more diverse. We decided to transform our originally Docker-built Android Jenkins Server into a more flexible architecture.

The new architecture includes a main Jenkins Server (Master) with multiple Android Build Environments (Slave). These slaves create clean environments through Docker, ensuring each build is performed in a consistent environment.

This article aims to document this transformation process. It serves not only as a personal learning review but also hopes to provide practical help to other developers facing similar challenges.


Article Overview

This article will guide two types of readers: CI/CD beginners and developers who want to understand GitHub’s new tools in depth. Through clear guides and practical tips, you will learn how to push container images to GitHub Container Registry.

I will demonstrate step by step how to set up GitHub Actions to fully automate the build and deployment process. This will make your development work more efficient while reducing the chance of human errors.


Before We Start

Before diving into GitHub Container Registry, we need to prepare a demo application. Let’s use the express framework to quickly build a simple application running on Node.js.

This application will serve as our foundation example for subsequent containerization and deployment.


Create Project Folder

First, we need to create a project folder for our demo application:

mkdir node_sample
cd node_sample

Install Express Package

Next, initialize the Node.js project and install the Express framework. The -y parameter will automatically accept all default settings:

npm init -y
npm install express

Create Application Main File

Now create the main file app.js for the application:

vim app.js

Paste the following code into the app.js file. This is a simple Express server that responds with “Hello, World!” at the root path:

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}`);
});

Create .gitignore File

To avoid pushing unnecessary files to version control, we need to create a .gitignore file:

TIP

Please visit gitignore.io to generate a .gitignore file, select Node type.


Test the Application

Now let’s test if the application works correctly:

node app.js

Open localhost:3000 in your browser, and you should see the following screen:


Create Dockerfile

Next, we’ll containerize the application. Dockerfile is a script file that tells Docker how to build container images:

vim Dockerfile

Add the following content to the Dockerfile. This file defines the container’s base image, working directory, files to copy, and startup commands:

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

Upload Docker Image to GitHub Container Registry

After completing the Dockerfile, we’ll upload the built container image to GitHub Container Registry (GHCR). GHCR is a container image hosting service provided by GitHub with high integration with GitHub accounts.

There are two ways to upload images to GHCR:

  1. Manual Upload via Command Line - Suitable for quick testing or one-time deployment
  2. Automated Upload via GitHub Actions - Suitable for Continuous Integration/Deployment (CI/CD) processes

Method 1: Manual Upload via Command Line

Let’s first learn how to manually upload container images via command line. This method allows you to directly control the entire upload process.


Step 1: Build Docker Image

Build the container image using the Dockerfile. The . indicates looking for the Dockerfile in the current directory:

docker build -t node_sample .

Step 2: Check Build Result

Verify if the image was built successfully:

docker images

You should see the node_sample image that was just built appear in the list.


Step 3: Tag the Image

To upload to GHCR, we need to tag the image with a name that conforms to GHCR format:

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

Remember to replace {NAMESPACE} with your GitHub username. For example: ghcr.io/john-doe/node_sample:latest


Step 4: Generate Personal Access Token

Before logging into GHCR, we need to generate a Personal Access Token for authentication.

Follow these steps to generate a token on GitHub:

Open GitHub → Top right Profile → Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token

In the permissions settings, please check the following three package-related permissions:

  • write:packages - Upload package permission
  • read:packages - Read package permission
  • delete:packages - Delete package permission

Step 5: Login to GitHub Container Registry

Use the token you just generated to log into GHCR. First set the environment variable:

export CR_PAT=YOUR_TOKEN

Then use the Docker login command:

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

Seeing the “Login Succeeded” message indicates successful login!


Step 6: Push Image to GHCR

Now you can push the image to GitHub Container Registry:

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

After the push is complete, you can see the container image you just uploaded on GitHub’s Packages page.


Method 2: Automated Upload Using GitHub Actions

Compared to manual upload, GitHub Actions provides a more elegant automated solution. It automatically builds and pushes container images whenever there are code changes, significantly improving development efficiency.

Advantages of GitHub Actions include:

  • Automatic Triggering: Executes automatically when code is pushed
  • No Local Environment Needed: Runs in GitHub’s cloud environment
  • Built-in Permission Management: No need to manually set access tokens

Create Workflow File

First, create a GitHub Actions workflow file in your project:

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

Paste the following YAML configuration into the file. This workflow will execute automatically when pushed to the release branch:

name: Create and publish a Docker image
on:
  push:
    branches: ["release"]
env:
  REGISTRY: ghcr.io
  IMAGE_NAME: $

jobs:
  build-and-push-image:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      attestations: write
      id-token: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
      - name: Log in to the Container registry
        uses: docker/login-action@v2
        with:
          registry: $
          username: $
          password: $
      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@v4
        with:
          images: $/$
      - name: Build and push Docker image
        id: push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: $
          labels: $
      - name: Generate artifact attestation
        uses: actions/attest-build-provenance@v1
        with:
          subject-name: $/$
          subject-digest: $
          push-to-registry: true

Workflow Configuration Explanation

This GitHub Actions workflow includes the following key configurations:

  • Trigger Condition: Executes when pushed to the release branch
  • Permission Settings: Includes permissions to read code and write packages
  • Execution Steps: Automatically checks out code, logs into GHCR, builds and pushes images

Push Project to GitHub

Now push the complete node_sample project to a GitHub repository. It’s recommended to first push to the main branch, then create a release branch to trigger the automated process:

git init
git add .
git commit -m "Initial commit with Node.js app and GitHub Actions workflow"
git branch -M main
git remote add origin https://github.com/YOUR_USERNAME/node_sample.git
git push -u origin main

# Create and push release branch to trigger workflow
git checkout -b release
git push -u origin release

When you push to the release branch, GitHub Actions will automatically execute the following process:

  1. Check out code
  2. Build Docker image
  3. Push to GitHub Container Registry

Using the Uploaded Container Image

Once the container image is successfully uploaded to GHCR, you and other developers can use this image anywhere. Here are two common usage methods:


Method 1: Direct Download and Run

Use Docker commands to directly download and run the container from GHCR:

docker pull ghcr.io/nickhuangcyh/node_sample:TAG
docker run -p 3000:3000 ghcr.io/nickhuangcyh/node_sample:TAG

Method 2: Use as Base Image

In other projects’ Dockerfiles, use the image on GHCR as a base image:

FROM ghcr.io/nickhuangcyh/node_sample:TAG
# Add additional configuration or applications on this basis

This method is particularly suitable for building composite applications or customizing existing images.

TIP

You can download the complete code of node_sample from node_sample for practice.


Summary

Through this article’s learning, you have mastered two ways to use GitHub Container Registry:

Manual upload method is suitable for quick testing and learning, helping you understand each step of the entire containerization process.

GitHub Actions automation method is the best choice for production environments, providing reliable CI/CD integration. When there are any code changes, the system automatically builds new container images and deploys them, significantly improving development team efficiency.

Next, you can try applying these technologies to your own projects or explore more advanced GitHub Actions features such as multi-stage builds, conditional deployment, etc.




    Enjoy Reading This Article?

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

  • Claude Code 使用技巧與最佳實踐 - Tips and Best Practices
  • 🤖 AI Agent Series (Part 1): Understanding the Core Interaction Logic of LLM, RAG, and MCP
  • 💡 Managing Multiple GitHub Accounts on One Computer: The Simplest SSH Configuration Method
  • 🚀 How to Use Excalidraw AI to Quickly Generate Professional Diagrams and Boost Work Efficiency!
  • Complete macOS Development Environment Setup Guide: Mobile Development Toolchain Configuration Tutorial