This comprehensive project will give you hands-on experience with all the AWS services mentioned in the job posting. Here's why this project is perfect for demonstrating your qualifications:
Key Benefits:
. Real-world scenario: E-commerce platform mirrors actual business requirements
. Production-ready: Includes security, monitoring, and compliance features
. Scalable architecture: Demonstrates understanding of enterprise-level design
. Full CI/CD pipeline: Shows DevOps integration skills
. Security-first approach: Meets compliance requirements that employers value
What Makes This Stand Out:
- Multi-tier architecture showing deep VPC understanding
- Container orchestration with ECS Fargate (modern approach)
- Blue/green deployments demonstrating zero-downtime strategies
- Comprehensive security including WAF, encryption, and monitoring
- Cost optimization through proper resource sizing and lifecycle policies
AWS Portfolio Project - Step-by-Step Implementation Guide
Pre-requisites Setup (Day 0)
- AWS Account Setup bash# Create AWS Account (if you don't have one) # Sign up at: https://aws.amazon.com/ # Enable billing alerts in CloudWatch # Set up MFA for root account
Install AWS CLI
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
Configure AWS CLI
aws configure
Enter your Access Key ID, Secret Access Key, Region (us-east-1), and output format (json)
- Development Environment bash# Install required tools # Node.js (for sample application) curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt-get install -y nodejs
Docker (for containerization)
sudo apt-get update
sudo apt-get install docker.io
sudo usermod -aG docker $USER
Git (for version control)
sudo apt-get install git
VS Code or your preferred editor
wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg
sudo install -o root -g root -m 644 packages.microsoft.gpg /etc/apt/trusted.gpg.d/
sudo sh -c 'echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/trusted.gpg.d/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list'
sudo apt update
sudo apt install code
- Project Structure Setup bash# Create project directory mkdir aws-ecommerce-infrastructure cd aws-ecommerce-infrastructure
Create directory structure
mkdir -p {
cloudformation/{network,security,compute,storage,cicd},
application/{frontend,backend},
scripts,
documentation,
monitoring
}
Initialize Git repository
git init
echo "# AWS E-commerce Infrastructure Project" > README.md
git add README.md
git commit -m "Initial commit"
WEEK 1: Network Foundation & Security
Day 1: VPC and Networking Setup
Step 1: Create Base VPC CloudFormation Template
bash# Create the main network template
touch cloudformation/network/vpc-base.yaml
File: cloudformation/network/vpc-base.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'E-commerce VPC with public and private subnets'
Parameters:
ProjectName:
Type: String
Default: ecommerce
Description: Name of the project
Environment:
Type: String
Default: production
AllowedValues: [development, staging, production]
Resources:
# VPC
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-vpc'
- Key: Environment
Value: !Ref Environment
# Internet Gateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-igw'
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
# Public Subnets
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [0, !GetAZs '']
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-public-subnet-1'
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [1, !GetAZs '']
CidrBlock: 10.0.2.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-public-subnet-2'
# Private Subnets
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [0, !GetAZs '']
CidrBlock: 10.0.10.0/24
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-private-subnet-1'
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [1, !GetAZs '']
CidrBlock: 10.0.20.0/24
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-private-subnet-2'
# Database Subnets
DBSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [0, !GetAZs '']
CidrBlock: 10.0.30.0/24
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-db-subnet-1'
DBSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [1, !GetAZs '']
CidrBlock: 10.0.40.0/24
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-db-subnet-2'
# NAT Gateways
NatGateway1EIP:
Type: AWS::EC2::EIP
DependsOn: InternetGatewayAttachment
Properties:
Domain: vpc
NatGateway2EIP:
Type: AWS::EC2::EIP
DependsOn: InternetGatewayAttachment
Properties:
Domain: vpc
NatGateway1:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGateway1EIP.AllocationId
SubnetId: !Ref PublicSubnet1
NatGateway2:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGateway2EIP.AllocationId
SubnetId: !Ref PublicSubnet2
# Route Tables
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-public-routes'
DefaultPublicRoute:
Type: AWS::EC2::Route
DependsOn: InternetGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet1
PublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet2
PrivateRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-private-routes-1'
DefaultPrivateRoute1:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable1
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway1
PrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable1
SubnetId: !Ref PrivateSubnet1
PrivateRouteTable2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-private-routes-2'
DefaultPrivateRoute2:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable2
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway2
PrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable2
SubnetId: !Ref PrivateSubnet2
Outputs:
VPC:
Description: A reference to the created VPC
Value: !Ref VPC
Export:
Name: !Sub '${ProjectName}-${Environment}-VPC'
PublicSubnets:
Description: A list of the public subnets
Value: !Join [",", [!Ref PublicSubnet1, !Ref PublicSubnet2]]
Export:
Name: !Sub '${ProjectName}-${Environment}-PublicSubnets'
PrivateSubnets:
Description: A list of the private subnets
Value: !Join [",", [!Ref PrivateSubnet1, !Ref PrivateSubnet2]]
Export:
Name: !Sub '${ProjectName}-${Environment}-PrivateSubnets'
DBSubnets:
Description: A list of the database subnets
Value: !Join [",", [!Ref DBSubnet1, !Ref DBSubnet2]]
Export:
Name: !Sub '${ProjectName}-${Environment}-DBSubnets'
Step 2: Deploy the VPC
`# Deploy the VPC stack
aws cloudformation create-stack \
--stack-name ecommerce-vpc \
--template-body file://cloudformation/network/vpc-base.yaml \
--parameters ParameterKey=ProjectName,ParameterValue=ecommerce \
ParameterKey=Environment,ParameterValue=production
Wait for stack completion
aws cloudformation wait stack-create-complete --stack-name ecommerce-vpc
Verify the stack
aws cloudformation describe-stacks --stack-name ecommerce-vpc`
Security Groups Configuration
Step 1: Create Security Groups Template
bashtouch cloudformation/security/security-groups.yaml
File: cloudformation/security/security-groups.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Security Groups for E-commerce Application'
Parameters:
ProjectName:
Type: String
Default: ecommerce
Environment:
Type: String
Default: production
Resources:
# ALB Security Group
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub '${ProjectName}-${Environment}-alb-sg'
GroupDescription: Security group for Application Load Balancer
VpcId:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-VPC'
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Description: 'HTTP from anywhere'
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: 'HTTPS from anywhere'
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-alb-sg'
# ECS Security Group
ECSSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub '${ProjectName}-${Environment}-ecs-sg'
GroupDescription: Security group for ECS tasks
VpcId:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-VPC'
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3000
ToPort: 3000
SourceSecurityGroupId: !Ref ALBSecurityGroup
Description: 'HTTP from ALB'
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-ecs-sg'
# RDS Security Group
RDSSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub '${ProjectName}-${Environment}-rds-sg'
GroupDescription: Security group for RDS database
VpcId:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-VPC'
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref ECSSecurityGroup
Description: 'PostgreSQL from ECS'
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-rds-sg'
# Bastion Host Security Group (for debugging)
BastionSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub '${ProjectName}-${Environment}-bastion-sg'
GroupDescription: Security group for bastion host
VpcId:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-VPC'
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0 # Restrict this to your IP in production
Description: 'SSH access'
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-bastion-sg'
Outputs:
ALBSecurityGroup:
Description: Security group for ALB
Value: !Ref ALBSecurityGroup
Export:
Name: !Sub '${ProjectName}-${Environment}-ALB-SG'
ECSSecurityGroup:
Description: Security group for ECS
Value: !Ref ECSSecurityGroup
Export:
Name: !Sub '${ProjectName}-${Environment}-ECS-SG'
RDSSecurityGroup:
Description: Security group for RDS
Value: !Ref RDSSecurityGroup
Export:
Name: !Sub '${ProjectName}-${Environment}-RDS-SG'
Step 2: Deploy Security Groups
`# Deploy security groups
aws cloudformation create-stack \
--stack-name ecommerce-security-groups \
--template-body file://cloudformation/security/security-groups.yaml \
--parameters ParameterKey=ProjectName,ParameterValue=ecommerce \
ParameterKey=Environment,ParameterValue=production
Wait for completion
aws cloudformation wait stack-create-complete --stack-name ecommerce-security-groups`
IAM Roles and Policies
Step 1: Create IAM Template
touch cloudformation/security/iam-roles.yaml
File: cloudformation/security/iam-roles.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'IAM Roles and Policies for E-commerce Application'
Resources:
# ECS Task Execution Role
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: ecommerce-ecs-task-execution-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
Policies:
- PolicyName: SecretsManagerAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource: !Ref DBPasswordSecret
# ECS Task Role
ECSTaskRole:
Type: AWS::IAM::Role
Properties:
RoleName: ecommerce-ecs-task-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: S3Access
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:DeleteObject
Resource:
- arn:aws:s3:::ecommerce-product-images/*
- arn:aws:s3:::ecommerce-user-uploads/*
- PolicyName: CloudWatchLogs
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
# CodePipeline Service Role
CodePipelineServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: ecommerce-codepipeline-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: CodePipelinePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:GetBucketVersioning
- s3:GetObject
- s3:GetObjectVersion
- s3:PutObject
Resource: '*'
- Effect: Allow
Action:
- codebuild:BatchGetBuilds
- codebuild:StartBuild
Resource: '*'
- Effect: Allow
Action:
- codedeploy:CreateDeployment
- codedeploy:GetApplication
- codedeploy:GetApplicationRevision
- codedeploy:GetDeployment
- codedeploy:GetDeploymentConfig
- codedeploy:RegisterApplicationRevision
Resource: '*'
# Database Password Secret
DBPasswordSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: ecommerce-db-password
Description: Password for RDS PostgreSQL database
GenerateSecretString:
SecretStringTemplate: '{"username": "postgres"}'
GenerateStringKey: 'password'
PasswordLength: 32
ExcludeCharacters: '"@/\'
Outputs:
ECSTaskExecutionRoleArn:
Description: ARN of the ECS Task Execution Role
Value: !GetAtt ECSTaskExecutionRole.Arn
Export:
Name: ecommerce-production-ECS-TaskExecutionRole-Arn
ECSTaskRoleArn:
Description: ARN of the ECS Task Role
Value: !GetAtt ECSTaskRole.Arn
Export:
Name: ecommerce-production-ECS-TaskRole-Arn
DBPasswordSecretArn:
Description: ARN of the database password secret
Value: !Ref DBPasswordSecret
Export:
Name: ecommerce-production-DB-PasswordSecret-Arn
Step 2: Deploy IAM Stack
`# Deploy IAM roles
aws cloudformation create-stack \
--stack-name ecommerce-iam-roles \
--template-body file://cloudformation/security/iam-roles.yaml \
--capabilities CAPABILITY_NAMED_IAM
Wait for completion
aws cloudformation wait stack-create-complete --stack-name ecommerce-iam-roles`
Create Sample Application
Step 1: Create Backend Application
`mkdir -p application/backend
cd application/backend
Initialize Node.js project
npm init -y
Install dependencies
npm install express cors helmet morgan dotenv pg
npm install -D nodemon
Create package.json scripts`
File: application/backend/package.json
{
"name": "ecommerce-api",
"version": "1.0.0",
"description": "E-commerce API backend",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"helmet": "^7.0.0",
"morgan": "^1.10.0",
"dotenv": "^16.3.1",
"pg": "^8.11.3"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
File: application/backend/server.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
// Health check endpoint
app.get('/health', (req, res) => {
res.status(200).json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// API routes
app.get('/api/products', (req, res) => {
res.json({
products: [
{ id: 1, name: 'Laptop', price: 999.99, category: 'Electronics' },
{ id: 2, name: 'Smartphone', price: 699.99, category: 'Electronics' },
{ id: 3, name: 'Headphones', price: 199.99, category: 'Audio' }
]
});
});
app.get('/api/products/:id', (req, res) => {
const productId = parseInt(req.params.id);
const product = {
id: productId,
name: 'Sample Product',
price: 99.99,
description: 'This is a sample product',
category: 'Sample Category'
};
res.json(product);
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({ error: 'Route not found' });
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
});
Step 2: Create Dockerfile
File: application/backend/Dockerfile
FROM node:18-alpine
# Create app directory
WORKDIR /usr/src/app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source code
COPY . .
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
# Change ownership to nodejs user
RUN chown -R nodejs:nodejs /usr/src/app
USER nodejs
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
CMD ["npm", "start"]
File: application/backend/healthcheck.js
const http = require('http');
const options = {
hostname: 'localhost',
port: 3000,
path: '/health',
method: 'GET'
};
const req = http.request(options, (res) => {
if (res.statusCode === 200) {
process.exit(0);
} else {
process.exit(1);
}
});
req.on('error', () => {
process.exit(1);
});
req.end();
Step 3: Test Locally
# Test the application locally
cd application/backend
npm install
npm run dev
# In another terminal, test the API
curl http://localhost:3000/health
curl http://localhost:3000/api/products
# Build and test Docker image
docker build -t ecommerce-api .
docker run -p 3000:3000 ecommerce-api
Here is the out come of the build and test docker image.
ECR Setup and Container Push
Step 1: Create ECR Repository
`# Create ECR repository
aws ecr create-repository \
--repository-name ecommerce-api \
--region us-east-1
Get login token. We will edit the command by copy and past our AWS account ID
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin .dkr.ecr.us-east-1.amazonaws.com`
Step 2: Build and Push Image
`cd application/backend
Build image
docker build -t ecommerce-api .
We have to edit the command by copy and past our AWS account ID
docker tag ecommerce-api:latest .dkr.ecr.us-east-1.amazonaws.com/ecommerce-api:latest
Push image
docker push .dkr.ecr.us-east-1.amazonaws.com/ecommerce-api:latest`
Step 3: Create Build Script
File: scripts/build-and-push.sh
`#!/bin/bash
Build and push script
set -e
Variables
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
AWS_REGION=us-east-1
IMAGE_NAME=ecommerce-api
IMAGE_TAG=${1:-latest}
Login to ECR
aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
Build image
echo "Building Docker image..."
docker build -t $IMAGE_NAME:$IMAGE_TAG application/backend/
Tag image
docker tag $IMAGE_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$IMAGE_TAG
Push image
echo "Pushing to ECR..."
docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$IMAGE_TAG
echo "Build and push completed successfully!"`
`# Make script executable
chmod +x scripts/build-and-push.sh
Run the script
./scripts/build-and-push.sh`
What You've Accomplished:
โ
VPC Infrastructure: Multi-tier network with proper subnet isolation
โ
Security Groups: Layered security with least privilege access
โ
IAM Roles: Secure role-based access control
โ
Sample Application: Containerized Node.js API
โ
ECR Repository: Container registry with pushed image
Verification Commands:
`# Check all your stacks
aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE
Verify VPC resources
aws ec2 describe-vpcs --filters "Name=tag:Name,Values=ecommerce-production-vpc"
aws ec2 describe-subnets --filters "Name=vpc-id,Values="
Check ECR image
aws ecr describe-images --repository-name ecommerce-api`
File: documentation/week1-progress.md
`#Check our Progress Report
Infrastructure Completed
- [x] VPC with 6 subnets across 2 AZs
- [x] NAT Gateways for high availability
- [x] Security groups with least privilege access
- [x] IAM roles for ECS and CI/CD
- [x] Containerized Node.js application
- [x] ECR repository with pushed image
Architecture Decisions
- Multi-AZ Design: Ensures high availability
- Private Subnets: Applications run in private subnets for security
- Defense in Depth: Multiple security layers (NACLs, Security Groups, IAM)
- Least Privilege: IAM policies grant minimal required permissions ` Application Infrastructure & Database Day 6: RDS Database Setup Step 1: Create RDS Template
touch cloudformation/storage/rds-database.yaml
File: cloudformation/storage/rds-database.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'RDS PostgreSQL Database for E-commerce Application'
Parameters:
ProjectName:
Type: String
Default: ecommerce
Environment:
Type: String
Default: production
DBInstanceClass:
Type: String
Default: db.t3.micro
Description: RDS instance class
AllowedValues:
- db.t3.micro
- db.t3.small
- db.t3.medium
- db.t3.large
DBPassword:
Type: String
NoEcho: true
Description: Master password for RDS instance (8-128 characters)
MinLength: 8
MaxLength: 128
AllowedPattern: ^[a-zA-Z0-9!@#$%^&*()_+=-]*$
ConstraintDescription: Must contain 8-128 alphanumeric characters
Resources:
# DB Subnet Group
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupName: !Sub '${ProjectName}-${Environment}-db-subnet-group'
DBSubnetGroupDescription: Subnet group for RDS database
SubnetIds: !Split
- ','
- Fn::ImportValue: !Sub '${ProjectName}-${Environment}-DBSubnets'
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-db-subnet-group'
# RDS Instance
RDSInstance:
Type: AWS::RDS::DBInstance
DeletionPolicy: Snapshot
Properties:
DBInstanceIdentifier: !Sub '${ProjectName}-${Environment}-postgres'
DBInstanceClass: !Ref DBInstanceClass
Engine: postgres
# EngineVersion: '15.4' # Comment out to use default version
AllocatedStorage: 20
StorageType: gp2
StorageEncrypted: true
DBName: ecommerce
MasterUsername: postgres
MasterUserPassword: !Ref DBPassword # Using parameter instead of Secrets Manager
VPCSecurityGroups:
- Fn::ImportValue: !Sub '${ProjectName}-${Environment}-RDS-SG'
DBSubnetGroupName: !Ref DBSubnetGroup
BackupRetentionPeriod: 7
PreferredBackupWindow: "03:00-04:00"
PreferredMaintenanceWindow: "sun:04:00-sun:05:00"
MultiAZ: false # Set to true for production
PubliclyAccessible: false
EnablePerformanceInsights: true
PerformanceInsightsRetentionPeriod: 7
DeletionProtection: false # Set to true for production
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-postgres'
- Key: Environment
Value: !Ref Environment
Outputs:
RDSEndpoint:
Description: RDS instance endpoint
Value: !GetAtt RDSInstance.Endpoint.Address
Export:
Name: !Sub '${ProjectName}-${Environment}-RDS-Endpoint'
RDSPort:
Description: RDS instance port
Value: !GetAtt RDSInstance.Endpoint.Port
Export:
Name: !Sub '${ProjectName}-${Environment}-RDS-Port'
RDSInstanceId:
Description: RDS instance identifier
Value: !Ref RDSInstance
Export:
Name: !Sub '${ProjectName}-${Environment}-RDS-InstanceId'
Step 2: Deploy RDS Stack
`# Deploy RDS database
aws cloudformation create-stack \
--stack-name ecommerce-rds \
--template-body file://cloudformation/storage/rds-database.yaml \
--parameters ParameterKey=DBPassword,ParameterValue=YourSecurePassword123!
This will take 10-15 minutes
aws cloudformation wait stack-create-complete --stack-name ecommerce-rds`
ECS Cluster Setup
Step 1: Create ECS Template
touch cloudformation/compute/ecs-cluster.yaml
File: cloudformation/compute/ecs-cluster.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'ECS Cluster and Service for E-commerce API'
Parameters:
ProjectName:
Type: String
Default: ecommerce
Environment:
Type: String
Default: production
ImageURI:
Type: String
Description: ECR image URI
DBEndpoint:
Type: String
Default: ''
Description: RDS endpoint (leave empty to auto-import)
Conditions:
HasDBEndpoint: !Not [!Equals [!Ref DBEndpoint, '']]
Resources:
# ECS Cluster
ECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Sub '${ProjectName}-${Environment}-cluster'
ClusterSettings:
- Name: containerInsights
Value: enabled
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-cluster'
# CloudWatch Log Group
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/ecs/${ProjectName}-${Environment}-api'
RetentionInDays: 7
# ECS Task Definition
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Sub '${ProjectName}-${Environment}-api'
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
Cpu: '256'
Memory: '512'
ExecutionRoleArn:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ECS-TaskExecutionRole-Arn'
TaskRoleArn:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ECS-TaskRole-Arn'
ContainerDefinitions:
- Name: !Sub '${ProjectName}-api'
Image: !Ref ImageURI
PortMappings:
- ContainerPort: 3000
Protocol: tcp
Environment:
- Name: NODE_ENV
Value: production
- Name: PORT
Value: '3000'
- Name: DB_HOST
Value: !If
- HasDBEndpoint
- !Ref DBEndpoint
- Fn::ImportValue: !Sub '${ProjectName}-${Environment}-RDS-Endpoint'
- Name: DB_PORT
Value: '5432'
- Name: DB_NAME
Value: ecommerce
- Name: DB_USER
Value: postgres
Secrets:
- Name: DB_PASSWORD
ValueFrom:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-DB-PasswordSecret-Arn'
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref LogGroup
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: ecs
# Simplified health check - using TCP instead of HTTP
# Remove if your container doesn't support health checks
Essential: true
# Application Load Balancer
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub '${ProjectName}-${Environment}-alb'
Scheme: internet-facing
Type: application
SecurityGroups:
- Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ALB-SG'
Subnets: !Split
- ','
- Fn::ImportValue: !Sub '${ProjectName}-${Environment}-PublicSubnets'
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-alb'
# Target Group
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub '${ProjectName}-${Environment}-tg'
Port: 3000
Protocol: HTTP
VpcId:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-VPC'
TargetType: ip
HealthCheckIntervalSeconds: 30
HealthCheckPath: /health
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
UnhealthyThresholdCount: 3
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: '30'
# ALB Listener - Fixed syntax
ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
ForwardConfig:
TargetGroups:
- TargetGroupArn: !Ref TargetGroup
Weight: 100
LoadBalancerArn: !Ref ApplicationLoadBalancer
Port: 80
Protocol: HTTP
# ECS Service
ECSService:
Type: AWS::ECS::Service
DependsOn: ALBListener
Properties:
ServiceName: !Sub '${ProjectName}-${Environment}-api-service'
Cluster: !Ref ECSCluster
TaskDefinition: !Ref TaskDefinition
LaunchType: FARGATE
DesiredCount: 2
NetworkConfiguration:
AwsvpcConfiguration:
SecurityGroups:
- Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ECS-SG'
Subnets: !Split
- ','
- Fn::ImportValue: !Sub '${ProjectName}-${Environment}-PrivateSubnets'
AssignPublicIp: DISABLED
LoadBalancers:
- ContainerName: !Sub '${ProjectName}-api'
ContainerPort: 3000
TargetGroupArn: !Ref TargetGroup
HealthCheckGracePeriodSeconds: 120
DeploymentConfiguration:
MaximumPercent: 200
MinimumHealthyPercent: 50
DeploymentCircuitBreaker:
Enable: true
Rollback: true
Outputs:
ClusterName:
Description: ECS Cluster Name
Value: !Ref ECSCluster
Export:
Name: !Sub '${ProjectName}-${Environment}-ECS-Cluster'
ServiceName:
Description: ECS Service Name
Value: !Ref ECSService
Export:
Name: !Sub '${ProjectName}-${Environment}-ECS-Service'
LoadBalancerDNS:
Description: Load Balancer DNS Name
Value: !GetAtt ApplicationLoadBalancer.DNSName
Export:
Name: !Sub '${ProjectName}-${Environment}-ALB-DNS'
LoadBalancerArn:
Description: Load Balancer ARN
Value: !Ref ApplicationLoadBalancer
Export:
Name: !Sub '${ProjectName}-${Environment}-ALB-Arn'
LoadBalancerURL:
Description: Load Balancer URL
Value: !Sub 'http://${ApplicationLoadBalancer.DNSName}'
Export:
Name: !Sub '${ProjectName}-${Environment}-ALB-URL'
Before Deploying, Validate:
aws cloudformation validate-template --template-body file://cloudformation/compute/ecs-cluster.yaml
# Check if these exports exist from your other stacks:
ecommerce-production
aws cloudformation list-exports --query 'Exports[?contains(Name,)].Name'
`# Get your AWS account ID
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
Set image URI
IMAGE_URI="$AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/ecommerce-api:latest"
Deploy ECS stack
aws cloudformation create-stack \
--stack-name ecommerce-ecs \
--template-body file://cloudformation/compute/ecs-cluster.yaml \
--parameters ParameterKey=ProjectName,ParameterValue=ecommerce \
ParameterKey=Environment,ParameterValue=production \
ParameterKey=ImageURI,ParameterValue=$IMAGE_URI
This will take 10-15 minutes
aws cloudformation wait stack-create-complete --stack-name ecommerce-ecs`
Day 8: S3 Buckets Setup
Step 1: Create S3 Template
touch cloudformation/storage/s3-buckets.yaml
File: cloudformation/storage/s3-buckets.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'S3 Buckets for E-commerce Application'
Parameters:
ProjectName:
Type: String
Default: ecommerce
Environment:
Type: String
Default: production
AllowedOrigins:
Type: String
Default: 'https://example.com,https://www.example.com'
Description: Allowed origins for CORS (comma-separated)
Resources:
# KMS Key for S3 Encryption
S3KMSKey:
Type: AWS::KMS::Key
Properties:
Description: KMS Key for S3 bucket encryption
KeyPolicy:
Version: '2012-10-17'
Statement:
- Sid: Enable IAM User Permissions
Effect: Allow
Principal:
AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
Action: 'kms:*'
Resource: '*'
- Sid: Allow S3 Service
Effect: Allow
Principal:
Service: s3.amazonaws.com
Action:
- kms:Decrypt
- kms:GenerateDataKey
Resource: '*'
S3KMSKeyAlias:
Type: AWS::KMS::Alias
Properties:
AliasName: !Sub 'alias/${ProjectName}-${Environment}-s3-key'
TargetKeyId: !Ref S3KMSKey
# CloudWatch Log Group for S3 (moved up for dependency order)
S3LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/s3/${ProjectName}-${Environment}'
RetentionInDays: 14
# Frontend Assets Bucket
FrontendBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub '${ProjectName}-${Environment}-frontend-${AWS::AccountId}'
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
VersioningConfiguration:
Status: Enabled
LifecycleConfiguration:
Rules:
- Id: DeleteOldVersions
Status: Enabled
NoncurrentVersionExpirationInDays: 30
- Id: DeleteIncompleteMultipartUploads
Status: Enabled
AbortIncompleteMultipartUpload:
DaysAfterInitiation: 7
# Removed invalid NotificationConfiguration
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-frontend'
- Key: Purpose
Value: 'Frontend static assets'
# Product Images Bucket
ProductImagesBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub '${ProjectName}-${Environment}-product-images-${AWS::AccountId}'
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
KMSMasterKeyID: !Ref S3KMSKey
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
VersioningConfiguration:
Status: Enabled
CorsConfiguration:
CorsRules:
- AllowedHeaders: ['*']
AllowedMethods: [GET, PUT, POST, DELETE]
AllowedOrigins: !Ref AllowedOrigins
MaxAge: 3600
LifecycleConfiguration:
Rules:
- Id: TransitionToIA
Status: Enabled
Transitions:
- Days: 30
StorageClass: STANDARD_IA
- Id: TransitionToGlacier
Status: Enabled
Transitions:
- Days: 90
StorageClass: GLACIER
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-product-images'
# User Uploads Bucket
UserUploadsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub '${ProjectName}-${Environment}-user-uploads-${AWS::AccountId}'
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
KMSMasterKeyID: !Ref S3KMSKey
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
VersioningConfiguration:
Status: Enabled
CorsConfiguration:
CorsRules:
- AllowedHeaders: ['*']
AllowedMethods: [PUT, POST]
AllowedOrigins: !Ref AllowedOrigins
MaxAge: 3600
LifecycleConfiguration:
Rules:
- Id: DeleteOldVersions
Status: Enabled
NoncurrentVersionExpirationInDays: 7
- Id: DeleteOldUploads
Status: Enabled
ExpirationInDays: 365
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-user-uploads'
# CloudFront Origin Access Control (replaces deprecated OAI)
CloudFrontOAC:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: !Sub '${ProjectName}-${Environment}-oac'
Description: !Sub 'OAC for ${ProjectName}-${Environment}'
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
# Bucket Policy for CloudFront OAC
FrontendBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref FrontendBucket
PolicyDocument:
Statement:
- Sid: AllowCloudFrontServicePrincipal
Effect: Allow
Principal:
Service: cloudfront.amazonaws.com
Action: 's3:GetObject'
Resource: !Sub '${FrontendBucket}/*'
Condition:
StringEquals:
'AWS:SourceArn': !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/*'
Outputs:
FrontendBucketName:
Description: Name of the frontend assets bucket
Value: !Ref FrontendBucket
Export:
Name: !Sub '${ProjectName}-${Environment}-Frontend-Bucket'
ProductImagesBucketName:
Description: Name of the product images bucket
Value: !Ref ProductImagesBucket
Export:
Name: !Sub '${ProjectName}-${Environment}-ProductImages-Bucket'
UserUploadsBucketName:
Description: Name of the user uploads bucket
Value: !Ref UserUploadsBucket
Export:
Name: !Sub '${ProjectName}-${Environment}-UserUploads-Bucket'
CloudFrontOAC:
Description: CloudFront Origin Access Control
Value: !Ref CloudFrontOAC
Export:
Name: !Sub '${ProjectName}-${Environment}-CloudFront-OAC'
FrontendBucketDomainName:
Description: Frontend bucket domain name
Value: !GetAtt FrontendBucket.DomainName
Export:
Name: !Sub '${ProjectName}-${Environment}-Frontend-Bucket-Domain'
S3KMSKeyId:
Description: KMS Key ID for S3 encryption
Value: !Ref S3KMSKey
Export:
Name: !Sub '${ProjectName}-${Environment}-S3-KMS-Key'
S3KMSKeyArn:
Description: KMS Key ARN for S3 encryption
Value: !GetAtt S3KMSKey.Arn
Export:
Name: !Sub '${ProjectName}-${Environment}-S3-KMS-Key-Arn'
Validation Commands:
`# Validate the template
aws cloudformation validate-template --template-body file://cloudformation/storage/s3-buckets.yaml
Deploy with custom origins
aws cloudformation create-stack \
--stack-name ecommerce-s3 \
--template-body file://cloudformation/storage/s3-buckets.yaml \
--parameters '[{"ParameterKey":"AllowedOrigins","ParameterValue":"https://yourdomain.com,https://www.yourdomain.com"}]'`{% endraw %}
Next steps to monitor the stack:
Check stack status:
{% raw %}aws cloudformation describe-stacks --stack-name ecommerce-s3
Monitor stack events (see what's happening):
aws cloudformation describe-stack-events --stack-name ecommerce-s3
Get stack resources (once complete):
aws cloudformation describe-stack-resources --stack-name ecommerce-s3
Get stack outputs (if any are defined):
aws cloudformation describe-stacks --stack-name ecommerce-s3 --query 'Stacks[0].Outputs'
loudFront Distribution
Step 1: Create CloudFront Template
touch cloudformation/storage/cloudfront-distribution.yaml
File: cloudformation/storage/cloudfront-distribution.yaml
# Or see all events in chronological order to understand the full timeline
aws cloudformation describe-stack-events --stack-name ecommerce-cloudfront --query 'reverse(sort_by(StackEvents, &Timestamp))[0:30].[Timestamp,LogicalResourceId,ResourceStatus,ResourceStatusReason]' --output table
AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudFront Distribution for E-commerce Application - Fully Fixed'
Parameters:
ProjectName:
Type: String
Default: ecommerce
Description: Project name prefix for resources
Environment:
Type: String
Default: production
AllowedValues: [production, staging, development]
Description: Deployment environment
Resources:
# S3 Bucket for Frontend (with proper ownership controls)
FrontendBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub '${ProjectName}-${Environment}-frontend-${AWS::AccountId}'
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: error.html
PublicAccessBlockConfiguration:
BlockPublicAcls: false
BlockPublicPolicy: false
IgnorePublicAcls: false
RestrictPublicBuckets: false
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerPreferred
CorsConfiguration:
CorsRules:
- AllowedHeaders: ['*']
AllowedMethods: [GET, HEAD]
AllowedOrigins: ['*']
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-frontend'
- Key: Environment
Value: !Ref Environment
# CloudFront Origin Access Control (OAC) with enhanced description
CloudFrontOAC:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: !Sub '${ProjectName}-${Environment}-oac'
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
Description: !Sub 'OAC for ${ProjectName}-${Environment} S3 bucket (Managed by CloudFormation)'
# Logging bucket with all required configurations
LoggingBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub '${ProjectName}-${Environment}-cf-logs-${AWS::AccountId}'
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: false # Required for CloudFront logging
IgnorePublicAcls: false # Required for CloudFront logging
BlockPublicPolicy: true
RestrictPublicBuckets: true
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerPreferred
LifecycleConfiguration:
Rules:
- Id: DeleteOldLogs
Status: Enabled
ExpirationInDays: 90
NoncurrentVersionExpirationInDays: 90
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-cf-logs'
- Key: Environment
Value: !Ref Environment
# Security Headers Policy with XSS protection
SecurityHeadersPolicy:
Type: AWS::CloudFront::ResponseHeadersPolicy
Properties:
ResponseHeadersPolicyConfig:
Name: !Sub '${ProjectName}-${Environment}-security-headers'
Comment: Security headers for e-commerce application
SecurityHeadersConfig:
StrictTransportSecurity:
AccessControlMaxAgeSec: 31536000
IncludeSubdomains: true
Override: true
ContentTypeOptions:
Override: true
FrameOptions:
FrameOption: DENY
Override: true
ReferrerPolicy:
ReferrerPolicy: strict-origin-when-cross-origin
Override: true
CustomHeadersConfig:
Items:
- Header: X-Custom-Header
Value: !Sub '${ProjectName}-${Environment}'
Override: false
# CloudFront Distribution with all fixes
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
DependsOn:
- SecurityHeadersPolicy
- CloudFrontOAC
- LoggingBucket
- FrontendBucket
Properties:
DistributionConfig:
Origins:
# S3 Origin
- DomainName: !GetAtt FrontendBucket.DomainName
Id: S3Origin
OriginAccessControlId: !Ref CloudFrontOAC
S3OriginConfig: {}
# ALB Origin with validation
- DomainName:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ALB-DNS'
Id: ALBOrigin
CustomOriginConfig:
HTTPPort: 80
HTTPSPort: 443
OriginProtocolPolicy: http-only
OriginSSLProtocols: [TLSv1.2]
OriginReadTimeout: 30
OriginKeepaliveTimeout: 5
DefaultCacheBehavior:
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # CachingOptimized
OriginRequestPolicyId: 88a5eaf4-2fd4-4709-b370-b4c650ea3fcf # CORS-S3Origin
ResponseHeadersPolicyId: !Ref SecurityHeadersPolicy
Compress: true
SmoothStreaming: false
AllowedMethods: [GET, HEAD, OPTIONS]
CachedMethods: [GET, HEAD, OPTIONS]
CacheBehaviors:
# API Path
- PathPattern: '/api/*'
TargetOriginId: ALBOrigin
ViewerProtocolPolicy: redirect-to-https
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
OriginRequestPolicyId: 216adef6-5c7f-47e4-b989-5492eafa07d3 # AllViewer
ResponseHeadersPolicyId: !Ref SecurityHeadersPolicy
AllowedMethods: [GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE]
CachedMethods: [GET, HEAD, OPTIONS]
# Static Assets Path
- PathPattern: '/static/*'
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # CachingOptimized
OriginRequestPolicyId: 88a5eaf4-2fd4-4709-b370-b4c650ea3fcf
ResponseHeadersPolicyId: !Ref SecurityHeadersPolicy
Compress: true
AllowedMethods: [GET, HEAD, OPTIONS]
CachedMethods: [GET, HEAD, OPTIONS]
Enabled: true
DefaultRootObject: index.html
HttpVersion: http2
IPV6Enabled: true
CustomErrorResponses:
- ErrorCode: 403
ResponseCode: 200
ResponsePagePath: /index.html
ErrorCachingMinTTL: 300
- ErrorCode: 404
ResponseCode: 200
ResponsePagePath: /index.html
ErrorCachingMinTTL: 300
PriceClass: PriceClass_100
ViewerCertificate:
CloudFrontDefaultCertificate: true
Logging:
Bucket: !GetAtt LoggingBucket.DomainName
IncludeCookies: false
Prefix: cloudfront-logs/
Comment: !Sub 'CloudFront distribution for ${ProjectName}-${Environment}'
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-cloudfront'
- Key: Environment
Value: !Ref Environment
# S3 Bucket Policy with proper dependencies
FrontendBucketPolicy:
Type: AWS::S3::BucketPolicy
DependsOn:
- CloudFrontDistribution
- FrontendBucket
Properties:
Bucket: !Ref FrontendBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: cloudfront.amazonaws.com
Action: s3:GetObject
Resource: !Sub '${FrontendBucket.Arn}/*'
Condition:
StringEquals:
'AWS:SourceArn': !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}'
# CloudWatch Log Group with retention policy
CloudFrontLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/cloudfront/${ProjectName}-${Environment}'
RetentionInDays: 30
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-cf-logs'
- Key: Environment
Value: !Ref Environment
# SNS Topic with proper naming
SNSAlarmTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: !Sub '${ProjectName}-${Environment}-cloudfront-alarms'
DisplayName: !Sub '${ProjectName}-${Environment} CloudFront Alerts'
Tags:
- Key: Name
Value: !Sub '${ProjectName}-${Environment}-cf-alerts'
- Key: Environment
Value: !Ref Environment
# Enhanced CloudWatch Alarms
HighErrorRateAlarm:
Type: AWS::CloudWatch::Alarm
DependsOn:
- CloudFrontDistribution
- SNSAlarmTopic
Properties:
AlarmName: !Sub '${ProjectName}-${Environment}-cloudfront-high-error-rate'
AlarmDescription: CloudFront high 4xx/5xx error rate (>5% for 15 minutes)
Namespace: AWS/CloudFront
MetricName: 4xxErrorRate
Dimensions:
- Name: DistributionId
Value: !Ref CloudFrontDistribution
- Name: Region
Value: Global
Statistic: Average
Period: 300
EvaluationPeriods: 3
Threshold: 5
ComparisonOperator: GreaterThanThreshold
TreatMissingData: notBreaching
AlarmActions:
- !Ref SNSAlarmTopic
OKActions:
- !Ref SNSAlarmTopic
HighOriginLatencyAlarm:
Type: AWS::CloudWatch::Alarm
DependsOn:
- CloudFrontDistribution
- SNSAlarmTopic
Properties:
AlarmName: !Sub '${ProjectName}-${Environment}-cloudfront-high-origin-latency'
AlarmDescription: CloudFront origin latency >3s for 15 minutes
Namespace: AWS/CloudFront
MetricName: OriginLatency
Dimensions:
- Name: DistributionId
Value: !Ref CloudFrontDistribution
- Name: Region
Value: Global
Statistic: Average
Period: 300
EvaluationPeriods: 3
Threshold: 3000
ComparisonOperator: GreaterThanThreshold
TreatMissingData: notBreaching
AlarmActions:
- !Ref SNSAlarmTopic
OKActions:
- !Ref SNSAlarmTopic
Outputs:
CloudFrontDistributionId:
Description: CloudFront Distribution ID
Value: !Ref CloudFrontDistribution
Export:
Name: !Sub '${ProjectName}-${Environment}-CloudFront-DistributionId'
CloudFrontDomainName:
Description: CloudFront Distribution Domain Name
Value: !GetAtt CloudFrontDistribution.DomainName
Export:
Name: !Sub '${ProjectName}-${Environment}-CloudFront-DomainName'
CloudFrontDistributionURL:
Description: CloudFront Distribution URL
Value: !Sub 'https://${CloudFrontDistribution.DomainName}'
Export:
Name: !Sub '${ProjectName}-${Environment}-CloudFront-URL'
FrontendBucketName:
Description: Frontend S3 Bucket Name
Value: !Ref FrontendBucket
Export:
Name: !Sub '${ProjectName}-${Environment}-Frontend-Bucket-Name'
FrontendWebsiteURL:
Description: Frontend Website URL
Value: !GetAtt FrontendBucket.WebsiteURL
Export:
Name: !Sub '${ProjectName}-${Environment}-Frontend-Website-URL'
LoggingBucketName:
Description: CloudFront Logging Bucket Name
Value: !Ref LoggingBucket
Export:
Name: !Sub '${ProjectName}-${Environment}-Logging-Bucket-Name'
SecurityHeadersPolicyId:
Description: Security Headers Policy ID
Value: !Ref SecurityHeadersPolicy
Export:
Name: !Sub '${ProjectName}-${Environment}-Security-Headers-Policy'
SNSAlarmTopicARN:
Description: SNS Alarm Topic ARN
Value: !Ref SNSAlarmTopic
Export:
Name: !Sub '${ProjectName}-${Environment}-SNS-Alarm-Topic'
Step 2: Deploy CloudFront
`# Deploy CloudFront distribution
aws cloudformation create-stack \
--stack-name ecommerce-cloudfront \
--template-body file://cloudformation/storage/cloudfront-distribution.yaml \
--parameters ParameterKey=ProjectName,ParameterValue=ecommerce \
ParameterKey=Environment,ParameterValue=production
This takes 15-20 minutes
aws cloudformation wait stack-create-complete --stack-name ecommerce-cloudfront`
Testing and Verification
Step 1: Create Test Scripts
mkdir -p scripts/testing
touch scripts/testing/test-infrastructure.sh
File: scripts/testing/test-infrastructure.sh
#!/bin/bash
set -e
echo "๐ Testing AWS E-commerce Infrastructure..."
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Test functions
test_vpc() {
echo -e "${YELLOW}Testing VPC...${NC}"
VPC_ID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=ecommerce-production-vpc" --query 'Vpcs[0].VpcId' --output text)
if [ "$VPC_ID" != "None" ] && [ "$VPC_ID" != "" ]; then
echo -e "${GREEN}โ VPC exists: $VPC_ID${NC}"
else
echo -e "${RED}โ VPC not found${NC}"
exit 1
fi
}
test_subnets() {
echo -e "${YELLOW}Testing Subnets...${NC}"
SUBNET_COUNT=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" --query 'length(Subnets)')
if [ "$SUBNET_COUNT" -ge 6 ]; then
echo -e "${GREEN}โ All subnets created: $SUBNET_COUNT${NC}"
else
echo -e "${RED}โ Missing subnets. Expected 6, found $SUBNET_COUNT${NC}"
exit 1
fi
}
test_rds() {
echo -e "${YELLOW}Testing RDS...${NC}"
RDS_STATUS=$(aws rds describe-db-instances --db-instance-identifier ecommerce-production-postgres --query 'DBInstances[0].DBInstanceStatus' --output text 2>/dev/null || echo "NOT_FOUND")
if [ "$RDS_STATUS" = "available" ]; then
echo -e "${GREEN}โ RDS instance is available${NC}"
else
echo -e "${RED}โ RDS instance not available. Status: $RDS_STATUS${NC}"
exit 1
fi
}
test_ecs() {
echo -e "${YELLOW}Testing ECS...${NC}"
CLUSTER_STATUS=$(aws ecs describe-clusters --clusters ecommerce-production-cluster --query 'clusters[0].status' --output text 2>/dev/null || echo "NOT_FOUND")
if [ "$CLUSTER_STATUS" = "ACTIVE" ]; then
echo -e "${GREEN}โ ECS cluster is active${NC}"
# Test ECS service
SERVICE_STATUS=$(aws ecs describe-services --cluster ecommerce-production-cluster --services ecommerce-production-api-service --query 'services[0].status' --output text 2>/dev/null || echo "NOT_FOUND")
if [ "$SERVICE_STATUS" = "ACTIVE" ]; then
echo -e "${GREEN}โ ECS service is active${NC}"
else
echo -e "${RED}โ ECS service not active. Status: $SERVICE_STATUS${NC}"
fi
else
echo -e "${RED}โ ECS cluster not active. Status: $CLUSTER_STATUS${NC}"
exit 1
fi
}
test_alb() {
echo -e "${YELLOW}Testing Application Load Balancer...${NC}"
ALB_DNS=$(aws cloudformation describe-stacks --stack-name ecommerce-ecs --query 'Stacks[0].Outputs[?OutputKey==`LoadBalancerDNS`].OutputValue' --output text 2>/dev/null || echo "NOT_FOUND")
if [ "$ALB_DNS" != "NOT_FOUND" ] && [ "$ALB_DNS" != "" ]; then
echo -e "${GREEN}โ ALB exists: $ALB_DNS${NC}"
# Test health endpoint
echo -e "${YELLOW}Testing API health endpoint...${NC}"
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://$ALB_DNS/health" || echo "000")
if [ "$HTTP_STATUS" = "200" ]; then
echo -e "${GREEN}โ API health check passed${NC}"
else
echo -e "${RED}โ API health check failed. HTTP Status: $HTTP_STATUS${NC}"
fi
else
echo -e "${RED}โ ALB not found${NC}"
exit 1
fi
}
test_s3() {
echo -e "${YELLOW}Testing S3 Buckets...${NC}"
FRONTEND_BUCKET=$(aws cloudformation describe-stacks --stack-name ecommerce-s3 --query 'Stacks[0].Outputs[?OutputKey==`FrontendBucketName`].OutputValue' --output text 2>/dev/null || echo "NOT_FOUND")
if [ "$FRONTEND_BUCKET" != "NOT_FOUND" ]; then
echo -e "${GREEN}โ Frontend bucket exists: $FRONTEND_BUCKET${NC}"
else
echo -e "${RED}โ Frontend bucket not found${NC}"
exit 1
fi
}
test_cloudfront() {
echo -e "${YELLOW}Testing CloudFront...${NC}"
CF_DOMAIN=$(aws cloudformation describe-stacks --stack-name ecommerce-cloudfront --query 'Stacks[0].Outputs[?OutputKey==`CloudFrontDomainName`].OutputValue' --output text 2>/dev/null || echo "NOT_FOUND")
if [ "$CF_DOMAIN" != "NOT_FOUND" ]; then
echo -e "${GREEN}โ CloudFront distribution exists: $CF_DOMAIN${NC}"
# Test CloudFront endpoint
echo -e "${YELLOW}Testing CloudFront endpoint...${NC}"
CF_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://$CF_DOMAIN" || echo "000")
if [ "$CF_STATUS" = "200" ] || [ "$CF_STATUS" = "403" ]; then
echo -e "${GREEN}โ CloudFront endpoint accessible${NC}"
else
echo -e "${RED}โ CloudFront endpoint not accessible. HTTP Status: $CF_STATUS${NC}"
fi
else
echo -e "${RED}โ CloudFront distribution not found${NC}"
exit 1
fi
}
# Run all tests
echo "๐ Starting infrastructure tests..."
test_vpc
test_subnets
test_rds
test_ecs
test_alb
test_s3
test_cloudfront
echo -e "${GREEN}๐ All infrastructure tests passed!${NC}"
# Display useful information
echo ""
echo "๐ Infrastructure Summary:"
echo "VPC ID: $VPC_ID"
echo "ALB DNS: $ALB_DNS"
echo "CloudFront Domain: $CF_DOMAIN"
echo "API Health Check: http://$ALB_DNS/health"
echo "API Products Endpoint: http://$ALB_DNS/api/products"
echo ""
echo "๐ Access your application:"
echo "Frontend (CloudFront): https://$CF_DOMAIN"
echo "API (Direct): http://$ALB_DNS/api/products"
Step 2: Run Infrastructure Tests
`# Make script executable
chmod +x scripts/testing/test-infrastructure.sh
Run tests
./scripts/testing/test-infrastructure.sh`
Step 3: Upload Sample Frontend Content
# Create simple frontend for testing
mkdir -p application/frontend/dist
# Save the enhanced HTML to your file
cat > application/frontend/dist/index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TechStore - Modern Electronics</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background: #f8f9fa;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 0 20px;
}
/* Header */
header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1rem 0;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
.logo {
font-size: 2rem;
font-weight: bold;
text-decoration: none;
color: white;
}
nav ul {
display: flex;
list-style: none;
gap: 2rem;
}
nav a {
color: white;
text-decoration: none;
font-weight: 500;
transition: opacity 0.3s;
}
nav a:hover {
opacity: 0.8;
}
.cart-info {
display: flex;
align-items: center;
gap: 1rem;
}
.cart-count {
background: #ff6b6b;
border-radius: 50%;
padding: 0.2rem 0.6rem;
font-size: 0.8rem;
font-weight: bold;
}
/* Hero Section */
.hero {
background: linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0.4)), url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 600"><rect fill="%23f0f0f0" width="1200" height="600"/><text x="600" y="300" text-anchor="middle" font-size="48" fill="%23999">Hero Background</text></svg>');
background-size: cover;
background-position: center;
color: white;
text-align: center;
padding: 6rem 0;
}
.hero h1 {
font-size: 3.5rem;
margin-bottom: 1rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
}
.hero p {
font-size: 1.3rem;
margin-bottom: 2rem;
opacity: 0.9;
}
.btn {
display: inline-block;
background: #ff6b6b;
color: white;
padding: 1rem 2rem;
text-decoration: none;
border-radius: 50px;
font-weight: bold;
transition: all 0.3s;
border: none;
cursor: pointer;
font-size: 1rem;
}
.btn:hover {
background: #ff5252;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(255, 107, 107, 0.4);
}
.btn-secondary {
background: transparent;
border: 2px solid #ff6b6b;
color: #ff6b6b;
}
.btn-secondary:hover {
background: #ff6b6b;
color: white;
}
/* API Status */
.api-status {
margin: 2rem 0;
padding: 1rem;
border-radius: 8px;
text-align: center;
font-weight: bold;
}
.status-loading {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
}
.status-ok {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
/* Categories */
.categories {
padding: 4rem 0;
background: white;
}
.section-title {
text-align: center;
font-size: 2.5rem;
margin-bottom: 3rem;
color: #333;
}
.categories-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 2rem;
margin-bottom: 3rem;
}
.category-card {
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
padding: 2rem;
border-radius: 15px;
text-align: center;
cursor: pointer;
transition: transform 0.3s, box-shadow 0.3s;
}
.category-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0,0,0,0.15);
}
.category-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
/* Products Section */
.products-section {
padding: 4rem 0;
background: #f8f9fa;
}
.filters {
display: flex;
justify-content: center;
gap: 1rem;
margin-bottom: 3rem;
flex-wrap: wrap;
}
.filter-btn {
background: white;
border: 2px solid #ddd;
padding: 0.5rem 1.5rem;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s;
}
.filter-btn.active,
.filter-btn:hover {
background: #667eea;
color: white;
border-color: #667eea;
}
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
}
.product-card {
background: white;
border-radius: 15px;
overflow: hidden;
box-shadow: 0 5px 15px rgba(0,0,0,0.08);
transition: transform 0.3s, box-shadow 0.3s;
cursor: pointer;
}
.product-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 35px rgba(0,0,0,0.15);
}
.product-image {
width: 100%;
height: 250px;
background: linear-gradient(45deg, #f0f0f0, #e0e0e0);
display: flex;
align-items: center;
justify-content: center;
font-size: 4rem;
color: #999;
}
.product-info {
padding: 1.5rem;
}
.product-title {
font-size: 1.25rem;
font-weight: bold;
margin-bottom: 0.5rem;
color: #333;
}
.product-description {
color: #666;
margin-bottom: 1rem;
font-size: 0.9rem;
}
.product-price {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.current-price {
font-size: 1.5rem;
font-weight: bold;
color: #ff6b6b;
}
.original-price {
text-decoration: line-through;
color: #999;
}
.discount {
background: #ff6b6b;
color: white;
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
}
.product-rating {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
}
.stars {
color: #ffd700;
}
.rating-count {
color: #666;
font-size: 0.9rem;
}
.add-to-cart {
width: 100%;
background: #667eea;
color: white;
border: none;
padding: 0.75rem;
border-radius: 8px;
font-weight: bold;
cursor: pointer;
transition: background 0.3s;
}
.add-to-cart:hover {
background: #5a6fd8;
}
/* Footer */
footer {
background: #333;
color: white;
padding: 3rem 0 1rem;
}
.footer-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
.footer-section h3 {
margin-bottom: 1rem;
color: #ff6b6b;
}
.footer-section ul {
list-style: none;
}
.footer-section a {
color: #ccc;
text-decoration: none;
transition: color 0.3s;
}
.footer-section a:hover {
color: #ff6b6b;
}
.footer-bottom {
text-align: center;
padding-top: 2rem;
border-top: 1px solid #555;
color: #ccc;
}
/* Cart Modal */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 1000;
}
.modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 2rem;
border-radius: 15px;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.close {
position: absolute;
top: 1rem;
right: 1rem;
font-size: 2rem;
cursor: pointer;
color: #999;
}
.cart-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 0;
border-bottom: 1px solid #eee;
}
.cart-total {
font-size: 1.5rem;
font-weight: bold;
text-align: right;
margin-top: 1rem;
color: #ff6b6b;
}
/* Responsive */
@media (max-width: 768px) {
.header-content {
flex-direction: column;
gap: 1rem;
}
nav ul {
flex-wrap: wrap;
justify-content: center;
}
.hero h1 {
font-size: 2.5rem;
}
.products-grid {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
}
.loading {
text-align: center;
padding: 2rem;
color: #666;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<!-- Header -->
<header>
<div class="container">
<div class="header-content">
<a href="#" class="logo">๐ TechStore</a>
<nav>
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#products">Products</a></li>
<li><a href="#categories">Categories</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
<div class="cart-info">
<span>Cart: <span class="cart-count" id="cartCount">0</span></span>
<button class="btn btn-secondary" onclick="openCart()">View Cart</button>
</div>
</div>
</div>
</header>
<!-- Hero Section -->
<section class="hero" id="home">
<div class="container">
<h1>Welcome to TechStore</h1>
<p>Discover the latest technology and electronics at unbeatable prices</p>
<a href="#products" class="btn">Shop Now</a>
</div>
</section>
<!-- API Status -->
<div class="container">
<div class="api-status status-loading" id="apiStatus">
<div class="spinner"></div>
Testing API connection...
</div>
</div>
<!-- Categories -->
<section class="categories" id="categories">
<div class="container">
<h2 class="section-title">Shop by Category</h2>
<div class="categories-grid">
<div class="category-card" onclick="filterProducts('smartphones')">
<div class="category-icon">๐ฑ</div>
<h3>Smartphones</h3>
<p>Latest mobile devices</p>
</div>
<div class="category-card" onclick="filterProducts('laptops')">
<div class="category-icon">๐ป</div>
<h3>Laptops</h3>
<p>Powerful computing</p>
</div>
<div class="category-card" onclick="filterProducts('headphones')">
<div class="category-icon">๐ง</div>
<h3>Audio</h3>
<p>Premium sound quality</p>
</div>
<div class="category-card" onclick="filterProducts('cameras')">
<div class="category-icon">๐ท</div>
<h3>Cameras</h3>
<p>Capture memories</p>
</div>
<div class="category-card" onclick="filterProducts('gaming')">
<div class="category-icon">๐ฎ</div>
<h3>Gaming</h3>
<p>Gaming gear</p>
</div>
<div class="category-card" onclick="filterProducts('accessories')">
<div class="category-icon">โก</div>
<h3>Accessories</h3>
<p>Tech accessories</p>
</div>
</div>
</div>
</section>
<!-- Products Section -->
<section class="products-section" id="products">
<div class="container">
<h2 class="section-title">Featured Products</h2>
<div class="filters">
<button class="filter-btn active" onclick="filterProducts('all')">All Products</button>
<button class="filter-btn" onclick="filterProducts('smartphones')">Smartphones</button>
<button class="filter-btn" onclick="filterProducts('laptops')">Laptops</button>
<button class="filter-btn" onclick="filterProducts('headphones')">Audio</button>
<button class="filter-btn" onclick="filterProducts('cameras')">Cameras</button>
<button class="filter-btn" onclick="filterProducts('gaming')">Gaming</button>
</div>
<div class="products-grid" id="productsGrid">
<div class="loading">
<div class="spinner"></div>
Loading products...
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer>
<div class="container">
<div class="footer-content">
<div class="footer-section">
<h3>About TechStore</h3>
<p>Your trusted partner for the latest technology and electronics. We offer high-quality products at competitive prices with excellent customer service.</p>
</div>
<div class="footer-section">
<h3>Quick Links</h3>
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#products">Products</a></li>
<li><a href="#categories">Categories</a></li>
<li><a href="#about">About Us</a></li>
</ul>
</div>
<div class="footer-section">
<h3>Customer Service</h3>
<ul>
<li><a href="#">Contact Us</a></li>
<li><a href="#">Shipping Info</a></li>
<li><a href="#">Returns</a></li>
<li><a href="#">FAQ</a></li>
</ul>
</div>
<div class="footer-section">
<h3>Connect</h3>
<ul>
<li><a href="#">Facebook</a></li>
<li><a href="#">Twitter</a></li>
<li><a href="#">Instagram</a></li>
<li><a href="#">LinkedIn</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>© 2025 TechStore. All rights reserved. | Built with AWS</p>
</div>
</div>
</footer>
<!-- Cart Modal -->
<div class="modal" id="cartModal">
<div class="modal-content">
<span class="close" onclick="closeCart()">×</span>
<h2>Shopping Cart</h2>
<div id="cartItems"></div>
<div class="cart-total" id="cartTotal">Total: $0.00</div>
<button class="btn" style="width: 100%; margin-top: 1rem;" onclick="checkout()">Proceed to Checkout</button>
</div>
</div>
<script>
// Sample product data
const products = [
{
id: 1,
name: "iPhone 15 Pro",
category: "smartphones",
price: 999,
originalPrice: 1099,
description: "Latest iPhone with titanium design and A17 Pro chip",
rating: 4.8,
reviews: 1234,
image: "๐ฑ"
},
{
id: 2,
name: "MacBook Pro M3",
category: "laptops",
price: 1999,
originalPrice: 2199,
description: "Powerful laptop with M3 chip for professionals",
rating: 4.9,
reviews: 856,
image: "๐ป"
},
{
id: 3,
name: "Sony WH-1000XM5",
category: "headphones",
price: 399,
originalPrice: 449,
description: "Industry-leading noise canceling headphones",
rating: 4.7,
reviews: 2103,
image: "๐ง"
},
{
id: 4,
name: "Canon EOS R5",
category: "cameras",
price: 3899,
originalPrice: 4299,
description: "Professional mirrorless camera with 45MP sensor",
rating: 4.6,
reviews: 445,
image: "๐ท"
},
{
id: 5,
name: "PlayStation 5",
category: "gaming",
price: 499,
originalPrice: 559,
description: "Next-gen gaming console with ray tracing",
rating: 4.8,
reviews: 3421,
image: "๐ฎ"
},
{
id: 6,
name: "iPad Pro 12.9\"",
category: "smartphones",
price: 1099,
originalPrice: 1199,
description: "Professional tablet with M2 chip",
rating: 4.7,
reviews: 1876,
image: "๐ฑ"
},
{
id: 7,
name: "Dell XPS 13",
category: "laptops",
price: 1299,
originalPrice: 1399,
description: "Ultrabook with InfinityEdge display",
rating: 4.5,
reviews: 934,
image: "๐ป"
},
{
id: 8,
name: "AirPods Pro",
category: "headphones",
price: 249,
originalPrice: 279,
description: "Wireless earbuds with active noise cancellation",
rating: 4.6,
reviews: 5672,
image: "๐ง"
}
];
let cart = [];
let currentFilter = 'all';
// Initialize the page
document.addEventListener('DOMContentLoaded', function() {
testAPIConnection();
displayProducts();
});
// Test API connection
function testAPIConnection() {
const apiStatus = document.getElementById('apiStatus');
// Simulate API test
setTimeout(() => {
const isConnected = Math.random() > 0.3; // 70% success rate for demo
if (isConnected) {
apiStatus.className = 'api-status status-ok';
apiStatus.innerHTML = 'โ
API Connection Successful - Backend services are running';
} else {
apiStatus.className = 'api-status status-error';
apiStatus.innerHTML = 'โ API Connection Failed - Using demo data';
}
}, 2000);
}
// Display products
function displayProducts() {
const grid = document.getElementById('productsGrid');
const filteredProducts = currentFilter === 'all' ? products : products.filter(p => p.category === currentFilter);
setTimeout(() => {
grid.innerHTML = filteredProducts.map(product => `
<div class="product-card">
<div class="product-image">${product.image}</div>
<div class="product-info">
<h3 class="product-title">${product.name}</h3>
<p class="product-description">${product.description}</p>
<div class="product-rating">
<span class="stars">${'โ
'.repeat(Math.floor(product.rating))}${product.rating % 1 ? 'โ' : ''}</span>
<span class="rating-count">${product.rating} (${product.reviews} reviews)</span>
</div>
<div class="product-price">
<span class="current-price">$${product.price}</span>
${product.originalPrice > product.price ? `
<span class="original-price">$${product.originalPrice}</span>
<span class="discount">${Math.round((1 - product.price/product.originalPrice) * 100)}% OFF</span>
` : ''}
</div>
<button class="add-to-cart" onclick="addToCart(${product.id})">Add to Cart</button>
</div>
</div>
`).join('');
}, 500);
}
// Filter products
function filterProducts(category) {
currentFilter = category;
// Update filter buttons
document.querySelectorAll('.filter-btn').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
// Show loading
document.getElementById('productsGrid').innerHTML = `
<div class="loading">
<div class="spinner"></div>
Loading ${category === 'all' ? 'all products' : category}...
</div>
`;
displayProducts();
}
// Add to cart
function addToCart(productId) {
const product = products.find(p => p.id === productId);
const existingItem = cart.find(item => item.id === productId);
if (existingItem) {
existingItem.quantity += 1;
} else {
cart.push({...product, quantity: 1});
}
updateCartCount();
// Show feedback
event.target.style.background = '#28a745';
event.target.textContent = 'Added!';
setTimeout(() => {
event.target.style.background = '#667eea';
event.target.textContent = 'Add to Cart';
}, 1000);
}
// Update cart count
function updateCartCount() {
const count = cart.reduce((total, item) => total + item.quantity, 0);
document.getElementById('cartCount').textContent = count;
}
// Open cart modal
function openCart() {
const modal = document.getElementById('cartModal');
const cartItems = document.getElementById('cartItems');
const cartTotal = document.getElementById('cartTotal');
if (cart.length === 0) {
cartItems.innerHTML = '<p style="text-align: center; color: #666; padding: 2rem;">Your cart is empty</p>';
cartTotal.textContent = 'Total: $0.00';
} else {
cartItems.innerHTML = cart.map(item => `
<div class="cart-item">
<div>
<strong>${item.name}</strong><br>
<small>${item.description}</small><br>
<small>Quantity: ${item.quantity}</small>
</div>
<div>
<strong>$${(item.price * item.quantity).toFixed(2)}</strong>
<button onclick="removeFromCart(${item.id})" style="margin-left: 1rem; background: #ff6b6b; color: white; border: none; padding: 0.2rem 0.5rem; border-radius: 4px; cursor: pointer;">Remove</button>
</div>
</div>
`).join('');
const total = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
cartTotal.textContent = `Total: $${total.toFixed(2)}`;
}
modal.style.display = 'block';
}
// Close cart modal
function closeCart() {
document.getElementById('cartModal').style.display = 'none';
}
// Remove from cart
function removeFromCart(productId) {
cart = cart.filter(item => item.id !== productId);
updateCartCount();
openCart(); // Refresh cart display
}
// Checkout
function checkout() {
if (cart.length === 0) {
alert('Your cart is empty!');
return;
}
const total = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
alert(`Thank you for your purchase! Total: $${total.toFixed(2)}\n\nThis is a demo - no actual payment processed.`);
cart = [];
updateCartCount();
closeCart();
}
// Close modal when clicking outside
window.onclick = function(event) {
const modal = document.getElementById('cartModal');
if (event.target === modal) {
closeCart();
}
}
// Smooth scrolling for navigation links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
</script>
</body>
</html>
EOF
# Upload to S3
FRONTEND_BUCKET=$(aws cloudformation describe-stacks --stack-name ecommerce-cloudfront --query 'Stacks[0].Outputs[?OutputKey==`FrontendBucketName`].OutputValue' --output text)
aws s3 cp application/frontend/dist/index.html s3://$FRONTEND_BUCKET/index.html --content-type text/html
# Upload to S3
FRONTEND_BUCKET=$(aws cloudformation describe-stacks --stack-name ecommerce-s3 --query 'Stacks[0].Outputs[?OutputKey==`FrontendBucketName`].OutputValue' --output text)
If you are in dist directory use this command
aws s3 cp index.html s3://$FRONTEND_BUCKET/index.html --content-type text/html
CI/CD Pipeline and Advanced Features
CodePipeline Setup
Step 1: Create CodeBuild Template
touch cloudformation/cicd/codebuild-project.yaml
File: cloudformation/cicd/codebuild-project.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'CodePipeline for E-commerce API deployment'
Parameters:
ProjectName:
Type: String
Default: ecommerce
Environment:
Type: String
Default: production
GitHubOwner:
Type: String
Description: GitHub repository owner
GitHubRepo:
Type: String
Description: GitHub repository name
Default: ecommerce-infrastructure
GitHubBranch:
Type: String
Description: GitHub branch
Default: main
GitHubToken:
Type: String
Description: GitHub personal access token
NoEcho: true
Resources:
# CodePipeline Service Role
CodePipelineServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub '${ProjectName}-${Environment}-codepipeline-role'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: CodePipelinePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:GetBucketVersioning
- s3:GetObject
- s3:GetObjectVersion
- s3:PutObject
- s3:GetBucketLocation
- s3:ListBucket
Resource:
- Fn::ImportValue: !Sub '${ProjectName}-${Environment}-Artifacts-Bucket'
- !Sub
- '${BucketArn}/*'
- BucketArn:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-Artifacts-Bucket'
- Effect: Allow
Action:
- codebuild:BatchGetBuilds
- codebuild:StartBuild
Resource:
- Fn::ImportValue: !Sub '${ProjectName}-${Environment}-CodeBuild-Project'
- Effect: Allow
Action:
- ecs:DescribeServices
- ecs:DescribeTaskDefinition
- ecs:DescribeTasks
- ecs:ListTasks
- ecs:RegisterTaskDefinition
- ecs:UpdateService
Resource: '*'
- Effect: Allow
Action:
- iam:PassRole
Resource: '*'
# CodeDeploy Application
CodeDeployApplication:
Type: AWS::CodeDeploy::Application
Properties:
ApplicationName: !Sub '${ProjectName}-${Environment}-ecs-app'
ComputePlatform: ECS
# CodeDeploy Service Role
CodeDeployServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub '${ProjectName}-${Environment}-codedeploy-role'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codedeploy.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS
# CodeDeploy Deployment Group
CodeDeployDeploymentGroup:
Type: AWS::CodeDeploy::DeploymentGroup
Properties:
ApplicationName: !Ref CodeDeployApplication
DeploymentGroupName: !Sub '${ProjectName}-${Environment}-deployment-group'
ServiceRoleArn: !GetAtt CodeDeployServiceRole.Arn
DeploymentConfigName: CodeDeployDefault.ECSAllAtOnce
ECSServices:
- ServiceName:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ECS-Service'
ClusterName:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ECS-Cluster'
LoadBalancerInfo:
TargetGroupInfoList:
- Name: !Sub '${ProjectName}-${Environment}-tg'
BlueGreenDeploymentConfiguration:
TerminateBlueInstancesOnDeploymentSuccess:
Action: TERMINATE
TerminationWaitTimeInMinutes: 5
DeploymentReadyOption:
ActionOnTimeout: CONTINUE_DEPLOYMENT
GreenFleetProvisioningOption:
Action: COPY_AUTO_SCALING_GROUP
# CodePipeline
CodePipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: !Sub '${ProjectName}-${Environment}-pipeline'
RoleArn: !GetAtt CodePipelineServiceRole.Arn
ArtifactStore:
Type: S3
Location:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-Artifacts-Bucket'
Stages:
- Name: Source
Actions:
- Name: SourceAction
ActionTypeId:
Category: Source
Owner: ThirdParty
Provider: GitHub
Version: 1
Configuration:
Owner: !Ref GitHubOwner
Repo: !Ref GitHubRepo
Branch: !Ref GitHubBranch
OAuthToken: !Ref GitHubToken
PollForSourceChanges: false
OutputArtifacts:
- Name: SourceOutput
- Name: Build
Actions:
- Name: BuildAction
ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: 1
Configuration:
ProjectName:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-CodeBuild-Project'
InputArtifacts:
- Name: SourceOutput
OutputArtifacts:
- Name: BuildOutput
- Name: Deploy
Actions:
- Name: DeployAction
ActionTypeId:
Category: Deploy
Owner: AWS
Provider: ECS
Version: 1
Configuration:
ClusterName:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ECS-Cluster'
ServiceName:
Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ECS-Service'
FileName: imagedefinitions.json
InputArtifacts:
- Name: BuildOutput
# GitHub Webhook
GitHubWebhook:
Type: AWS::CodePipeline::Webhook
Properties:
Name: !Sub '${ProjectName}-${Environment}-github-webhook'
TargetPipeline: !Ref CodePipeline
TargetAction: SourceAction
TargetPipelineVersion: !GetAtt CodePipeline.Version
RegisterWithThirdParty: true
Authentication: GITHUB_HMAC
AuthenticationConfiguration:
SecretToken: !Ref GitHubToken
Filters:
- JsonPath: "$.ref"
MatchEquals: !Sub "refs/heads/${GitHubBranch}"
Outputs:
CodePipelineName:
Description: CodePipeline name
Value: !Ref CodePipeline
Export:
Name: !Sub '${ProjectName}-${Environment}-Pipeline'
CodeDeployApplication:
Description: CodeDeploy application name
Value: !Ref CodeDeployApplication
Export:
Name: !Sub '${ProjectName}-${Environment}-CodeDeploy-App'
GitHub Repository Setup
Step 1: Create GitHub Repository
bash#
Create a new directory for your GitHub repo
mkdir ../ecommerce-infrastructure-repo
cd ../ecommerce-infrastructure-repo
# Initialize Git repository
git init
git branch -M main
# Copy your project files
cp -r ../aws-ecommerce-infrastructure/* .
# Create README
cat > README.md << 'EOF'
# E-commerce Infrastructure on AWS
This repository contains the Infrastructure as Code (IaC) for a production-ready e-commerce platform built on AWS.
## Architecture Overview
- **Frontend**: React SPA served via CloudFront + S3
- **Backend**: Node.js API running on ECS Fargate
- **Database**: RDS PostgreSQL with encryption
- **Storage**: S3 buckets with lifecycle policies
- **CDN**: CloudFront for global content delivery
- **CI/CD**: CodePipeline + CodeDeploy for automated deployments
## Deployment Instructions
1. Deploy VPC: `aws cloudformation create-stack --stack-name ecommerce-vpc --template-body file://cloudformation/network/vpc-base.yaml`
2. Deploy Security Groups: `aws cloudformation create-stack --stack-name ecommerce-security-groups --template-body file://cloudformation/security/security-groups.yaml`
3. Deploy IAM Roles: `aws cloudformation create-stack --stack-name ecommerce-iam-roles --template-body file://cloudformation/security/iam-roles.yaml --capabilities CAPABILITY_NAMED_IAM`
## Security Features
- VPC with private subnets
- Security groups with least privilege
- IAM roles with minimal permissions
- Encrypted storage (S3, RDS)
- WAF protection
- CloudWatch monitoring
## Cost Optimization
- Fargate for serverless containers
- S3 lifecycle policies
- CloudFront edge caching
- RDS right-sizing
EOF
# Create .gitignore
cat > .gitignore << 'EOF'
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# IDE files
.vscode/
.idea/
*.swp
*.swo
# Node.js
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# AWS
.aws/
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Build outputs
dist/
build/
EOF
# Add all files and commit
git add .
git commit -m "Initial commit: AWS E-commerce Infrastructure"
# Create GitHub repository (you'll need GitHub CLI or do this manually)
# gh repo create ecommerce-infrastructure --public --source=. --
Step 2: Create GitHub Personal Access Token
Go to GitHub โ Settings โ Developer settings โ Personal access tokens
Click "Generate new token (classic)"
Select scopes: repo, admin:repo_hook
Copy the token for later use
Top comments (0)