Skip to content

Latest commit

 

History

54 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WeMove Reusable CI Workflows

A collection of reusable GitHub Actions workflows (on: workflow_call) and composite actions shared across WeMoveEU projects. Caller repositories reference these by path and version tag from their own .github/workflows.

Using these workflows

Pin to the floating major tag — the current major is @v14:

uses: WemoveEU/ci-workflows/.github/workflows/docker-build.yml@v14
uses: WemoveEU/ci-workflows/.github/actions/docker-smoke@v14

Releases follow semver with a floating major (see RELEASING.md): each release gets an immutable vMAJOR.MINOR.PATCH tag, and the floating vMAJOR tag is moved to point at it. Consumers pinned to @v14 pick up backward-compatible minor/patch fixes automatically; a breaking change cuts a new major (v15) that you opt into deliberately (Dependabot opens the PR). Always pin the floating major, never a branch.

The workflows and actions available:


docker-build.yml

Builds a Docker image and pushes it to GitHub Container Registry (ghcr.io/<name>). This is the foundational workflow that the others build on. It supports staging and production environments via its tagging strategy.

Inputs

  • dockerfile: Path to the Dockerfile used for building the image. Default Dockerfile.

  • name: Name of the image in the registry (pushed to ghcr.io/<name>). Default ${{github.repository}}.

  • dir: The root of app sources / build context. Use if your app is in a subdirectory, e.g. frontend. Default ..

  • overlay: Name of an artifact to unpack over the checked-out source before building (e.g. env files). Use a unique name based on ${{github.run_id}} to avoid races between concurrent runs. The artifact is deleted after it is unpacked.

  • prepare: Shell commands run (via bash) after unpacking the overlay, before building the image.

  • args: Docker build args (one variable, or multiline format), passed to the build-args input of docker/build-push-action.

  • production_branch: The name of the production branch. Default main.

Secrets

  • personal_token: Optional personal access token used to check out private repositories and submodules. Falls back to github.token when not provided.

Image tagging strategy

Tags are generated by docker/metadata-action from the git context:

  • feature/* branches → feature tag + the branch ref
  • Semver tags (v1.2.3) → version / major.minor / major / release tags
  • Push to production_branchlatest
  • Push to the default branch when it is not the production branch → staging

If the ref matches none of these rules (e.g. a release/* or otherwise unrecognised branch), no tag is produced. In that case the image is built but not pushed — the run still validates the Dockerfile, and a warning is emitted — rather than failing with tag is needed when pushing to registry.

Jobs

The release job runs on ubuntu-latest with contents: read, packages: write, and actions: write permissions (the last is needed because the overlay artifact is deleted via the Actions API). Its steps:

  1. Checkout Code: actions/checkout clones the repository and its submodules (fetch-depth: 20).

  2. Apply Overlay: If overlay is set, actions/download-artifact downloads it and geekyeggo/delete-artifact removes it. Create the overlay in the caller with actions/upload-artifact; its files are unpacked into the project directory. See this example.

  3. Run Prepare Script: Executes the prepare shell commands if provided.

  4. Generate Image Tag: docker/metadata-action produces tags per the strategy above.

  5. Set up Docker Buildx: docker/setup-buildx-action.

  6. Login to GitHub Container Registry: docker/login-action (authenticates to ghcr.io with GITHUB_TOKEN). Skipped when no tag matched.

  7. Build and Push: docker/build-push-action builds from dir using dockerfile and pushes the generated tags (build-only when no tag matched).

Sample usage

name: Deploy
on:
  push:
    branches:
      - main
      - "release/*"
jobs:
  release:
    uses: WemoveEU/ci-workflows/.github/workflows/docker-build.yml@v14
    with:
      production_branch: ${{vars.CI_PRODUCTION_BRANCH}}
      dockerfile: server/Dockerfile
      dir: server
      name: crm/server

deploy-strapi.yml

Orchestrates a Strapi deploy by calling docker-build.yml twice — backend first (backend/Dockerfile, image <repo>/backend), then frontend (frontend/Dockerfile, image <repo>/frontend). Between them, a wait-for-backend job polls the live backend until it is ready.

Inputs

  • backend_args: Docker build args for the backend image. Default ''.
  • frontend_args: Docker build args for the frontend image. Default ''.
  • production_branch: The name of the production branch. Default main.
  • deploy_marker: Filename in the Strapi public folder whose presence means the backend is deployed. The health check polls https://strapi.${{vars.DOMAIN}}/<deploy_marker> and only runs when this input is provided.

Secrets

  • personal_token: Optional personal access token, forwarded to docker-build.yml for private repositories and submodules.

Notes

  • The wait-for-backend job selects its GitHub Environment dynamically: production when on the vars.CI_PRODUCTION_BRANCH branch or a tag starting with v; otherwise staging.
  • Overlay names are made run-unique with ${{github.run_id}} (backend_env_<run_id>, frontend_env_<run_id>) to avoid collisions between concurrent runs.
  • Calling repositories must define the repository variables vars.CI_PRODUCTION_BRANCH and vars.DOMAIN.

notify.yml

Posts a Slack notification (green on success, red on failure) via slackapi/slack-github-action (incoming-webhook mode). Call it as a final step from a caller workflow.

Secrets

  • slack_webhook_url: Slack channel incoming-webhook URL (required).

python-build.yml

Builds a Python package with uv: checks out the repo (actions/checkout), installs uv (astral-sh/setup-uv), and runs uv build. Takes no inputs.


dependabot-auto-merge.yml

Enables auto-merge on Dependabot PRs for low-risk update types. Call it from a caller workflow triggered on: pull_request; the reusable only acts on PRs authored by dependabot[bot].

Auto-merge only completes once the target branch's required status checks pass, so this depends on a branch ruleset (require a PR + required checks) on the consuming repo. Without that ruleset gh pr merge --auto has nothing to wait for and the PR merges immediately.

Arming is retried five times, and falls back to a direct merge. That covers two real failure modes: gh pr merge --auto intermittently returns a transient GraphQL error, and auto-merge cannot be enabled at all on a PR whose required checks have already gone green — which fast CI makes common, and which otherwise leaves the PR sitting green and unmerged. The direct merge is not a bypass: branch protection still refuses it while required checks are pending or failing.

Inputs

  • merge-method: merge | squash | rebase. Default squash.

  • allowed-update-types: Comma-separated Dependabot update-types to auto-merge, matched as whole tokens (no spaces around the commas). Default version-update:semver-patch,version-update:semver-minor. Anything outside the list — majors included — is left for manual review.

Secrets

  • app-id / app-private-key: Optional GitHub App credentials used to arm the merge. Pass these whenever merging is meant to trigger something downstream. A merge armed with GITHUB_TOKEN has its push event suppressed by GitHub's recursion guard, so a deploy that runs on: push to the default branch never runs — the PR goes green and nothing ships. Omit them and the workflow falls back to GITHUB_TOKEN and warns in the run log.

    The App needs two repository permissions: Contents (read/write) and Pull requests (read/write); Metadata (read) is mandatory and comes along. It does not need Workflows (write) — that permission governs pushing workflow files through the Contents API, not merging a PR that changes them.

Sample usage

name: Dependabot auto-merge

on:
  pull_request:

permissions:
  contents: write
  pull-requests: write

jobs:
  auto-merge:
    if: github.actor == 'dependabot[bot]'
    uses: WeMoveEU/ci-workflows/.github/workflows/dependabot-auto-merge.yml@v14
    secrets:
      app-id: ${{ secrets.DEPENDABOT_POLICY_APP_ID }}
      app-private-key: ${{ secrets.DEPENDABOT_POLICY_APP_KEY }}

The caller's permissions: block must grant contents: write and pull-requests: write — a called workflow's effective GITHUB_TOKEN permissions are capped by the caller's.


docker-smoke

Composite action (.github/actions/docker-smoke) that builds the repo's Docker image, runs the container, and verifies it actually serves an HTTP request sent with the production Host header. This catches two classes of bug a plain build check cannot: image-build breaks (e.g. a base-image bump dropping a tool the Dockerfile needs), and runtime serving breaks that only appear under the real hostname (e.g. a preview server's allowedHosts rejecting an unlisted Host with 403).

Use it as a step inside a job named ci so the required-status-check context stays exactly ci (a reusable workflow would produce ci / <job> and break an org ruleset's required-check match).

Inputs

  • host: Host header to send — the production hostname the app is served under. Required.
  • port: Container port to publish and probe. Default 8080.
  • path: Request path to probe. Default /.
  • expected-status: HTTP status code the probe must return. Default 200.
  • dockerfile: Path to the Dockerfile. Default Dockerfile.
  • context: Docker build context. Default ..
  • startup-timeout: Seconds to wait for the container to start responding. Default 90.
  • build-only: When "true", only build the image and skip running/probing it. Use for apps that can't boot standalone in CI (need runtime secrets, a database, etc.). Default "false".

Sample usage

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: WemoveEU/ci-workflows/.github/actions/docker-smoke@v14
        with:
          host: app.example.org
          port: "3000"

Releases

Version tags v1v13 predate the current scheme and are frozen point releases. The floating-major convention (see RELEASING.md) starts at v14.

  • v14.2dependabot-auto-merge.yml: arming is now retried (5 attempts) with a direct-merge fallback, so a transient GraphQL error no longer strands a PR and an already-green PR still merges. Consolidates logic that existed only in the inline copies in wemove.eu, youmove, wemove-charity.eu and pubstatic, which this lets them drop.
  • v14.1dependabot-auto-merge.yml: accepts optional app-id / app-private-key secrets so the merge is armed with a GitHub App token and fires a real push event (a GITHUB_TOKEN-armed merge does not, leaving push-triggered deploys silently unrun). Falls back to GITHUB_TOKEN when the secrets are omitted, and warns. The update-type gate is now a whole-token, fail-closed match — previously an empty update-type from fetch-metadata satisfied contains() and could auto-merge a major.
  • v14 — First release under the semver + floating-major scheme. Ships the docker-smoke composite action (build + run + production-Host-header probe, with a build-only mode).
  • v13 — Adds a reusable Dependabot auto-merge workflow and an actionlint CI gate.
  • v12docker-build.yml: when no tag rule matches the ref, the image is now built without pushing (with a warning) instead of failing with tag is needed when pushing to registry. Registry login is skipped in that case.
  • v11 — README rewritten as a repo overview covering all four workflows; action versions corrected.

About

Shared workflows for GH Actions

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors