Amplify App Provisioning — How It Works
Analysis of how the infrastructure repository provisions Amplify applications via CloudFormation, based on the amm.sh script and the two CloudFormation templates.
Overview
Section titled “Overview”Amplify apps are provisioned as two CloudFormation stacks (not CDK), deployed by infrastructure/amm.sh as part of the partition provisioning step. The deployment is currently gated to SandboxKyle002 only — Alpha001 and Alpha002 Amplify apps were created earlier, outside this pipeline.
amm.sh └── for each partition: Step 2.5.1: CloudFormation → amplify.cfn.yaml (Amplify App + IAM roles + env vars) Step 2.5.2: CloudFormation → amplifyBranch.cfn.yaml (Branch + Domain association) Step 2.5.3: AWS CLI → update-app (Compute Role workaround) Step 2.5.4: AWS CLI → start-job (Initial deployment trigger)Gate Condition
Section titled “Gate Condition”# infrastructure/amm.sh, line 464-465# not applicable until arda-frontend-app can read from the secret managerif [[ "${infrastructure}" != "Alpha001" && "${infrastructure}" != "Alpha002" ]]; thenOnly SandboxKyle002 executes the Amplify steps. The comment suggests the intent was to bring Alpha001/Alpha002 under the same management once the frontend app supports Secrets Manager integration.
Step 2.5.1 — Create the Amplify Application
Section titled “Step 2.5.1 — Create the Amplify Application”Template: infrastructure/src/main/cfn/amplify.cfn.yaml
Stack name: ${infrastructure}-${partition}-Amplify
Command:
aws cloudformation deploy \ --capabilities CAPABILITY_IAM \ --stack-name "${infrastructure}-${partition}-Amplify" \ --template-file "src/main/cfn/amplify.cfn.yaml" \ --parameter-overrides \ "Infrastructure=${infrastructure}" "Partition=${partition}" \ "Repo=Arda-cards/kyle-frontend-app" \ "AppName=kyle-frontend-app" \ "AccessToken=${AMPLIFY_GITHUB_ACCESSTOKEN}"Parameters:
| Parameter | Value | Source |
|---|---|---|
Infrastructure | e.g., SandboxKyle002 | amm.sh argument |
Partition | e.g., kyle | amm.sh loop variable |
Repo | Arda-cards/kyle-frontend-app | Hardcoded per infrastructure (would be Arda-cards/arda-frontend-app for Alpha001/Alpha002) |
AppName | kyle-frontend-app | Hardcoded per infrastructure |
AccessToken | GitHub PAT | 1Password (Arda-SystemsOAM/Amplify_GitHub_AccessToken) or GH Actions secret |
Resources created:
-
AWS::Amplify::App— The Amplify application.Platform: "WEB_COMPUTE"— SSR-capable platform required for Next.js 14+Repository: Connected to the GitHub repo via HTTPS URL + AccessTokenEnableBranchAutoDeletion: trueCustomRules: SPA fallback (/<*>→/index.htmlwith 404 status)ComputeRoleArn: References the Compute Role (see below)IAMServiceRole: References the Service Role (see below)
-
AmplifyComputeRole(IAM Role) — Assumed by Amplify during builds. Permissions:secretsmanager:GetSecretValuescoped toarn:aws:secretsmanager:${region}:${account}:secret:${Infrastructure}-*cognito-idp:AdminCreateUser,cognito-idp:AdminGetUser,cognito-idp:AdminUpdateUserAttributesscoped to all user pools in the account- CloudWatch Logs (
CreateLogStream,DescribeLogStreams,PutLogEvents) scoped to/aws/amplify/*
-
AmplifyServiceRole(IAM Role) — Amplify’s own service role.- Managed policy:
AdministratorAccess-Amplify - CloudWatch Logs (
DescribeLogGroups,CreateLogGroup)
- Managed policy:
Environment variables (15 total, configured on the Amplify App resource):
| Variable | Source | CloudFormation Pattern |
|---|---|---|
ARDA_API_KEY | Secrets Manager | {{resolve:secretsmanager:${...ArdaApiKeyArn}:SecretString}} |
ARDA_SIGNUP_SECRET_KEY | Secrets Manager | {{resolve:secretsmanager:${...ArdaSignupSecretKeyArn}:SecretString}} |
BASE_URL | CloudFormation export | !ImportValue ${Infrastructure}-${Partition}-API-GatewayBaseUrl |
COGNITO_REGION | Static | !Ref "AWS::Region" |
COGNITO_USER_POOL_ID | CloudFormation export | !ImportValue ${Infrastructure}-${Partition}-API-UserPoolId |
HUBSPOT_API_BASE | Static | "https://api.hubapi.com" |
HUBSPOT_CLIENT_SECRET | Secrets Manager | {{resolve:secretsmanager:${...HubspotClientSecretArn}:SecretString}} |
HUBSPOT_PRIVATE_ACCESS_TOKEN | Secrets Manager | {{resolve:secretsmanager:${...HubspotPrivateAccessTokenArn}:SecretString}} |
NEXT_PUBLIC_COGNITO_CLIENT_ID | CloudFormation export | !ImportValue ${Infrastructure}-${Partition}-API-WebClientId |
NEXT_PUBLIC_COGNITO_CLIENT_SECRET | Secrets Manager | {{resolve:secretsmanager:${...WebSecretArn}:SecretString:clientSecret::}} |
NEXT_PUBLIC_COGNITO_REGION | Static | !Ref "AWS::Region" |
NEXT_PUBLIC_COGNITO_USER_POOL_ID | CloudFormation export | !ImportValue ${Infrastructure}-${Partition}-API-UserPoolId |
NEXT_PUBLIC_DEPLOY_ENV | Static | "PRODUCTION" |
NEXT_PUBLIC_PYLON_APP_ID | Static | "cfd1f3e2-b840-4ce2-ad95-03b1ec18c74d" |
NEXT_PUBLIC_FRONTEND_URL | CloudFormation export | !ImportValue ${Infrastructure}-${Partition}-API-ApplicationBaseUrl |
PYLON_WIDGET_SECRET | Secrets Manager | {{resolve:secretsmanager:${...PylonWidgetSecretArn}:SecretString}} |
Exports:
${Infrastructure}-${Partition}-I-AmplifyAppId— The Amplify App ID (used by the branch stack and the compute role workaround)${Infrastructure}-${Partition}-I-AmplifyComputeRoleArn— The Compute Role ARN (used by the workaround step)
Step 2.5.2 — Create the Branch and Domain
Section titled “Step 2.5.2 — Create the Branch and Domain”Template: infrastructure/src/main/cfn/amplifyBranch.cfn.yaml
Stack name: ${infrastructure}-${partition}-AmplifyBranch
Command:
aws cloudformation deploy \ --stack-name "${infrastructure}-${partition}-AmplifyBranch" \ --template-file "src/main/cfn/amplifyBranch.cfn.yaml" \ --parameter-overrides \ "Infrastructure=${infrastructure}" "Partition=${partition}" \ "Branch=${default_branch}"The default_branch variable is hardcoded to "main" (line 482 of amm.sh).
Resources created:
-
AWS::Amplify::Branch— Connects a git branch to the Amplify app.AppId: Imported from${Infrastructure}-${Partition}-I-AmplifyAppIdBranchName:main(parameterized, but alwaysmainin current usage)EnableAutoBuild: true— hardcoded, not parameterized. This is the single template change required by Development Blueprint Step 1: add anEnableAutoBuildparameter with defaulttrueso the demo app can passfalse.EnablePullRequestPreview: trueFramework: "Next.js - SSR"Stage: "PRODUCTION"
-
AWS::Amplify::Domain— Associates a custom domain with the branch.DomainName: Imported from${Infrastructure}-${Partition}-API-ApplicationDomainSubDomainSettings: Prefix""(root of the domain), mapped to the branch
Step 2.5.3 — Compute Role Workaround
Section titled “Step 2.5.3 — Compute Role Workaround”Works around a CloudFormation bug where the ComputeRoleArn property on the Amplify app is not correctly applied by CloudFormation.
APP_ID="$(aws cloudformation list-exports --output text \ --query "Exports[?Name=='${infrastructure}-${partition}-I-AmplifyAppId'].Value")"
COMPUTE_ROLE_ARN="$(aws cloudformation list-exports --output text \ --query "Exports[?Name=='${infrastructure}-${partition}-I-AmplifyComputeRoleArn'].Value")"
COMPUTE_ROLE_ARN_VALUE="$(aws amplify get-app --app-id "${APP_ID}" \ --query "app.computeRoleArn" --output text)"
if [[ "${COMPUTE_ROLE_ARN_VALUE}" != "${COMPUTE_ROLE_ARN}" ]]; then aws amplify update-app \ --app-id "${APP_ID}" \ --compute-role-arn "${COMPUTE_ROLE_ARN}"fiThe script reads the intended ComputeRoleArn from CloudFormation exports, compares it with the actual value on the Amplify app, and calls aws amplify update-app to correct it if they differ.
Step 2.5.4 — Initial Deployment
Section titled “Step 2.5.4 — Initial Deployment”Triggers the first build after the Amplify app and branch are created.
aws amplify start-job \ --app-id "${APP_ID}" \ --branch-name "${default_branch}" \ --job-type "RELEASE" \ --job-reason "Initial deploy after CloudFormation"This is a StartJob call with jobType: RELEASE. Amplify pulls the code from the connected branch (main), runs the amplify.yml build spec, resolves environment variables from Secrets Manager and CloudFormation exports, and deploys the .next/ artifacts.
Key Observations
Section titled “Key Observations”- Two stacks, not one: The Amplify app and the branch/domain are separate CloudFormation stacks. This allows updating the branch (e.g., changing
EnableAutoBuild) without redeploying the app and its environment variables. - Cross-stack dependency chain: The branch template imports
AmplifyAppIdfrom the app template. The app template imports 5 CloudFormation exports (GatewayBaseUrl,UserPoolId,WebClientId,ApplicationBaseUrl,ApplicationDomain) and 6 Secrets Manager ARN exports (ArdaApiKeyArn,ArdaSignupSecretKeyArn,HubspotClientSecretArn,HubspotPrivateAccessTokenArn,WebSecretArn,PylonWidgetSecretArn) from the CDK infrastructure stacks. All of these already exist for thedemopartition. - Environment variable resolution timing: The
{{resolve:secretsmanager:...}}syntax resolves at CloudFormation deploy time, not at Amplify build time. CloudFormation retrieves the secret values when the stack is created or updated, and Amplify receives the resolved values as plain strings. This is why the GitHub Actions pipeline needs no access to Secrets Manager or CloudFormation exports — it only callsStartJob. - Environment variables are on the app, not the branch: All branches of the same Amplify app share the same environment variables. There is no per-branch env var override in this template.
- Branch is always
main: Thedefault_branchis hardcoded. For SandboxKyle002, the Amplify app tracksmainofkyle-frontend-app. For Alpha001/Alpha002 (if they were managed here), the current branch connections aredev,stage, andmainrespectively — each is a separate Amplify app with its own env vars. EnableAutoBuildis hardcoded: The branch template hardcodesEnableAutoBuild: true(line 21). This is the only template modification required for the new pipeline — adding it as a parameter with defaulttrueso the demo app can passfalse. See Development Blueprint Step 1.Repois a parameter: The template is generic. ChangingRepoandAppNameis sufficient to point it at a different repository (e.g.,arda-frontend-appinstead ofkyle-frontend-app).- GitHub PAT required: The
AccessTokenparameter is a GitHub Personal Access Token that grants Amplify read access to the repository. Stored in 1Password (Arda-SystemsOAM/Amplify_GitHub_AccessToken) for local runs, or in GitHub Actions secrets for CI. - CloudFormation, not CDK: These are raw CloudFormation templates, not CDK constructs. They are deployed by
amm.shdirectly viaaws cloudformation deploy, not throughnpx cdk. WEB_COMPUTEplatform: The app uses the SSR-capable platform. This is relevant because AWS Amplify does not supportStartDeployment(manual zip upload) forWEB_COMPUTEapps — onlyStartJob(which triggers Amplify’s own build from the connected branch).
Implications for the New Pipeline
Section titled “Implications for the New Pipeline”Given the WEB_COMPUTE / StartDeployment limitation:
StartJobis the only deployment API available for SSR apps on Amplify. The new pipeline must useStartJobto trigger Amplify’s own build.- The branch connection matters:
StartJobbuilds from the connected branch. To deploy from a different branch (e.g.,test-deploy), a separate Amplify app connected to that branch is needed. - This template is reusable: Creating a temporary test Amplify app connected to
test-deployis straightforward — deploy the same two CloudFormation stacks withBranch=test-deployandRepo=Arda-cards/arda-frontend-app. The environment variables resolve identically (sameInfrastructure/Partitionpattern). - The
Repoparameter must be changed fromkyle-frontend-apptoarda-frontend-appwhen deploying for the real frontend.
Example: Creating a demo Partition Amplify App
Section titled “Example: Creating a demo Partition Amplify App”The demo partition (Alpha001, account 009765408297) has full CDK infrastructure (Cognito, Aurora DB, API Gateway, DNS at demo.app.arda.cards) but no Amplify app. Creating one connected to a demo branch in arda-frontend-app requires no template changes — only amm.sh parameters.
What Already Exists
Section titled “What Already Exists”All CloudFormation exports and Secrets Manager entries required by amplify.cfn.yaml are already provisioned by the demo partition’s CDK stacks:
Alpha001-demo-API-GatewayBaseUrl(BASE_URL)Alpha001-demo-API-UserPoolId(COGNITO_USER_POOL_ID)Alpha001-demo-API-WebClientId(NEXT_PUBLIC_COGNITO_CLIENT_ID)Alpha001-demo-API-ApplicationBaseUrl(NEXT_PUBLIC_FRONTEND_URL)Alpha001-demo-API-ApplicationDomain(custom domain:demo.app.arda.cards)Alpha001-demo-API-ArdaApiKeyArn,Alpha001-demo-I-ArdaSignupSecretKeyArn, etc. (Secrets Manager)
Commands
Section titled “Commands”Step 1 — Create the Amplify app:
aws cloudformation deploy \ --capabilities CAPABILITY_IAM \ --stack-name "Alpha001-demo-Amplify" \ --template-file "src/main/cfn/amplify.cfn.yaml" \ --tags "Infrastructure=Alpha001" \ --tags "Partition=demo" \ --parameter-overrides \ "Infrastructure=Alpha001" "Partition=demo" \ "Repo=Arda-cards/arda-frontend-app" \ "AppName=arda-frontend-app" \ "AccessToken=${AMPLIFY_GITHUB_ACCESSTOKEN}"Step 2 — Create the branch and domain:
aws cloudformation deploy \ --stack-name "Alpha001-demo-AmplifyBranch" \ --template-file "src/main/cfn/amplifyBranch.cfn.yaml" \ --tags "Infrastructure=Alpha001" \ --tags "Partition=demo" \ --parameter-overrides \ "Infrastructure=Alpha001" "Partition=demo" \ "Branch=demo"Step 3 — Compute Role workaround:
APP_ID="$(aws cloudformation list-exports --output text \ --query "Exports[?Name=='Alpha001-demo-I-AmplifyAppId'].Value")"
COMPUTE_ROLE_ARN="$(aws cloudformation list-exports --output text \ --query "Exports[?Name=='Alpha001-demo-I-AmplifyComputeRoleArn'].Value")"
COMPUTE_ROLE_ARN_VALUE="$(aws amplify get-app --app-id "${APP_ID}" \ --query "app.computeRoleArn" --output text)"
if [[ "${COMPUTE_ROLE_ARN_VALUE}" != "${COMPUTE_ROLE_ARN}" ]]; then aws amplify update-app \ --app-id "${APP_ID}" \ --compute-role-arn "${COMPUTE_ROLE_ARN}"fiStep 4 — Initial deployment:
aws amplify start-job \ --app-id "${APP_ID}" \ --branch-name "demo" \ --job-type "RELEASE" \ --job-reason "Initial deploy after CloudFormation"Prerequisites
Section titled “Prerequisites”- The
demobranch must exist in thearda-frontend-apprepository - The existing
AMPLIFY_GITHUB_ACCESSTOKENPAT has access toArda-cardsrepos — no new token needed - The AWS CLI must be configured for the Alpha001 account (
009765408297)
CloudFormation Exports for the Workflow
Section titled “CloudFormation Exports for the Workflow”After the Amplify app and branch stacks are deployed, the following CloudFormation exports are automatically available for the GitHub Actions workflow:
Alpha001-demo-I-AmplifyAppId— created byamplify.cfn.yamlAlpha001-demo-I-AmplifyBranchName— created byamplifyBranch.cfn.yaml
No changes to the purpose-configuration repository are needed. The workflow derives the frontend IAM role from the existing aws_role in demo.properties and reads the Amplify parameters from these CloudFormation exports.
What Does NOT Change
Section titled “What Does NOT Change”amplify.cfn.yamltemplate — fully parameterized, no modifications needed- CDK infrastructure — the
demopartition’s exports already exist - Secrets Manager entries —
Alpha001-demo-I-*andAlpha001-demo-API-*are already provisioned - DNS —
demo.app.arda.cardsis already configured
Note: amplifyBranch.cfn.yaml does require a one-line change to parameterize EnableAutoBuild (see Development Blueprint Step 1). This change is backward-compatible — the default value of true preserves existing behavior for all current deployments.
Cleanup
Section titled “Cleanup”To remove the test Amplify app after validation:
aws cloudformation delete-stack --stack-name "Alpha001-demo-AmplifyBranch"aws cloudformation wait stack-delete-complete --stack-name "Alpha001-demo-AmplifyBranch"aws cloudformation delete-stack --stack-name "Alpha001-demo-Amplify"The branch stack must be deleted first (it depends on the app stack’s exported AmplifyAppId).
Copyright: © Arda Systems 2025-2026, All rights reserved