Skip to content

Getting Started ​

KevinDeBenedetti/github-workflows is a library of reusable GitHub Actions workflows and composite actions. Callers stay minimal — CI steps run consistently across all your repos.

Prerequisites ​

  • A GitHub repository
  • Appropriate secrets set in your repo/org (see each workflow's docs)

How it works ​

Workflows are called with workflow_call and referenced from your own workflow files:

yaml
jobs:
  ci:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/<name>.yml@main

Pass a workflow only the secrets it declares (a secrets: block naming each one) and grant the calling job only the permissions its page lists — never secrets: inherit. A reusable workflow gets at most what its caller grants. See the security policy.

Production tip: Pin to a release tag (@vX.Y.Z) or the minor alias (@v0.<minor>) instead of @main. The workflows call their composite actions with uses: $/…, resolved to the workflow's own commit, so a pin covers the actions too.

GitHub-hosted jobs run on ubuntu-26.04, a pinned image rather than ubuntu-latest: when GitHub moves ubuntu-latest to a new Ubuntu release, your jobs only change image with a release of these workflows, like any other change. A workflow with a runner input runs on any other label — another image (runner: '["ubuntu-24.04"]'), an arm64 runner or a self-hosted scale set.

Important: All workflow files must live at the flat root of .github/workflows/. GitHub Actions does not support reusable workflows in subdirectories.


Quick start by stack ​

Node.js ​

yaml
# .github/workflows/ci.yml
name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  ci:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/ci-node.yml@main

Auto-detects pnpm or bun from your lockfile. Runs lint → typecheck → test → build. → Full reference


Python ​

yaml
# .github/workflows/ci.yml
name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  ci:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/ci-python.yml@main

Uses uv and ruff. Runs lint → format check → test. → Full reference


Shell scripts ​

yaml
# .github/workflows/ci.yml
name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  ci:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/ci-shell.yml@main

Runs ShellCheck → actionlint → Bats. → Full reference


Helm ​

yaml
jobs:
  ci:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/ci-helm.yml@main
    with:
      chart-paths: 'charts/*'
      run-template: true

Runs helm lint and an optional helm template dry-run on all charts.


Kubernetes ​

yaml
jobs:
  ci:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/ci-kubernetes.yml@main
    with:
      kubeconform-paths: kubernetes/

Validates Kubernetes manifests with kubeconform (CRDs-catalog enabled by default).


Terraform ​

yaml
jobs:
  ci:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/ci-terraform.yml@main
    with:
      tf-dir: terraform

Runs terraform validate and terraform fmt -check -diff.


Ansible ​

yaml
jobs:
  ci:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/ci-ansible.yml@main
    with:
      ansible-dir: ansible/
      run-syntax-check: true

Runs ansible-lint and an optional ansible-playbook --syntax-check.


Docker ​

yaml
jobs:
  deploy:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/cd-docker.yml@main
    permissions:
      contents: read
      packages: write      # push the image to GHCR
      deployments: write   # production/preview environment record
      id-token: write      # sign the image attestation (Sigstore)
      attestations: write  # store the attestation
    with:
      image-name: my-app
      tag-latest: true

Builds and pushes a multi-platform image to GHCR (ghcr.io). → Full reference


BuildKit (self-hosted or GitHub-hosted) ​

yaml
jobs:
  deploy:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/cd-buildkit.yml@main
    permissions:
      contents: read
      packages: write  # push the image and its layer cache to GHCR
    with:
      image-name: my-app
      runner: '"my-build-scale-set"'  # omit for Docker Buildx on ubuntu-26.04

Builds and pushes an image to GHCR with rootless BuildKit inside the job container on a self-hosted runner (no Docker daemon), or with Docker Buildx on a GitHub-hosted one. → Full reference


GitHub Pages ​

yaml
jobs:
  deploy:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/cd-pages.yml@main
    permissions:
      contents: read
      pages: write     # publish the Pages deployment
      id-token: write  # deploy-pages authenticates with OIDC
    with:
      output-directory: dist

→ Full reference


Docs sync ​

Verify this repo's docs before the centralized docs site picks them up. The site polls public repos for docs/ changes on a schedule, so no token, App or secret is needed.

yaml
jobs:
  docs:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/cd-docs.yml@main
    with:
      docs-directory: docs

→ Full reference


Automated releases ​

yaml
jobs:
  release:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/release.yml@main
    # No token secret: release-please runs on GITHUB_TOKEN (enable "Allow
    # GitHub Actions to create and approve pull requests" in the repo settings).
    permissions:
      contents: write        # release branch, tags and releases
      issues: write          # release-please labels the release PR
      pull-requests: write   # open/update the release PR

Uses release-please. Automatically moves a v<major> tag after each release. → Full reference


Pre-releases of an integration branch ​

yaml
jobs:
  prerelease:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/prerelease.yml@main
    with:
      publish: true
    permissions:
      contents: write

Tags each releasable commit of a branch such as dev as a GitHub pre-release, v<next>-rc.<n>, computed from the Conventional Commits since the last release tag. To merge a pull request for each pre-release instead, call release.yml on that branch with target-branch (pre-releases from a second branch). → Full reference


Security scanning ​

yaml
jobs:
  security:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/security.yml@main
    permissions:
      security-events: write  # upload the Gitleaks and CodeQL results to code scanning
      actions: read           # code scanning reads the workflow run metadata
      contents: read

Combines Gitleaks secret scanning, CodeQL SAST, and optional dependency audits. → Full reference


PR descriptions (Claude Code) ​

Rewrites a managed block at the top of each pull request description on every push, from the commits and the diff. Runs on your Claude subscription: store a token from claude setup-token as the CLAUDE_CODE_OAUTH_TOKEN secret.

yaml
on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  describe:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/pr-description.yml@main
    permissions:
      contents: read
      pull-requests: write
    secrets:
      CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

→ Full reference


GHCR cleanup ​

Deletes old image versions in GHCR every week, from the repository that publishes them. Release tags, moving tags and images younger than 30 days are never deleted.

yaml
on:
  schedule:
    - cron: "23 4 * * 1"
  workflow_dispatch:

jobs:
  cleanup:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/cleanup-ghcr.yml@main
    permissions:
      packages: write
    with:
      packages: my-app   # default: the repository name
      dry-run: true      # remove once a manual run's log looks right

→ Full reference


Workflow runs cleanup ​

Deletes old workflow runs, with their logs and artifacts, every week. Runs younger than 30 days, the 10 most recent runs of each workflow and the runs of open pull requests are never deleted; a deleted workflow file leaves the Actions sidebar. artifacts-older-than-days also deletes the old artifacts of the runs kept, which count against the account's Actions storage.

yaml
on:
  schedule:
    - cron: "43 4 * * 1"
  workflow_dispatch:

jobs:
  cleanup:
    uses: KevinDeBenedetti/github-workflows/.github/workflows/cleanup-runs.yml@main
    permissions:
      actions: write
      pull-requests: read
    with:
      artifacts-older-than-days: 3 # the runs kept lose their old artifacts too
      dry-run: true      # remove once a manual run's log looks right

→ Full reference


All available workflows ​

WorkflowDescriptionDoc
ci-node.ymlNode.js CI (lint → typecheck → test → build)→
ci-python.ymlPython CI (lint → format → test)→
ci-shell.ymlShell CI (ShellCheck → actionlint → Bats)→
ci-ansible.ymlAnsible CI (ansible-lint + syntax check)→
ci-helm.ymlHelm CI (lint + template dry-run)→
ci-kubernetes.ymlKubernetes CI (kubeconform manifest validation)→
ci-terraform.ymlTerraform CI (validate + fmt check)→
cd-docker.ymlBuild & push multi-platform Docker image to GHCR→
cd-buildkit.ymlBuild & push image with BuildKit: rootless on self-hosted runners (experimental), Buildx on GitHub-hosted ones→
cd-pages.ymlBuild & deploy static site to GitHub Pages→
cd-docs.ymlSync docs to a centralized docs repository→
release.ymlAutomated releases via release-please→
propagate-release.ymlA pull request in each consumer repository moves its pins to the new release→
security.ymlSecret scan + CodeQL SAST + dependency audit→
check-bot-commits.ymlGuard PRs against bot-authored commits→
pr-description.ymlPR description written by Claude Code on every push (Claude subscription)→
cleanup-ghcr.ymlScheduled GHCR retention: keeps releases and recent images, deletes the rest and old layer caches→
cleanup-runs.ymlScheduled workflow-run retention: keeps recent runs and the last runs of each workflow, deletes the rest, and optionally old artifacts→

Composite actions ​

Actions can be used individually inside your own workflow steps:

yaml
steps:
  - uses: KevinDeBenedetti/github-workflows/.github/actions/setup-node@main
    with:
      node-version: '24'
ActionDescription
setup-nodeInstall Node.js + pnpm/bun with cache
setup-pythonInstall Python + uv with cache
shellcheckRun ShellCheck on all .sh files
batsRun Bats shell unit tests
detect-changesOutput a JSON matrix of changed apps in a monorepo
actionlintValidate GitHub Actions workflow files
kubeconformValidate Kubernetes manifests
check-commit-msgConventional Commits on commit messages (CI + commit-msg hook)
setup-ansibleInstall ansible-lint or ansible from a hash-locked requirements file
setup-claude-codeInstall the Claude Code CLI, pinned and checked against a SHA-256
pr-descriptionWrite the PR description with Claude Code into a managed block of the PR body
check-docs-linksCheck for broken links in docs
check-docs-external-linksCheck the http(s) links of changed docs pages with lychee
check-vitepress-mdValidate VitePress markdown
notify-deploymentPost a deployment status notification
lighthouse-resultsSummarize a Lighthouse CI run and post one commit status per page
prereleaseCompute the next pre-release of a branch and publish it as a GitHub pre-release
cleanup-runsDelete the old workflow runs of the repository, keeping the recent ones
propagate-releaseOpen a pull request in each consumer repository that moves its pins to a release

Examples ​

Complete ready-to-use caller files live in examples/:

FileStack
ci-cd-pages.ymlNode.js CI → GitHub Pages → release-please
next.ymlNext.js
nuxt.ymlNuxt
vue-react.ymlVue / React (Vite)
fastapi.ymlFastAPI (Python)
monorepo.ymlMonorepo with change detection
vitepress.ymlVitePress docs site
pr-description.ymlPR descriptions written by Claude Code
cleanup-ghcr.ymlWeekly GHCR retention
cleanup-runs.ymlWeekly workflow-run retention