AWS Web Stacks is a library of CloudFormation templates for hosting web applications on AWS using EKS (Elastic Kubernetes Service). The templates create a fully-managed hosting environment with an EKS cluster, worker nodes, S3 buckets for static assets, an ECR repository for Docker images, and optional RDS and ElastiCache instances. All resources are created in a self-contained VPC.
The CloudFormation templates are written in troposphere.
Available templates:
| Standard Region | AWS GovCloud | |
|---|---|---|
| Without NAT Gateway | ![]() |
![]() |
| With NAT Gateway | ![]() |
![]() |
The templates are available with and without NAT gateways. If you select the version without NAT gateways, instances in private subnets will not have internet access. This is fine if your application only needs to communicate with AWS services (which are accessed via VPC endpoints) and doesn't need to reach external APIs or package repositories.
If your application needs outbound internet access from private subnets (e.g., to pull Docker images, install OS packages, or call external APIs), use the version with NAT gateways.
Stack creation typically takes 30-35 minutes. The slowest resources are:
- CloudFront distribution (~10 min): Propagates to edge locations
- RDS instance (~10 min): Creates and initializes the database
- EKS cluster (~10 min): Provisions control plane and nodegroup
The templates automatically create an ACM certificate for your domain in us-east-1
(required for CloudFront). You'll need to approve the certificate validation via email
or DNS records, depending on your domain provider.
Note: .io domains have known issues with ACM email validation. If you use a
.io domain, you may need to create the certificate manually and pass the ARN via
the AssetsCloudFrontCertArn parameter.
The templates support three encryption modes for EBS volumes, RDS, and ElastiCache:
- AES-256 (default): AWS-managed keys, no additional cost
- Customer-managed CMK: You provide a KMS key ARN via
CustomerManagedCmkArn - No encryption: Set
UseAES256Encryptiontofalse
For EBS encryption on EKS nodes, you may also need to set up a key policy allowing the EC2 service to use the key. See the AWS EBS encryption docs for details.
Each stack creates:
- VPC: Public and private subnets across two availability zones
- EKS Cluster: Managed Kubernetes control plane
- Nodegroup: Auto-scaling group of worker nodes
- ECR Repository: Docker image registry with scan-on-push
- S3 Buckets: Public assets bucket (with optional CloudFront) and private assets bucket
- RDS Instance (optional): MySQL or PostgreSQL with optional read replica
- ElastiCache (optional): Memcached or Redis (with optional cluster mode)
- CloudFront (optional): CDN for static assets with custom domain support
To access your EKS cluster with kubectl:
aws eks update-kubeconfig --name <cluster-name> --alias <alias>
kubectl get nodes --context <alias>For Pod Identity-enabled clusters, the EBS CSI driver uses pod identity for
authentication. After enabling EksUseAccessConfig=true, you may need to
restart the EBS CSI controller:
kubectl rollout restart deployment ebs-csi-driver -n kube-systemThe v3 templates support AWS Pod Identity for fine-grained IAM permissions to pods. This requires:
EksUseAccessConfig=true: EnablesAccessConfigwithauthenticationMode: APIeks-pod-identity-agentadd-on: Installed automatically- IAM roles with pod identity trust policies: Created for EBS CSI driver
See "Upgrading from v2" below for full migration instructions.
Install dependencies (requires uv):
uv sync --locked
Build all templates:
make templates
This generates four YAML templates in the content/ directory.
Environment variables control which templates are generated:
USE_EKS=on: Build EKS templates (required)USE_GOVCLOUD=on: Build GovCloud-compatible templatesUSE_NAT_GATEWAY=on: Include NAT gateway in private subnets
Examples:
# Standard region, no NAT
USE_EKS=on python -c 'import stack' > content/eks-no-nat.yaml
# Standard region, with NAT
USE_EKS=on USE_NAT_GATEWAY=on python -c 'import stack' > content/eks-nat.yaml
# GovCloud, with NAT
USE_EKS=on USE_GOVCLOUD=on USE_NAT_GATEWAY=on python -c 'import stack' > content/gc-nat.yamlThe v3 templates are EKS-only. Several parameters from v2 have been removed and one new required parameter has been added. When updating an existing stack, you must adjust the parameters accordingly.
Parameters to remove (no longer supported):
AdministratorIPAddressBastionAMIBastionInstanceTypeBastionKeyNameBastionTypeElasticsearchInstanceTypeElasticsearchVersionElasticsearchVolumeSizeUseSFTPServer
New required parameter:
EksUseAccessConfig: Controls whether the EKS cluster uses the modernAccessConfig(authenticationMode: API) with Pod Identity support.Set to
falseinitially when migrating an existing cluster. This prevents CloudFormation from trying to replace the cluster (which fails if the cluster has a custom name).After the stack update succeeds, manually enable API authentication:
- aws eks update-cluster-config
--name <cluster-name> --access-config authenticationMode=API
Then update the stack again with
EksUseAccessConfig=trueto let CloudFormation manage the AccessConfig, Pod Identity agent, and EBS CSI driver.For new stacks, set to
truefrom the start.
Notes:
AssetsBucketAccessControlshould be set toPrivatewhen migrating existing stacks to avoid S3 ACL errors. New stacks can omit this (defaults to no ACL).After the initial update with
EksUseAccessConfig=false, enable API auth via CLI, then update again withEksUseAccessConfig=true.The EBS CSI controller may need a restart after enabling Pod Identity:
kubectl rollout restart deployment ebs-csi-driver -n kube-system
These templates provision VPCs with dual-stack networking enabled, meaning each subnet can use both IPv4 and IPv6 addresses:
IPv6 support to the VPC is provided via an Amazon-provided IPv6 CIDR block added by setting
AWS::EC2::VPCCidrBlock(AmazonProvidedIpv6CidrBlock: true).NOTE:
AWS::EC2::VPCCidrBlockis used rather than trying to set IPv6 properties on the VPC resource directly, since CloudFormation does not support IPv6 configuration onAWS::EC2::VPCas neitherAssignGeneratedIPv6CidrBlocknorIPv6CidrBlockOptionsare valid.Each subnet receives a /64 IPv6 prefix via
!Select [N, !Cidr [!Select [0, !GetAtt Vpc.Ipv6CidrBlocks], 4, 64]](the 3rdFn::Cidrarg is "cidrBits": 128 - 64 = 64) withDependsOnon the VPCCidrBlock.
IPv6 routing follows these conventions:
Public subnets route all IPv6 traffic (
::/0) through the Internet Gateway (IGW).Private subnets route all IPv6 traffic (
::/0) through the Egress-Only Internet Gateway. The NAT gateway is only used for NAT64 traffic (64:ff9b::/96), enabling IPv6-only workloads to reach IPv4 endpoints. Services like RDS and ElastiCache remain IPv4-only.NOTE: EC2 does not allow a NAT gateway as the next hop for
::/0. Private subnets must use the Egress-Only IGW for general outbound IPv6 traffic.
The EKS cluster's IP family (IPv4 or IPv6) is set at creation time and is
immutable. This means an existing IPv4 cluster cannot be modified to support IPv6.
If you need an IPv6 pod network, you would need to recreate the AWS::EKS::Cluster
resource.
If IPv6 support on the cluster is not required, updating an existing IPv4-only stack (v2) to a dual-stack template (v3) is an in-place update:
- VPC/subnets get Modify (IPv6 CIDR added, no replacement)
- New resources such as VPCCidrBlock, egress-only IGW, v6 routes) are Add
Additionally, dual-stack ingress is possible without cluster changes: the k8s-web-cluster Ansible role can update the Traefik ingress controller to provision a dual-stack Network Load Balancer, routing both IPv4 and IPv6 traffic to the cluster's IPv4 pods.
These changes should be applied after the CloudFormation stack update:
- Set Ansible variable
k8s_traefik_dualstack: true. - Redeploy the Traefik Helm chart via the k8s-web-cluster Ansible role.
- Traefik updates the AWS Network Load Balancer (NLB) to dual-stack.
See :doc:`CONTRIBUTING` for development setup, testing, and release process.
Copyright 2017, 2018, 2026 Jean-Phillipe Serafin, Caktus Consulting Group, LLC
