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.
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@v14Releases 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— build and push a Docker image to GitHub Container Registry.deploy-strapi.yml— build and deploy a Strapi backend + frontend.notify.yml— post a Slack notification.python-build.yml— build a Python package withuv.docker-smoke— composite action: build the image, run it, and probe it with the production Host header.
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.
-
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-argsinput of docker/build-push-action. -
production_branch: The name of the production branch. Default
main.
- personal_token: Optional personal access token used to check out private repositories and submodules. Falls back to
github.tokenwhen not provided.
Tags are generated by docker/metadata-action from the git context:
feature/*branches →featuretag + the branch ref- Semver tags (
v1.2.3) →version/major.minor/major/releasetags - Push to
production_branch→latest - 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.
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:
-
Checkout Code:
actions/checkoutclones the repository and its submodules (fetch-depth: 20). -
Apply Overlay: If
overlayis set,actions/download-artifactdownloads it andgeekyeggo/delete-artifactremoves it. Create the overlay in the caller withactions/upload-artifact; its files are unpacked into the project directory. See this example. -
Run Prepare Script: Executes the
prepareshell commands if provided. -
Generate Image Tag:
docker/metadata-actionproduces tags per the strategy above. -
Set up Docker Buildx:
docker/setup-buildx-action. -
Login to GitHub Container Registry:
docker/login-action(authenticates toghcr.iowithGITHUB_TOKEN). Skipped when no tag matched. -
Build and Push:
docker/build-push-actionbuilds fromdirusingdockerfileand pushes the generated tags (build-only when no tag matched).
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/serverOrchestrates 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.
- 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
publicfolder whose presence means the backend is deployed. The health check pollshttps://strapi.${{vars.DOMAIN}}/<deploy_marker>and only runs when this input is provided.
- personal_token: Optional personal access token, forwarded to
docker-build.ymlfor private repositories and submodules.
- The
wait-for-backendjob selects its GitHub Environment dynamically: production when on thevars.CI_PRODUCTION_BRANCHbranch or a tag starting withv; 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_BRANCHandvars.DOMAIN.
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.
- slack_webhook_url: Slack channel incoming-webhook URL (required).
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.
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).
- 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".
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"Version tags v1–v13 predate the current scheme and are frozen point releases. The
floating-major convention (see RELEASING.md) starts at v14.
- v14 — First release under the semver + floating-major scheme. Ships the
docker-smokecomposite action (build + run + production-Host-header probe, with abuild-onlymode). - v13 — Adds a reusable Dependabot auto-merge workflow and an actionlint CI gate.
- v12 —
docker-build.yml: when no tag rule matches the ref, the image is now built without pushing (with a warning) instead of failing withtag 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.