ADR 008 CI CD Pipeline - steilerDev/cornerstone GitHub Wiki
ADR-008: CI/CD Pipeline with GitHub Actions and Semantic Release
Status
Accepted
Context
Cornerstone is entering feature development and needs automated quality assurance, versioning, and Docker image publishing. Manual processes for running quality gates, tagging releases, and building container images do not scale and are error-prone. The project already follows Conventional Commits and has established quality gate commands (lint, typecheck, test, format:check, build, npm audit), so a CI/CD pipeline should enforce these automatically on every pull request and automate releases on merge to main.
Key requirements:
- Run all quality gates on every PR before merge is allowed
- Automatically derive semantic versions from commit messages (Conventional Commits)
- Build and publish Docker images to a container registry with proper version tags
- Scan Docker images for CVEs and surface results in GitHub Security
- Keep dependency versions up to date with minimal manual effort
- Minimize operational complexity (small project, small team)
Alternatives Considered
GitLab CI
- Would require migrating the repository or mirroring to GitLab
- GitHub Actions has tighter integration with GitHub Issues, PRs, Projects, and Wiki
- The team already uses GitHub for all project management tooling
- No compelling advantage for this project's needs
conventional-changelog (manual versioning)
- Generates changelogs from Conventional Commits but requires manual version bumping
- Developers must remember to run
npm version, tag, and push - semantic-release fully automates the version-bump-tag-release cycle with zero manual steps
GitHub Container Registry (GHCR)
- Free for public repositories and tightly integrated with GitHub
- DHI (Docker Hardened Images) base images are hosted on Docker Hub and authenticated via Docker Hub credentials
- Using Docker Hub for both base image pulls and published images keeps authentication simpler (one set of credentials)
- Docker Scout (CVE scanning) integrates natively with Docker Hub
Trivy / Grype for image scanning
- Mature, well-regarded open-source scanners
- Docker Scout integrates natively with Docker Hub and outputs SARIF for the GitHub Security tab
- Since the project already uses Docker Hub for DHI images and publishing, Docker Scout is the natural choice
Decision
Adopt the following CI/CD stack:
1. GitHub Actions for CI/CD
All automation runs as GitHub Actions workflows. This provides native integration with the repository, free compute for public repos, and a large ecosystem of reusable actions.
2. Semantic Release for Automated Versioning
semantic-release analyzes Conventional Commit messages to determine the next version number, generates GitHub Releases with release notes, and triggers downstream release steps (Docker image build and push).
No @semantic-release/changelog or @semantic-release/git plugins. These plugins commit back to the repository, which would require a Personal Access Token (PAT) to push to the protected main branch. Instead, release notes live exclusively in GitHub Releases, avoiding the need for write-back credentials.
3. GitHub Flow Branching Strategy
- Feature branches are created from
main - Pull requests target
mainand must pass all quality gates - Merging to
maintriggers a release (if there are releasable commits) - No long-lived branches (no
develop, norelease/*)
4. Docker Hub for Image Registry
Published images go to Docker Hub with semantic version tags (e.g., 1.0.0, 1.0, 1, latest). DHI base images at dhi.io accept Docker Hub authentication, so the same DOCKERHUB_USERNAME / DOCKERHUB_TOKEN secrets authenticate to both registries.
5. Docker Scout for CVE Scanning (Warn-Only)
Docker Scout scans the built image and uploads results as SARIF to the GitHub Security tab. This is configured as warn-only -- upstream base image CVEs should not block releases. The team reviews findings in the Security tab and addresses them on a best-effort basis.
6. Dependabot for Dependency Updates
GitHub Dependabot is configured to open PRs for outdated npm dependencies. These PRs go through the same CI quality gates as any other PR, ensuring updates do not break the build.
7. Single CI Job
The codebase is small (three npm workspace packages). Running quality gates in parallel jobs would add more overhead from setup/checkout/install duplication than it saves in wall-clock time. All quality gates run sequentially in a single job:
npm ci(install)npm run lintnpm run format:checknpm run typechecknpm testnpm audit --audit-level=moderatenpm run build
8. Branch Protection (Manual Configuration)
Branch protection rules for main are configured manually via repository settings (not via API or IaC) since the GitHub API requires admin-level access. Required settings:
- Require PR reviews before merging
- Require status checks to pass (the CI job)
- Require branches to be up to date before merging
- Do not allow bypassing the above rules
Required Repository Secrets
| Secret | Purpose |
|---|---|
DOCKERHUB_USERNAME |
Docker Hub username for image push and DHI pull |
DOCKERHUB_TOKEN |
Docker Hub access token |
Note: GITHUB_TOKEN is provided automatically by GitHub Actions and is sufficient for semantic-release to create GitHub Releases (no PAT needed).
Consequences
Positive
- Every PR is automatically validated against all quality gates before merge
- Version numbers are derived deterministically from commit messages -- no manual bumping
- Docker images are built and published automatically on every release
- CVE scan results are visible in the GitHub Security tab for ongoing awareness
- Dependencies stay up to date via automated Dependabot PRs
- Simple single-job pipeline is easy to understand, debug, and maintain
- No PAT or write-back credentials required -- the default
GITHUB_TOKENis sufficient
Negative
- Commit messages must strictly follow Conventional Commits (already enforced by team convention, but a CI linter like commitlint could be added later if discipline slips)
- Docker Hub credentials must be configured as repository secrets before the first release
- Branch protection requires manual setup in repository settings
- Single CI job means a lint failure blocks feedback on test results (acceptable tradeoff at this scale)
- Docker Scout warn-only means CVEs are visible but not blocking -- requires discipline to review
- First release version is determined by semantic-release based on the commit history at the time of the first merge to
main
Deviation Log
| Date | Deviation | Resolution |
|---|---|---|
| 2026-09-30 | Lint and format gates had silently dropped from CI. Section 7 lists npm run lint and npm run format:check as quality gates, but the CI workflow no longer ran either step, so ESLint errors and Prettier drift could merge to beta with a green Quality Gates. |
Code was wrong; the ADR's intent stands. Restored by the PR for #2111 (with #2024): the static-analysis job runs an ESLint step (fails on errors; warnings remain non-fatal until #2118) and npm run format:check. static-analysis now runs on every PR; the expensive app steps inside it are gated per step by the app path filter rather than skipping the whole job. |
| 2026-09-30 | Skipped required jobs counted as passed in the gate aggregators (#2043). E2E Gates did not depend on the jobs whose skip it had to interpret, so a skipped upstream (e.g. a failed Docker build short-circuiting the E2E shards) could surface as a passing gate; Quality Gates likewise treated a skipped detect-changes / static-analysis as success. |
Code was wrong. E2E Gates now needs detect-changes, docker, and e2e-warmup in addition to the E2E jobs, and requires every E2E job to conclude success whenever the app or e2e path filters matched; skipped is accepted only when nothing testable changed. Quality Gates now requires detect-changes and static-analysis to conclude success — skipped is not passed. |
| 2026-09-30 | Section 7 ("Single CI Job") and the related Consequences bullets are historical. CI has long since been split into parallel jobs (detect-changes, static-analysis, sharded Jest, docker, sharded E2E) behind the Quality Gates and E2E Gates aggregator checks; the sequential single-job layout no longer exists. |
Wiki outdated; left in place as the original decision record. The gate set in section 7 (lint, format, typecheck, test, audit, build) remains the contract; its single-job layout does not. .github/workflows/ci.yml is the authoritative description of job topology. |