As an AWS CloudFormation expert and Magento specialist, I’m often asked: “How do I deploy Magento 2 on AWS using Infrastructure as Code?” This is an excellent question because CloudFormation allows you to define your entire infrastructure in templates, making deployments repeatable, scalable, and maintainable.
In this comprehensive guide, we’ll walk through deploying a production-ready Magento 2 instance on AWS using CloudFormation templates. We’ll use AWS-native tools and services to create a scalable, high-availability architecture.
What We’re Building
By the end of this tutorial, you’ll have:
- EC2 Instance: Running Magento 2 with Apache/PHP
- RDS Database: Managed MySQL/MariaDB for Magento data
- ElastiCache: Redis cluster for caching and sessions
- S3 Bucket: For media storage and backups
- Application Load Balancer: For high availability
- Auto Scaling Group: To handle traffic spikes
- CloudFront CDN: For global content delivery
- VPC: Secure network infrastructure
- Security Groups: Proper network security
Prerequisites
Before we start, ensure you have:
- An AWS account with appropriate permissions
- AWS CLI installed and configured (see our AWS CLI guide)
- Basic understanding of CloudFormation
- Magento Marketplace access keys
- Basic knowledge of YAML/JSON
Understanding AWS Services for Magento
Let’s understand what each AWS service does in our Magento architecture:
EC2: Virtual servers running your Magento application RDS: Managed database service (MySQL/MariaDB) ElastiCache: Managed Redis for caching S3: Object storage for media files and backups ALB: Distributes traffic across multiple EC2 instances Auto Scaling: Automatically adds/removes servers based on demand CloudFront: CDN for fast global content delivery VPC: Isolated network environment Security Groups: Virtual firewalls
Step 1: Prepare Your Environment
Install and Configure AWS CLI
First, ensure AWS CLI is installed and configured:
# Verify AWS CLI installation
aws --version
# Configure AWS credentials
aws configure
# You'll be prompted for:
# - AWS Access Key ID
# - AWS Secret Access Key
# - Default region (e.g., us-east-1)
# - Default output format (json)
Verify AWS Access
Test your AWS access:
# List your S3 buckets
aws s3 ls
# Check your IAM identity
aws sts get-caller-identity
Get Magento Access Keys
You’ll need Magento Marketplace access keys:
- Go to Magento Marketplace
- Log in or create an account
- Navigate to “My Access Keys”
- Create a new access key pair
- Save the public and private keys securely
Step 2: Create S3 Bucket for CloudFormation Templates
We’ll store our CloudFormation templates in S3 for better organization:
# Create S3 bucket for templates
aws s3 mb s3://magento-cloudformation-templates-$(date +%s)
# Note: Bucket names must be globally unique
# Replace with your unique bucket name
Step 3: Create the Main CloudFormation Template
Let’s create our main CloudFormation template. This will define all our infrastructure:
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Magento 2 on AWS - Production Ready Infrastructure'
Parameters:
EnvironmentName:
Type: String
Default: magento-prod
Description: Environment name for resource naming
InstanceType:
Type: String
Default: t3.large
AllowedValues:
- t3.medium
- t3.large
- t3.xlarge
- m5.large
- m5.xlarge
Description: EC2 instance type for Magento
DBInstanceClass:
Type: String
Default: db.t3.medium
Description: RDS instance class
DBName:
Type: String
Default: magento2
Description: Database name
DBUsername:
Type: String
Default: magento_admin
NoEcho: true
Description: Database master username
DBPassword:
Type: String
NoEcho: true
MinLength: 8
Description: Database master password
MagentoPublicKey:
Type: String
Description: Magento Marketplace Public Key
NoEcho: true
MagentoPrivateKey:
Type: String
Description: Magento Marketplace Private Key
NoEcho: true
AdminEmail:
Type: String
Default: [email protected]
Description: Magento admin email
AdminPassword:
Type: String
NoEcho: true
MinLength: 8
Description: Magento admin password
KeyPairName:
Type: AWS::EC2::KeyPair::KeyName
Description: EC2 Key Pair for SSH access
Resources:
# VPC and Networking
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub '${EnvironmentName}-VPC'
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 '${EnvironmentName}-PublicSubnet1'
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 '${EnvironmentName}-PublicSubnet2'
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [0, !GetAZs '']
CidrBlock: 10.0.3.0/24
Tags:
- Key: Name
Value: !Sub '${EnvironmentName}-PrivateSubnet1'
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [1, !GetAZs '']
CidrBlock: 10.0.4.0/24
Tags:
- Key: Name
Value: !Sub '${EnvironmentName}-PrivateSubnet2'
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub '${EnvironmentName}-IGW'
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${EnvironmentName}-PublicRT'
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
# NAT Gateway for private subnet internet access
NATGatewayEIP:
Type: AWS::EC2::EIP
DependsOn: InternetGatewayAttachment
Properties:
Domain: vpc
NATGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NATGatewayEIP.AllocationId
SubnetId: !Ref PublicSubnet1
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${EnvironmentName}-PrivateRT'
DefaultPrivateRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NATGateway
PrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref PrivateSubnet1
PrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref PrivateSubnet2
# Security Groups
WebServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub '${EnvironmentName}-WebServerSG'
GroupDescription: Security group for Magento web servers
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Description: HTTP access
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: HTTPS access
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
Description: SSH access
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub '${EnvironmentName}-WebServerSG'
DatabaseSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub '${EnvironmentName}-DatabaseSG'
GroupDescription: Security group for RDS database
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: !Ref WebServerSecurityGroup
Description: MySQL access from web servers
Tags:
- Key: Name
Value: !Sub '${EnvironmentName}-DatabaseSG'
ElastiCacheSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub '${EnvironmentName}-ElastiCacheSG'
GroupDescription: Security group for ElastiCache Redis
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 6379
ToPort: 6379
SourceSecurityGroupId: !Ref WebServerSecurityGroup
Description: Redis access from web servers
Tags:
- Key: Name
Value: !Sub '${EnvironmentName}-ElastiCacheSG'
# S3 Bucket for Media Files
MagentoMediaBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub '${EnvironmentName}-media-${AWS::AccountId}'
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
VersioningConfiguration:
Status: Enabled
LifecycleConfiguration:
Rules:
- Id: DeleteOldVersions
Status: Enabled
NoncurrentVersionExpirationInDays: 30
# IAM Role for EC2 Instances
EC2InstanceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
Policies:
- PolicyName: S3Access
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:DeleteObject
- s3:ListBucket
Resource:
- !Sub '${MagentoMediaBucket}/*'
- !GetAtt MagentoMediaBucket.Arn
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref EC2InstanceRole
# RDS Subnet Group
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Subnet group for RDS database
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
Tags:
- Key: Name
Value: !Sub '${EnvironmentName}-DBSubnetGroup'
# RDS Database Instance
MagentoDatabase:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: !Sub '${EnvironmentName}-database'
DBName: !Ref DBName
DBInstanceClass: !Ref DBInstanceClass
Engine: mariadb
EngineVersion: '10.11'
MasterUsername: !Ref DBUsername
MasterUserPassword: !Ref DBPassword
AllocatedStorage: 100
StorageType: gp3
StorageEncrypted: true
VPCSecurityGroups:
- !Ref DatabaseSecurityGroup
DBSubnetGroupName: !Ref DBSubnetGroup
BackupRetentionPeriod: 7
PreferredBackupWindow: '03:00-04:00'
PreferredMaintenanceWindow: 'sun:04:00-sun:05:00'
MultiAZ: false
PubliclyAccessible: false
Tags:
- Key: Name
Value: !Sub '${EnvironmentName}-Database'
# ElastiCache Subnet Group
ElastiCacheSubnetGroup:
Type: AWS::ElastiCache::SubnetGroup
Properties:
Description: Subnet group for ElastiCache
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
# ElastiCache Redis Cluster
MagentoRedisCache:
Type: AWS::ElastiCache::ReplicationGroup
Properties:
ReplicationGroupId: !Sub '${EnvironmentName}-redis-cache'
Description: Redis cluster for Magento cache
Engine: redis
CacheNodeType: cache.t3.medium
NumCacheClusters: 2
AutomaticFailoverEnabled: true
MultiAZEnabled: true
CacheSubnetGroupName: !Ref ElastiCacheSubnetGroup
SecurityGroupIds:
- !Ref ElastiCacheSecurityGroup
AtRestEncryptionEnabled: true
TransitEncryptionEnabled: false
Tags:
- Key: Name
Value: !Sub '${EnvironmentName}-RedisCache'
# Application Load Balancer
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub '${EnvironmentName}-ALB'
Type: application
Scheme: internet-facing
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
SecurityGroups:
- !Ref WebServerSecurityGroup
Tags:
- Key: Name
Value: !Sub '${EnvironmentName}-ALB'
ALBTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub '${EnvironmentName}-TargetGroup'
Port: 80
Protocol: HTTP
VpcId: !Ref VPC
HealthCheckPath: /health
HealthCheckIntervalSeconds: 30
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
UnhealthyThresholdCount: 3
TargetType: instance
ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref ALBTargetGroup
LoadBalancerArn: !Ref ApplicationLoadBalancer
Port: 80
Protocol: HTTP
# Launch Template for Auto Scaling
LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: !Sub '${EnvironmentName}-LaunchTemplate'
LaunchTemplateData:
ImageId: ami-0c55b159cbfafe1f0 # Amazon Linux 2023 - Update with your region's AMI
InstanceType: !Ref InstanceType
KeyName: !Ref KeyPairName
IamInstanceProfile:
Arn: !GetAtt EC2InstanceProfile.Arn
SecurityGroupIds:
- !Ref WebServerSecurityGroup
UserData:
Fn::Base64: !Sub |
#!/bin/bash
yum update -y
yum install -y httpd php php-mysqlnd php-xml php-gd php-mbstring php-curl php-zip php-intl php-bcmath php-soap php-opcache git
# Install Composer
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
# Install Redis PHP extension
yum install -y php-pecl-redis
# Configure PHP
sed -i 's/memory_limit = .*/memory_limit = 2G/' /etc/php.ini
sed -i 's/max_execution_time = .*/max_execution_time = 1800/' /etc/php.ini
sed -i 's/upload_max_filesize = .*/upload_max_filesize = 64M/' /etc/php.ini
sed -i 's/post_max_size = .*/post_max_size = 64M/' /etc/php.ini
# Install AWS CLI
yum install -y aws-cli
# Mount EFS (if using EFS for shared storage)
# yum install -y amazon-efs-utils
# mkdir -p /var/www/html
# mount -t efs ${EFSFileSystemId}:/ /var/www/html
# Download and install Magento
cd /var/www/html
composer create-project --repository-url=https://repo.magento.com/ \
magento/project-community-edition . \
--username=${MagentoPublicKey} \
--password=${MagentoPrivateKey}
# Set permissions
chown -R apache:apache /var/www/html
find . -type f -exec chmod 644 {} \;
find . -type d -exec chmod 755 {} \;
chmod +x bin/magento
# Install Magento
/usr/bin/php bin/magento setup:install \
--base-url=http://${ApplicationLoadBalancer.DNSName}/ \
--base-url-secure=https://${ApplicationLoadBalancer.DNSName}/ \
--db-host=${MagentoDatabase.Endpoint.Address} \
--db-name=${DBName} \
--db-user=${DBUsername} \
--db-password=${DBPassword} \
--admin-firstname=Admin \
--admin-lastname=User \
--admin-email=${AdminEmail} \
--admin-user=admin \
--admin-password=${AdminPassword} \
--language=en_US \
--currency=USD \
--timezone=America/New_York \
--use-rewrites=1 \
--backend-frontname=admin \
--session-save=redis \
--session-save-redis-host=${MagentoRedisCache.RedisEndpoint.Address} \
--session-save-redis-port=6379 \
--session-save-redis-db=0 \
--cache-backend=redis \
--cache-backend-redis-server=${MagentoRedisCache.RedisEndpoint.Address} \
--cache-backend-redis-port=6379 \
--cache-backend-redis-db=1 \
--page-cache=redis \
--page-cache-redis-server=${MagentoRedisCache.RedisEndpoint.Address} \
--page-cache-redis-port=6379 \
--page-cache-redis-db=2
# Configure Apache
echo '<VirtualHost *:80>
ServerName ${ApplicationLoadBalancer.DNSName}
DocumentRoot /var/www/html/pub
<Directory /var/www/html/pub>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog /var/log/httpd/magento_error.log
CustomLog /var/log/httpd/magento_access.log combined
</VirtualHost>' > /etc/httpd/conf.d/magento.conf
# Enable Apache modules
systemctl enable httpd
systemctl start httpd
# Set production mode
/usr/bin/php bin/magento deploy:mode:set production
/usr/bin/php bin/magento setup:static-content:deploy -f
/usr/bin/php bin/magento indexer:reindex
# Setup cron
echo '* * * * * /usr/bin/php /var/www/html/bin/magento cron:run' | crontab -u apache -
# Auto Scaling Group
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AutoScalingGroupName: !Sub '${EnvironmentName}-ASG'
VPCZoneIdentifier:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplate
Version: !GetAtt LaunchTemplate.LatestVersionNumber
MinSize: 1
MaxSize: 4
DesiredCapacity: 2
TargetGroupARNs:
- !Ref ALBTargetGroup
HealthCheckType: ELB
HealthCheckGracePeriod: 300
Tags:
- Key: Name
Value: !Sub '${EnvironmentName}-Instance'
PropagateAtLaunch: true
# Auto Scaling Policies
ScaleUpPolicy:
Type: AWS::AutoScaling::ScalingPolicy
Properties:
AdjustmentType: ChangeInCapacity
AutoScalingGroupName: !Ref AutoScalingGroup
Cooldown: 300
ScalingAdjustment: 1
ScaleDownPolicy:
Type: AWS::AutoScaling::ScalingPolicy
Properties:
AdjustmentType: ChangeInCapacity
AutoScalingGroupName: !Ref AutoScalingGroup
Cooldown: 300
ScalingAdjustment: -1
# CloudWatch Alarms for Auto Scaling
CPUAlarmHigh:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub '${EnvironmentName}-CPU-High'
AlarmDescription: Alarm when CPU exceeds 70%
MetricName: CPUUtilization
Namespace: AWS/EC2
Statistic: Average
Period: 300
EvaluationPeriods: 2
Threshold: 70
ComparisonOperator: GreaterThanThreshold
Dimensions:
- Name: AutoScalingGroupName
Value: !Ref AutoScalingGroup
CPUAlarmLow:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub '${EnvironmentName}-CPU-Low'
AlarmDescription: Alarm when CPU falls below 25%
MetricName: CPUUtilization
Namespace: AWS/EC2
Statistic: Average
Period: 300
EvaluationPeriods: 2
Threshold: 25
ComparisonOperator: LessThanThreshold
Dimensions:
- Name: AutoScalingGroupName
Value: !Ref AutoScalingGroup
Outputs:
LoadBalancerDNS:
Description: DNS name of the load balancer
Value: !GetAtt ApplicationLoadBalancer.DNSName
Export:
Name: !Sub '${AWS::StackName}-LoadBalancerDNS'
DatabaseEndpoint:
Description: RDS database endpoint
Value: !GetAtt MagentoDatabase.Endpoint.Address
Export:
Name: !Sub '${AWS::StackName}-DatabaseEndpoint'
RedisEndpoint:
Description: ElastiCache Redis endpoint
Value: !GetAtt MagentoRedisCache.RedisEndpoint.Address
Export:
Name: !Sub '${AWS::StackName}-RedisEndpoint'
MediaBucketName:
Description: S3 bucket for media files
Value: !Ref MagentoMediaBucket
Export:
Name: !Sub '${AWS::StackName}-MediaBucket'
VPCId:
Description: VPC ID
Value: !Ref VPC
Export:
Name: !Sub '${AWS::StackName}-VPCId'
Save this as magento-cloudformation.yaml.
Step 4: Upload Template to S3
Upload your template to S3:
# Upload template to S3
aws s3 cp magento-cloudformation.yaml s3://magento-cloudformation-templates-<your-bucket-id>/
# Or use the S3 URL in CloudFormation console
Step 5: Deploy Using AWS CLI
Deploy the stack using AWS CLI:
aws cloudformation create-stack \
--stack-name magento-production \
--template-body file://magento-cloudformation.yaml \
--parameters \
ParameterKey=EnvironmentName,ParameterValue=magento-prod \
ParameterKey=InstanceType,ParameterValue=t3.large \
ParameterKey=DBInstanceClass,ParameterValue=db.t3.medium \
ParameterKey=DBName,ParameterValue=magento2 \
ParameterKey=DBUsername,ParameterValue=magento_admin \
ParameterKey=DBPassword,ParameterValue=YourSecurePassword123! \
ParameterKey=MagentoPublicKey,ParameterValue=your_public_key \
ParameterKey=MagentoPrivateKey,ParameterValue=your_private_key \
ParameterKey=AdminEmail,ParameterValue=[email protected] \
ParameterKey=AdminPassword,ParameterValue=AdminSecurePass123! \
ParameterKey=KeyPairName,ParameterValue=your-key-pair-name \
--capabilities CAPABILITY_NAMED_IAM \
--region us-east-1
Monitor Stack Creation
Watch the stack creation progress:
# Check stack status
aws cloudformation describe-stacks --stack-name magento-production
# Watch stack events
aws cloudformation describe-stack-events --stack-name magento-production --max-items 10
# Or use the console
aws cloudformation describe-stacks --stack-name magento-production --query 'Stacks[0].StackStatus'
Step 6: Alternative - Deploy Using AWS Console
If you prefer the AWS Console:
-
Navigate to CloudFormation:
- Go to AWS Console → CloudFormation
- Click “Create stack”
-
Choose Template:
- Select “Upload a template file”
- Upload your
magento-cloudformation.yaml
-
Specify Stack Details:
- Stack name:
magento-production - Fill in all parameters
- Stack name:
-
Configure Stack Options:
- Add tags if needed
- Configure stack failure options
-
Review and Create:
- Review all settings
- Acknowledge IAM capabilities
- Click “Create stack”
Step 7: Post-Deployment Configuration
After the stack is created, perform additional configurations:
Get Stack Outputs
# Get all outputs
aws cloudformation describe-stacks \
--stack-name magento-production \
--query 'Stacks[0].Outputs'
# Get specific output
aws cloudformation describe-stacks \
--stack-name magento-production \
--query 'Stacks[0].Outputs[?OutputKey==`LoadBalancerDNS`].OutputValue' \
--output text
Configure S3 for Media Storage
Connect Magento to S3 for media storage:
# Install S3 media extension or configure manually
# Access your EC2 instance
ssh -i your-key.pem ec2-user@<instance-ip>
# Install AWS SDK for PHP
composer require aws/aws-sdk-php
Set Up CloudFront Distribution
Create a CloudFront distribution for CDN:
# Create CloudFront distribution
aws cloudfront create-distribution \
--origin-domain-name <your-alb-dns-name> \
--default-root-object index.php
Step 8: Verify Deployment
Check All Services
# Verify EC2 instances
aws ec2 describe-instances \
--filters "Name=tag:Name,Values=magento-prod-Instance" \
--query 'Reservations[*].Instances[*].[InstanceId,State.Name,PublicIpAddress]'
# Verify RDS
aws rds describe-db-instances \
--db-instance-identifier magento-prod-database
# Verify ElastiCache
aws elasticache describe-replication-groups \
--replication-group-id magento-prod-redis-cache
# Verify Load Balancer
aws elbv2 describe-load-balancers \
--names magento-prod-ALB
Test Magento Installation
- Get the Load Balancer DNS:
ALB_DNS=$(aws cloudformation describe-stacks \
--stack-name magento-production \
--query 'Stacks[0].Outputs[?OutputKey==`LoadBalancerDNS`].OutputValue' \
--output text)
echo "Access your store at: http://$ALB_DNS"
- Access the storefront and admin panel
- Verify Redis caching is working
- Test database connectivity
Step 9: Advanced Configurations
Add EFS for Shared Storage
For multi-instance deployments, add EFS:
EFSFileSystem:
Type: AWS::EFS::FileSystem
Properties:
PerformanceMode: generalPurpose
Encrypted: true
FileSystemTags:
- Key: Name
Value: !Sub '${EnvironmentName}-EFS'
EFSMountTarget1:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId: !Ref EFSFileSystem
SubnetId: !Ref PrivateSubnet1
SecurityGroups:
- !Ref WebServerSecurityGroup
Add CloudFront CDN
Create a CloudFront distribution:
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- DomainName: !GetAtt ApplicationLoadBalancer.DNSName
Id: ALBOrigin
CustomOriginConfig:
HTTPPort: 80
OriginProtocolPolicy: http-only
Enabled: true
DefaultRootObject: index.php
DefaultCacheBehavior:
TargetOriginId: ALBOrigin
ViewerProtocolPolicy: redirect-to-https
AllowedMethods:
- DELETE
- GET
- HEAD
- OPTIONS
- PATCH
- POST
- PUT
CachedMethods:
- GET
- HEAD
ForwardedValues:
QueryString: true
Cookies:
Forward: all
Add SSL Certificate
Use ACM for SSL:
SSLCertificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: yourdomain.com
ValidationMethod: DNS
SubjectAlternativeNames:
- www.yourdomain.com
Step 10: Monitoring and Maintenance
Set Up CloudWatch Alarms
# Create alarm for high CPU
aws cloudwatch put-metric-alarm \
--alarm-name magento-high-cpu \
--alarm-description "Alarm when CPU exceeds 80%" \
--metric-name CPUUtilization \
--namespace AWS/EC2 \
--statistic Average \
--period 300 \
--threshold 80 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 2
Enable Detailed Monitoring
# Enable detailed monitoring for EC2 instances
aws ec2 monitor-instances --instance-ids <instance-id>
Set Up Automated Backups
# RDS automated backups are already configured
# For file backups, use AWS Backup
aws backup create-backup-plan --backup-plan file://backup-plan.json
Troubleshooting Common Issues
Issue: Stack Creation Fails
Check:
# View stack events
aws cloudformation describe-stack-events \
--stack-name magento-production \
--max-items 20
# Check specific resource failures
aws cloudformation describe-stack-resources \
--stack-name magento-production \
--logical-resource-id <resource-name>
Issue: EC2 Instances Not Joining Target Group
Solutions:
- Check security group rules
- Verify user data script executed successfully
- Check CloudWatch logs:
aws logs tail /aws/ec2/magento --follow
Issue: Database Connection Failed
Check:
- Security group allows traffic from web servers
- Database is in private subnet
- Endpoint is correct:
aws rds describe-db-instances \
--db-instance-identifier magento-prod-database \
--query 'DBInstances[0].Endpoint.Address'
Issue: Redis Connection Failed
Check:
- ElastiCache security group configuration
- Redis endpoint:
aws elasticache describe-replication-groups \
--replication-group-id magento-prod-redis-cache \
--query 'ReplicationGroups[0].NodeGroups[0].PrimaryEndpoint.Address'
Cost Optimization Tips
- Use Reserved Instances: For predictable workloads
- Right-size Instances: Start small, scale up as needed
- Use Spot Instances: For non-critical workloads
- Enable S3 Lifecycle Policies: Move old files to Glacier
- Use CloudFront: Reduce data transfer costs
- Monitor Costs: Set up billing alerts
# Set up billing alert
aws budgets create-budget \
--account-id <your-account-id> \
--budget file://budget.json
Security Best Practices
- Use Private Subnets: Keep databases and cache in private subnets
- Security Groups: Follow least privilege principle
- Encryption: Enable encryption at rest and in transit
- IAM Roles: Use IAM roles instead of access keys
- VPC Flow Logs: Enable for network monitoring
- Regular Updates: Keep AMIs and software updated
# Enable VPC Flow Logs
aws ec2 create-flow-logs \
--resource-type VPC \
--resource-ids <vpc-id> \
--traffic-type ALL \
--log-destination-type s3 \
--log-destination arn:aws:s3:::your-logs-bucket/
Updating the Stack
To update your stack:
# Update stack
aws cloudformation update-stack \
--stack-name magento-production \
--template-body file://magento-cloudformation.yaml \
--parameters file://parameters.json
Deleting the Stack
To clean up resources:
# Delete stack (this deletes all resources)
aws cloudformation delete-stack --stack-name magento-production
# Monitor deletion
aws cloudformation describe-stacks --stack-name magento-production
Warning: This will delete all resources including databases. Make sure to backup first!
Understanding the Architecture
Request Flow
- User Request → CloudFront CDN (if configured)
- CloudFront → Application Load Balancer
- ALB → EC2 Instance (Auto Scaling selects healthy instance)
- EC2 → Checks Redis cache
- If cache miss → Queries RDS database
- Response → Cached in Redis → Returned to user
Component Responsibilities
- VPC: Network isolation and security
- ALB: Traffic distribution and health checks
- Auto Scaling: Automatic capacity management
- RDS: Managed database with backups
- ElastiCache: Fast in-memory caching
- S3: Scalable media storage
- CloudFront: Global content delivery
Key Takeaways
- Infrastructure as Code: CloudFormation makes deployments repeatable
- High Availability: Multi-AZ deployment ensures uptime
- Auto Scaling: Automatically handles traffic spikes
- Managed Services: RDS and ElastiCache reduce operational overhead
- Security: VPC and security groups provide network isolation
- Cost Management: Right-size resources and use reserved instances
Next Steps
- Set Up CI/CD: Automate deployments with CodePipeline
- Configure Monitoring: Set up CloudWatch dashboards
- Implement Backups: Automate database and file backups
- Add SSL: Configure ACM certificate and HTTPS
- Optimize Performance: Tune Redis and database settings
- Set Up Logging: Centralize logs with CloudWatch Logs
Resources for Further Learning
- AWS CloudFormation Documentation
- Magento 2 on AWS Whitepaper
- AWS Well-Architected Framework
- Magento 2 Performance Best Practices
Conclusion
Congratulations! You’ve successfully deployed Magento 2 on AWS using CloudFormation. This infrastructure provides:
- Scalability: Auto Scaling handles traffic automatically
- Reliability: Multi-AZ deployment ensures high availability
- Performance: Redis caching and CloudFront CDN optimize speed
- Security: VPC isolation and security groups protect your infrastructure
- Maintainability: Infrastructure as Code makes updates easy
Remember:
- Always test changes in a development environment first
- Monitor costs and optimize resource usage
- Keep your CloudFormation templates in version control
- Document any customizations you make
- Set up billing alerts to avoid surprises
With this foundation, you can now focus on building your e-commerce business while AWS handles the infrastructure complexity!
Happy deploying! 🚀