DevOps & Cloud

AWS Cloud Infrastructure: EC2, S3, CloudFront & Lambda Guide

14 min readMarch 15, 2026
AWS bulut altyapısıAWS EC2AWS S3AWS CloudFrontAWS LambdaAWS TürkiyeBulut bilişimCloud computingAWS rehberiServerlessAWS VPCAWS deployment

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:

bash
# 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.

json
{
  "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.

bash
# 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:

bash
# 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.

javascript
// 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.

bash
# 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:

  1. Infrastructure as Code: Manage your infrastructure as code using CloudFormation or Terraform
  2. Cost Monitoring: Control costs with AWS Cost Explorer and Budget alarms
  3. Security: Configure IAM roles with the principle of least privilege; never use the root account for daily operations
  4. Monitoring: Set up proactive monitoring with CloudWatch metrics, logs, and alarms
  5. 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

Have a Flutter Project?

I build high-performance Flutter applications for iOS, Android, and web.

Get in Touch