Reusable GitHub Actions workflows for building Docker images with secure GitOps deployments.
┌─────────────────────┐ ┌─────────────────────┐
│ GitHub Repo │ │ Harbor │
│ (source code) │────►│ (container images) │
│ │ │ │
│ • Builds images │ └──────────┬──────────┘
│ • NO GitOps access │ │
└─────────────────────┘ │ polls for new images
▼
┌─────────────────────────┐
│ ArgoCD Image Updater │
│ (runs in cluster) │
├─────────────────────────┤
│ • Detects new tags │
│ • Updates k8s-apps- │
│ config via Git │
│ • Uses ArgoCD's own │
│ repo credentials │
└───────────┬─────────────┘
│ commits
▼
┌─────────────────────────┐
│ k8s-apps-config │
│ (GitOps repo) │
└───────────┬─────────────┘
│ syncs
▼
┌─────────────────────────┐
│ ArgoCD │
│ (deploys to cluster) │
└─────────────────────────┘
| Component | Access Level | Why |
|---|---|---|
| GitHub Actions (source repos) | Harbor only | Can't modify GitOps = can't affect prod |
| ArgoCD Image Updater | GitOps repo write | Runs in-cluster, uses existing ArgoCD credentials |
| Prod deployments | Require PR approval | Human review before production changes |
Key benefit: Compromising a GitHub repo's secrets cannot directly affect production workloads.
Builds Docker images using docker buildx bake, pushes them to Harbor (registry.landcareresearch.co.nz) and pushes the build index as an OCI artifact. Harbor is internal-only, so builds run on the self-hosted tak-k8s-ci runner.
jobs:
build:
uses: manaakiwhenua/github-workflows/.github/workflows/docker-build.yml@main
with:
bake_file: docker-bake.hcl
bake_target: default
harbor_username: robot$github-builder
runs_on: tak-k8s-ci
secrets:
HARBOR_TOKEN: ${{ secrets.HARBOR_TOKEN }}Outputs:
build_id- Build identifier (e.g.,42-abc123def)registry_prefix- Registry path for imagesmetadata_url- URL to build index JSON
Generates promotion information for manual PR-based deployments. Useful for prod environments.
Reads the build index that docker-build.yml pushed to Harbor (builds/<image_name>:<build_id>).
Harbor is internal-only, so run it on a self-hosted runner.
jobs:
promote-prod:
uses: manaakiwhenua/github-workflows/.github/workflows/k8s-promote.yml@main
with:
environment: prod
build_id: ${{ needs.build.outputs.build_id }}
harbor_username: robot$github-builder
runs_on: tak-k8s-ci
secrets:
HARBOR_TOKEN: ${{ secrets.HARBOR_TOKEN }}Configure these secrets in your GitHub organization or repository:
| Secret | Description |
|---|---|
HARBOR_TOKEN |
Token for the Harbor push robot named in the harbor_username input (e.g. robot$github-builder) |
Note: No GitOps credentials needed! Image Updater handles deployment.
Add a docker-bake.hcl to your repository:
variable "REGISTRY_PREFIX" {}
variable "IMAGE_TAG" {}
target "default" {
targets = ["app"]
}
target "app" {
context = "."
dockerfile = "Dockerfile"
tags = ["${REGISTRY_PREFIX}/app:${IMAGE_TAG}"]
}Deploy ArgoCD Image Updater to your cluster (see k8s-apps-config/clusters/tak-k8s-nonprod/applications/argocd-image-updater/).
Add Image Updater annotations to your ArgoCD Application:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: dev-my-app
annotations:
argocd-image-updater.argoproj.io/image-list: |
app=registry.landcareresearch.co.nz/mwlr-private/my-app/app
argocd-image-updater.argoproj.io/app.update-strategy: newest-build
argocd-image-updater.argoproj.io/app.allow-tags: "regexp:^[0-9]+-[a-f0-9]+$"
argocd-image-updater.argoproj.io/write-back-method: git- Push to main → GitHub Actions builds & pushes images
- Image Updater detects new image (polls every 2 min)
- Image Updater commits to k8s-apps-config
- ArgoCD syncs changes to cluster
- Push to main → GitHub Actions builds & pushes images
- Developer creates PR to k8s-apps-config with new image
- Team reviews and approves PR
- Merge → ArgoCD syncs to production
# .github/workflows/ci.yml
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
uses: manaakiwhenua/github-workflows/.github/workflows/docker-build.yml@main
with:
bake_file: docker-bake.hcl
bake_target: default
push: ${{ github.event_name != 'pull_request' }}
harbor_username: robot$github-builder
runs_on: tak-k8s-ci
secrets:
HARBOR_TOKEN: ${{ secrets.HARBOR_TOKEN }}
summary:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: |
echo "## Build Complete" >> $GITHUB_STEP_SUMMARY
echo "Build ID: \`${{ needs.build.outputs.build_id }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Dev deployment: Automatic via Image Updater" >> $GITHUB_STEP_SUMMARYImage Updater expects manifests at:
clusters/{cluster}/applications/{app-name}/{environment}/{component}/deployment.yaml
Example:
clusters/tak-k8s-nonprod/applications/barcode-data-portal-mwlr/dev/fastapi-app/deployment.yaml