Files
consentos/.github/workflows/release.yml
James Cottrill 80dfc15319 ci: release workflow — build + push container images to GHCR on release (#6)
* feat: add release workflow to build and push container images to GHCR

Triggers on GitHub Release publish. Builds three container images
(consentos-api, consentos-scanner, consentos-admin-ui) and pushes
them to ghcr.io/consentos/ tagged with the semver release version
(e.g. v1.0.0, 1.0), plus ``latest``.

Release flow:
  1. Merge PRs to master.
  2. Tag: ``git tag v1.0.0 && git push origin v1.0.0``
  3. Create a GitHub Release from the tag.
  4. Workflow fires, images land on GHCR.
  5. Deploy by pointing Helm values or docker-compose at the tag.

Uses ``docker/metadata-action`` for tag derivation and
``docker/build-push-action`` for the builds. Auth uses the
default ``GITHUB_TOKEN`` with ``packages: write`` — no extra
secrets needed.

The admin-ui image uses the repo root as the build context (same
as ``docker-compose.prod.yml``) so the Dockerfile can pull in
``apps/banner/`` alongside ``apps/admin-ui/`` and bundle the
banner output at the nginx root.

* chore: auto-graduate changelog on release + CI path filters

CI workflow (``ci.yml``):
  - Uses ``dorny/paths-filter`` to detect which apps changed. Each
    job group (api, scanner, banner, admin-ui) now has an
    ``if: needs.changes.outputs.<app> == 'true'`` guard so it only
    runs when files under its ``apps/<app>/`` directory were
    modified. A docs-only or infra-only PR no longer triggers the
    full lint + test + build matrix.
2026-04-18 16:14:40 +01:00

182 lines
5.7 KiB
YAML

# Cut a release and build container images.
#
# Triggered manually via ``workflow_dispatch`` (Actions tab → Run
# workflow) when you're ready to ship. Not on every PR merge.
#
# Flow:
# 1. ``ietf-tools/semver-action`` derives the next version from
# conventional commit messages since the last tag (feat → minor,
# fix → patch, breaking → major).
# 2. ``requarks/changelog-action`` generates release notes from the
# commit diff between the new and previous tags.
# 3. ``ncipollo/release-action`` creates the GitHub Release.
# 4. ``requarks/changelog-action`` writes CHANGELOG.md and
# ``stefanzweifel/git-auto-commit-action`` commits it back to
# master with ``[skip ci]``.
# 5. All three container images are built and pushed to GHCR,
# tagged with the semver version + ``latest``.
name: Release
on:
workflow_dispatch:
jobs:
# ── Version + Release + Changelog ────────────────────────────────
version:
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
new: ${{ steps.semver.outputs.next }}
newStrict: ${{ steps.semver.outputs.nextStrict }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get next version
id: semver
uses: ietf-tools/semver-action@v1
with:
token: ${{ github.token }}
branch: master
- name: Generate release notes
id: changelog
uses: requarks/changelog-action@v1
with:
token: ${{ github.token }}
fromTag: master
toTag: ${{ steps.semver.outputs.current }}
writeToFile: false
- name: Create release
uses: ncipollo/release-action@v1.12.0
with:
allowUpdates: true
draft: false
makeLatest: true
tag: ${{ steps.semver.outputs.next }}
body: ${{ steps.changelog.outputs.changes }}
token: ${{ github.token }}
- name: Write CHANGELOG.md
uses: requarks/changelog-action@v1
with:
token: ${{ github.token }}
fromTag: ${{ steps.semver.outputs.next }}
toTag: ${{ steps.semver.outputs.current }}
writeToFile: true
- name: Commit CHANGELOG.md
uses: stefanzweifel/git-auto-commit-action@v4
with:
branch: master
commit_message: "docs: update CHANGELOG.md for ${{ steps.semver.outputs.next }} [skip ci]"
file_pattern: CHANGELOG.md
# ── Build and push container images ──────────────────────────────
build-api:
name: API image
needs: version
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v5
id: meta
with:
images: ghcr.io/consentos/consentos-api
tags: |
type=semver,pattern={{version}},value=${{ needs.version.outputs.newStrict }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.version.outputs.newStrict }}
type=raw,value=latest
- uses: docker/build-push-action@v6
with:
context: apps/api
file: apps/api/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-scanner:
name: Scanner image
needs: version
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v5
id: meta
with:
images: ghcr.io/consentos/consentos-scanner
tags: |
type=semver,pattern={{version}},value=${{ needs.version.outputs.newStrict }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.version.outputs.newStrict }}
type=raw,value=latest
- uses: docker/build-push-action@v6
with:
context: apps/scanner
file: apps/scanner/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-admin-ui:
name: Admin UI image
needs: version
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v5
id: meta
with:
images: ghcr.io/consentos/consentos-admin-ui
tags: |
type=semver,pattern={{version}},value=${{ needs.version.outputs.newStrict }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.version.outputs.newStrict }}
type=raw,value=latest
# Context is the repo root — the admin-ui Dockerfile pulls in
# apps/banner/ alongside apps/admin-ui/ and bundles the banner
# output at the nginx root.
- uses: docker/build-push-action@v6
with:
context: .
file: apps/admin-ui/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}