Skip to content

Registry Publishing

Publishing your module to container registries makes it available for deployment across clusters. This guide covers registry options and publishing workflows.

Container Registries

Docker Hub

bash
# Login
docker login

# Tag image
docker tag my-module:v1.0.0 myorg/my-module:v1.0.0

# Push
docker push myorg/my-module:v1.0.0

GitHub Container Registry (ghcr.io)

bash
# Login with PAT
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin

# Tag
docker tag my-module:v1.0.0 ghcr.io/myorg/my-module:v1.0.0

# Push
docker push ghcr.io/myorg/my-module:v1.0.0

Google Container Registry

bash
# Configure auth
gcloud auth configure-docker

# Tag
docker tag my-module:v1.0.0 gcr.io/my-project/my-module:v1.0.0

# Push
docker push gcr.io/my-project/my-module:v1.0.0

Amazon ECR

bash
# Get login token
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com

# Tag
docker tag my-module:v1.0.0 123456789.dkr.ecr.us-east-1.amazonaws.com/my-module:v1.0.0

# Push
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/my-module:v1.0.0

Azure Container Registry

bash
# Login
az acr login --name myregistry

# Tag
docker tag my-module:v1.0.0 myregistry.azurecr.io/my-module:v1.0.0

# Push
docker push myregistry.azurecr.io/my-module:v1.0.0

Helm Chart Registries

OCI-based Registry

bash
# Login
helm registry login ghcr.io -u USERNAME -p $GITHUB_TOKEN

# Package chart
helm package ./charts/my-module

# Push
helm push my-module-1.0.0.tgz oci://ghcr.io/myorg/charts

ChartMuseum

bash
# Add repo
helm repo add myrepo https://charts.example.com

# Push (requires chartmuseum-push plugin)
helm cm-push ./charts/my-module myrepo

GitHub Pages

bash
# Package chart
helm package ./charts/my-module -d ./docs

# Generate index
helm repo index ./docs --url https://myorg.github.io/charts

# Commit and push to gh-pages branch

Automated Publishing

GitHub Actions - Container

yaml
name: Publish Container

on:
  push:
    tags:
      - 'v*'

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

    steps:
      - uses: actions/checkout@v4

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

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
            type=sha

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

GitHub Actions - Helm Chart

yaml
name: Publish Helm Chart

on:
  push:
    tags:
      - 'v*'

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

    steps:
      - uses: actions/checkout@v4

      - name: Install Helm
        uses: azure/setup-helm@v3

      - name: Login to GHCR
        run: |
          echo ${{ secrets.GITHUB_TOKEN }} | \
            helm registry login ghcr.io -u ${{ github.actor }} --password-stdin

      - name: Extract version
        id: version
        run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT

      - name: Update Chart version
        run: |
          sed -i "s/^version:.*/version: ${{ steps.version.outputs.VERSION }}/" charts/my-module/Chart.yaml
          sed -i "s/^appVersion:.*/appVersion: \"${{ steps.version.outputs.VERSION }}\"/" charts/my-module/Chart.yaml

      - name: Package chart
        run: helm package ./charts/my-module

      - name: Push chart
        run: |
          helm push my-module-${{ steps.version.outputs.VERSION }}.tgz \
            oci://ghcr.io/${{ github.repository_owner }}/charts

GitLab CI

yaml
stages:
  - build
  - publish

variables:
  DOCKER_TLS_CERTDIR: "/certs"
  IMAGE_NAME: $CI_REGISTRY_IMAGE

build:
  stage: build
  image: docker:24-dind
  services:
    - docker:dind
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $IMAGE_NAME:$CI_COMMIT_SHA .
    - docker push $IMAGE_NAME:$CI_COMMIT_SHA

publish:
  stage: publish
  image: docker:24-dind
  services:
    - docker:dind
  only:
    - tags
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker pull $IMAGE_NAME:$CI_COMMIT_SHA
    - docker tag $IMAGE_NAME:$CI_COMMIT_SHA $IMAGE_NAME:$CI_COMMIT_TAG
    - docker tag $IMAGE_NAME:$CI_COMMIT_SHA $IMAGE_NAME:latest
    - docker push $IMAGE_NAME:$CI_COMMIT_TAG
    - docker push $IMAGE_NAME:latest

Image Signing

Cosign

bash
# Install cosign
brew install cosign

# Generate key pair
cosign generate-key-pair

# Sign image
cosign sign --key cosign.key ghcr.io/myorg/my-module:v1.0.0

# Verify signature
cosign verify --key cosign.pub ghcr.io/myorg/my-module:v1.0.0

Keyless Signing with GitHub Actions

yaml
- name: Sign image
  uses: sigstore/cosign-installer@v3

- name: Sign with OIDC
  run: cosign sign --yes ghcr.io/${{ github.repository }}:${{ steps.meta.outputs.version }}
  env:
    COSIGN_EXPERIMENTAL: 1

Security Scanning

Trivy

yaml
- name: Scan image
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ghcr.io/${{ github.repository }}:${{ steps.meta.outputs.version }}
    format: 'sarif'
    output: 'trivy-results.sarif'

- name: Upload results
  uses: github/codeql-action/upload-sarif@v2
  with:
    sarif_file: 'trivy-results.sarif'

Snyk

yaml
- name: Run Snyk
  uses: snyk/actions/docker@master
  with:
    image: ghcr.io/${{ github.repository }}:${{ steps.meta.outputs.version }}
  env:
    SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

Image Tags

Tagging Strategy

Tag PatternExampleUse Case
Semantic versionv1.2.3Production releases
Major.Minorv1.2Latest patch in minor
Major onlyv1Latest in major
latestlatestMost recent stable
SHAsha-abc1234Exact commit
BranchmainDevelopment

Implementation

yaml
tags: |
  type=semver,pattern=v{{version}}
  type=semver,pattern=v{{major}}.{{minor}}
  type=semver,pattern=v{{major}}
  type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
  type=sha,prefix=sha-

Private Registries

Kubernetes Image Pull Secret

bash
# Create secret
kubectl create secret docker-registry regcred \
  --docker-server=ghcr.io \
  --docker-username=myuser \
  --docker-password=$GITHUB_TOKEN \
  --docker-email=me@example.com \
  -n tinysystems

In Helm Values

yaml
# values.yaml
imagePullSecrets:
  - name: regcred

image:
  repository: ghcr.io/myorg/my-module
  tag: "v1.0.0"
  pullPolicy: IfNotPresent

Service Account

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-module
  namespace: tinysystems
imagePullSecrets:
  - name: regcred

Multi-Architecture Images

Building with Buildx

bash
# Create builder
docker buildx create --name multiarch --use

# Build and push multi-arch
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t ghcr.io/myorg/my-module:v1.0.0 \
  --push .

Inspect Manifest

bash
# View platforms
docker manifest inspect ghcr.io/myorg/my-module:v1.0.0

Release Checklist

Before publishing a release:

  1. Version bump

    • Update Chart.yaml version and appVersion
    • Update any version references in code
  2. Testing

    • All tests pass
    • Integration tests complete
    • Image builds successfully
  3. Documentation

    • CHANGELOG updated
    • README current
    • API docs generated
  4. Security

    • Vulnerability scan passes
    • No secrets in image
    • Base image updated
  5. Tagging

    bash
    git tag -a v1.0.0 -m "Release v1.0.0"
    git push origin v1.0.0

Next Steps

Build flow-based applications on Kubernetes