CI/CD Architecture
Hephaestus uses trunk-based continuous deployment powered by GitHub Actions. Every merge to main triggers automatic staging deployment, with production requiring manual approval.
๐๏ธ Architecture Overviewโ
โก Pipeline Timelineโ
| Stage | Duration | Notes |
|---|---|---|
| Quality gates | ~3 min | Parallel with tests |
| Tests | ~3 min | Parallel with gates |
| Docker builds | ~3 min (cached) | Registry cache, ~7 min cold |
| Release cut | ~1 min | On Version PR merge: tag + GitHub Release |
| Staging deploy | ~2 min | Automatic |
| Your verification | You decide | Check staging |
| Production deploy | ~2 min | After approval |
| Total | ~12 min + verification | ~8 min with full cache hits |
๐ Release Flowโ
Every merge to main runs CI and updates the accumulating Version PR (changesets). Merging that PR
cuts the release โ tag vX.Y.Z, GitHub Release, docker tags X.Y.Z/X.Y/latest, then staging
(automatic) and production (after approval). Full flow: Release Management.
๐ก๏ธ Quality Gatesโ
Before any release, code must pass:
| Gate (leg) | Tool | Purpose |
|---|---|---|
Migration chain + drift (Database) | Liquibase | Full chain applies empty โ head, then schema is diffed against JPA entities |
Changelog immutability (Migrations) | git diff | Released changesets + master.xml are append-only |
| OpenAPI sync | Diff check | Client โ Server sync |
| Java formatting | Spotless | Code style |
| TypeScript | Biome + tsc | Lint + typecheck |
๐ Securityโ
- CodeQL โ SAST scanning via GitHub's Default Setup (automatic, zero maintenance)
- Trivy โ Scans dependencies for CVEs
- TruffleHog โ Secret detection in code and history
- Renovate โ Monitors dependencies for vulnerabilities
- Environment protection โ Production requires approval
CodeQL Default Setupโ
CodeQL runs automatically via GitHub's Default Setup (enabled in repository settings), providing:
- Scans on every push to main and protected branches
- Scans on pull request creation and updates
- Weekly scheduled scans for the full codebase
- Incremental analysis (20% faster on PRs)
- Zero maintenance โ GitHub manages query updates
This is more efficient than a custom workflow and doesn't consume CI minutes.
๐ฆ Environmentsโ
| Environment | Protection | Deploys On |
|---|---|---|
| Preview (Coolify) | None | Every PR |
| Staging | None | Every tag |
| Production | Approval required | Tag + approval |
GitHub Environment Setupโ
- Settings โ Environments โ New environment
- Create
staging(no rules) - Create
productionwith Required reviewers
๐ Preview Deploymentsโ
Coolify handles PR previews:
- Built directly on server (fast!)
- URL:
pr-{number}.preview.hephaestus.cit.tum.de - Auto-cleanup on PR close
โ๏ธ Key Workflowsโ
| Workflow | Trigger | Purpose |
|---|---|---|
cicd.yml | Push to main, PRs | Orchestrator: change detection + workflow dispatch |
ci-quality-gates.yml | Called by cicd.yml | Code quality, formatting, schema validation |
ci-tests.yml | Called by cicd.yml | Unit, integration, visual tests |
ci-docker-build.yml | Called by cicd.yml | Docker image builds per component |
ci-security-scan.yml | Called by cicd.yml | Dependency scanning (Trivy), secret detection |
verify-changesets.yml | PRs | Fails shipped-code PRs that carry no changeset |
version-pr.yml | Push to main | Maintains the accumulating Version PR (changesets) |
release.yml | On CI/CD Success | Cuts a release when the Version PR merged: tag + GitHub Release, deploys staging, gates production |
deploy-staging.yml | Called by release.yml | Deploys to staging |
deploy-prod.yml | workflow_dispatch | Deploys to production (manual trigger) |
Workflow Architectureโ
The cicd.yml workflow:
- Detects changes using
dorny/paths-filter - Dispatches sub-workflows with component-specific flags
- Aggregates results in the CI Status Gate job
๐ฏ Performance Optimizationsโ
Path-Based Filteringโ
CI only runs jobs for components that actually changed:
| Component | Triggers On |
|---|---|
| Webapp | webapp/**, package.json, pnpm-lock.yaml, pnpm-workspace.yaml, .npmrc, .node-version |
| Application Server | server/**, scripts/db-utils.sh (includes webhook receiver โ ADR 0008) |
| CI Config | .github/workflows/**, .github/actions/** โ runs all jobs |
Benefits:
- Webapp-only changes skip Java tests (~3 min saved)
- Docs-only changes skip all CI jobs (~7 min saved)
- CI config changes run everything (safety net)
Docker Layer Cachingโ
Docker builds use registry-based caching to store intermediate layers in ghcr.io:
| Component | First Build | Cached Build | Cache Size |
|---|---|---|---|
| Application Server | ~7 min | ~30 sec | ~500 MB |
| Webapp | ~5 min | ~30 sec | ~200 MB |
How it works:
cache-from: Pulls cached layers from registry (main branch + current branch)cache-to: Pushes new layers withmode=max(all intermediate layers)- Separate cache tags per platform:
image:cache-linux-amd64,image:cache-linux-arm64 - Native builds: amd64 on x86 runners, arm64 on ARM runners (no QEMU emulation)
Benefits over GitHub Actions Cache:
- No 10GB size limit (registry is unlimited)
- No eviction after 7 days
- Works across branches (PRs benefit from main's cache)
- Persistent (survives cache clearing)
Docker Hub Authentication (Recommended)โ
Without Docker Hub authentication, base image metadata resolution can be rate-limited, adding 2+ minutes to every Docker build. To avoid this:
- Create a Docker Hub account (free tier is sufficient)
- Generate an access token at hub.docker.com/settings/security
- Add repository variable and secret:
- Variable:
DOCKERHUB_USERNAME= your Docker Hub username - Secret:
DOCKERHUB_TOKEN= your access token
- Variable:
The Docker build workflow will automatically use these credentials if present, falling back to anonymous (slower) access if not configured.
Parallel Executionโ
- 6 test types run in parallel (3 app-server, 3 webapp). Webhook reception is part of the app-server test surface since ADR 0008.
- 5 quality-gate legs run in parallel (App Server, Webapp, OpenAPI, Database, Migrations), plus a legacy-cleanup guard
- 4 Docker builds ร 2 architectures (amd64 + arm64)
fail-fast: falseensures all jobs complete for full feedback
Concurrency Controlโ
- Outdated PR runs cancelled automatically
- Release runs never cancelled
๐ ๏ธ Running CI Locallyโ
Before pushing, run the same checks that CI runs:
Quick Check (Recommended)โ
# Format and check all services
pnpm run format && pnpm run check
Per-Service Commandsโ
# Webapp
pnpm run check:webapp # Full check (format + lint + typecheck)
pnpm run test:webapp # Unit tests
# Application Server (Java) โ includes the integration.core.webhook receiver
pnpm run format:java:check # Check formatting
cd server && ./mvnw test -Dgroups="unit" # Unit tests
Common Issuesโ
| Issue | Solution |
|---|---|
| Formatting errors | Run pnpm run format |
| TypeScript errors | Run pnpm run typecheck:webapp to see details |
| Test failures | Check the specific test output for details |
| OpenAPI out of sync | Run pnpm run generate:api |
| Database schema drift | Run pnpm run db:draft-changelog |
๐ CI Featuresโ
Test Resultsโ
All test suites generate JUnit XML reports that are displayed in the Test Results tab of each workflow run:
- Application Server: Unit, integration, and architecture tests (incl. the in-process Pi mentor agent and the webhook receiver per ADR 0008)
- Webapp: Unit tests and Storybook interaction tests
Job Summaryโ
Each CI run generates a rich Job Summary in the Actions UI with:
- Overall status with emoji indicators
- Results table for each workflow (quality gates, tests, security, Docker)
- Components changed table (from path filtering)
- Failure-specific troubleshooting guides with fix commands
- Performance metrics showing skipped workflows
Workflow Timelineโ
The CI Status Gate job generates a visual Mermaid timeline showing:
- Job execution order and duration
- Parallel job execution
- Runner wait times
- Critical path identification
This helps identify bottlenecks and optimization opportunities.
๐ Adding a New Serviceโ
When adding a new service to the monorepo, update CI configuration in this order:
Step 1: Path Detection (cicd.yml)โ
Add a path filter and output for the new service:
# In detect-changes job outputs:
outputs:
new-service: ${{ steps.filter.outputs.new-service }}
# In paths-filter step:
filters: |
new-service:
- 'server/new-service/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
Update the any-code aggregate output to include the new service.
Step 2: Quality Gates (ci-quality-gates.yml)โ
- Add to matrix:
matrix:
check: [
# ... existing checks
new-service-quality,
]
- Add case statement in "Determine if check should run":
"new-service-quality")
echo "run=${{ inputs.new_service_changed }}" >> $GITHUB_OUTPUT
;;
- Add quality check step with the appropriate linting/type checking commands.
Step 3: Tests (ci-tests.yml)โ
- Add to matrix:
matrix:
test-type: [
# ... existing tests
new-service-unit,
new-service-integration, # if applicable
]
- Add case statement in "Determine if test should run":
"new-service-unit"|"new-service-integration")
echo "run=${{ inputs.new_service_changed }}" >> $GITHUB_OUTPUT
;;
-
Add test execution step with the test commands.
-
Add test result upload for JUnit reporting.
Step 4: Docker Build (ci-docker-build.yml)โ
Add a new build job:
new-service-build:
name: "Docker: new-service"
if: inputs.should_skip != 'true' && inputs.new_service_changed == 'true'
uses: ls1intum/.github/.github/workflows/build-and-push-docker-image.yml@main
with:
image-name: "ls1intum/hephaestus/new-service"
docker-file: "./server/new-service/Dockerfile"
docker-context: "./server/new-service"
# ... rest of config
Step 5: Caching (setup-caches/action.yml)โ
Add the new service's cache-types to the appropriate conditions:
# For Node.js services:
- name: Cache Node.js dependencies
if: contains(fromJSON('["...", "new-service-quality", "new-service-unit"]'), inputs.cache-type)
# For Java services:
- name: Cache Maven dependencies
if: contains(fromJSON('["...", "new-service-unit"]'), inputs.cache-type)
Step 6: Update Workflow Inputsโ
In cicd.yml, add the new input to workflow calls:
with:
new_service_changed: ${{ (needs.detect-changes.outputs.new-service == 'true' || ...) && 'true' || 'false' }}
Verification Checklistโ
After adding a new service, verify:
- Path filter correctly detects changes to new service
- Quality gates run only when new service changes
- Tests run only when new service changes
- Docker build runs only when new service changes
- CI config changes trigger all jobs (safety net)
- JUnit reports appear in Test Results tab