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:
jobs:
ci:
uses: KevinDeBenedetti/github-workflows/.github/workflows/<name>.yml@mainPass 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 withuses: $/…, 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
# .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@mainAuto-detects pnpm or bun from your lockfile. Runs lint → typecheck → test → build. → Full reference
Python
# .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@mainUses uv and ruff. Runs lint → format check → test. → Full reference
Shell scripts
# .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@mainRuns ShellCheck → actionlint → Bats. → Full reference
Helm
jobs:
ci:
uses: KevinDeBenedetti/github-workflows/.github/workflows/ci-helm.yml@main
with:
chart-paths: 'charts/*'
run-template: trueRuns helm lint and an optional helm template dry-run on all charts.
Kubernetes
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
jobs:
ci:
uses: KevinDeBenedetti/github-workflows/.github/workflows/ci-terraform.yml@main
with:
tf-dir: terraformRuns terraform validate and terraform fmt -check -diff.
Ansible
jobs:
ci:
uses: KevinDeBenedetti/github-workflows/.github/workflows/ci-ansible.yml@main
with:
ansible-dir: ansible/
run-syntax-check: trueRuns ansible-lint and an optional ansible-playbook --syntax-check.
Docker
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: trueBuilds and pushes a multi-platform image to GHCR (ghcr.io). → Full reference
BuildKit (self-hosted or GitHub-hosted)
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.04Builds 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
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: distDocs 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.
jobs:
docs:
uses: KevinDeBenedetti/github-workflows/.github/workflows/cd-docs.yml@main
with:
docs-directory: docsAutomated releases
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 PRUses release-please. Automatically moves a v<major> tag after each release. → Full reference
Pre-releases of an integration branch
jobs:
prerelease:
uses: KevinDeBenedetti/github-workflows/.github/workflows/prerelease.yml@main
with:
publish: true
permissions:
contents: writeTags 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
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: readCombines 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.
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 }}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.
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 rightWorkflow 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.
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 rightAll available workflows
| Workflow | Description | Doc |
|---|---|---|
ci-node.yml | Node.js CI (lint → typecheck → test → build) | → |
ci-python.yml | Python CI (lint → format → test) | → |
ci-shell.yml | Shell CI (ShellCheck → actionlint → Bats) | → |
ci-ansible.yml | Ansible CI (ansible-lint + syntax check) | → |
ci-helm.yml | Helm CI (lint + template dry-run) | → |
ci-kubernetes.yml | Kubernetes CI (kubeconform manifest validation) | → |
ci-terraform.yml | Terraform CI (validate + fmt check) | → |
cd-docker.yml | Build & push multi-platform Docker image to GHCR | → |
cd-buildkit.yml | Build & push image with BuildKit: rootless on self-hosted runners (experimental), Buildx on GitHub-hosted ones | → |
cd-pages.yml | Build & deploy static site to GitHub Pages | → |
cd-docs.yml | Sync docs to a centralized docs repository | → |
release.yml | Automated releases via release-please | → |
propagate-release.yml | A pull request in each consumer repository moves its pins to the new release | → |
security.yml | Secret scan + CodeQL SAST + dependency audit | → |
check-bot-commits.yml | Guard PRs against bot-authored commits | → |
pr-description.yml | PR description written by Claude Code on every push (Claude subscription) | → |
cleanup-ghcr.yml | Scheduled GHCR retention: keeps releases and recent images, deletes the rest and old layer caches | → |
cleanup-runs.yml | Scheduled 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:
steps:
- uses: KevinDeBenedetti/github-workflows/.github/actions/setup-node@main
with:
node-version: '24'| Action | Description |
|---|---|
setup-node | Install Node.js + pnpm/bun with cache |
setup-python | Install Python + uv with cache |
shellcheck | Run ShellCheck on all .sh files |
bats | Run Bats shell unit tests |
detect-changes | Output a JSON matrix of changed apps in a monorepo |
actionlint | Validate GitHub Actions workflow files |
kubeconform | Validate Kubernetes manifests |
check-commit-msg | Conventional Commits on commit messages (CI + commit-msg hook) |
setup-ansible | Install ansible-lint or ansible from a hash-locked requirements file |
setup-claude-code | Install the Claude Code CLI, pinned and checked against a SHA-256 |
pr-description | Write the PR description with Claude Code into a managed block of the PR body |
check-docs-links | Check for broken links in docs |
check-docs-external-links | Check the http(s) links of changed docs pages with lychee |
check-vitepress-md | Validate VitePress markdown |
notify-deployment | Post a deployment status notification |
lighthouse-results | Summarize a Lighthouse CI run and post one commit status per page |
prerelease | Compute the next pre-release of a branch and publish it as a GitHub pre-release |
cleanup-runs | Delete the old workflow runs of the repository, keeping the recent ones |
propagate-release | Open a pull request in each consumer repository that moves its pins to a release |
Examples
Complete ready-to-use caller files live in examples/:
| File | Stack |
|---|---|
ci-cd-pages.yml | Node.js CI → GitHub Pages → release-please |
next.yml | Next.js |
nuxt.yml | Nuxt |
vue-react.yml | Vue / React (Vite) |
fastapi.yml | FastAPI (Python) |
monorepo.yml | Monorepo with change detection |
vitepress.yml | VitePress docs site |
pr-description.yml | PR descriptions written by Claude Code |
cleanup-ghcr.yml | Weekly GHCR retention |
cleanup-runs.yml | Weekly workflow-run retention |