Cloud Basics for Backend Engineers: EC2, S3, RDS, IAM, CloudWatch
Enough AWS to be dangerous: EC2 instance types, S3 storage, RDS databases, IAM roles, and CloudWatch observability.
Cloud Basics
You don't need an AWS certification to be effective as a backend engineer, but you do need enough cloud fluency to reason about where your service runs, where its data lives, who's allowed to touch what, and how you'd know if something broke. This guide covers the five AWS services that show up in almost every backend job — EC2, S3, RDS, IAM, and CloudWatch — at the depth a backend engineer actually uses them, not the depth a cloud architect needs.
1. Why This Matters Even If "DevOps Owns Infra"
Even on teams with a dedicated platform/DevOps function, backend engineers routinely need to:
- Debug why a service can't reach a database (security group / VPC issue)
- Understand why an S3 upload is failing (IAM permissions, not application bugs)
- Read CloudWatch metrics to diagnose a memory leak or throttling
- Estimate the cost impact of a design choice (an unbounded S3
ListObjectsloop, an oversized RDS instance) - Reason about instance sizing when proposing a new service
The mental model that transfers everywhere: cloud "primitives" map to things you already understand — EC2 is a server, S3 is a filesystem/blob store accessed over HTTP, RDS is a managed database, IAM is an authorization system, and CloudWatch is logging + metrics + alerting. The complexity is in the knobs, not the concepts.
2. EC2: Elastic Compute Cloud
EC2 gives you a virtual machine — a raw server you control at the OS level. It's the foundation everything else (including container platforms like ECS/EKS) ultimately runs on top of.
Instance families
| Family | Optimized for | Backend use case |
|---|---|---|
T-series (t3, t4g) | Burstable, low baseline cost | Dev/staging, low-traffic services |
M-series (m6i, m7g) | Balanced CPU:memory | General-purpose API services (most common default) |
C-series (c6i, c7g) | Compute-optimized (high CPU:memory ratio) | CPU-bound workloads — encoding, heavy computation |
R-series (r6i, r7g) | Memory-optimized | In-memory caches, large JVM heaps, data processing |
I-series (i4i) | High local NVMe I/O | Self-managed databases, high-throughput storage |
The g suffix (e.g., m7g, t4g) denotes Graviton — AWS's ARM-based processors. They're typically 20-40% cheaper for the same performance on workloads that don't depend on x86-specific native libraries. For a standard Spring Boot service on a JVM, Graviton is usually a safe, meaningful cost win — verify your base Docker image has ARM64 support first.
Purchasing options
| Option | Discount vs on-demand | Commitment | Best for |
|---|---|---|---|
| On-Demand | None (baseline) | None | Unpredictable or short-lived workloads |
| Reserved Instances | ~40-60% | 1-3 year term | Steady-state production baseline capacity |
| Savings Plans | ~40-65% | 1-3 year $/hr commitment | Flexible across instance families |
| Spot Instances | Up to 90% | None, but can be reclaimed with ~2 min notice | Stateless, interruption-tolerant batch/worker jobs |
Never run your primary stateful database or a service that can't tolerate sudden termination on Spot Instances. Spot is excellent for stateless horizontally-scaled workers (batch jobs, async queue consumers) that can be safely retried elsewhere.
Sizing in practice
# From an EC2 instance, check what you're actually using before resizing
top # CPU/memory snapshot
vmstat 1 5 # memory, swap, CPU over 5 seconds
free -h # memory headroom
# Right-sizing rule of thumb: if steady-state CPU is consistently
# under 30-40%, you're likely over-provisioned. If it's pinned near
# 100% under normal load with no headroom for spikes, scale up or out.3. S3: Simple Storage Service
S3 is object storage — think of it as a massively durable, HTTP-accessible key-value store for files, not a filesystem you mount and randomly seek within.
Storage classes
| Class | Retrieval | Cost | Use case |
|---|---|---|---|
| S3 Standard | Milliseconds | Highest | Frequently accessed files (user uploads, active assets) |
| S3 Intelligent-Tiering | Milliseconds | Auto-optimized | Unknown/changing access patterns |
| S3 Standard-IA (Infrequent Access) | Milliseconds | Lower storage, retrieval fee | Backups accessed occasionally |
| S3 Glacier Instant Retrieval | Milliseconds | Very low | Compliance archives needing occasional fast access |
| S3 Glacier Flexible Retrieval | Minutes to hours | Lowest | Long-term archives, rarely accessed |
A backend engineer's actual S3 usage
// Generating a presigned URL — lets a client upload/download directly to S3
// without your service proxying the bytes through its own compute
S3Presigner presigner = S3Presigner.create();
PutObjectRequest putRequest = PutObjectRequest.builder()
.bucket("acme-invoices")
.key("invoices/2026/08/inv-" + invoiceId + ".pdf")
.contentType("application/pdf")
.build();
PresignedPutObjectRequest presignedRequest = presigner.presignPutObject(
b -> b.signatureDuration(Duration.ofMinutes(10)).putObjectRequest(putRequest)
);
String uploadUrl = presignedRequest.url().toString();
// Return uploadUrl to the client; it PUTs the file directly to S3Presigned URLs are the standard pattern for file uploads/downloads in a backend service. Proxying large file bytes through your application server wastes compute, memory, and bandwidth on work that's purely I/O. Generate a short-lived presigned URL and let the client talk to S3 directly.
Key S3 concepts for backend work
- Buckets are globally named, regionally located — bucket names are unique across all of AWS, but the data lives in one region.
- Keys are strings, not paths —
invoices/2026/08/inv-123.pdflooks like a directory structure but S3 is a flat key-value namespace; the "folders" in the console are a UI convenience. - Versioning protects against accidental overwrite/delete — enable it on buckets holding anything irreplaceable.
- Lifecycle rules automatically transition or expire objects (e.g., move to Glacier after 90 days, delete after 7 years) — set these instead of writing cleanup cron jobs.
- Bucket policies vs IAM policies: a bucket policy is attached to the bucket (resource-based); an IAM policy is attached to a user/role (identity-based). Both are evaluated, and either can grant or deny.
4. RDS: Relational Database Service
RDS is a managed relational database — AWS handles patching, backups, failover, and storage scaling, while you keep full SQL access to the engine of your choice (PostgreSQL, MySQL, etc.).
| Feature | What it does | Why it matters |
|---|---|---|
| Multi-AZ deployment | Synchronous standby replica in a different Availability Zone | Automatic failover (~1-2 min) on primary failure, no manual intervention |
| Read replicas | Asynchronous copies, can serve read traffic | Offload reporting/analytics queries from the primary; replicas can lag |
| Automated backups | Daily snapshot + transaction logs | Point-in-time restore to any second within the retention window |
| Storage autoscaling | Grows storage automatically as data grows | Avoids manual intervention for growing tables |
| Parameter groups | Engine-level config (max connections, buffer sizes) | Tune the database engine without SSH access to the underlying host |
Read replicas are eventually consistent, not synchronous. A common bug: writing data then immediately reading it back from a replica for the same request — the write may not have propagated yet, and the read returns stale or missing data. Route read-after-write logic to the primary, and use replicas only for queries that can tolerate lag (reporting, dashboards, search indexing).
# Connecting from an app — the connection string is the entire "cloud"
# abstraction most backend code actually needs to know about
jdbc:postgresql://order-db.abc123xyz.us-east-1.rds.amazonaws.com:5432/ordersMulti-AZ is for availability, not read scaling. The standby in a Multi-AZ pair is not queryable — it exists purely as a failover target. If you need to scale read throughput, add read replicas separately; Multi-AZ and read replicas solve different problems and are often used together.
5. IAM: Identity and Access Management
IAM controls who (identity) can do what (action) to which resource (scope), and is the single most important AWS service to understand correctly — misconfigured IAM is the root cause of most cloud security incidents.
Core concepts
| Concept | What it is |
|---|---|
| User | A long-lived identity for a person (or, historically, an application — now discouraged) |
| Role | A temporary identity assumed by a service, application, or federated user — no long-lived credentials |
| Policy | A JSON document defining allowed/denied actions on resources |
| Group | A collection of users sharing the same policies |
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowInvoiceBucketReadWrite",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::acme-invoices/invoices/*"
},
{
"Sid": "AllowListBucketOnly",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::acme-invoices",
"Condition": {
"StringLike": { "s3:prefix": "invoices/*" }
}
}
]
}The single most important IAM habit: least privilege, scoped resources, never "Action": "*" on "Resource": "*". A policy that grants an EC2 instance role full S3 access "to make development easier" turns a minor application bug into an incident where every bucket in the account is readable/writable from a compromised service.
Roles for EC2/ECS — never hardcode credentials
Never put AWS access keys in application config, environment variables in source control, or .env files committed to Git. Attach an IAM role to the EC2 instance, ECS task, or Lambda function instead — the AWS SDK automatically discovers and rotates temporary credentials via the instance/task metadata service. This eliminates an entire category of leaked-credential incidents.
Policy evaluation logic
- By default, everything is implicitly denied.
- An explicit
Allowin any applicable policy grants the permission. - An explicit
Denyanywhere always wins, overriding anyAllow.
6. CloudWatch: Logs, Metrics, and Alarms
CloudWatch is where you go to answer "is it broken, and why" without SSH-ing into anything.
| Capability | What it does | Example use |
|---|---|---|
| Logs | Centralized, searchable log aggregation | Application logs shipped from EC2/ECS via the CloudWatch agent |
| Metrics | Time-series numeric data (CPU, memory, request count, custom app metrics) | CPUUtilization, DatabaseConnections, a custom orders.created counter |
| Alarms | Threshold-based triggers on a metric | Page on-call if p99 latency > 2s for 5 consecutive minutes |
| Dashboards | Visual composition of metrics/logs | An "Order Service Health" dashboard for on-call |
| Logs Insights | Query language over log data | `fields @timestamp, @message |
# CloudWatch Logs Insights query — find the top error types in the last hour
fields @timestamp, @message
| filter @message like /ERROR/
| stats count(*) as errorCount by bin(5m)
| sort errorCount descA realistic alarm
# CloudFormation-style alarm definition
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: order-service-high-p99-latency
MetricName: TargetResponseTime
Namespace: AWS/ApplicationELB
ExtendedStatistic: p99
Period: 60
EvaluationPeriods: 5
Threshold: 2.0
ComparisonOperator: GreaterThanThreshold
AlarmActions:
- arn:aws:sns:us-east-1:123456789012:pagerduty-criticalCustom application metrics matter as much as infrastructure metrics. CPU and memory tell you the box is healthy; they don't tell you that checkout is failing. Emit business-relevant metrics from your service (orders.created, payment.failed, queue.depth) alongside the infrastructure defaults — that's usually what actually pages you before customers notice.
7. Cost Awareness
Cost is a design constraint, not just a finance concern. A few habits that consistently matter:
- Right-size before you scale out — an oversized
m6i.2xlargerunning at 15% CPU is a bigger, cheaper-to-fix problem than most people assume. - S3 lifecycle rules over manual cleanup — transitioning to Glacier or expiring old objects is nearly free to set up and directly reduces storage cost every month.
- Watch data transfer, not just compute — cross-AZ and cross-region traffic is billed and frequently the surprise line item on a bill.
- Reserved Instances / Savings Plans for steady-state baseline — Spot/On-Demand for elastic peaks on top of that baseline.
- CloudWatch alarms on cost anomalies, not just performance — AWS Budgets/Cost Anomaly Detection catches a runaway
PutMetricDataloop or a forgotten unused RDS instance before it becomes a five-figure surprise.
Key takeaways
- Cloud services map to concepts you already know — EC2 is a VM, S3 is object storage over HTTP, RDS is a managed database, IAM is authZ, CloudWatch is logs/metrics/alarms. Complexity is in configuration, not concepts.
- Use Graviton (ARM,
g-suffix) instance types by default for standard JVM workloads — meaningful cost savings with no code change, provided your images support ARM64. - Presigned S3 URLs let clients upload/download directly, keeping large file bytes off your application servers entirely.
- Read replicas are asynchronous and can lag — never rely on immediate read-after-write consistency against a replica.
- Multi-AZ RDS is for failover/availability; it is not a mechanism for scaling read throughput — use read replicas for that.
- IAM roles attached to EC2/ECS/Lambda eliminate the need for hardcoded credentials — the SDK fetches and rotates temporary credentials automatically.
- An explicit
Denyin any IAM policy always wins over anyAllow, and everything is implicitly denied unless explicitly allowed. - Emit custom application metrics (business events, not just infrastructure) to CloudWatch — infra health and business health are different signals.
Interview Questions
- What's the difference between EC2, ECS, and Lambda, and when would you choose each?
- Why might you choose a Graviton (ARM) instance over an equivalent x86 instance?
- What's the difference between On-Demand, Reserved, Savings Plans, and Spot pricing? What workloads suit Spot?
- What is a presigned URL in S3, and why is it preferable to proxying file uploads through your application server?
- Explain S3 storage classes and when you'd use Glacier vs Standard-IA vs Standard.
- What's the difference between RDS Multi-AZ and RDS read replicas? What problem does each solve?
- Why can reading from a read replica immediately after a write return stale data? How would you work around it?
- What is the difference between an IAM user, an IAM role, and an IAM policy?
- Why is attaching an IAM role to an EC2 instance preferred over storing AWS access keys in application configuration?
- How does IAM policy evaluation work when there's both an
Allowand aDenythat apply to the same request? - What's the difference between a CloudWatch metric and a CloudWatch log, and when do you reach for each while debugging?
- How would you set up an alarm to page on-call when p99 latency exceeds a threshold?
- What's the practical difference between a bucket policy and an IAM policy for controlling S3 access?
- Describe how you'd right-size an EC2 instance that appears over-provisioned.