Skip to content

CI/CD

Every repository in the ecosystem uses GitHub Actions. This page describes the common patterns and conventions.

Core principles

  1. Check on the PR, deploy on main. A PR runs build + tests and deploys nothing. Deployment only starts after a merge into main.
  2. Build only what changed. paths-filter determines which parts changed and only those services are rebuilt.
  3. No concurrent deploys. A concurrency group queues production deploys so they never run at the same time.
  4. 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 nav that 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:

  1. Sync the repository on the host with git fetch && git reset --hard,
  2. docker exec <nginx> nginx -tvalidate the configuration,
  3. 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_TAG variable 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.