The CI/CD engine of the Forge Platform. Reusable Azure DevOps templates so every service gets the same build, scan and deploy flow without duplicating pipeline code.
conveyor/scripts/ are version-controlled even though the Classic Release is not.Open Conveyor in Azure DevOps ↗
conveyor/ ├── templates/ # Reusable Azure DevOps YAML pipeline templates │ ├── application-ci-pipeline.yml # CI: build, Trivy scan, push to ECR │ ├── application-cd-pipeline.yml # CD: update Ledger manifest │ ├── application-rollback-pipeline.yml # Emergency rollback │ ├── ec2-deploy-pipeline.yml # EC2 deployment via SSM │ └── languages/ # Language-specific build steps │ ├── dotnet.yml │ └── python.yml ├── scripts/ # Version-controlled CD scripts │ ├── copy-artifact.sh # Cross-account ECR copy via crane │ └── update-ledger.sh # Update image.tag in Ledger (optimistic locking) └── docs/ ├── PIPELINE_STRATEGY.md # Full architecture rationale └── CLASSIC_RELEASE_SETUP_GUIDE.md # Step-by-step ADO setup
The build template. Every service uses this. Flow: checkout → Trivy filesystem scan → language build (dotnet/python) → Docker build (Build ID tag only) → Trivy image scan → ECR login → verify repo exists → push.
| Parameter | Default | Description |
|---|---|---|
imageName | — | ECR image name (required) |
language | docker | docker, dotnet, or python |
environment | — | dev, qa, or prod-wealth |
awsServiceConnection | aws | ADO service connection name |
Updates image.tag in a Ledger Application manifest. Used by Classic Release Pipeline stages. Uses yq for safe YAML editing with optimistic locking and retry on push conflict.
Deploys to EC2 via SSM Run Command. Sends docker-compose.yml, runs forge-refresh-env, writes IMAGE_TAG, pulls and restarts containers. Targets EC2s by tag.
Emergency rollback. Validates the target image exists in ECR (shows last 10 tags if not), then calls update-ledger.sh to revert image.tag.
Called by Classic Release Pipeline stages. Version-controlled in Git even though the Classic Release pipeline definition itself is not.
Copies a Docker image from Dev ECR to another account's ECR using crane (registry-to-registry, no Docker daemon). Verifies digest after copy. Saves OIDC creds before assuming roles to avoid credential chaining issues.
./copy-artifact.sh <IMAGE_NAME> <IMAGE_TAG> <TARGET_ENV> # Env vars: TARGET_ACCOUNT (required), SOURCE_ACCOUNT (default: dev)
Clones Ledger, updates image.tag in the specified manifest (prefers yq, falls back to sed), commits and pushes. Uses HTTPS + System.AccessToken on ADO agents. Implements optimistic locking — records remote SHA before editing, retries if another pipeline pushed concurrently.
./update-ledger.sh <IMAGE_NAME> <IMAGE_TAG> <TARGET_ENV> <MANIFEST_FILE> [RELEASE_NAME]
Application repos reference Conveyor as a repository resource. A typical CI pipeline is ~15 lines.
trigger: branches: include: [main] resources: repositories: - repository: conveyor type: git name: Forge/Conveyor stages: - stage: BuildAndPush jobs: - template: templates/application-ci-pipeline.yml@conveyor parameters: imageName: "orders-api" language: "dotnet" environment: "dev"
A Classic Release Pipeline picks up the build artifact and promotes through environments. See Release Management for the full promotion flow.
stages: - stage: BuildAndPush jobs: - template: templates/application-ci-pipeline.yml@conveyor parameters: imageName: "dagster" language: "python" environment: "dev" - stage: Deploy dependsOn: BuildAndPush jobs: - template: templates/ec2-deploy-pipeline.yml@conveyor parameters: componentName: "dagster" ec2TagKey: "forge:dagster-enabled" environment: "dev"
No static credentials anywhere. Conveyor uses OIDC + IAM role chaining.
ConveyorExecutionRole in security-tooling (Hub) via wildcard subject trustConveyorDeployRole-<env> in target account (Spoke)| Policy | Enforcement |
|---|---|
| Immutable tags | Build.BuildId only. No latest. ECR repos configured as immutable. |
| No ECR auto-create | Pipeline fails with clear error if repo doesn't exist. Platform team provisions via Blueprint. |
| Mandatory Trivy scans | Filesystem + image scan on every build. Currently warn-only (exitCode: 0). |
| No static credentials | OIDC + short-lived assumed roles. No IAM user keys. |
| Optimistic locking | update-ledger.sh verifies remote SHA before push. Retries on conflict. |