Why this matters

When I deployed this portfolio site to AWS, I had to wire together about eight different services before a single request could reach the application. If you've only ever deployed to Heroku or Railway, AWS can feel like you're assembling a plane mid-flight. But once you understand what each piece does and why it exists, it clicks.

This article covers the services you'll actually use when deploying a containerised web application on AWS — not an exhaustive reference, but the mental model you need to understand how they connect.

EC2 — Elastic Compute Cloud

EC2 is virtual machines in the cloud. You pick an instance type (t3.micro, m5.large, etc.), an operating system, and you get a Linux or Windows server you can SSH into and run anything on. EC2 is the foundation — almost everything else in AWS either runs on EC2 under the hood or exists as an alternative to managing EC2 yourself.

When would you use EC2 directly? When you need full control over the server — custom kernel settings, specific OS configuration, long-running processes that don't fit containers. For most web applications today, you'd reach for ECS or Lambda instead.

Key concepts: instance types (compute, memory, storage optimised), AMIs (Amazon Machine Images — the OS template), security groups (stateful firewall rules per instance), and Elastic IPs (static public IPs).

ECS — Elastic Container Service

ECS is AWS's container orchestration service. You give it a Docker container and it runs it, scales it, restarts it if it crashes, and distributes traffic across instances. Think of it as AWS's answer to Kubernetes — less powerful but significantly less configuration overhead.

ECS has two launch types:

  • EC2 launch type — ECS manages containers running on EC2 instances you provision and manage.
  • Fargate launch type — AWS manages the underlying servers entirely. You define CPU and memory requirements, push your container, and AWS handles the rest. No SSH access to the host, no patching, no capacity planning. This is what I use for this portfolio.

Key ECS concepts:

  • Cluster — the logical grouping of tasks/services
  • Task Definition — a blueprint specifying which container image to run, CPU/memory allocation, environment variables, port mappings, and log configuration. Immutable once created — updates create new revisions.
  • Task — a running instance of a task definition. One task = one or more containers running together.
  • Service — keeps a desired number of tasks running, handles rolling deployments, integrates with the load balancer. When you call aws ecs update-service --force-new-deployment, you're telling the service to replace running tasks with new ones using the current task definition.

ECR — Elastic Container Registry

ECR is a private Docker registry hosted by AWS. When you build a Docker image locally, you push it to ECR, and ECS pulls it from there when launching tasks. It's the equivalent of Docker Hub but private and integrated with AWS IAM for access control.

Workflow: docker build → docker tag → docker push to ECR, then ECS pulls the image when deploying. ECR also runs automatic vulnerability scanning on images using the same CVE database Trivy uses.

aws ecr get-login-password --region ap-southeast-2 | docker login --username AWS --password-stdin <account>.dkr.ecr.ap-southeast-2.amazonaws.com
docker build -t portfolio .
docker tag portfolio:latest <account>.dkr.ecr.ap-southeast-2.amazonaws.com/portfolio:latest
docker push <account>.dkr.ecr.ap-southeast-2.amazonaws.com/portfolio:latest

ALB — Application Load Balancer

The ALB sits in front of your ECS tasks and distributes incoming HTTP/HTTPS traffic across them. It operates at Layer 7 (HTTP), which means it can route based on URL paths, hostnames, and headers — not just IP/port like a classic load balancer.

Key concepts: Target Groups (a set of targets — ECS tasks in this case — that receive traffic), Listeners (rules on which port to accept traffic — 80 for HTTP, 443 for HTTPS), and Health Checks (the ALB regularly hits a path like / on each task and only sends traffic to healthy ones). During a rolling deployment, the ALB keeps sending traffic to old tasks until new ones pass health checks.

VPC — Virtual Private Cloud

A VPC is your private network inside AWS. All your resources — EC2 instances, ECS tasks, RDS databases — live inside a VPC. You control the IP ranges, subnets, routing tables, and which resources are internet-accessible versus private.

Subnets split the VPC into segments: public subnets have a route to the internet gateway (where your ALB lives), and private subnets don't (where your ECS tasks and databases should live). Security Groups act as per-resource firewalls — you define which ports accept inbound traffic from which sources.

The default VPC AWS creates for you works fine for learning, but for production you want a custom VPC with proper public/private subnet separation.

IAM — Identity and Access Management

IAM controls who and what can do what in your AWS account. There are two things you interact with constantly:

  • IAM Users — human users with credentials (access key + secret). The principle of least privilege applies: nikhil-cli should only have the permissions it actually needs.
  • IAM Roles — identities assumed by AWS services. Your ECS tasks need a role (task execution role) to pull images from ECR, write logs to CloudWatch, and access Secrets Manager. No hardcoded credentials — the task assumes the role at runtime.

IAM policies are JSON documents listing allowed (or denied) actions on resources. When you get an AccessDeniedException, you're missing a policy statement. When you're building something and not sure what permissions are needed, use IAM Access Analyzer to see what's actually being called.

Route 53

Route 53 is AWS's DNS service. It translates your domain name (nikhilsankhla.com) into the IP address of your ALB. For a standard web deployment: create an A record (or ALIAS record for ALB) pointing your domain to the ALB's DNS name. Route 53 also handles health-check-based routing and latency-based routing if you're running in multiple regions.

CloudWatch

CloudWatch is AWS's observability service — logs, metrics, and alarms in one place. ECS tasks automatically ship stdout/stderr to CloudWatch Log Groups when configured in the task definition. When a deployment fails or a task crashes, CloudWatch logs are where you go first.

Log groups are named by convention — /ecs/portfolio for a service called portfolio. CloudWatch also collects CPU and memory metrics for your ECS tasks, and you can set alarms that trigger auto-scaling policies or SNS notifications.

How they connect in a real deployment

Here's the full request path for this portfolio site: DNS query hits Route 53, resolves to the ALB. ALB listener on port 443 terminates TLS and forwards to the target group. Target group sends the request to a healthy ECS task running in a private subnet. The task (a Fargate container running the Flask app) handles the request and responds. CloudWatch receives the task's logs in the background. IAM ensures the task can only access ECR and CloudWatch — nothing else.

When I push a new image to ECR and trigger aws ecs update-service --force-new-deployment, ECS starts a new task with the updated image, waits for ALB health checks to pass, then drains and stops the old task. Zero downtime.

What to learn next

Once you're comfortable with these core services: RDS for managed databases, Secrets Manager for environment variables and credentials (instead of hardcoding in task definitions), S3 for static assets, and Auto Scaling for handling traffic spikes. The AWS Well-Architected Framework is worth reading once you have hands-on experience — it gives a structured way to think about reliability, security, performance, and cost in cloud architectures.