Skip to content

CI/CD Pipeline Architecture

MeshFlows CI/CD runs on Forgejo Actions (workflow YAML under .github/workflows/). The pipeline builds container images, gates on tests, publishes immutable release bundles, and deploys to Kubernetes environments with selective image updates.

Older docs mentioning GitLab CI, Kaniko, ArgoCD, Flux, or Woodpecker are obsolete.

Canonical operator reference: .github/workflows/README.md.

Workflows Overview

Workflow Trigger Purpose
build.yml push, PR, manual Selective/full image build → release-bundle artifact → dispatch deploy
test.yml push, PR, manual, workflow_call Structure, pytest, flows YAML, K8s yamllint
deploy.yml manual, API dispatch, flows push Single deploy entry point (engine, wiki, workflows)
infra.yml manual Gateway API CRDs, nginx GF, cert-manager, ClusterIssuers
security-audit.yml weekly, manual Dependency audit
smoke.yml manual Runner smoke test

Architecture

graph TD
    subgraph triggers [Triggers]
        PUSH[Push / PR]
        FLOWS[Push flows/** only]
        MANUAL[workflow_dispatch]
    end

    subgraph build [build.yml]
        DETECT[Detect changed paths]
        BUILD[Build engine + wiki images]
        BUNDLE[Publish release bundle]
        GATE[Test gate via test.yml]
    end

    subgraph deploy [deploy.yml]
        DEV[Deploy dev]
        PROMO[Deploy tst + lab parallel]
        FLOWS_DEP[Upload flows + reload]
    end

    PUSH --> DETECT --> BUILD --> BUNDLE --> GATE
    GATE -->|pass| DEV
    GATE -->|main merge| PROMO
    FLOWS --> FLOWS_DEP
    MANUAL --> deploy
    DEV --> PROMO

Deploy is not inlined in build.yml. After the test gate, build.yml starts a separate deploy.yml run via the Forgejo workflow_dispatch API on deploy-<env> runners (host kubectl). Nested workflow_call is avoided because Forgejo would run deploy jobs on the build runner where kubectl is unavailable.

Promotion Strategy

Event / branch Auto-deploy targets
Pull request dev only
Push to feature/fix/docs branch dev
Push to main (merge) dev → then tst + lab (parallel)
Push flows/ only to main tst (no engine rebuild)
Push flows/ only to other branches dev
Manual build.yml full build no auto-deploy
lab / acc / prd manual deploy.yml with release_id

Release Bundles

Each successful build publishes artifact release-bundle-<release_id>.json:

Field Meaning
release_id Immutable tag vYYYY.MM.DD.<run_number> (suffix -dev / -pr<N> on develop/PR)
built_services Engine images built in this run
service_tags Map service → tag
selective_deploy true when only changed services were built
deploy_services Comma list or all
wiki_image_tag Set when wiki was rebuilt

Selective deploy: only built_services get the new tag. Other deployments keep their image from cluster ConfigMap engine-images-lock (deploy_engine_images.py restores unselected services).

Version diff: deploy skips services whose running tag already matches release_id (--skip-unchanged).

Successful deploys append to platform-data/meshflows-release-index (last 30 releases) for lab manual picking.

Smart Change Detection

On push/PR, build.yml detects changed paths and sets flags per service (engine/services/gateway/ → build gateway only, wiki/ → build wiki, etc.). Feature/fix/infra branches and PRs diff three-dot against main (origin/main...HEAD); pushes to main/develop use only the current push range. When no engine paths changed, build jobs no-op but the test gate and verify-only deploy still run.

Flows validation runs in test.yml when flows/ changed; flows-only pushes skip build.yml entirely and trigger deploy.yml directly.

Job Flow (build.yml)

graph LR
    DC[detect-changes]
    BES[build-engine-services]
    BW[build-wiki]
    PUB[publish-release-bundle]
    TG[test gate]
    RD[resolve-deploy-targets]
    TD[trigger-deploy-dev]
    TP[trigger-deploy-promotion]

    DC --> BES
    DC --> BW
    BES --> PUB
    BW --> PUB
    PUB --> TG
    TG --> RD --> TD
    RD --> TP
    TD --> TP

Forgejo note: build-engine-services and build-wiki always run (no-op when unchanged). Upstream skipped jobs would skip dependents even with always() — this pattern avoids that.

Job Flow (deploy.yml)

Major jobs (simplified):

  1. setup — resolve environment, namespace, release bundle fields
  2. deploy-platform-data — shared Postgres, release index (non-flows deploys)
  3. deploy-engine — kustomize + deploy_engine_images.py, secret reconcile
  4. deploy-wiki — wiki rollout (when in bundle)
  5. deploy-workflows — upload flows to storage, orchestrator reload

Flows-only pushes run validation jobs then deploy-workflows only.

Concurrency: one deploy per environment component (engine / wiki / workflows).

Environments and Namespaces

Environment Namespace HTTP NodePort HTTPS NodePort
lab meshflows-lab 31100 31101
dev meshflows-dev 31200 31201
tst meshflows-tst 31300 31301
acc meshflows-acc 31400 31401
prd meshflows-prd 31500 31501

WAN 80/443 → edge-router 30080/30443 (Fritzbox PAT).

Orchestrator Postgres is per environment (meshflows-<env>). Platform Postgres in platform-data is shared (Forgejo + release index only).

Secrets and Forgejo Environments

Deploy jobs bind environment: name: ${{ environment }} (lab|dev|tst|acc|prd). Environment secrets override repository secrets.

No kubeconfig in Forgejo: runners labeled deploy-<env> have host kubectl access.

Before rollout, deploy reconciles auth secrets from Forgejo into Kubernetes (empty CI value → keep existing K8s secret; missing entirely → generate fallback):

  • meshflows-identity-authIDENTITY_JWT_SECRET, IDENTITY_ENCRYPTION_KEY, …
  • meshflows-orchestrator-authORCH_RELOAD_TOKEN
  • meshflows-rabbitmq-auth, meshflows-storage-auth, meshflows-ses-auth, meshflows-capability-registry-auth, scheduler tokens

Full table: .github/workflows/README.md.

Runners

Label Role
meshflows Build, test, setup
deploy-<env> kubectl deploy

Setup: CI Runner README.

Manual Operations

Lab deploy with release picker

  1. deploy.ymlenvironment=lab, empty release_id → lists recent releases
  2. Re-run with chosen release_id

Full rebuild before promotion

build.yml manual → full_build=true, then deploy.yml with the new release_id.

Redeploy identical version

Re-run deploy.yml with the same release_id (config/secrets reconcile without new images).

Infrastructure

infra.yml → Gateway API CRDs, nginx Gateway Fabric, cert-manager, edge-router, ClusterIssuers.

Troubleshooting

Symptom Likely cause
Auto-deploy skipped Test gate failed; check build summary → Deploy triggered
Dispatch failed Missing/invalid WORKFLOW_DISPATCH_TOKEN (write:repository PAT)
ImagePullBackOff after selective build Tag purged from registry; run full build or redeploy with good release_id
Selective deploy broke unrelated service Check engine-images-lock ConfigMap; redeploy with selective_deploy=false
Manual deploy lists no releases Index fills after first successful deploy; use release_id from build artifact
Auth 401 after redeploy Forgejo secret empty and K8s secret missing — set Forgejo secret and redeploy