This guide walks through the complete setup of Awesome Foundation infrastructure in a new AWS Organization.
- AWS root account with Organizations enabled
- GitHub organization with a repository for this infrastructure code
- AWS CLI installed locally
- Admin access to create GitHub organization variables/secrets
The setup follows this order:
- AWS Organization Setup - Root account + stage-based member accounts
- GitHub Actions OIDC - Deploy to root, then StackSet to member accounts
- GitHub Variables - Configure role ARNs for CI/CD
- Core Infrastructure - VPC and Web stacks
- Optional: Bastion - For database/private subnet access
- Optional: SSO - For human access via AWS IAM Identity Center
┌─────────────────────────────────────────────────────────────────────┐
│ AWS Organization │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Root │ │ Dev │ │ Test │ │ Prod │ │
│ │ Account │ │ Account │ │ Account │ │ Account │ │
│ │ │ │ 10.8.0.0 │ │ 10.9.0.0 │ │ 10.10.0.0 │ │
│ │ - SSO │ │ /16 CIDR │ │ /16 CIDR │ │ /16 CIDR │ │
│ │ - StackSet │ │ │ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
- Log into your root AWS account
- Go to AWS Organizations → Create Organization
- Enable all features (recommended)
Create three member accounts for the stage-based deployment model:
| Account Name | Purpose | Email (example) |
|---|---|---|
awesome-dev |
Development | aws+dev@yourcompany.com |
awesome-test |
Testing/Staging | aws+test@yourcompany.com |
awesome-prod |
Production | aws+prod@yourcompany.com |
In AWS Organizations:
- Click Add an AWS account → Create an AWS account
- Enter account name and email
- Repeat for each environment
Note the Account IDs - you'll need them later.
In the root account:
- Go to CloudFormation → StackSets
- If prompted, enable trusted access with AWS Organizations
This must be done manually first to bootstrap CI/CD.
- Log into the root account via AWS Console
- Go to CloudFormation → Create stack
- Upload
github_actions_oidc/github_actions.yml - Parameters:
- TrustedGithubOrgOrRepo:
your-github-org/core-infrastructure(grant access only to this repository)
- TrustedGithubOrgOrRepo:
- Stack name:
github-actions-oidc - Create the stack
After creation, note the role ARN:
arn:aws:iam::<ROOT_ACCOUNT_ID>:role/awesome-gha-allow-all-role
Before the StackSet workflow can run, you need to configure the root account role as a GitHub secret.
Go to GitHub → Organization Settings → Secrets and variables → Actions → Secrets
Add this organization secret:
| Secret Name | Value |
|---|---|
AWESOME_AWS_DEPLOY_ROLE_ROOT |
arn:aws:iam::<ROOT_ACCOUNT_ID>:role/awesome-gha-allow-all-role |
The StackSet deployment is automated via the github_actions_oidc_stackset.yml workflow.
Trigger deployment:
- Push any change to
github_actions_oidc/github_actions.ymlor the workflow file - Or manually trigger the workflow via GitHub Actions
The workflow will:
- Create/update a CloudFormation StackSet named
github-actions-oidc - Deploy to all member accounts in the organization
- Set
TrustedGithubOrgOrRepotoyour-github-org/*(all org repos) - Enable auto-deployment for any new accounts added to the organization
After StackSet deployment completes, verify the roles exist in each member account:
# For each account, the role ARN will be:
arn:aws:iam::<DEV_ACCOUNT_ID>:role/awesome-gha-allow-all-role
arn:aws:iam::<TEST_ACCOUNT_ID>:role/awesome-gha-allow-all-role
arn:aws:iam::<PROD_ACCOUNT_ID>:role/awesome-gha-allow-all-roleConfigure GitHub organization variables so workflows can authenticate to each account.
Go to GitHub → Organization Settings → Secrets and variables → Actions → Variables
Add these organization variables:
| Variable Name | Value |
|---|---|
AWESOME_AWS_DEFAULT_REGION |
eu-central-1 (or your preferred AWS region) |
AWESOME_AWS_DEPLOY_ROLE_DEV |
arn:aws:iam::<DEV_ACCOUNT_ID>:role/awesome-gha-allow-all-role |
AWESOME_AWS_DEPLOY_ROLE_TEST |
arn:aws:iam::<TEST_ACCOUNT_ID>:role/awesome-gha-allow-all-role |
AWESOME_AWS_DEPLOY_ROLE_PROD |
arn:aws:iam::<PROD_ACCOUNT_ID>:role/awesome-gha-allow-all-role |
Important:
AWESOME_AWS_DEFAULT_REGIONmust be set before running any workflows. All workflows use this variable to determine which AWS region to deploy to.
The root account secret (AWESOME_AWS_DEPLOY_ROLE_ROOT) should already be configured from Phase 2.2. This secret is used by SSO and StackSet workflows.
Create a test workflow or manually trigger a workflow to verify access works:
# .github/workflows/test_aws_access.yml
name: Test AWS Access
on: workflow_dispatch
permissions:
id-token: write
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- env: dev
role: ${{ vars.AWESOME_AWS_DEPLOY_ROLE_DEV }}
- env: test
role: ${{ vars.AWESOME_AWS_DEPLOY_ROLE_TEST }}
- env: prod
role: ${{ vars.AWESOME_AWS_DEPLOY_ROLE_PROD }}
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ matrix.role }}
aws-region: ${{ vars.AWESOME_AWS_DEFAULT_REGION }}
- name: Test access
run: |
echo "Testing ${{ matrix.env }} account"
aws sts get-caller-identityWith CI/CD working, deploy the foundational stacks.
Important: We recommend using a dedicated infrastructure domain (e.g.,
companyname.dev) rather than your production domain (companyname.com). See Why We Use a Dedicated Infrastructure Domain for the rationale.
Before deploying, update the domain in the VPC template:
- Edit
awesome-vpc/awesome-vpc.yml- update the Route53 hosted zone domain parameter - THIS CANNOT BE CHANGED LATER
Search for example.dev and replace with your domain.
The VPC stack must be deployed first as other stacks depend on it.
Configure Availability Zones:
Before deploying, review the AZ configuration in .github/workflows/vpc_deploy.yml:
env:
DEPLOY_AZ_ONE: 1 # 1=deploy, 0=skip
DEPLOY_AZ_TWO: 1
DEPLOY_AZ_THREE: 0 # Enable for 3-AZ redundancyNote: The VPC stack can technically run in a single AZ, but the Web stack requires at least 2 AZs due to ALB requirements. Configure at minimum
DEPLOY_AZ_ONE: 1andDEPLOY_AZ_TWO: 1.
Trigger deployment:
- Push changes to
awesome-vpc/on themasterbranch, OR - Manually trigger the
vpc_deploy.ymlworkflow
The workflow deploys to all three environments (dev, test, prod) in parallel.
What gets created:
- VPC with public/private subnets across configured AZs
- Internet Gateway and NAT Gateways (one per enabled AZ)
- Route tables and security groups
- Route53 hosted zone (
dev.yourdomain.com,test.yourdomain.com,prod.yourdomain.com) - DynamoDB VPC endpoint
After VPC is deployed, deploy the web infrastructure.
Configure Availability Zones:
The Web stack must have matching AZ settings with the VPC stack. Review .github/workflows/web_deploy.yml:
env:
DEPLOY_AZ_ONE: 1 # Must match VPC settings
DEPLOY_AZ_TWO: 1 # Must match VPC settings
DEPLOY_AZ_THREE: 0 # Must match VPC settingsImportant: The Web stack requires a minimum of 2 AZs because AWS Application Load Balancers must have subnets in at least 2 different Availability Zones. Attempting to deploy with only 1 AZ will fail.
Trigger deployment:
- Push changes to
awesome-web/on themasterbranch, OR - Manually trigger the
web_deploy.ymlworkflow
What gets created:
- ECS cluster (Fargate + Fargate Spot)
- Public and private Application Load Balancers (with subnets in each enabled AZ)
- ACM SSL certificate (wildcard for
*.stage.yourdomain.com) - ALB listener rules priority management Lambda
- S3 bucket for ALB access logs
Build and push the HAProxy sidecar image to ECR:
Trigger deployment:
- Push changes to
awesome-haproxy/on themasterbranch
This creates ECR repositories and pushes the HAProxy image to all environments.
At this point, you can deploy applications to ECS. An application deployment typically needs:
- ECR Repository - For Docker images
- ECS Task Definition - Container configuration
- ECS Service - Running tasks behind the ALB
- ALB Listener Rule - Route traffic to the service
Example minimal ECS service CloudFormation snippet:
Resources:
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: my-app
NetworkMode: awsvpc
RequiresCompatibilities: [FARGATE]
Cpu: 256
Memory: 512
ExecutionRoleArn: !ImportValue awesome-web-ECSTaskExecutionRole
ContainerDefinitions:
- Name: app
Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/my-app:latest
PortMappings:
- ContainerPort: 8080
Service:
Type: AWS::ECS::Service
Properties:
Cluster: default
TaskDefinition: !Ref TaskDefinition
DesiredCount: 2
LaunchType: FARGATE
NetworkConfiguration:
AwsvpcConfiguration:
Subnets:
- !ImportValue awesome-vpc-PrivateSubnet1Id
- !ImportValue awesome-vpc-PrivateSubnet2Id
SecurityGroups:
- !ImportValue awesome-vpc-PermissiveSecurityGroup
LoadBalancers:
- ContainerName: app
ContainerPort: 8080
TargetGroupArn: !Ref TargetGroupDeploy the bastion host for SSH access to resources in private subnets (databases, internal services).
Edit awesome-bastion/authorized_users with GitHub usernames who should have access:
github_username_1
github_username_2
SSH public keys are fetched from GitHub at container startup.
Trigger deployment:
- Push changes to
awesome-bastion/on themasterbranch
What gets created:
- ECS service running SSH containers
- Network Load Balancer on port 22
- DNS record:
bastion.stage.yourdomain.com
# Direct connection
ssh -p 22 bastion.dev.yourdomain.com
# Port forwarding to RDS
ssh -L 5432:my-database.cluster-xxx.eu-central-1.rds.amazonaws.com:5432 \
bastion.dev.yourdomain.com
# Then connect locally
psql -h localhost -p 5432 -U myuser mydatabaseSet up AWS IAM Identity Center for human access to AWS accounts.
- Log into the root account
- Go to IAM Identity Center (formerly AWS SSO)
- Click Enable
- Note the Identity Store ID (e.g.,
d-1234567890) - Note the Instance ARN (e.g.,
arn:aws:sso:::instance/ssoins-1234567890)
By default, AWS does not allow IAM users or roles (including SSO roles) to access billing information. To enable billing access:
- While still logged into the root account as the root user
- Click your account name in the top-right corner of the console
- Select Account
- Scroll down to IAM user and role access to Billing information
- Click Edit and enable the setting
- Click Update
Edit aws_sso/aws_sso_access.yml and update the mappings:
Mappings:
ConfigMap:
OrgAccount:
AccountIdProd: '<YOUR_PROD_ACCOUNT_ID>'
AccountIdDev: '<YOUR_DEV_ACCOUNT_ID>'
AccountIdTest: '<YOUR_TEST_ACCOUNT_ID>'
SSOMap:
Instance:
IdentityStore: '<YOUR_IDENTITY_STORE_ID>'
IAMDirectory: '<YOUR_SSO_INSTANCE_ARN>'Trigger deployment:
- Push changes to
aws_sso/aws_sso_access.ymlon themasterbranch
What gets created:
- SSO Groups (Developers, Ops)
- Permission Sets (DeveloperAccess, AdministratorAccess)
- Account assignments linking groups to accounts
Edit aws_sso/aws_sso_users.yml to define users:
users:
- username: jane.doe
email: jane.doe@yourcompany.com
first_name: Jane
last_name: Doe
groups:
- Developers
- Ops
- username: john.smith
email: john.smith@yourcompany.com
first_name: John
last_name: Smith
groups:
- DevelopersPush changes to trigger the user sync workflow.
Users can configure their local AWS CLI:
# ~/.aws/config
[profile dev-developer]
sso_start_url = https://your-sso-portal.awsapps.com/start
sso_region = eu-central-1
sso_account_id = <DEV_ACCOUNT_ID>
sso_role_name = DeveloperAccess
region = eu-central-1
[profile prod-admin]
sso_start_url = https://your-sso-portal.awsapps.com/start
sso_region = eu-central-1
sso_account_id = <PROD_ACCOUNT_ID>
sso_role_name = AdministratorAccess
region = eu-central-1Login with:
aws sso login --profile dev-developerAfter completing the bootstrap, verify:
- GitHub Actions can assume roles in all accounts (dev, test, prod, root)
- VPC stack deployed in all environments
- Web stack deployed in all environments
- Route53 hosted zones created with correct domains
- ACM certificates validated and active
- ECS cluster visible in each account
- ALBs accessible (should return 503 with no services)
- (Optional) Bastion accessible via SSH
- (Optional) SSO users can log in and access accounts
- Verify the OIDC provider exists in the target account
- Check the trusted repository/org matches your GitHub org
- Ensure the workflow has
id-token: writepermission
- Check CloudFormation StackSet operations in root account
- Verify Organizations trusted access is enabled
- Check individual stack instances for errors
- Check for CIDR conflicts with existing VPCs
- Verify you have sufficient Elastic IP quota for NAT Gateways
- Check Route53 hosted zone doesn't already exist
- Verify Route53 hosted zone is authoritative for the domain
- Check DNS validation records were created
- If using external DNS, add the CNAME records manually
After bootstrap:
- Set up application repositories with deployment workflows
- Configure monitoring and alerting (CloudWatch, Datadog, etc.)
- Set up database infrastructure (RDS, ElastiCache)
- Configure secrets management (SSM Parameter Store, Secrets Manager)
- Implement backup and disaster recovery procedures