AWS CI/CD
Set up OIDC (OpenID Connect) federation for GitHub Actions to authenticate with AWS without storing long-lived access keys.
For generating workflow files and general CI/CD setup, see CI/CD Setup.
Overview
GitHub Actions mints a short-lived OIDC token for each workflow run. AWS verifies this token against the IAM Identity Provider and issues temporary credentials for the specified IAM Role. No access keys are stored in GitHub.
Step 1: Create IAM OIDC Identity Provider
Do this once per AWS account (repeat in each member account that CI will deploy to).
- Switch to the member account (e.g.,
tsdevstack-dev) - Go to IAM > Identity Providers > Add provider
- Enter:
- Provider type: OpenID Connect
- Provider URL:
https://token.actions.githubusercontent.com(thumbprint is fetched automatically) - Audience:
sts.amazonaws.com
- Click Add provider
Step 2: Create IAM Role for GitHub Actions
Do this once per AWS account.
-
Go to IAM > Roles > Create role
-
Select Web identity:
- Identity provider:
token.actions.githubusercontent.com - Audience:
sts.amazonaws.com
- Identity provider:
-
Fill in the GitHub repository restriction:
- GitHub organization: Your GitHub username or org
- GitHub repository: Your repo name (e.g.,
my-project) - GitHub branch:
mainto restrict deploys to main only, or leave empty for all branches
-
Skip attaching managed policies (you'll add an inline policy next)
-
Role name:
github-actions-deploy -
Verify the trust policy — AWS shows it before creation. Confirm the
StringLikecondition matches your intent:Repository only (any branch can deploy — use
*wildcard):Repository + branch (only
maincan deploy):Replace
YOUR_ORG/YOUR_REPOwith your values. Edit the policy JSON directly if needed. -
Click Create role
Step 3: Add Inline Policy
AWS limits roles to 10 managed policies. Use a single inline policy to cover all services needed for deployment.
- Go to IAM > Roles > click the github-actions-deploy role you just created
- Go to Permissions tab > Add permissions > Create inline policy
- Click JSON and paste this policy:
- Policy name:
tsdevstack-deploy
Why an inline policy? AWS limits roles to 10 managed policies by default. A single inline policy covers all 15+ services needed for deployment without hitting that limit.
Step 4: Copy the Role ARN
Copy the ARN from the role page. Format: arn:aws:iam::123456789012:role/github-actions-deploy
Step 5: Repeat for Each Environment
Switch to each member account (staging, prod) and repeat Steps 1-4.
GitHub Secrets
Go to your GitHub repository > Settings > Secrets and variables > Actions.
For each environment, set 2 secrets:
The framework has no naming convention for environments. dev, staging, prod are common choices, but you can use any name. The suffix is always the UPPERCASE version of your environment name.
Private npm packages — NPM_TOKEN (optional, single global secret)
If your project depends on private npm packages, add a single NPM_TOKEN repository secret — not per-environment. Registry tokens identify your developer / org account, not a deployment target, so the same value works for every env.
The token is auto-detected and wired through generated workflows when an .npmrc exists at your project root. See CI/CD Setup — Private npm packages for the full setup.
User Secrets (Required Before First Deployment)
The CI workflow pushes framework-generated secrets automatically (cloud-secrets:push --skip-user-secrets), but user secrets must be created manually in AWS Secrets Manager before the first CI deployment.
Without these secrets, deployment will fail. The CI pipeline cannot prompt for interactive input.
Creating Secrets in AWS Console
- Switch to the member account for the target environment (e.g., dev)
- Go to Secrets Manager and select your region
- Click Store a new secret
- Secret type: Select Other type of secret
- Key/value: Switch to Plaintext tab, enter only the value (e.g.,
example.com) - Click Next
- Secret name: Enter the full name (e.g.,
myapp-shared-DOMAIN) - Click Next > Next > Store
Repeat for each required secret in each environment.
Secret Naming Format
Secrets in AWS Secrets Manager follow the format: {project-name}-{scope}-{KEY}
Where {project-name} is the project.name from your .tsdevstack/config.json.
For example, if your project name is myapp:
Required User Secrets
These are the minimum secrets required by the framework:
Your project may have additional user secrets (e.g., STRIPE_KEY, TWILIO_SID). Any secret defined in .secrets.user.json that is not framework-generated must also be created manually in AWS Secrets Manager before deployment. Use the same naming format: {project-name}-shared-{KEY}.
Alternative: Using the CLI
If you have local credentials configured (see Account Setup), you can push all user secrets from your machine:
This will prompt for DOMAIN, RESEND_API_KEY, EMAIL_FROM, and any custom secrets interactively.
Workflow Authentication Pattern
The generated workflows authenticate using aws-actions/configure-aws-credentials:
The id-token: write permission is required — without it, GitHub won't issue the OIDC token and authentication will fail silently.
Troubleshooting
"Not authorized to perform sts:AssumeRoleWithWebIdentity"
- Trust policy missing or wrong repository condition
id-token: writepermission not set in workflow- OIDC Identity Provider not created in the target account
Check the trust policy matches your repository exactly. The sub claim format is repo:ORG/REPO:ref:refs/heads/BRANCH.
"Could not assume role with OIDC"
- Wrong Role ARN in GitHub secret
- Role doesn't exist in the target account
- Audience mismatch (must be
sts.amazonaws.com)
ECR login fails
Role doesn't have ecr:GetAuthorizationToken permission. Ensure the inline policy includes ECR actions.