Skip to content
ExponaBLOG
AWS FOR DEVELOPERS · DAY 014 / 100

EC2 essentials: a complete AWS guide for production teams

Virtual machines, chosen and configured well.

Expona Admin7 min read

Introduction

EC2 is where most of your stateless workloads will run in AWS—web servers, API backends, workers, dashboards. Getting the instance type, storage, and access patterns right from the start saves you from costly refactors and security incidents months into production.

This guide walks you through EC2 essentials: a complete AWS guide for production teams. We'll cover choosing the right instance family for your workload, building custom AMIs to slash boot times, configuring EBS volumes for predictable performance, and accessing your instances securely without managing SSH keys. By the end, you'll have a template for launching production-grade EC2 clusters that scale reliably.

Instance families — matching compute to your workload

AWS groups EC2 instances into families based on their hardware strengths. Picking the wrong family wastes money; picking the right one means you can run fewer instances and still meet your SLAs.

The four main families you'll encounter are:

  • General purpose (M-series): Balanced CPU, memory, and networking. Best for web servers, small databases, application servers, and most SaaS backends. Start here unless you have a specific reason not to.
  • Compute optimized (C-series): High CPU-to-memory ratio. Use for batch jobs, ML inference, video transcoding, or heavy number-crunching that doesn't need much RAM.
  • Memory optimized (R-series): Massive RAM pools. For in-memory databases, caches, real-time analytics, and trading systems where you load entire datasets into memory.
  • Burstable (T-series): CPU credits that let you spike when needed, then return to baseline. Ideal for development environments, low-traffic apps, and workloads with predictable idle periods.

A real-world example: your team launches a NestJS API on an m6i.large (general purpose) for the first sprint. Traffic is light, CPU sits at 15%, memory at 40%. You could downsize to a t3.medium (burstable) and save 60% on compute cost while still handling traffic spikes thanks to CPU credits. But if you're running a PostgreSQL replica that holds your hot working set in shared_buffers, you'd want an r6i.xlarge (memory optimized) instead.

Always profile your app before choosing. Launch a general-purpose instance, run realistic traffic through CloudWatch, and look at CPU and memory utilization over a week. Then right-size.

AMIs — bake images, launch fast

An AMI (Amazon Machine Image) is a pre-configured template you launch instances from. AWS provides dozens of public AMIs with common operating systems. But you'll want to build your own.

Why? Because every time you launch an instance from a bare Linux AMI, you have to:

  1. Install your runtime (Node.js, .NET, Java, Go).
  2. Install system dependencies (PostgreSQL client, ffmpeg, gRPC tooling).
  3. Copy your application code.
  4. Build or compile your app.
  5. Start the service.

All of this takes 3–10 minutes depending on your stack. When you auto-scale from 5 to 20 instances, you're waiting 10+ minutes for the fleet to warm up. Users see elevated latency. You might miss your spike.

Baking an AMI means you do steps 1–4 once, snapshot the result, and launch new instances from it. Boot time drops to 30–60 seconds. Scaling is fast. Deployments are reproducible.

Use Packer (by HashiCorp) to automate AMI creation. Here's a minimal example:

bash
# packer-config.hcl
source "amazon-ebs" "web_server" {
  ami_name      = "my-api-{{timestamp}}"
  instance_type = "t3.micro"
  source_ami_filter {
    filters = {
      name = "amzn2-ami-hvm-*"
      root-device-type = "ebs"
    }
    most_recent = true
    owners      = ["amazon"]
  }
}

build {
  sources = ["source.amazon-ebs.web_server"]

  provisioner "shell" {
    inline = [
      "sudo yum update -y",
      "sudo yum install -y nodejs npm",
      "cd /tmp && npm install -g pm2"
    ]
  }

  provisioner "file" {
    source      = "./app"
    destination = "/home/ec2-user/app"
  }

  provisioner "shell" {
    inline = [
      "cd /home/ec2-user/app && npm ci --production",
      "sudo pm2 install pm2-auto-pull"
    ]
  }
}

Run packer build packer-config.hcl, wait 2–3 minutes, and you have an AMI with Node.js, npm, pm2, and your app code ready to go. Launch 50 instances from it and they're all production-ready in under a minute.

Version your AMIs. Tag them with the commit hash, date, or semantic version. Store the Packer config in your repo alongside your application code. Treat AMIs like you treat Docker images—immutable, auditable, repeatable.

EBS volumes — block storage with tunable performance

EBS (Elastic Block Store) is the persistent block storage you attach to EC2 instances. Every EC2 instance needs at least one EBS volume for the root filesystem. You can attach additional volumes for data, logs, or databases.

Three volume types matter for production:

  • gp3 (general purpose): The default. 3,000 baseline IOPS, 125 MB/s baseline throughput. Good for most workloads. You pay per GB per month plus a small extra charge if you provision above baseline.
  • io2 (provisioned IOPS): When you need guaranteed, consistent high IOPS (say, 20,000+). Expensive but essential for databases handling thousands of concurrent connections.
  • st1 (throughput optimized HDD): Cheap, high throughput, high latency. Use for big data, Hadoop, streaming workloads where you don't care about random access latency.

A common mistake: using the default gp3 with baseline IOPS when your application actually needs more. Your database is slow, you increase instance size (expensive), and latency barely improves. The real problem is disk IOPS.

Launch a t3.medium for your PostgreSQL dev environment with a 100 GB gp3 volume at baseline (3,000 IOPS, 125 MB/s). Run pgbench to simulate load. If you see iowait high and queries slow, increase the volume's provisioned IOPS to 6,000 or 10,000. Cost increases maybe $10–$20/month, but your database is now responsive again.

When provisioning EBS for production, ask:

  • How many random read/write operations per second does your app need?
  • Do you need consistent performance (io2) or is occasional throttling acceptable (gp3)?
  • How much total throughput (MB/s) will you drive simultaneously?
  • Can you use snapshots and encryption without breaking your compliance or performance SLA?

Always enable EBS-optimized instances (it's free or included on newer instance types). Attach EBS volumes to the instance's NVMe controller for lower latency. Monitor VolumeReadOps, VolumeWriteOps, and VolumeThroughputPercentage in CloudWatch so you catch throttling before users do.

Session Manager — secure shell access without SSH keys

SSH keys are a classic attack surface. Developers lose them, leave them in Docker images, check them into repos. Then you're rotating keys across 50 instances, or worse, someone has root access to your fleet.

AWS Systems Manager Session Manager offers shell access directly from the AWS console or CLI. No SSH keys. No open port 22. Access is logged to CloudTrail. Permissions are managed via IAM.

To use it:

  1. Create an IAM role for your EC2 instances with the AmazonSSMManagedInstanceCore policy.
  2. Launch instances with that role.
  3. Ensure the instance can reach the Systems Manager API (via a VPC endpoint or NAT).
  4. Use the AWS CLI: aws ssm start-session --target i-0123456789abcdef0.

That's it. You're in a shell on the instance. No keys to distribute. No port 22 open. All session activity is logged.

Here's an IAM policy example:

bash
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Attach the AWS managed policy AmazonSSMManagedInstanceCore to the role, then create an instance profile and launch your instance with it.

You can also restrict access further. Only allow certain IAM users to start sessions on instances with a specific tag, or only during business hours. Session Manager integrates with your CloudTrail logs, so you have a full audit trail of who accessed what and when.

One caveat: if your instance is in a private subnet with no internet egress, it needs a VPC endpoint for Systems Manager. Create endpoints for ssm, ssmmessages, and ec2messages in your VPC. It's a few clicks, and then even air-gapped instances can use Session Manager.

Common mistakes

Launching instances without a custom AMI. You pay for launch time and operational complexity. Every instance takes 5+ minutes to provision. Deployments are slow. Scale and use Packer.

Ignoring EBS performance tuning. You upgrade to a bigger, more expensive instance type when the real bottleneck is disk IOPS. Monitor CloudWatch metrics and tune volume provisioning separately from compute.

Using EC2 as a database without considering memory. You pick a general-purpose instance for your Redis cache or in-memory store. It runs out of memory, pages to disk, and becomes useless. Use memory-optimized instances for stateful workloads.

Keeping SSH keys in repos or CI/CD pipelines. Session Manager is simpler, more auditable, and safer. Stop copying keys around.

Not tagging instances. You launch 200 instances and forget what each one does. You can't filter by project, environment, or cost center. Tag everything from day one.

Key takeaways

  • Choose the right instance family first: general purpose for balanced workloads, compute for CPU-heavy jobs, memory for data-in-memory systems, burstable for variable load.
  • Bake custom AMIs with Packer to reduce boot time from 5+ minutes to 30–60 seconds and make deployments reproducible.
  • Use gp3 for most workloads and tune IOPS separately from storage size. Monitor CloudWatch metrics to catch throttling early.
  • Replace SSH keys with Session Manager for simpler, more auditable access control.
  • Profile your actual workload before right-sizing. Small instances with high utilization often outperform large instances running idle.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles

EC2 essentials | Developer Guide by Expona Engineering