You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Gates all deployments behind a protected GitHub environment (deploy) requiring manual approval — triggered by deploy label (with optional type qualifiers)
Deploys to AWS using OIDC federation assuming CDK bootstrap roles (no long-lived credentials)
Stack naming: main-<compute_type>-prd for production, ephemeral for PRs/branches
On successful deployment: creates a GitHub Release (drafted → published) with tagged main and cdk-*.out artifacts
Cleanup targets stacks tagged with github:* context keys (presence of any github:sha != none), gated behind approval with cancel-in-progress concurrency
Decisions (from discussion)
Question
Decision
PR deployments
Opt-in via deploy label (with optional type qualifiers)
Synth strategy
Once in build.yml for ALL registered compute_types, deploy the exact artifact — no re-synth
Cleanup approval
Always manually gated — later runs cancel prior pending requests
Cost gate
No — resource review in approval is sufficient
Permissions boundary
Yes — use CDK bootstrap roles (deploy, lookup, file-publishing, image-publishing)
main deploy approval
Always require — never skip, even after PR merge
Deploy selection
Label-driven: deploy = all registered types, deploy:<type> = only that type
Baselines
Per-compute_type against main-<compute_type>-prd — stored as release artifacts
build.yml always synthesizes all registered compute_types (today: [agentcore]). Labels only control what deploy.yml deploys.
Labels
Label
Types deployed
Use case
deploy
All registered types
Standard full deployment
deploy:agentcore
agentcore only
Deploy only agentcore
deploy:ecs
ecs only
Deploy only ECS (when available)
deploy:*
All (same as deploy)
Explicit "all" synonym
No deploy* label
Nothing deployed
Default (CI only)
Resolution logic (in deploy.yml)
- name: Resolve deploy targets from labelsid: targetsrun: | LABELS='${{ toJson(github.event.pull_request.labels.*.name) }}' # All registered compute_types (must match build.yml matrix) ALL_TYPES='["agentcore"]' if echo "$LABELS" | jq -e 'index("deploy:*")' > /dev/null; then # deploy:* = all (explicit synonym) echo "matrix=$ALL_TYPES" >> "$GITHUB_OUTPUT" elif echo "$LABELS" | jq -e '[.[] | select(startswith("deploy:"))] | length > 0' > /dev/null; then # Specific type labels — deploy only those TYPES=$(echo "$LABELS" | jq '[.[] | select(startswith("deploy:")) | ltrimstr("deploy:")]') echo "matrix=$TYPES" >> "$GITHUB_OUTPUT" elif echo "$LABELS" | jq -e 'index("deploy")' > /dev/null; then # Plain "deploy" = all registered types echo "matrix=$ALL_TYPES" >> "$GITHUB_OUTPUT" else echo 'matrix=[]' >> "$GITHUB_OUTPUT" fi
Release Flow
Successful deployments from main produce GitHub Releases:
main merge
→ build.yml (synth ALL registered compute_types in matrix)
→ upload artifacts: cdk-agentcore.out, (cdk-ecs.out when available, ...)
→ deploy.yml (approval gate — downloads exact artifacts, label filters which deploy)
→ successful deployment
→ Draft Release created:
Tag: v<date>-<short-sha> (e.g. v2026.05.11-abc1234)
Assets:
- cdk-agentcore.out.tar.gz
- (cdk-ecs.out.tar.gz when available)
- agentcore.resource-types.json (baseline)
- (ecs.resource-types.json when available)
→ Publish Release
Baselines live in releases, not in the repo. The diff step downloads the baseline from the latest published release for that compute_type:
- name: Download baseline from latest releaserun: | LATEST=$(gh release view --json tagName -q .tagName 2>/dev/null || echo "") if [[ -n "$LATEST" ]]; then gh release download "$LATEST" \ --pattern "${{ matrix.compute_type }}.resource-types.json" \ --dir /tmp/baseline/ || true fi
This means:
No baseline commits polluting the repo history
Baselines are immutable (tied to a release tag)
First deploy (no prior release) has no baseline → everything shows as "new" (correct)
Rollback = re-deploy from a prior release's cdk-*.out artifact
Synth-Once, Deploy-Exact Artifact
The cdk.out is synthesized exactly once per compute_type during build.yml. The deploy.yml never re-synths — it downloads and deploys the exact artifact:
All stacks deployed via this pipeline are identified by the 13 github:* tags applied via CDK context (PR #91, #93). Cleanup identifies CI-deployed stacks by checking github:sha != none. Additionally:
Tags.of(stack).add('compute_type',computeType);
The compute_type tag enables per-type baseline queries and cost attribution.
RFC: Automated Deployment Pipeline with Protected Environments
Status: Core complete — remaining work tracked in #72 (revised per feedback)
Author: @scottschreckengaust
Related: #70 (context stack names), #72 (ephemeral cleanup)
Current state (reconciled 2026-06-05)
The core deploy pipeline is delivered and on
main:deployenvironment, GH→bootstrap-role assumption, account bootstrap)compute_typesynth, immutablecdk-<type>.outartifacts, 13github:*tags) — PRs feat(ci): synth-per-variant build with github:* context in artifact #91, feat(cdk): add 4 additional github:* resource tags #93, feat(ci): rename computeVariant to compute_type and apply as resource tag #97scripts/cleanup-ephemeral-stacks.shonly; no EventBridge schedule, no CloudWatch audit, no CDK construct, no tests). PR feat(ops): automated ephemeral stack cleanup script #109 is stale (untouched since 2026-05-18).CONTRIBUTING.md)Next to pick up: ephemeral cleanup automation (Phase 4) — see acceptance criteria in #72, building on the script in PR #109.
Summary
Establish a GitHub Actions deployment pipeline that:
build.yml(always all registered types)cdk-<compute_type>.outas immutable deployment artifacts (synth once, deploy exact artifact)deploy) requiring manual approval — triggered bydeploylabel (with optional type qualifiers)main-<compute_type>-prdfor production, ephemeral for PRs/branchesmainandcdk-*.outartifactsgithub:*context keys (presence of anygithub:sha!=none), gated behind approval with cancel-in-progress concurrencyDecisions (from discussion)
deploylabel (with optional type qualifiers)build.ymlfor ALL registered compute_types, deploy the exact artifact — no re-synthmaindeploy approvaldeploy= all registered types,deploy:<type>= only that typemain-<compute_type>-prd— stored as release artifactsDesign
Architecture
Label-Driven Deploy Selection
Key principle: Build ALL, deploy selectively
build.ymlalways synthesizes all registered compute_types (today:[agentcore]). Labels only control whatdeploy.ymldeploys.Labels
deploydeploy:agentcoreagentcoreonlydeploy:ecsecsonlydeploy:*deploy)deploy*labelResolution logic (in
deploy.yml)Release Flow
Successful deployments from
mainproduce GitHub Releases:Baselines live in releases, not in the repo. The diff step downloads the baseline from the latest published release for that compute_type:
This means:
cdk-*.outartifactSynth-Once, Deploy-Exact Artifact
The
cdk.outis synthesized exactly once per compute_type duringbuild.yml. Thedeploy.ymlnever re-synths — it downloads and deploys the exact artifact:This guarantees what was tested in CI is exactly what gets deployed — no
new Date()drift, no env var differences, no CDK version skew.Permissions: CDK Bootstrap Role Assumption
The GitHub OIDC role only needs permission to assume the CDK bootstrap roles. This is the CDK security best practice:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": [ "arn:aws:iam::ACCOUNT:role/cdk-hnb659fds-deploy-role-*", "arn:aws:iam::ACCOUNT:role/cdk-hnb659fds-lookup-role-*", "arn:aws:iam::ACCOUNT:role/cdk-hnb659fds-file-publishing-role-*", "arn:aws:iam::ACCOUNT:role/cdk-hnb659fds-image-publishing-role-*" ] }, { "Sid": "CleanupENIs", "Effect": "Allow", "Action": [ "ec2:DescribeNetworkInterfaces", "ec2:DetachNetworkInterface", "ec2:DeleteNetworkInterface", "cloudformation:ListStacks", "cloudformation:DescribeStacks", "cloudformation:DeleteStack", "cloudformation:ListStackResources" ], "Resource": "*" } ] }Trust policy (OIDC):
{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::ACCOUNT:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" }, "StringLike": { "token.actions.githubusercontent.com:sub": "repo:aws-samples/sample-autonomous-cloud-coding-agents:*" } } }] }Stack Naming and Tagging
mainmain-agentcore-prdtruemaindeploy:ecsmain-ecs-prdtruedeploypr-42-abc1234-agentcorefalsedeploy:ecspr-42-abc1234-ecsfalsedeploycommit-abc1234-agentcorefalseAll stacks deployed via this pipeline are identified by the 13
github:*tags applied via CDK context (PR #91, #93). Cleanup identifies CI-deployed stacks by checkinggithub:sha!=none. Additionally:The
compute_typetag enables per-type baseline queries and cost attribution.GitHub Environment:
deployEnvironment secrets:
AWS_ROLE_ARNarn:aws:iam::ACCOUNT:role/GitHubActionsCDKRoleAWS_REGIONus-east-1Cleanup Workflow
Resource Baseline and Diff (via Releases)
Diff output example (shown to approver in Step Summary)
Approval Gate: What Reviewers Should Check
The deployment summary provides:
cdk diff(property-level changes from the synthesized artifact)Per new resource type, verify:
awspricingMCPaws service-quotas list-service-quotas --service-code <code>*permissions does CDK grant?RemovalPolicy.DESTROY? Orphan risk?Implementation Plan
Phase 1: Foundation
deploy(no self-approval, no bypass, prevent self-review)sts:AssumeRoleto CDK bootstrap rolesPhase 2: Build pipeline
build.yml(PR feat(ci): synth-per-variant build with github:* context in artifact #91) — currently[agentcore]cdk.context.jsonwith all 13github:*tags +compute_type+stackName(PR feat(ci): synth-per-variant build with github:* context in artifact #91)github:*resource tags via CDK context (PR feat(ci): synth-per-variant build with github:* context in artifact #91, feat(cdk): add 4 additional github:* resource tags #93)cdk-<compute_type>-outimmutable artifact per matrix leg (PR feat(ci): synth-per-variant build with github:* context in artifact #91)compute_typefrom context in CDK and apply as resource tag (PR feat(ci): rename computeVariant to compute_type and apply as resource tag #97)computeVariant→compute_typeinbuild.ymlcontext generation (PR feat(ci): rename computeVariant to compute_type and apply as resource tag #97)Phase 3: Deploy pipeline
deploy.yml— downloads exact artifact, never re-synths (PR feat(ci): add deploy pipeline with OIDC, dynamic stack naming, and deploy-intent artifact #98)deploy= all,deploy:<type>= one) (PR feat(ci): add deploy pipeline with OIDC, dynamic stack naming, and deploy-intent artifact #98)cdk diffoutput to step summaryPhase 4: Cleanup
cleanup-ephemeral-stacks.shto target bygithub:shatag presencecleanup.ymlwith approval gate andcancel-in-progressPhase 5: Observability
CONTRIBUTING.mdSecurity Considerations
github:shatag (applied to all CI-deployed stacks)main-*-prdstacks cannot be accidentally deletedReferences
github:*context in artifactgithub:*resource tags (13 total)