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.
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 |
|---|---|---|---|---|
|
Run backend tests using Maven |
|
|
- |
|
Run frontend tests using npm |
|
|
- |
|
Build and cache Maven artifacts |
|
|
cached target |
|
Deploy JAR to GitHub Packages |
|
|
- |
|
Build and push Docker image via JIB |
|
|
|
|
Package and push Helm charts |
|
|
- |
|
Update ArgoCD deployment manifests in |
|
|
- |
|
Submit dependency graph to GitHub |
|
- |
- |
|
Upload build artifacts |
|
- |
- |
|
Complete GitFlow release process |
|
- |
- |
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.
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
# ...
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:inChart.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 |
|---|---|---|
|
Built-in token for repository operations |
Git operations, GitHub API |
|
Standard secret name for GitHub Packages access (Maven/NPM) |
|
|
Docker Hub Personal Access Token |
|
|
Access to ArgoCD configuration repository |
|
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
12. Docker Compose Version Synchronization
12.1. Pattern Overview
Projects can automatically synchronize Docker Compose file versions with POM versions:
-
Store image versions in
pom.xmlproperties (<revision>,<admin-service.version>) -
Workflow triggers when
pom.xmlchanges on develop branch -
Extract versions using grep/sed
-
Update Docker Compose files (
dev.yml,admin-service.yml) -
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 |
13. Version Strategy for Docker/Helm Artifacts
|
TODO: Implement build number conversion for SNAPSHOT versions:
|
14. Related Documentation
-
Development Workflow - GitFlow branching and release process
-
Helm Chart Patterns - Helm chart structure and configuration
-
ArgoCD Deployment - GitOps deployment patterns
-
Manual Helm Chart Release - Manual Helm release workflow