GitHub Actions CI/CD Architecture

1. Overview

This guide documents the CI/CD pipeline architecture used across Event and Membership Management projects. The system uses a combination of reusable workflows (defined centrally) and project-specific workflows that consume them.

CI/CD is one element of the broader EMS service baseline — the parent context (Maven module shape, Helm chart, ArgoCD manifests, OTel, profiles) lives in Microservice Pattern. Read that page first when bootstrapping a new service; this page focuses on the GitHub Actions layer specifically.

1.1. Key Principles

  • DRY (Don’t Repeat Yourself) - Common build logic is centralized in reusable workflows

  • Consistency - All projects follow the same patterns and conventions

  • Conditional Execution - Only run tests for changed components (frontend/backend)

  • GitOps - Deployments are managed through ArgoCD and Git-based configuration

2. Workflow Architecture

The CI/CD system consists of two layers:

Reusable Workflows (in christhonie/event/.github/workflows/)

Centrally managed, versioned workflow definitions that encapsulate common operations like testing, building, and deploying.

Project Workflows (in each repository’s .github/workflows/)

Project-specific workflows that orchestrate the reusable workflows and define triggers.

Diagram

The five wrapper workflows above are the canonical inventory for a backend or MCP-adapter service. Portal services (those serving an Angular SPA) add a frontend filter to pr-non-main.yml and pass require-npm: true to maven-package.yml.

3. Path Filtering Strategy

3.1. Excluding Non-Build Directories

Use paths-ignore at the workflow trigger level to prevent unnecessary builds when only documentation or configuration files change:

on:
  push:
    branches:
      - develop
    # NOTE: Keep paths-ignore in sync with push-main.yml and pr-non-main.yml
    paths-ignore:
      - '*.md'
      - '*.adoc'
      - '.devcontainer/**'
      - '.husky/**'
      - '.jhipster/**'
      - 'docs/**'
      - 'src/main/helm/**'
When modifying the paths-ignore list, update it in all workflow files that share this pattern. The sync note comment serves as a reminder.

3.2. Conditional Job Execution

Use the dorny/paths-filter action to conditionally run frontend or backend tests based on what files changed:

jobs:
  paths-filter:
    runs-on: ubuntu-latest
    outputs:
      frontend: ${{ steps.filter.outputs.frontend }}
      backend: ${{ steps.filter.outputs.backend }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v2
        id: filter
        with:
          filters: |
            frontend:
              - '.github/workflows/**'
              - 'package*.json'
              - 'angular.json'
              - 'tsconfig*.json'
              - 'webpack/**'
              - 'src/main/webapp/**'
            backend:
              - '.github/workflows/**'
              - 'pom.xml'
              - 'src/main/java/**'
              - 'src/main/resources/**'
              - 'src/test/java/**'
              - 'src/test/resources/**'

  test-fe:
    needs: paths-filter
    if: needs.paths-filter.outputs.frontend == 'true'
    uses: christhonie/event/.github/workflows/test-fe.yml@main
    # ...

  test-be:
    needs: paths-filter
    if: needs.paths-filter.outputs.backend == 'true'
    uses: christhonie/event/.github/workflows/test-be.yml@main
    # ...

4. Reusable Workflows Reference

The following reusable workflows are available in christhonie/event/.github/workflows/:

Workflow Purpose Inputs Secrets Outputs

test-be.yml

Run backend tests using Maven

maven-profiles, jdk-version

MAVEN_PASSWORD

-

test-fe.yml

Run frontend tests using npm

node-version

NPM_PASSWORD

-

maven-package.yml

Build and cache Maven artifacts

maven-profiles, jdk-version, require-npm, node-version

MAVEN_PASSWORD

cached target

maven-deploy.yml

Deploy JAR to GitHub Packages

maven-profiles, jdk-version

MAVEN_PASSWORD

-

docker-build.yml

Build and push Docker image via JIB

maven-profiles, jdk-version

DOCKER_PAT

image-tag

helm-build.yml

Package and push Helm charts

maven-profiles, jdk-version

DOCKER_PAT

-

argocd-update.yml

Update ArgoCD deployment manifests in christhonie/idl-xnl-jhb-rc01

environment (req), service-name (req), image-tag (opt), chart-version (opt — bumps targetRevision), build-sha (opt — sets podAnnotations.app.kubernetes.io/build-sha to force pod rollover on SNAPSHOT rebuilds), argocd-repo (opt, default christhonie/idl-xnl-jhb-rc01)

ARGOCD_REPO_TOKEN

-

dependencygraph.yml

Submit dependency graph to GitHub

maven-profiles, jdk-version

-

-

publish.yml

Upload build artifacts

jdk-version

-

-

gitflow-release-finish.yml

Complete GitFlow release process

jdk-version

-

-

4.1. Workflow Naming Convention

Workflow names use emoji prefixes for quick visual identification:

🚀

Deployment and push workflows

Testing workflows

📦

Packaging workflows

🐋

Docker workflows

Helm/Kubernetes workflows

GitFlow release workflows

📰

Publishing workflows

🔄

Synchronization workflows

5. Pipeline Flows

5.1. Pull Request Pipeline

When a PR is created or updated against non-main branches:

PR created/updated
       │
       ▼
  paths-filter
       │
       ├──► test-fe (if frontend changed)
       │
       └──► test-be (if backend changed)
       │
       ▼
  All tests must pass for merge

5.2. Develop Branch Pipeline

When code is pushed to the develop branch:

Push to develop
       │
       ▼
  paths-filter
       │
       ├──► test-fe ──┐
       │              │
       └──► test-be ──┤
                      │
                      ▼
                  package
                      │
       ┌──────────────┼──────────────┐
       │              │              │
       ▼              ▼              ▼
   publish     docker-build   dependencygraph

5.3. Main Branch Pipeline (Release)

When code is pushed to the main branch (typically after a release merge):

Push to main
       │
       ▼
    package (prod profile)
       │
       ├──────────────┬──────────────┐
       │              │              │
       ▼              ▼              ▼
   publish     docker-build     helm-build
                                     │
                          ┌──────────┼──────────────┐
                          ▼                         ▼
                  argocd-update             gitflow-release-finish
                  (env=stage)              (merges main → develop;
                                            bumps SNAPSHOT version)

5.4. Release Branch Pipeline

When code is pushed to a release/** branch (created by manual-release-start.yml):

Push to release/**
       │
       ▼
   test-be
   (verifies the RC before main is updated)

This is intentionally minimal — packaging, image build and chart push are deferred until the release branch is merged into main. The release branch is a candidate, not a deliverable.

5.5. Manual Release Start

A workflow_dispatch trigger on manual-release-start.yml lets a human cut a release branch:

workflow_dispatch
   inputs: { version: "X.Y.Z" }
       │
       ▼
   mvn gitflow:release-start
   (creates release/X.Y.Z branch off develop,
    rewrites <revision> from X.Y.Z-SNAPSHOT to X.Y.Z)
       │
       ▼
   gh pr create -B main -H release/X.Y.Z
   (opens a PR back to main for review;
    merging the PR triggers push-main.yml)

This is the only way new versions enter main. There is no direct push from develop to main.

5.6. Helm-Only Pipeline

Changes to src/main/helm/** trigger a dedicated Helm build without full Java/Angular builds:

on:
  push:
    branches:
      - main
      - develop
    paths:
      - 'src/main/helm/**'
  workflow_dispatch:

6. Consuming Workflows

6.1. Referencing Reusable Workflows

Project workflows reference reusable workflows using the uses keyword:

jobs:
  test-be:
    uses: christhonie/event/.github/workflows/test-be.yml@main
    with:
      maven-profiles: dev
      jdk-version: '17'
    secrets:
      MAVEN_PASSWORD: ${{ secrets.EVENT_PACKAGE_REPO_TOKEN }}

6.2. Versioning Strategy

Recommended: Use @main to stay current with the latest reusable workflow improvements.

uses: christhonie/event/.github/workflows/test-be.yml@main

Alternative: Pin to a specific tag for stability when needed:

uses: christhonie/event/.github/workflows/[email protected]

7. Job Dependencies and Caching

7.1. Sequential Execution with needs

Use needs to define job dependencies:

jobs:
  package:
    uses: christhonie/event/.github/workflows/maven-package.yml@main
    # ...

  docker-build:
    needs: [package]  # Wait for package to complete
    uses: christhonie/event/.github/workflows/docker-build.yml@main
    # ...

7.2. Caching Strategy

The maven-package workflow caches the target/ directory, which is then reused by:

  • docker-build - For building Docker images

  • helm-build - For packaging Helm charts

  • publish - For uploading artifacts

  • dependencygraph - For dependency analysis

7.3. Running Jobs After Skipped Dependencies

Use if: always() to run jobs even when dependencies were skipped:

docker-build:
  needs: [package]
  if: always()  # Run even if tests were skipped
  uses: christhonie/event/.github/workflows/docker-build.yml@main

8. Environment Variables Pattern

Centralize fallback logic at the job level to avoid repetition:

jobs:
  helm-build:
    runs-on: ubuntu-latest
    env:
      JDK_VERSION: ${{ inputs.jdk-version || '17' }}
      MAVEN_PROFILES: ${{ inputs.maven-profiles || 'dev' }}

    steps:
      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          java-version: ${{ env.JDK_VERSION }}
          # ...

      - name: Package Helm
        run: mvn helm:package -P${{ env.MAVEN_PROFILES }}

9. Required Repository Settings

When bootstrapping a new service repo, two GitHub settings must be configured before the first push to develop:

9.1. Default workflow permissions: write

Settings → Actions → General → Workflow permissions → "Read and write permissions".

The reusable workflows declare permissions: contents: write (e.g. dependencygraph.yml, gitflow-release-finish.yml). GitHub blocks reusable workflows from elevating beyond the calling workflow’s default GITHUB_TOKEN permissions. New repos default to read, which causes the calling workflow to fail with startup_failure and no jobs created — an opaque error with no log output.

This is the single most common bring-up mistake. Mirror what event-admin-service already has set.

CLI shortcut to flip it:

gh api -X PUT repos/<owner>/<repo>/actions/permissions/workflow \
  -f default_workflow_permissions=write \
  -F can_approve_pull_request_reviews=true

9.2. Reusable workflow access on christhonie/event

On the christhonie/event repo: Settings → Actions → General → Access → "Accessible from repositories owned by the user 'christhonie'".

Without this, calls to christhonie/event/.github/workflows/@main from a service repo fail with "workflow not found" or a permissions error. The default for new repos is "Not accessible". Once set to "user-owned", every existing and future repo under christhonie/ can consume the reusable workflows.

This setting is configured once on christhonie/event, not per service repo.

9.3. service-name ↔ chart name ↔ ArgoCD manifest filename

The service-name input passed to argocd-update.yml must match three things:

  • The Helm chart’s name: in Chart.yaml

  • The Docker image name on Docker Hub (christhonie/<service-name>)

  • The ArgoCD manifest filename in idl-xnl-jhb-rc01: argocd/<service-name>-<env>.yml

It does not need to match the GitHub repo name — for example, christhonie/ems-mcp-server ships chart name ems-mcp-server, image christhonie/ems-mcp-server, and manifest ems-mcp-server-prod.yml. The repo name happens to match in this case, but the convention links chart/image/manifest, not repo.

10. Secrets Configuration

10.1. Required Secrets

Secret Purpose Used By

GITHUB_TOKEN

Built-in token for repository operations

Git operations, GitHub API

EVENT_PACKAGE_REPO_TOKEN

Standard secret name for GitHub Packages access (Maven/NPM)

maven-package, test-be, test-fe, maven-deploy

DOCKER_PAT

Docker Hub Personal Access Token

docker-build, helm-build

ARGOCD_REPO_TOKEN

Access to ArgoCD configuration repository

argocd-update

10.2. Secret Variable Mapping

The EVENT_PACKAGE_REPO_TOKEN secret is internally mapped to workflow-specific variables:

# In workflow steps
secrets:
  MAVEN_PASSWORD: ${{ secrets.EVENT_PACKAGE_REPO_TOKEN }}
  NPM_PASSWORD: ${{ secrets.EVENT_PACKAGE_REPO_TOKEN }}

10.3. Maven Repository Configuration

The setup-java action creates a settings.xml file with credentials:

- name: Set up JDK
  uses: actions/setup-java@v3
  with:
    java-version: '17'
    distribution: 'temurin'
    cache: maven
    server-id: github-christhonie
    server-username: MAVEN_USERNAME
    server-password: MAVEN_PASSWORD

- name: Build with Maven
  run: mvn --batch-mode package
  env:
    MAVEN_USERNAME: christhonie
    MAVEN_PASSWORD: ${{ secrets.EVENT_PACKAGE_REPO_TOKEN }}

The generated settings.xml will contain:

<servers>
  <server>
    <id>github-christhonie</id>
    <username>${env.MAVEN_USERNAME}</username>
    <password>${env.MAVEN_PASSWORD}</password>
  </server>
</servers>

11. Artifact Management

11.1. Build Artifacts

Build artifacts (JAR files) are uploaded to GitHub Actions artifacts for investigation:

- name: Upload Artifacts
  uses: actions/upload-artifact@v4
  with:
    name: build-artifacts
    path: target/*.jar
    retention-days: 14
Artifacts are automatically purged after the retention period.

11.2. Dependency Graph (SBOM)

The dependencygraph workflow submits the Maven dependency graph to GitHub’s dependency graph feature, enabling:

  • Security vulnerability scanning

  • Software Bill of Materials (SBOM) generation

  • Dependency alerts

12. Docker Compose Version Synchronization

12.1. Pattern Overview

Projects can automatically synchronize Docker Compose file versions with POM versions:

  1. Store image versions in pom.xml properties (<revision>, <admin-service.version>)

  2. Workflow triggers when pom.xml changes on develop branch

  3. Extract versions using grep/sed

  4. Update Docker Compose files (dev.yml, admin-service.yml)

  5. Auto-commit changes with bot attribution

12.2. Example Implementation

Reference: event-admin-ui/.github/workflows/sync-docker-versions.yml

name: '🔄 Sync Docker Compose Versions'

on:
  push:
    branches:
      - develop
    paths:
      - 'pom.xml'

env:
  DOCKER_REGISTRY: christhonie
  GATEWAY_IMAGE_NAME: event-admin-ui
  ADMIN_IMAGE_NAME: event-admin-service

jobs:
  sync-versions:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Extract versions from POM
        id: versions
        run: |
          REVISION=$(grep -oP '(?<=<revision>)[^<]+' pom.xml | head -1)
          echo "revision=$REVISION" >> $GITHUB_OUTPUT

      - name: Update Docker Compose files
        run: |
          sed -i "s|${DOCKER_REGISTRY}/${GATEWAY_IMAGE_NAME}:[^[:space:]]*|${DOCKER_REGISTRY}/${GATEWAY_IMAGE_NAME}:${{ steps.versions.outputs.revision }}|g" src/main/docker/dev.yml

      - name: Commit and push
        run: |
          git config user.name "github-actions[bot]"
          git commit -am "[AutoTask] Sync Docker image versions"
          git push

TODO: Standardize sync-docker-versions.yml into a reusable workflow to avoid duplication across projects.

13. Version Strategy for Docker/Helm Artifacts

TODO: Implement build number conversion for SNAPSHOT versions:

  • Docker containers and Helm charts ending in -SNAPSHOT should include a build number

  • Pattern: 1.2.3-SNAPSHOT1.2.3-SNAPSHOT-{buildNumber} or 1.2.3-{buildNumber}

  • Ensures each build has a unique, retrievable version identifier

  • Build number options: GitHub run number, timestamp, or short commit SHA