CourseDevops

Container Image Automatisch Pushen naar GitHub Container Registry (GHCR)

Inhoudsopgave

  1. Introductie
  2. GitHub Personal Access Token (PAT) Aanmaken
  3. Docker Login naar GHCR
  4. Container Image Bouwen
  5. Handmatig Pushen naar GHCR
  6. GitHub Actions Setup
  7. Verificatie

Introductie

In deze tutorial leren we hoe je een container image automatisch kunt deployen naar GitHub Container Registry (GHCR) met GitHub Actions.

Wat is GitHub Container Registry?

GitHub Container Registry (ghcr.io) is de container registry van GitHub, vergelijkbaar met Docker Hub maar met een aantal voordelen:

Waar vind je je Packages?

Onder het tabblad Packages op je GitHub Profiel kan je al je gepubliceerde container images zien:

URL: https://github.com/USERNAME?tab=packages (vervang USERNAME door je GitHub gebruikersnaam)

GitHub Packages


Stap 1: GitHub Personal Access Token (PAT) Aanmaken

Om in te loggen op GHCR heb je een Personal Access Token (PAT) nodig van GitHub.

1.1 Navigeer naar Settings

Klik rechtsboven op je GitHub gebruikersnaamSettings in de dropdown

GitHub Settings

1.2 Developer Settings

In het menu links, scroll helemaal naar beneden en klik op Developer settings

Developer Settings

1.3 Personal Access Token Genereren

  1. Klik links op Personal Access TokensTokens (classic)
  2. Klik rechts op Generate new tokenGenerate new token (classic)

Personal Access Token

1.4 Token Configureren

Configureer je token als volgt:

Note: Geef je token een beschrijvende naam (bijvoorbeeld: GHCR_ACCESS_TOKEN)

Expiration: Kies een expiration date

Select scopes: Selecteer de volgende permissies:

PAT Permissions

1.5 Token Opslaan

  1. Klik onderaan op Generate token
  2. ⚠️ BELANGRIJK: Kopieer de token onmiddellijk en sla deze op in een password manager
  3. Eenmaal weggeklikt kan je de token niet meer zien
Voorbeeld token: ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Stap 2: Docker Login naar GHCR

Nu gaan we inloggen op GHCR via de terminal:

2.1 Login Commando

Methode 1: Via command line (minder veilig)

docker login ghcr.io --username YOUR_GITHUB_USERNAME --password YOUR_PAT_TOKEN

Voorbeeld:

docker login ghcr.io --username MilanVives --password ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Output:

WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your credentials are stored unencrypted in '/Users/milan/.docker/config.json'.
Configure a credential helper to remove this warning. See
https://docs.docker.com/go/credential-store/

Login Succeeded

Methode 2: Via stdin (veiliger)Aanbevolen

echo "YOUR_PAT_TOKEN" | docker login ghcr.io --username YOUR_GITHUB_USERNAME --password-stdin

2.2 Naamconventies voor GHCR Images

Belangrijk: Images voor GHCR moeten een specifieke naamgeving volgen:

Format:

ghcr.io/GITHUB_USERNAME/IMAGE_NAME:TAG

Regels:

Voorbeelden:

ghcr.io/milanvives/containerizedapp:latest
ghcr.io/milanvives/myapp:v1.0.0
ghcr.io/milanvives/backend:develop

⚠️ Let op: De volledige naam moet in lowercase zijn!


Stap 3: Container Image Bouwen

Nu maken we een eenvoudige containerized applicatie om te testen.

3.1 Project Structuur

Maak een nieuwe directory aan voor je project:

mkdir containerizedapp
cd containerizedapp

3.2 Maak script.sh

Maak een eenvoudig bash script:

#!/bin/bash

echo "=== Hello from GHCR Container ==="
echo "Container is running successfully!"
echo "Built with GitHub Actions ✓"

Maak het executable:

chmod +x script.sh

3.3 Maak Dockerfile

# Use Alpine Linux as base image (lightweight)
FROM alpine:latest

# Install bash
RUN apk add --no-cache bash

# Set working directory
WORKDIR /app

# Copy script
COPY script.sh .

# Make script executable
RUN chmod +x script.sh

# Run script on container start
ENTRYPOINT ["./script.sh"]

3.4 Build de Container Image

Commando:

docker build -t ghcr.io/YOUR_GITHUB_USERNAME/containerizedapp:latest .

Voorbeeld:

docker build -t ghcr.io/milanvives/containerizedapp:latest .

Output:

[+] Building 3.8s (11/11) FINISHED                         docker:desktop-linux
 => [internal] load build definition from Dockerfile                       0.0s
 => => transferring dockerfile: 160B                                       0.0s
 => [internal] load metadata for docker.io/library/alpine:latest           1.8s
 => [auth] library/alpine:pull token for registry-1.docker.io              0.0s
 => [internal] load .dockerignore                                          0.0s
 => => transferring context: 2B                                            0.0s
 => [1/5] FROM docker.io/library/alpine:latest@sha256:4b7ce07...           0.0s
 => [internal] load build context                                          0.0s
 => => transferring context: 85B                                           0.0s
 => [2/5] RUN apk add --no-cache bash                                      1.7s
 => [3/5] WORKDIR /app                                                     0.0s
 => [4/5] COPY script.sh .                                                 0.0s
 => [5/5] RUN chmod +x script.sh                                           0.1s
 => exporting to image                                                     0.0s
 => => exporting layers                                                    0.0s
 => => writing image sha256:f1ff4ce6dbbaab6df8550393274466b68156e1...      0.0s
 => => naming to ghcr.io/milanvives/containerizedapp:latest                0.0s

3.5 Test de Image Lokaal (Optioneel)

docker run --rm ghcr.io/milanvives/containerizedapp:latest

Expected output:

=== Hello from GHCR Container ===
Container is running successfully!
Built with GitHub Actions ✓

Stap 4: Handmatig Pushen naar GHCR

4.1 Push de Image

Commando:

docker push ghcr.io/YOUR_GITHUB_USERNAME/containerizedapp:latest

Voorbeeld:

docker push ghcr.io/milanvives/containerizedapp:latest

Output:

The push refers to repository [ghcr.io/milanvives/containerizedapp]
0d9b595fd133: Pushed
5cecc193ecd1: Pushed
7abcdc4e5fd7: Pushed
a43bfea15c65: Pushed
0e64f2360a44: Pushed
latest: digest: sha256:0d7be30482f3c21c324935453d236520b23f2ef074177ed0529a4b11402a8449 size: 1358

4.2 Verificatie

Check je packages online:

URL: https://github.com/YOUR_USERNAME?tab=packages

Voorbeeld: https://github.com/MilanVives?tab=packages

Gepushte Package


Stap 5: Build & Push Automatiseren met GitHub Actions

Nu gaan we het build en push process automatiseren met GitHub Actions.

5.1 GitHub Repository Aanmaken

Maak een nieuwe repository aan op GitHub en push je code:

Stap 1: Initialiseer Git Repository

git init
git branch -M main

Stap 2: Voeg Remote Repository Toe

git remote add origin git@github.com:YOUR_USERNAME/containerizedapp.git

Voorbeeld:

git remote add origin git@github.com:MilanVives/containerizedapp.git

Stap 3: Add, Commit, Push

git add .
git commit -m "Initial commit: Add Dockerfile and script.sh"
git push -u origin main

Output:

Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 404 bytes | 404.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:MilanVives/containerizedapp.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

5.2 Directory Structuur voor GitHub Actions

Maak de benodigde directories aan:

mkdir -p .github/workflows

Resulterende structuur:

containerizedapp/
├── .github/
│   └── workflows/
│       └── publish-image.yml    # GitHub Actions workflow
├── Dockerfile
└── script.sh

5.3 GitHub Secret Configureren

We moeten de PAT token als secret toevoegen aan de repository.

Stap 1: Navigeer naar Repository Settings

Ga naar je repository op GitHub:

Repository Secret

Stap 2: Secret Aanmaken

⚠️ Belangrijk:

5.4 GitHub Actions Workflow Maken

Maak een nieuw bestand aan: .github/workflows/publish-image.yml

Volledige workflow:

name: Build and Push to GHCR

# Trigger: Bij elke push naar main branch
on:
  push:
    branches:
      - main
  # Optioneel: Handmatige trigger
  workflow_dispatch:

# Environment variables
env:
  REGISTRY: ghcr.io
  IMAGE_NAME: $

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    
    # Permissies nodig voor GHCR
    permissions:
      contents: read
      packages: write

    steps:
      # Stap 1: Checkout code
      - name: Checkout repository
        uses: actions/checkout@v4

      # Stap 2: Login naar GHCR
      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: $
          username: $
          password: $

      # Stap 3: Extract metadata (tags, labels)
      - name: Extract metadata for Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: $/$
          tags: |
            # Tag met branch naam
            type=ref,event=branch
            # Tag met commit SHA
            type=sha,prefix=-
            # Tag 'latest' alleen voor main branch
            type=raw,value=latest,enable=

      # Stap 4: Set up Docker Buildx
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      # Stap 5: Build en Push image
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: $
          labels: $
          # Cache layers voor snellere builds
          cache-from: type=gha
          cache-to: type=gha,mode=max

      # Stap 6: Output image details
      - name: Image digest
        run: echo "Image pushed with digest $"

5.5 Uitleg van de Workflow

Triggers:

on:
  push:
    branches: [main]
  workflow_dispatch:

Permissions:

permissions:
  contents: read    # Lees repository inhoud
  packages: write   # Schrijf naar GHCR

Environment Variables:

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: $  # Automatisch: username/repo-name

Metadata Tags:

Docker Buildx:

5.6 Workflow Committen en Pushen

git add .github/workflows/publish-image.yml
git commit -m "Add GitHub Actions workflow for GHCR"
git push origin main

Na de push:

  1. GitHub Actions start automatisch
  2. Je kan de voortgang zien onder: Actions tab in je repository
  3. Bij succes wordt de image naar GHCR gepusht

Action Succeeded


Verificatie

6.1 Check GitHub Actions Status

Stap 1: Ga naar Actions Tab

In je repository: Klik op Actions (bovenaan)

Verwachte Output:

Stap 2: Bekijk Logs (bij problemen)

Klik op de workflow run → Klik op de job → Bekijk de logs

6.2 Verificeer Package op GHCR

URL: https://github.com/YOUR_USERNAME?tab=packages

Je zou nu je nieuwe package moeten zien met:

Nieuwe Package Versie

6.3 Pull en Test de Image

Test of de image correct werkt:

# Pull de image van GHCR
docker pull ghcr.io/YOUR_USERNAME/containerizedapp:latest

# Run de container
docker run --rm ghcr.io/YOUR_USERNAME/containerizedapp:latest

Expected Output:

=== Hello from GHCR Container ===
Container is running successfully!
Built with GitHub Actions ✓

Troubleshooting

Probleem 1: Permission Denied bij GHCR

Error:

Error: denied: permission_denied: write_package

Oplossing:

  1. Check of GH_PAT secret correct is aangemaakt
  2. Verify PAT heeft write:packages permissie
  3. Check permissions in workflow file:
    permissions:
      contents: read
      packages: write
    

Probleem 2: Image Naam Incorrect

Error:

invalid reference format: repository name must be lowercase

Oplossing:

Probleem 3: Workflow Triggered Niet

Mogelijk redenen:

  1. Workflow file niet in .github/workflows/ directory
  2. YAML syntax errors
  3. Branch naam komt niet overeen met trigger

Verificatie:

# Check YAML syntax
yamllint .github/workflows/publish-image.yml

# Check file locatie
ls -la .github/workflows/

Probleem 4: PAT Expired

Error:

Error: Bad credentials

Oplossing:

  1. Genereer nieuwe PAT (stap 1)
  2. Update GH_PAT secret in repository settings

Advanced: Package Visibility Aanpassen

Public vs Private Packages

Default: GHCR packages zijn private

Publiek maken:

  1. Ga naar je package: https://github.com/users/USERNAME/packages/container/PACKAGE_NAME
  2. Klik op Package settings (rechtsboven)
  3. Scroll naar beneden naar Danger Zone
  4. Klik op Change visibilityPublic

⚠️ Waarschuwing: Publieke packages kunnen door iedereen gepulled worden!


Best Practices

1. Security

DO:

DON’T:

2. Tagging Strategy

Semantic Versioning:

tags: |
  type=semver,pattern=
  type=semver,pattern=.
  type=semver,pattern=
  type=sha

Branch-based:

tags: |
  type=ref,event=branch
  type=ref,event=pr
  type=sha,prefix=-

3. Multi-stage Builds

Optimaliseer je Dockerfile:

# Build stage
FROM alpine:latest AS builder
RUN apk add --no-cache bash
WORKDIR /app
COPY script.sh .
RUN chmod +x script.sh

# Runtime stage (smaller final image)
FROM alpine:latest
RUN apk add --no-cache bash
WORKDIR /app
COPY --from=builder /app/script.sh .
ENTRYPOINT ["./script.sh"]

4. Caching

GitHub Actions Cache:

- name: Build and push
  uses: docker/build-push-action@v5
  with:
    cache-from: type=gha
    cache-to: type=gha,mode=max

Voordelen:


Volgende Stappen

1. Multi-Architecture Builds

Build voor verschillende platformen:

- name: Set up QEMU
  uses: docker/setup-qemu-action@v3

- name: Build multi-arch
  uses: docker/build-push-action@v5
  with:
    platforms: linux/amd64,linux/arm64
    push: true

2. Automated Testing

Voeg tests toe voor build:

- name: Run tests
  run: |
    docker build -t test-image .
    docker run --rm test-image ./run-tests.sh

3. Deploy naar Kubernetes

Gebruik GHCR image in Kubernetes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
      - name: myapp
        image: ghcr.io/username/containerizedapp:latest
        imagePullPolicy: Always
      imagePullSecrets:
      - name: ghcr-secret

Conclusie

Je hebt nu geleerd:

✅ GitHub Personal Access Token (PAT) aanmaken
✅ Docker login naar GHCR
✅ Container images bouwen en taggen voor GHCR
✅ Handmatig images pushen naar GHCR
✅ GitHub Actions workflow maken voor automatische builds
✅ Troubleshooting common issues
✅ Best practices voor security en efficiency

Voordelen van GHCR + GitHub Actions:

Happy containerizing! 🐳