Skip to content

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.

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)
Terminal window
# infrastructure/amm.sh, line 464-465
# not applicable until arda-frontend-app can read from the secret manager
if [[ "${infrastructure}" != "Alpha001" && "${infrastructure}" != "Alpha002" ]]; then

Only 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:

Terminal window
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:

ParameterValueSource
Infrastructuree.g., SandboxKyle002amm.sh argument
Partitione.g., kyleamm.sh loop variable
RepoArda-cards/kyle-frontend-appHardcoded per infrastructure (would be Arda-cards/arda-frontend-app for Alpha001/Alpha002)
AppNamekyle-frontend-appHardcoded per infrastructure
AccessTokenGitHub PAT1Password (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 + AccessToken
    • EnableBranchAutoDeletion: true
    • CustomRules: SPA fallback (/<*>/index.html with 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:GetSecretValue scoped to arn:aws:secretsmanager:${region}:${account}:secret:${Infrastructure}-*
    • cognito-idp:AdminCreateUser, cognito-idp:AdminGetUser, cognito-idp:AdminUpdateUserAttributes scoped 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)

Environment variables (15 total, configured on the Amplify App resource):

VariableSourceCloudFormation Pattern
ARDA_API_KEYSecrets Manager{{resolve:secretsmanager:${...ArdaApiKeyArn}:SecretString}}
ARDA_SIGNUP_SECRET_KEYSecrets Manager{{resolve:secretsmanager:${...ArdaSignupSecretKeyArn}:SecretString}}
BASE_URLCloudFormation export!ImportValue ${Infrastructure}-${Partition}-API-GatewayBaseUrl
COGNITO_REGIONStatic!Ref "AWS::Region"
COGNITO_USER_POOL_IDCloudFormation export!ImportValue ${Infrastructure}-${Partition}-API-UserPoolId
HUBSPOT_API_BASEStatic"https://api.hubapi.com"
HUBSPOT_CLIENT_SECRETSecrets Manager{{resolve:secretsmanager:${...HubspotClientSecretArn}:SecretString}}
HUBSPOT_PRIVATE_ACCESS_TOKENSecrets Manager{{resolve:secretsmanager:${...HubspotPrivateAccessTokenArn}:SecretString}}
NEXT_PUBLIC_COGNITO_CLIENT_IDCloudFormation export!ImportValue ${Infrastructure}-${Partition}-API-WebClientId
NEXT_PUBLIC_COGNITO_CLIENT_SECRETSecrets Manager{{resolve:secretsmanager:${...WebSecretArn}:SecretString:clientSecret::}}
NEXT_PUBLIC_COGNITO_REGIONStatic!Ref "AWS::Region"
NEXT_PUBLIC_COGNITO_USER_POOL_IDCloudFormation export!ImportValue ${Infrastructure}-${Partition}-API-UserPoolId
NEXT_PUBLIC_DEPLOY_ENVStatic"PRODUCTION"
NEXT_PUBLIC_PYLON_APP_IDStatic"cfd1f3e2-b840-4ce2-ad95-03b1ec18c74d"
NEXT_PUBLIC_FRONTEND_URLCloudFormation export!ImportValue ${Infrastructure}-${Partition}-API-ApplicationBaseUrl
PYLON_WIDGET_SECRETSecrets 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:

Terminal window
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-AmplifyAppId
    • BranchName: main (parameterized, but always main in current usage)
    • EnableAutoBuild: truehardcoded, not parameterized. This is the single template change required by Development Blueprint Step 1: add an EnableAutoBuild parameter with default true so the demo app can pass false.
    • EnablePullRequestPreview: true
    • Framework: "Next.js - SSR"
    • Stage: "PRODUCTION"
  • AWS::Amplify::Domain — Associates a custom domain with the branch.

    • DomainName: Imported from ${Infrastructure}-${Partition}-API-ApplicationDomain
    • SubDomainSettings: Prefix "" (root of the domain), mapped to the branch

Works around a CloudFormation bug where the ComputeRoleArn property on the Amplify app is not correctly applied by CloudFormation.

Terminal window
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}"
fi

The 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.

Triggers the first build after the Amplify app and branch are created.

Terminal window
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.

  • 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 AmplifyAppId from 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 the demo partition.
  • 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 calls StartJob.
  • 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: The default_branch is hardcoded. For SandboxKyle002, the Amplify app tracks main of kyle-frontend-app. For Alpha001/Alpha002 (if they were managed here), the current branch connections are dev, stage, and main respectively — each is a separate Amplify app with its own env vars.
  • EnableAutoBuild is hardcoded: The branch template hardcodes EnableAutoBuild: true (line 21). This is the only template modification required for the new pipeline — adding it as a parameter with default true so the demo app can pass false. See Development Blueprint Step 1.
  • Repo is a parameter: The template is generic. Changing Repo and AppName is sufficient to point it at a different repository (e.g., arda-frontend-app instead of kyle-frontend-app).
  • GitHub PAT required: The AccessToken parameter 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.sh directly via aws cloudformation deploy, not through npx cdk.
  • WEB_COMPUTE platform: The app uses the SSR-capable platform. This is relevant because AWS Amplify does not support StartDeployment (manual zip upload) for WEB_COMPUTE apps — only StartJob (which triggers Amplify’s own build from the connected branch).

Given the WEB_COMPUTE / StartDeployment limitation:

  1. StartJob is the only deployment API available for SSR apps on Amplify. The new pipeline must use StartJob to trigger Amplify’s own build.
  2. The branch connection matters: StartJob builds from the connected branch. To deploy from a different branch (e.g., test-deploy), a separate Amplify app connected to that branch is needed.
  3. This template is reusable: Creating a temporary test Amplify app connected to test-deploy is straightforward — deploy the same two CloudFormation stacks with Branch=test-deploy and Repo=Arda-cards/arda-frontend-app. The environment variables resolve identically (same Infrastructure/Partition pattern).
  4. The Repo parameter must be changed from kyle-frontend-app to arda-frontend-app when 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.

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)

Step 1 — Create the Amplify app:

Terminal window
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:

Terminal window
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:

Terminal window
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}"
fi

Step 4 — Initial deployment:

Terminal window
aws amplify start-job \
--app-id "${APP_ID}" \
--branch-name "demo" \
--job-type "RELEASE" \
--job-reason "Initial deploy after CloudFormation"
  • The demo branch must exist in the arda-frontend-app repository
  • The existing AMPLIFY_GITHUB_ACCESSTOKEN PAT has access to Arda-cards repos — no new token needed
  • The AWS CLI must be configured for the Alpha001 account (009765408297)

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 by amplify.cfn.yaml
  • Alpha001-demo-I-AmplifyBranchName — created by amplifyBranch.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.

  • amplify.cfn.yaml template — fully parameterized, no modifications needed
  • CDK infrastructure — the demo partition’s exports already exist
  • Secrets Manager entriesAlpha001-demo-I-* and Alpha001-demo-API-* are already provisioned
  • DNSdemo.app.arda.cards is 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.

To remove the test Amplify app after validation:

Terminal window
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).