EC2, ASG, EKS, and Fargate: A Complete Guide to Choosing Your AWS Compute Architecture
14 min read

EC2, ASG, EKS, and Fargate: A Complete Guide to Choosing Your AWS Compute Architecture

Choosing a compute service on AWS isn’t just about picking “the newest” or “the most popular.” Each service was born from a different need, and choosing wrong can mean over-engineering a simple system or under-engineering a critical one. The four services that most often cause confusion — EC2, Auto Scaling Groups, EKS, and Fargate — each have very different philosophies, trade-offs, and sweet spots. This article dissects all four in depth so you can make architecture decisions with confidence.

The Big Picture — Four Levels of Abstraction

Before diving into the details of each service, it’s important to understand that these four services aren’t equivalent choices. They sit at different abstraction levels, and each trades control for operational ease.

%%{init: {'flowchart': {'padding': 20}} }%%
graph TB
    subgraph "AWS Compute Abstraction Levels"
        direction TB
        A["EC2<br/>Direct Virtual Machine<br/>Full control, manage everything"]
        B["Auto Scaling Group<br/>EC2 + Autoscaling Controller<br/>Automatic horizontal scaling"]
        C["EKS<br/>Managed Kubernetes<br/>Industry-standard container orchestration"]
        D["Fargate<br/>Serverless Container<br/>No server management at all"]
    end

    A -->|"+ autoscaling"| B
    B -->|"+ container orchestration"| C
    C -->|"- node management"| D

    style A fill:#fef3c7,stroke:#d97706,color:#000
    style B fill:#fde68a,stroke:#d97706,color:#000
    style C fill:#bfdbfe,stroke:#2563eb,color:#000
    style D fill:#bbf7d0,stroke:#16a34a,color:#000

The basic principle is simple: the higher the abstraction level, the smaller the operational burden — but the greater the per-unit cost and the more limited the control you have.


Amazon EC2 — The Classic Virtual Machine

EC2 is the foundation of compute on AWS. This service gives you a virtual machine with specs you choose yourself — CPU, RAM, disk, OS — then you’re free to use it like a physical server. SSH directly, install anything, configure however you like.

Why EC2 Is Still Relevant

In the era of containers and serverless, EC2 feels “old-fashioned.” But precisely because it’s the lowest level, EC2 becomes the right choice when you need full control. Legacy applications that can’t be containerized, workloads needing direct hardware access (GPU computing, high-performance networking), or systems depending on specific OS configurations — all of these fit better on EC2.

Characteristics and Trade-offs

StrengthsWeaknesses
✅ Full control over OS and configuration❌ Manual scaling without ASG
✅ Good for legacy applications and monoliths❌ Full responsibility: patching, security, availability
✅ Most flexible — install anything❌ Single point of failure if only one instance
✅ Easy to understand for engineers at any level❌ Slower provisioning than containers
✅ Per-second billing — efficient for stable workloads❌ Not automatically high-availability

When EC2 Is the Right Choice

EC2 suits these scenarios: a monolith that hasn’t been refactored, stateful workloads storing data locally, systems needing kernel modules or special drivers, and experiments or learning environments where simplicity matters more than scalability.

EC2 Anti-Patterns

The most common mistake with EC2 is running a single instance for production without redundancy. One instance means one point of failure — if the instance dies from hardware failure (which in the cloud isn’t a question of “if” but “when”), the entire system goes down with it.

graph LR
    subgraph "❌ Anti-Pattern: Single EC2"
        U1["User"] --> EC1["EC2<br/>(single instance)"] --> DB1["Database"]
    end

    style EC1 fill:#fecaca,stroke:#dc2626,color:#000
    style U1 fill:#f5f5f4,stroke:#78716c,color:#000
    style DB1 fill:#e0e7ff,stroke:#4f46e5,color:#000
graph LR
    subgraph "✅ Correct: Multi-AZ + Load Balancer"
        U2["User"] --> ALB["ALB"] --> EC2a["EC2-a<br/>AZ-1"]
        ALB --> EC2b["EC2-b<br/>AZ-2"]
        EC2a --> DB2["Database"]
        EC2b --> DB2
    end

    style ALB fill:#bbf7d0,stroke:#16a34a,color:#000
    style EC2a fill:#bfdbfe,stroke:#2563eb,color:#000
    style EC2b fill:#bfdbfe,stroke:#2563eb,color:#000
    style U2 fill:#f5f5f4,stroke:#78716c,color:#000
    style DB2 fill:#e0e7ff,stroke:#4f46e5,color:#000

EC2 Best Practices

  • Use an IAM Role attached to the instance, not access keys stored on disk.
  • Store configuration in SSM Parameter Store or Secrets Manager, not hardcoded in the AMI.
  • Create a custom AMI for deployment consistency — every new instance must be identical.
  • Enable the CloudWatch agent for CPU, memory, and disk monitoring from day one.

Auto Scaling Group — EC2 That Can Breathe

ASG isn’t a new compute service. ASG is a controller managing a group of EC2 instances automatically — adding instances when traffic rises, reducing when quiet, and replacing dead instances. If EC2 is a car, ASG is its cruise control system.

How ASG Works

ASG works based on three parameters: minimum (the lowest number of instances always alive), desired (the currently desired instance count), and maximum (the scaling upper bound). Scaling policies determine when desired changes — based on CPU utilization, request counts, custom metrics, or schedules.

graph LR
    CW["CloudWatch Alarm<br/>'CPU > 70% for 5 minutes'"]
    ASG["Auto Scaling Group<br/>min: 2 | desired: 2 | max: 8"]
    LT["Launch Template<br/>t3.medium, AMI-xxx"]

    CW -->|"trigger scale-out"| ASG
    ASG -->|"launch new instance from"| LT

    ASG --> EC2a["EC2 Instance 1<br/>AZ-1"]
    ASG --> EC2b["EC2 Instance 2<br/>AZ-2"]
    ASG -.->|"new instance"| EC2c["EC2 Instance 3<br/>AZ-1"]

    ALB["Application<br/>Load Balancer"] --> EC2a
    ALB --> EC2b
    ALB -.-> EC2c

    style CW fill:#fef3c7,stroke:#d97706,color:#000
    style ASG fill:#bfdbfe,stroke:#2563eb,color:#000
    style LT fill:#e0e7ff,stroke:#4f46e5,color:#000

Characteristics and Trade-offs

StrengthsWeaknesses
✅ Automatic high availability — dead instances replaced immediately❌ Still must manage the OS and patching
✅ Metric-based horizontal scaling❌ Relatively slow scaling (1-5 minutes per new instance)
✅ Native ALB/NLB integration❌ Not suitable for very sudden burst traffic
✅ Multi-AZ for fault tolerance❌ Applications must be stateless for scaling to work correctly
✅ Mixed instance types (spot + on-demand)❌ Warm-up time makes scaling non-instant

When ASG Is the Right Choice

ASG is the natural choice for traditional web backends, API servers, and EC2-based worker queues. This pattern is the most common in the industry because it balances simplicity with reliability. If your application isn’t containerized yet but needs high availability and autoscaling, ASG is the answer.

ASG Anti-Patterns

The biggest mistake with ASG is making the application stateful then expecting horizontal scaling to work. If every instance stores sessions in local memory, adding a new instance doesn’t help — users whose session lives on instance A will get errors when their next request lands on instance B.

graph LR
    subgraph "❌ Anti-Pattern: Sessions in Local Memory"
        R1["Request 1"] --> IA["Instance A<br/>session stored"]
        R2["Request 2"] --> IB["Instance B<br/>no session ⚠️"]
    end

    style IA fill:#fef3c7,stroke:#d97706,color:#000
    style IB fill:#fecaca,stroke:#dc2626,color:#000
    style R1 fill:#f5f5f4,stroke:#78716c,color:#000
    style R2 fill:#f5f5f4,stroke:#78716c,color:#000
graph LR
    subgraph "✅ Correct: Sessions in an External Store"
        R3["Request 1"] --> IC["Instance A"] --> RD["Redis<br/>session store"]
        R4["Request 2"] --> ID["Instance B"] --> RD
    end

    style IC fill:#bfdbfe,stroke:#2563eb,color:#000
    style ID fill:#bfdbfe,stroke:#2563eb,color:#000
    style RD fill:#bbf7d0,stroke:#16a34a,color:#000
    style R3 fill:#f5f5f4,stroke:#78716c,color:#000
    style R4 fill:#f5f5f4,stroke:#78716c,color:#000

ASG Best Practices

  • Always use a Launch Template (not the deprecated Launch Configuration) with tracked versions.
  • Put an ALB or NLB in front of the ASG — don’t expose instances directly.
  • Design the application stateless from the start. Store sessions, caches, and files in external services.
  • Combine target tracking scaling (for example a 60% CPU target) with scheduled scaling for known traffic patterns.
  • Use a mixed instances policy with spot instances to save up to 70% in costs.

Amazon EKS — Kubernetes Without Managing the Control Plane

EKS is managed Kubernetes on AWS. AWS manages the control plane (API server, etcd, scheduler), you manage the workload and — if using EC2 mode — the worker nodes. Kubernetes itself has become the de facto standard for container orchestration, and EKS makes adopting it on AWS far easier than building a cluster yourself.

Why Kubernetes, and Why EKS

Kubernetes solves the problems that arise when you have many containers: how to schedule them onto the right servers, how to ensure replica counts are correct, how to do rolling updates without downtime, and how to manage networking between services. EKS removes the heaviest burden — managing the control plane — so teams can focus on workloads.

graph TB
    subgraph "EKS Cluster"
        subgraph "AWS Managed"
            CP["Control Plane<br/>API Server, etcd,<br/>Scheduler, Controller Manager"]
        end
        subgraph "You Manage (EC2 Mode)"
            NG["Node Group"]
            NG --> N1["Worker Node 1<br/>(EC2)"]
            NG --> N2["Worker Node 2<br/>(EC2)"]
            NG --> N3["Worker Node 3<br/>(EC2)"]
        end
        subgraph "Workload"
            N1 --> P1["Pod A"]
            N1 --> P2["Pod B"]
            N2 --> P3["Pod C"]
            N2 --> P4["Pod D"]
            N3 --> P5["Pod E"]
        end
    end
    CP ---|"orchestrate"| NG

    style CP fill:#bbf7d0,stroke:#16a34a,color:#000
    style NG fill:#bfdbfe,stroke:#2563eb,color:#000

Characteristics and Trade-offs

StrengthsWeaknesses
✅ Kubernetes compliant — portable across clouds❌ Very high learning curve
✅ Huge CNCF ecosystem (Helm, Istio, ArgoCD, etc.)❌ Control plane cost ($0.10/hr ≈ $73/month) on top of worker costs
✅ Good for large-scale microservice architectures❌ Over-engineering for simple systems
✅ Rolling updates, canary deployments, blue-green native❌ More complex debugging than direct EC2
✅ Lower vendor lock-in than ECS❌ Needs a team that understands Kubernetes

When EKS Is the Right Choice

EKS is right when you run large-scale microservices (dozens to hundreds of services), have a team already experienced with Kubernetes, need portability across clouds (multi-cloud strategy), or are building an internal platform engineering layer. If you’re only running 2-3 services, EKS is most likely over-engineering.

EKS Anti-Patterns

The fatal mistake that often happens is adopting EKS without understanding Kubernetes. Teams that don’t understand the concepts of Pods, Services, Ingress, ConfigMaps, and RBAC will spend more time debugging infrastructure than building features.

graph LR
    subgraph "❌ Anti-Pattern: EKS Without Readiness"
        T1["3-person team"] --> EKS1["EKS"] --> S1["2 services"]
        S1 --> H1["80% of time<br/>spent on infra"]
    end

    style T1 fill:#f5f5f4,stroke:#78716c,color:#000
    style EKS1 fill:#fecaca,stroke:#dc2626,color:#000
    style S1 fill:#fef3c7,stroke:#d97706,color:#000
    style H1 fill:#fecaca,stroke:#dc2626,color:#000
graph LR
    subgraph "✅ Correct: EKS When Scale & Team Are Ready"
        T2["10+ person team"] --> EKS2["EKS"] --> S2["20+ services"]
        S2 --> H2["Infra handled<br/>by platform team"]
    end

    style T2 fill:#f5f5f4,stroke:#78716c,color:#000
    style EKS2 fill:#bfdbfe,stroke:#2563eb,color:#000
    style S2 fill:#bbf7d0,stroke:#16a34a,color:#000
    style H2 fill:#bbf7d0,stroke:#16a34a,color:#000

Another common mistake is running databases inside Kubernetes. Stateful workloads like databases require storage, backup, and failover management that’s far better handled by managed services (RDS, Aurora, ElastiCache).

EKS Best Practices

  • Use namespaces for isolation between teams or environments, and apply ResourceQuotas to prevent one team from exhausting cluster resources.
  • Implement HPA (Horizontal Pod Autoscaler) for CPU/memory-based scaling, and VPA (Vertical Pod Autoscaler) for right-sizing resource requests.
  • Use IRSA (IAM Role for Service Account) so each Pod only has the AWS access it needs — don’t use node-level IAM roles.
  • Require resource requests and limits in every Pod. Without them, the scheduler can’t make good placement decisions.
  • Implement an observability stack from the start: Prometheus for metrics, Grafana for dashboards, Fluent Bit for log forwarding.

AWS Fargate — Containers Without Servers

Fargate shifts the paradigm: you define the container (image, CPU, memory), and AWS runs it. No instances to SSH into, no OS to patch, no capacity planning for nodes. Fargate can be used through two orchestrators: ECS (simpler) or EKS (if you’ve already invested in Kubernetes).

How Fargate Works

Every task or pod running on Fargate gets its own isolated micro-VM. You don’t share a kernel with other tenants. This makes Fargate more secure by default than containers running on shared EC2 nodes.

graph LR
    subgraph "Without Fargate (EC2 Mode)"
        EC2["EC2 Instance<br/>(you manage)"]
        EC2 --> C1["Container A"]
        EC2 --> C2["Container B"]
        EC2 --> C3["Container C"]
    end

    subgraph "With Fargate"
        F1["Fargate Task<br/>Container A<br/>(isolated micro-VM)"]
        F2["Fargate Task<br/>Container B<br/>(isolated micro-VM)"]
        F3["Fargate Task<br/>Container C<br/>(isolated micro-VM)"]
    end

    style EC2 fill:#fef3c7,stroke:#d97706,color:#000
    style F1 fill:#bbf7d0,stroke:#16a34a,color:#000
    style F2 fill:#bbf7d0,stroke:#16a34a,color:#000
    style F3 fill:#bbf7d0,stroke:#16a34a,color:#000

Characteristics and Trade-offs

StrengthsWeaknesses
✅ Zero server management — no OS, patching, or capacity planning❌ More expensive per vCPU/GB than direct EC2
✅ Per-task/pod security isolation (micro-VM)❌ Cold starts can reach 30-60 seconds for large images
✅ Per-second billing based on resources used❌ Can’t SSH into the runtime environment
✅ Scale-to-zero possible (with ECS)❌ Limited control — can’t tune the kernel or mount host volumes
✅ Good for sporadic and event-driven workloads❌ Not suitable for GPU workloads or high-performance computing

When Fargate Is the Right Choice

Fargate shines in three main scenarios. First, background jobs and event-driven workers — tasks triggered by events (SQS messages, S3 uploads, scheduled cron) that die after finishing. Second, microservices with low-to-medium traffic that don’t justify the cost of managing EC2 nodes. Third, small teams without a dedicated infra engineer who want to focus on writing code, not managing servers.

Fargate Anti-Patterns

A common mistake is running all workloads on Fargate without considering cost. For workloads running 24/7 with high utilization, EC2 (especially with Reserved Instances or Savings Plans) can be 3-5x cheaper.

graph LR
    subgraph "❌ Anti-Pattern: Everything on Fargate"
        SA1["Service A<br/>24/7, high CPU"] --> FG1["Fargate"] --> C1["Very high cost"]
        SB1["Service B<br/>24/7, high CPU"] --> FG2["Fargate"] --> C2["Very high cost"]
    end

    style SA1 fill:#fef3c7,stroke:#d97706,color:#000
    style SB1 fill:#fef3c7,stroke:#d97706,color:#000
    style FG1 fill:#fecaca,stroke:#dc2626,color:#000
    style FG2 fill:#fecaca,stroke:#dc2626,color:#000
    style C1 fill:#fecaca,stroke:#dc2626,color:#000
    style C2 fill:#fecaca,stroke:#dc2626,color:#000
graph LR
    subgraph "✅ Correct: Hybrid — Match the Workload"
        SA2["Service A<br/>24/7, high CPU"] --> EC2["EC2 + ASG"] --> C3["Low cost"]
        SB2["Service B<br/>event-driven"] --> FG3["Fargate"] --> C4["Minimal cost"]
    end

    style SA2 fill:#fef3c7,stroke:#d97706,color:#000
    style SB2 fill:#e0e7ff,stroke:#4f46e5,color:#000
    style EC2 fill:#bfdbfe,stroke:#2563eb,color:#000
    style FG3 fill:#bbf7d0,stroke:#16a34a,color:#000
    style C3 fill:#bbf7d0,stroke:#16a34a,color:#000
    style C4 fill:#bbf7d0,stroke:#16a34a,color:#000

Fargate Best Practices

  • Set resource requests accurately. Fargate bills based on the vCPU and memory you request, not what you use. Over-provisioning directly wastes money.
  • Use Fargate Spot for workloads tolerant of interruption (batch processing, data pipelines). Save up to 70%.
  • Optimize container image size. Smaller images mean faster cold starts. Use multi-stage builds and alpine base images.
  • Implement proper health checks so the orchestrator knows when a task needs restarting.

Direct Comparison

The following table summarizes the fundamental differences between the four services across various aspects.

AspectEC2ASGEKSFargate
Abstraction levelLowLow–MediumHighVery High
AutoscalingManualAutomatic horizontalHPA + Cluster AutoscalerAutomatic per task/pod
Manage serversYes, fullyYes, but ASG helps replaceYes for worker nodesNot at all
Provisioning time1-3 minutes1-5 minutesSeconds (pod scheduling)30-60 seconds (cold start)
Security isolationShared tenancy (default)Shared tenancyShared nodeMicro-VM per task
Per-unit costCheapestCheapMedium + control planeMost expensive per vCPU
Good forLegacy, stateful, GPUTraditional web/APILarge microservicesEvent-driven, small teams
Learning curveLowLowHighLow–Medium
Vendor lock-inMediumMediumLow (K8s portable)High

Decision Guide — Choose the Right One

No need to memorize every detail. Use the following decision tree to guide your architecture decision.

graph TD
    Start["Need to run an<br/>application on AWS"] --> Q1{"Is the app already<br/>containerized?"}

    Q1 -->|"Not yet"| Q2{"Need autoscaling?"}
    Q2 -->|"No"| EC2["✅ EC2<br/>Single or multi-instance"]
    Q2 -->|"Yes"| ASG["✅ EC2 + ASG<br/>Traditional autoscaling"]

    Q1 -->|"Yes"| Q3{"Does the team<br/>understand Kubernetes?"}
    Q3 -->|"No"| Q4{"Need to manage<br/>servers yourself?"}
    Q4 -->|"No"| FGECS["✅ Fargate + ECS<br/>Serverless container, simple"]
    Q4 -->|"Yes"| ECSEC2["✅ ECS + EC2<br/>Simple container orchestration"]

    Q3 -->|"Yes"| Q5{"How many<br/>services?"}
    Q5 -->|"< 10"| FGECS2["✅ Fargate + ECS/EKS<br/>Kubernetes without node mgmt"]
    Q5 -->|"> 10"| Q6{"Need full control<br/>over nodes?"}
    Q6 -->|"Yes"| EKSEC2["✅ EKS + EC2 Node Group<br/>Full Kubernetes power"]
    Q6 -->|"No"| EKSFG["✅ EKS + Fargate<br/>Serverless Kubernetes"]

    style EC2 fill:#fef3c7,stroke:#d97706,color:#000
    style ASG fill:#fde68a,stroke:#d97706,color:#000
    style FGECS fill:#bbf7d0,stroke:#16a34a,color:#000
    style ECSEC2 fill:#bfdbfe,stroke:#2563eb,color:#000
    style FGECS2 fill:#bbf7d0,stroke:#16a34a,color:#000
    style EKSEC2 fill:#bfdbfe,stroke:#2563eb,color:#000
    style EKSFG fill:#d9f99d,stroke:#65a30d,color:#000

Production Architecture per Type

Theory without real examples is hard to digest. Here are realistic production architectures for each approach.

Single EC2 — Minimum Viable Production

graph LR
    User["User"] --> EC2["EC2 Instance<br/>t3.medium<br/>App + Nginx"]
    EC2 --> RDS["RDS<br/>PostgreSQL"]
    EC2 --> S3["S3<br/>Static Assets"]

    style EC2 fill:#fef3c7,stroke:#d97706,color:#000

Single point of failure. Suitable for early MVPs or internal tools. Don’t use it for significant public traffic.

EC2 + ASG — The Most Common Pattern

graph LR
    User["User"] --> ALB["ALB"]
    ALB --> EC2a["EC2<br/>AZ-1"]
    ALB --> EC2b["EC2<br/>AZ-2"]
    ALB --> EC2c["EC2<br/>AZ-1"]

    EC2a --> RDS["RDS Multi-AZ"]
    EC2b --> RDS
    EC2c --> RDS

    EC2a --> Redis["ElastiCache<br/>Redis"]
    EC2b --> Redis
    EC2c --> Redis

    ASG["ASG<br/>min:2 max:6"] -.->|"manage"| EC2a
    ASG -.-> EC2b
    ASG -.-> EC2c

    style ALB fill:#e0e7ff,stroke:#4f46e5,color:#000
    style ASG fill:#bfdbfe,stroke:#2563eb,color:#000

High availability, horizontal scaling, multi-AZ. The most common pattern for web backends and traditional APIs.

EKS + EC2 Node Group — Full Power Kubernetes

graph TB
    User["User"] --> ALB["ALB + Ingress Controller"]
    ALB --> SvcA["Service A<br/>(3 replicas)"]
    ALB --> SvcB["Service B<br/>(2 replicas)"]
    ALB --> SvcC["Service C<br/>(4 replicas)"]

    subgraph "EKS Cluster"
        SvcA
        SvcB
        SvcC
    end

    SvcA --> RDS["Aurora"]
    SvcB --> DDB["DynamoDB"]
    SvcC --> SQS["SQS"] --> Worker["Worker Pods"]

    style ALB fill:#e0e7ff,stroke:#4f46e5,color:#000

Full control over nodes, efficient for stable and heavy workloads. Good for medium-to-large platforms with a team that understands Kubernetes.

EKS + Fargate — Serverless Kubernetes

graph TB
    User["User"] --> ALB["ALB"]

    subgraph "EKS + Fargate"
        ALB --> PodA["Pod A<br/>(Fargate)"]
        ALB --> PodB["Pod B<br/>(Fargate)"]
        SQS["SQS"] --> PodC["Worker Pod<br/>(Fargate)"]
        Cron["EventBridge<br/>Schedule"] --> PodD["Cron Pod<br/>(Fargate)"]
    end

    PodA --> RDS["Aurora Serverless"]
    PodB --> RDS

    style PodA fill:#bbf7d0,stroke:#16a34a,color:#000
    style PodB fill:#bbf7d0,stroke:#16a34a,color:#000
    style PodC fill:#bbf7d0,stroke:#16a34a,color:#000
    style PodD fill:#bbf7d0,stroke:#16a34a,color:#000

No worker nodes, minimal operations. Ideal for small teams or non-constant workloads.

Fargate + ECS — Event-Driven Worker

graph LR
    S3["S3 Upload"] --> EB["EventBridge"]
    SQS["SQS Queue"] --> ECS["ECS Service"]
    Schedule["Cron Schedule"] --> EB

    EB --> FG1["Fargate Task<br/>Image Processor"]
    ECS --> FG2["Fargate Task<br/>Queue Worker"]

    FG1 --> S3Out["S3 Output"]
    FG2 --> DB["DynamoDB"]

    style FG1 fill:#bbf7d0,stroke:#16a34a,color:#000
    style FG2 fill:#bbf7d0,stroke:#16a34a,color:#000

Event-driven, scale-to-zero, cost-efficient for sporadic workloads. No servers running when there’s no work.


The Most Common Architecture Mistakes

Based on experience across systems of various scales, here are the mistakes that repeatedly appear.

Using EKS for 2-3 services. The overhead of managing Kubernetes — RBAC, networking, monitoring, upgrades — isn’t worth the benefit for small systems. Use ECS + Fargate instead.

Using a single EC2 for production. One instance = one point of failure. At minimum use an ASG with min: 2 across two different AZs.

Running databases in Kubernetes. StatefulSets and PersistentVolumes do exist, but managing backups, failovers, and performance tuning for databases in K8s is far more complicated than using RDS or Aurora.

Using Fargate for all workloads without cost analysis. Workloads running 24/7 with high utilization are almost always cheaper on EC2 with Reserved Instances.

Choosing technology based on the resume, not the need. “We use EKS because it’s cool” isn’t an architecture reason. Every choice must be justifiable with concrete technical and business needs.


Summary

  • EC2 — the classic virtual machine with full control. Good for legacy, stateful workloads, and experiments. Don’t use a single instance for production.
  • ASG — not new compute, but a controller for EC2. Provides horizontal autoscaling and high availability. Applications must be stateless.
  • EKS — managed Kubernetes for large-scale microservices. Powerful but complex. Only adopt when the team and scale are ready.
  • Fargate — serverless containers without server management. Ideal for event-driven, background jobs, and small teams. Watch the cost for 24/7 workloads.
  • The higher the abstraction, the smaller the operational burden — but the greater the per-unit cost and the more limited the control.
  • There’s no “most correct” choice — only the most appropriate for your context. Use the decision tree to guide the decision.
  • Hybrid is the realistic answer — many production systems combine EC2 (for steady-state heavy workloads) with Fargate (for event-driven and sporadic ones).
  • Choose based on need, not hype — EKS for 2 services is over-engineering. A single EC2 for production is under-engineering. Find the sweet spot in between.

Portfolio