Skip to content

CD: from main to running tasks

2 min read · main part only; folded sections are optional

After this you can answer: What happens, job by job, after a merge to main?

flowchart TB
CI["CI green on main"] --> P["publish<br/>build 3 ARM64 images :sha<br/>push to ECR"]
P --> A{"approval<br/>(environment dev)"}
A --> D["deploy<br/>guard → phase A → migrate → phase B"]
D --> C["check revisions,<br/>smoke test, record commit"]
  1. Start. workflow_run fires when CI completes on main. Both jobs check that CI succeeded and that it ran for a push.
  2. publish checks out exactly the commit CI tested. The tag is its 7-character SHA. It logs in with the publish role, skips images that already exist (tags are immutable), and builds the rest for ARM64 on an ARM runner.
  3. deploy runs in GitHub environment dev. A required reviewer configured there makes it wait for a person to click Approve.
  4. deploy logs in with the deploy role, runs the deploy guard, checks the tag exists in all three repositories, and refuses committed image-tag overrides.
  5. Then the two phases (next chapters): infrastructure and migration first, the new app version second, then checks.

Build once, promote: deploy never builds. It runs the exact bytes publish pushed, so what was scanned and tested is what runs.

Concurrency permits one publish per commit and one uncancelled deploy per environment. GitHub keeps at most one waiting run.

A manual run (“Run workflow”) has two inputs. With image_tag set, publish is skipped and that existing image is deployed: a redeploy or an app rollback. With allow_older_infra, the guard lets older infrastructure code through, on purpose.

The owner will restrict dev to main in GitHub. Prevent self-review stays OFF because there is one reviewer. Teams use a second person to approve production deploys. OIDC’s environment subject does not identify a branch.

From → toWhat happensWhat allows it
CI workflow → CD publish jobCD starts after CI finished on main
  • trigger workflow_run: workflows [CI], types [completed], branches [main]; the job checks CI's conclusionpublish
CD publish job → CD deploy jobdeploy runs after publish; waits for approval when environment dev has a required reviewer
  • trigger needs: publish; environment: dev; the required reviewer is configured in GitHub, outside the workflowdeploy
CD publish job → GitHub publish roleswaps its GitHub OIDC token for 1-hour AWS credentials
  • iam trust policy: Federated = the GitHub OIDC provider, sts:AssumeRoleWithWebIdentity, aud = sts.amazonaws.com, sub = <subject prefix>:ref:refs/heads/maincreateCicd
CD publish job → ECR repositoriesdocker push api, web and worker :<git sha>
  • iam publish role push-images: ecr:GetAuthorizationToken (*), and push/pull actions on the 3 repositories onlypublishPolicyDocument
Play the deploy flow on the map →

GitHub → Actions → CD → a run → job deploy → Review deployments. In AWS: ECR → the repositories → images tagged with the SHA.

SymptomCauseWhere to look
“Not authorized to perform sts:AssumeRoleWithWebIdentity”The token’s sub does not match the role’s trust policyOIDC
Publish: not authorized for ecr:BatchGetImage on a new repositoryThe role’s policy reaches AWS only with the next infrastructure applyThe publish role’s policy
Deploy stops at “Read the committed config”An image-tag override is committed in Pulumi.dev.yamlThe step’s error line
Why is publish not behind the approval too?

Publishing only stores images; it changes nothing that runs. Publishing before the approval means the deploy starts right after the click.

Why never cancel a running deploy?

A cancelled infrastructure apply or migration can leave AWS half changed. Waiting is safer, and the stack lock stops a second apply anyway.

What is the risk of a single approval click?

The reviewer sees a commit, not its age. That is how an old approved run once deleted newer resources; the deploy guard now refuses it (see rollback and the guard).

Advanced: Details: names and variables

Role ARNs, repository and function names come from GitHub variables, not from the workflow file, so a new stack needs no edit there. A role ARN is an address, not a password, so it is a variable, not a secret. The deploy job may run up to 110 minutes; its AWS credentials last 2 hours. In GitHub → Settings → Environments → dev, choose Selected branches and tags, then add only the Branch rule main. Keep Prevent self-review unchecked for this solo project. The owner will make this change; this session did not inspect or apply the live settings. See GitHub’s environment configuration.

Answer these without scrolling up. If one feels hard, reread that section.

  1. What are the two CD jobs, and which one needs approval?
  2. What is the image tag?
  3. What does “build once, promote” mean here?
  4. What does a manual run with image_tag do?
  5. Why is the deploy never cancelled by a newer run?

Redraw it from memory: CI green → publish (3 ARM64 images :sha → ECR) → approval → deploy (guard, phase A, migrate, phase B, checks)

Try this: on the map, play the deploy flow and stop at the approval step

Read this book online: https://book.theversehub.in/