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):
- setup — resolve environment, namespace, release bundle fields
- deploy-platform-data — shared Postgres, release index (non-flows deploys)
- deploy-engine — kustomize +
deploy_engine_images.py, secret reconcile - deploy-wiki — wiki rollout (when in bundle)
- 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-auth←IDENTITY_JWT_SECRET,IDENTITY_ENCRYPTION_KEY, …meshflows-orchestrator-auth←ORCH_RELOAD_TOKENmeshflows-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¶
deploy.yml→environment=lab, emptyrelease_id→ lists recent releases- 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 |
Related Guides¶
- Develop and Test — end-to-end developer loop
- Secrets Management
- Manual K8s Deployment
- Lab HTTPS Setup