CI/CD¶
Every repository in the ecosystem uses GitHub Actions. This page describes the common patterns and conventions.
Core principles¶
- Check on the PR, deploy on main. A PR runs build + tests and deploys
nothing. Deployment only starts after a merge into
main. - Build only what changed.
paths-filterdetermines which parts changed and only those services are rebuilt. - No concurrent deploys. A
concurrencygroup queues production deploys so they never run at the same time. - Secrets only in GitHub Secrets. They are never written into a workflow file.
The usual workflow shape¶
name: deploy
on:
push:
branches: [main]
paths:
- 'backend/**'
- 'frontend/**'
- '.github/workflows/deploy.yml'
workflow_dispatch: # allow manual runs
concurrency:
group: deploy-production
cancel-in-progress: false # never interrupt a deploy midway
cancel-in-progress: false matters
A deploy cut off midway can leave a half-finished state — the new container not started, the old one already stopped. Let the deploy run to completion and queue the next one behind it.
The check stage (PR)¶
| Check | What it does |
|---|---|
| Build | Whether the code compiles |
| Unit tests | Business logic |
| Integration tests | testcontainers — a real PostgreSQL/Redis |
| Lint | Code style |
| Strict documentation build | Catches broken links and missing files |
The strict documentation check¶
MkDocs is built in --strict mode. In that mode warnings become errors:
- broken internal links,
- files referenced in
navthat do not exist, - files that exist but are not in
nav.
That keeps documentation breakage from reaching production.
The deploy stage¶
It connects to the host over SSH. The secrets required (common naming):
| Secret | Meaning |
|---|---|
DEPLOY_HOST |
Server address |
DEPLOY_USER |
SSH user |
DEPLOY_SSH_KEY |
Private key |
DEPLOY_PORT |
SSH port (optional, defaults to 22) |
DEPLOY_PATH |
Path on the host |
An SSH key, not a password
Deployment uses an SSH key, not a password. A key is easy to revoke, less likely to leak into logs by accident, and can be rotated without redistributing it to a group of people.
Partial builds¶
In a monorepo, one change should not require rebuilding every service:
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
backend: 'backend/**'
frontend: 'frontend/**'
edge: 'nginx/**'
Only the changed services are then rebuilt. If the edge (nginx) changed, there
is no full rebuild — just a config sync + nginx -t + reload.
Deploying the edge configuration¶
The edge nginx conf.d is managed through git. The deploy sequence:
- Sync the repository on the host with
git fetch && git reset --hard, docker exec <nginx> nginx -t— validate the configuration,- On success,
nginx -s reload.
Never skip nginx -t
Reloading with a broken config makes nginx fail to come back up — and at
that point every domain goes down. nginx -t must run before the
reload, and a failure must stop the deploy.
Self-hosted runners¶
iOS and Windows builds (code signing, notarisation, MSIX packaging) use self-hosted macOS / Windows runners. They need platform-specific tooling and certificates, so they cannot run on cloud runners.
Versioning and rollback¶
- Images are tagged. A
<SVC>_IMAGE_TAGvariable on the host can pin a specific version. - For static sites, the archive of the previous build is unpacked again.
CI/CD in this repository¶
The docs-gerege-mn repository has two workflows:
| Workflow | When | What it does |
|---|---|---|
ci.yml |
PR + push to main |
MkDocs --strict build; leaves an artifact |
deploy.yml |
Push to main + manual |
Build → copy → refresh the container → install the edge vhost → check the live site |
See This docs platform for the detail.