DevOps & Cloud
AWS Cloud Infrastructure: EC2, S3, CloudFront & Lambda Guide
AWS (Amazon Web Services) provides a comprehensive cloud infrastructure for hosting modern applications in a scalable and reliable manner. With EC2 you get virtual servers, S3 offers object storage, CloudFront delivers CDN capabilities, Lambda enables serverless functions, and VPC creates isolated network architectures. In this article, I will cover how I use AWS cloud infrastructure in production environments and explain the critical role each service plays in different scenarios.
My experience automating AWS deployment processes across multiple projects has significantly accelerated infrastructure decisions. The right combination of services makes it possible to achieve optimal results in both cost and performance.
EC2: Virtual Server Management
Instance Types and Selection Criteria
EC2 instance selection should be based on your application's CPU, memory, and network requirements:
- t3.micro/t3.small: Development environments and low-traffic applications
- t3.medium/t3.large: Medium-scale API servers and web applications
- c5/c6i series: CPU-intensive operations (video encoding, data processing)
- r5/r6i series: Memory-intensive applications (in-memory cache, databases)
- m5/m6i series: General-purpose balanced workloads
You can quickly launch an EC2 instance using the AWS CLI:
# Create a security group
aws ec2 create-security-group \
--group-name api-server-sg \
--description "API Server Security Group" \
--vpc-id vpc-0abc123def456
# Open HTTP and SSH ports
aws ec2 authorize-security-group-ingress \
--group-id sg-0abc123 \
--protocol tcp --port 80 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id sg-0abc123 \
--protocol tcp --port 22 --cidr 203.0.113.0/24
# Launch an EC2 instance
aws ec2 run-instances \
--image-id ami-0abcdef1234567890 \
--instance-type t3.medium \
--key-name my-key-pair \
--security-group-ids sg-0abc123 \
--subnet-id subnet-0abc123 \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=api-server},{Key=Environment,Value=production}]'High Availability with Auto Scaling
A single EC2 instance is not sufficient for production environments. With an Auto Scaling Group (ASG), you can build an architecture that automatically responds to traffic spikes. By defining a Launch Template, you standardize AMI, instance type, and user data settings, while the ASG manages the minimum, maximum, and desired instance counts.
When configuring an ASG, I recommend using a target tracking scaling policy. For example, you can configure it to add new instances when CPU utilization exceeds 70% and remove instances when it drops below 30%. This approach prevents unnecessary costs while preserving user experience during traffic spikes.
IAM: Identity and Access Management
Principle of Least Privilege
AWS IAM (Identity and Access Management) controls who can access your resources and under what conditions. Applying the principle of least privilege in production is a critical security requirement. Every service and user should only have the permissions needed to fulfill their specific role.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3ReadOnly",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-app-assets-prod",
"arn:aws:s3:::my-app-assets-prod/*"
]
},
{
"Sid": "AllowCloudWatchLogs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:eu-central-1:123456789:log-group:/app/myapi:*"
}
]
}By attaching IAM roles to your EC2 instances, you can securely access AWS services without using access keys. This approach eliminates the risk of hardcoded credentials and solves the credential rotation problem entirely.
S3: Object Storage and Static Hosting
S3 is an infinitely scalable object storage service. It is ideal for static files, media content, backups, and log files.
# Create an S3 bucket
aws s3 mb s3://my-app-assets-prod --region eu-central-1
# Set bucket policy for CloudFront access
aws s3api put-bucket-policy --bucket my-app-assets-prod --policy '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontAccess",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-app-assets-prod/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::123456789:distribution/E1234"
}
}
}
]
}'
# Lifecycle policy - move to Glacier after 90 days
aws s3api put-bucket-lifecycle-configuration \
--bucket my-app-assets-prod \
--lifecycle-configuration '{
"Rules": [
{
"ID": "MoveToGlacier",
"Status": "Enabled",
"Filter": { "Prefix": "backups/" },
"Transitions": [
{ "Days": 90, "StorageClass": "GLACIER" }
]
}
]
}'S3 Storage Classes
Choosing the right storage class is critical for cost optimization:
- S3 Standard: Frequently accessed data (API response cache, active media files)
- S3 Intelligent-Tiering: Data with unpredictable access patterns
- S3 Glacier: Archival data (log backups, old reports)
- S3 Glacier Deep Archive: Long-term archival (compliance data)
Automated Cost Management with S3 Lifecycle
Lifecycle policies should be used not only for storage class transitions but also for cleaning up old versions and incomplete multipart uploads:
# Advanced lifecycle policy
aws s3api put-bucket-lifecycle-configuration \
--bucket my-app-assets-prod \
--lifecycle-configuration '{
"Rules": [
{
"ID": "CleanupOldVersions",
"Status": "Enabled",
"Filter": {},
"NoncurrentVersionExpiration": {
"NoncurrentDays": 30
},
"AbortIncompleteMultipartUpload": {
"DaysAfterInitiation": 7
}
},
{
"ID": "ArchiveLogs",
"Status": "Enabled",
"Filter": { "Prefix": "logs/" },
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER" }
],
"Expiration": { "Days": 365 }
}
]
}'With this configuration, log files are moved to Infrequent Access after 30 days, then to Glacier after 90 days, and automatically deleted after 365 days. Such tiered transitions can save hundreds of dollars monthly on large-scale projects.
Lambda: Serverless Functions
AWS Lambda allows you to run code without managing servers. It is ideal for event-driven architectures, API Gateway integration, and scheduled tasks.
// Lambda function - Image processing triggered by S3 event
const { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3');
const sharp = require('sharp');
const s3 = new S3Client({ region: 'eu-central-1' });
exports.handler = async (event) => {
const bucket = event.Records[0].s3.bucket.name;
const key = event.Records[0].s3.object.key;
// Retrieve the original image
const original = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
const imageBuffer = await streamToBuffer(original.Body);
// Generate thumbnails in different sizes
const sizes = [
{ width: 150, suffix: 'thumb' },
{ width: 600, suffix: 'medium' },
{ width: 1200, suffix: 'large' }
];
for (const size of sizes) {
const resized = await sharp(imageBuffer)
.resize(size.width)
.webp({ quality: 80 })
.toBuffer();
const outputKey = key.replace(/\.[^.]+$/, `-${size.suffix}.webp`);
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: `processed/${outputKey}`,
Body: resized,
ContentType: 'image/webp'
}));
}
return { statusCode: 200, body: 'Images processed successfully' };
};To minimize Lambda cold start issues, you can use Provisioned Concurrency or keep your function package small to reduce initialization time. Additionally, sharing common libraries through Lambda Layers is an effective way to reduce deployment package sizes.
VPC: Network Isolation and Security
VPC (Virtual Private Cloud) enables you to run your AWS resources in an isolated network environment. You can create layered security using public and private subnets:
- Public Subnet: Internet-facing resources like Load Balancers and bastion hosts
- Private Subnet: Resources that should not be publicly accessible, such as API servers and databases
- NAT Gateway: Allows resources in private subnets to access the internet for outbound traffic
- Security Groups: Instance-level firewall rules
- Network ACL: Additional security layer at the subnet level
For production environments, I strongly recommend creating subnets in at least two Availability Zones and keeping your databases inside private subnets. This architecture is a fundamental requirement for both security and high availability.
CloudFront: Global Distribution with CDN
CloudFront minimizes latency by serving your static and dynamic content from edge locations around the world. Adding a CloudFront distribution in front of your S3 buckets improves performance while reducing S3 costs. Using Origin Access Control (OAC) to block direct access to the S3 bucket and routing all traffic through CloudFront is the best security practice.
# Create CloudFront distribution (AWS CLI)
aws cloudfront create-distribution \
--distribution-config '{
"CallerReference": "my-app-dist-2025",
"Origins": {
"Quantity": 1,
"Items": [
{
"Id": "S3Origin",
"DomainName": "my-app-assets-prod.s3.eu-central-1.amazonaws.com",
"S3OriginConfig": {
"OriginAccessIdentity": ""
},
"OriginAccessControlId": "E2QWRUHAPOMQZL"
}
]
},
"DefaultCacheBehavior": {
"TargetOriginId": "S3Origin",
"ViewerProtocolPolicy": "redirect-to-https",
"CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
"Compress": true,
"AllowedMethods": { "Quantity": 2, "Items": ["GET", "HEAD"] }
},
"Enabled": true,
"Comment": "Production CDN"
}'Triggering CloudFront cache invalidation automatically after deployments ensures users always see the latest content. However, since invalidation has a cost, using version hashes in file names for cache busting is a more cost-effective approach.
Cost Optimization
To keep AWS costs under control, apply these strategies:
- Reserved Instances / Savings Plans: Acquire resources at 30-60% below on-demand pricing for predictable workloads
- Spot Instances: Up to 90% discount for interruption-tolerant workloads (batch processing, CI/CD)
- Right-sizing: Review CloudWatch metrics and downsize over-provisioned instances
- S3 Intelligent-Tiering: Automatic cost optimization for data with unpredictable access patterns
- NAT Gateway alternative: Use NAT Instances in low-traffic environments to save on monthly NAT Gateway costs
Use AWS Cost Explorer to analyze monthly spending by service and set up Budget alarms to be immediately notified of unexpected cost increases.
Practical Tips and Summary
To effectively use AWS cloud infrastructure, keep these points in mind:
- Infrastructure as Code: Manage your infrastructure as code using CloudFormation or Terraform
- Cost Monitoring: Control costs with AWS Cost Explorer and Budget alarms
- Security: Configure IAM roles with the principle of least privilege; never use the root account for daily operations
- Monitoring: Set up proactive monitoring with CloudWatch metrics, logs, and alarms
- Multi-AZ: Run critical services across multiple Availability Zones for high availability
Combining AWS services correctly directly impacts both the scalability and reliability of your application. Starting small and scaling as needed is the healthiest AWS deployment strategy.
Related Articles
Docker Container Guide: Dockerfile, Compose & Multi-Stage Builds
Docker containerization guide. Writing Dockerfiles, multi-service Docker Compose, multi-stage build optimization, and production deployment.
CI/CD Pipeline: Automated Testing & Deployment with GitHub Actions
CI/CD pipeline setup with GitHub Actions. Automated testing, builds, Docker image creation, and AWS deployment.
Microservice Architecture with Kubernetes: Pod, Service & Deployment
Microservice architecture with Kubernetes. Pod management, Service discovery, Deployment strategies, Ingress, and Helm charts.
Have a Flutter Project?
I build high-performance Flutter applications for iOS, Android, and web.
Get in Touch