On This Page
1Overview2Architecture Overview
3AWS Service Topology4Implementation Guide
5Decision Criteria6Cost Model
7Anti-Patterns to Avoid8References

Overview

AWS offers two primary container platforms — ECS (Elastic Container Service) and EKS (Elastic Kubernetes Service) — each with a Fargate launch type that removes EC2 instance management. The containerisation journey on AWS starts with ECR for image storage, moves through a CI/CD pipeline that builds and pushes images, and ends with either ECS or EKS running those images behind an Application Load Balancer.

The Fargate model means AWS manages the underlying infrastructure. You define CPU, memory, and network requirements per task, and AWS provisions the right compute. No SSH into nodes, no cluster upgrades, no capacity planning for the container host layer.

Architecture Overview

%%{init:{'theme':'base','themeVariables':{'fontSize':'14px','fontFamily':'IBM Plex Sans, system-ui, sans-serif','primaryColor':'#DBEAFE','primaryTextColor':'#1e3a5f','primaryBorderColor':'#2563EB','lineColor':'#374151','clusterBkg':'#F9FAFB','clusterBorder':'#D1D5DB','edgeLabelBackground':'#FFFFFF'},'flowchart':{'curve':'orthogonal','padding':30,'nodeSpacing':65,'rankSpacing':75,'useMaxWidth':true}}}%% flowchart TD START([Developer Pushes Code]) START --> CI[CI Pipeline\nBuild container image\nRun tests and SAST] CI --> SCAN{Image Security\nScan Passed?} SCAN -->|Vulnerabilities found| FAIL[Block deployment\nNotify engineer\nLog to Security Hub] SCAN -->|Clean| ECR[Push to ECR\nImmutable tags\nLifecycle policy] ECR --> DEPLOY{Deployment\nTarget} DEPLOY -->|No Kubernetes needed| ECS[ECS Fargate\nTask Definition\nService with desired count] DEPLOY -->|Kubernetes ecosystem| EKS[EKS Fargate\nor EC2 node groups\nHelm charts] ECS --> ALB[Application Load Balancer\nPath-based routing\nHealth check per target group] EKS --> ALB ALB --> USERS([Users Reach the Application]) style START fill:#4f8ef7,color:#fff style USERS fill:#10b981,color:#fff style FAIL fill:#fef3c7 style ECR fill:#e0f2fe

AWS Service Topology

%%{init:{'theme':'base','themeVariables':{'fontSize':'14px','fontFamily':'IBM Plex Sans, system-ui, sans-serif','primaryColor':'#DBEAFE','primaryTextColor':'#1e3a5f','primaryBorderColor':'#2563EB','lineColor':'#374151','clusterBkg':'#F9FAFB','clusterBorder':'#D1D5DB','edgeLabelBackground':'#FFFFFF'},'flowchart':{'curve':'orthogonal','padding':30,'nodeSpacing':65,'rankSpacing':75,'useMaxWidth':true}}}%% flowchart TD USER[Internet Traffic] subgraph INGRESS["Ingress Layer"] R53[Route 53\nDNS with health checks\nLatency-based routing] ACM[ACM Certificate\nAuto-renewed TLS\nWildcard support] ALB_C[Application Load Balancer\nHTTPS listener\nTarget group per service] WAF_C[AWS WAF\nRate limiting\nOWASP rule groups] end subgraph PLATFORM["ECS Fargate Cluster"] SVC1[Service A\n2 vCPU, 4 GB\nDesired count: 3] SVC2[Service B\n1 vCPU, 2 GB\nAuto-scaling policy] SVC3[Service C\n0.5 vCPU, 1 GB\nScheduled task] TASK_DEF[Task Definitions\nContainer image + tag\nEnvironment variables from SSM] end subgraph REGISTRY["Amazon ECR"] REPO[Private Repository\nImmutable image tags\nLifecycle: keep last 10] SCAN_ECR[Enhanced Scanning\nContinuous CVE scan\nFindings to Security Hub] end subgraph NETWORK["VPC Network"] PUB[Public Subnets\nALB only\nNo container workloads] PRIV[Private Subnets\nAll ECS tasks\nNAT Gateway for egress] end USER --> R53 --> ACM --> WAF_C --> ALB_C ALB_C --> SVC1 & SVC2 & SVC3 TASK_DEF -.->|Defines| SVC1 & SVC2 & SVC3 REPO --> TASK_DEF SCAN_ECR -.->|Scans| REPO ALB_C --> PUB SVC1 & SVC2 & SVC3 --> PRIV style USER fill:#4f8ef7,color:#fff style SCAN_ECR fill:#e0f2fe style PRIV fill:#e0f2fe

Implementation Guide

CDK Stack — ECS Fargate Service

import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import { Duration } from 'aws-cdk-lib';

export class ContainerPlatformStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // VPC — private subnets for tasks
    const vpc = new ec2.Vpc(this, 'AppVpc', {
      maxAzs: 3,
      natGateways: 1,
      subnetConfiguration: [
        { name: 'Public', subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24 },
        { name: 'Private', subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
          cidrMask: 24 },
      ],
    });

    // ECR repository with scanning
    const repo = new ecr.Repository(this, 'AppRepo', {
      repositoryName: 'app-service',
      imageScanOnPush: true,
      imageTagMutability: ecr.TagMutability.IMMUTABLE,
      lifecycleRules: [{ maxImageCount: 10, description: 'Keep last 10 images' }],
    });

    // ECS Cluster
    const cluster = new ecs.Cluster(this, 'AppCluster', {
      vpc,
      containerInsights: true,
    });

    // Fargate service with ALB
    const service = new ecsPatterns.ApplicationLoadBalancedFargateService(
      this, 'AppService', {
        cluster,
        cpu: 512,
        memoryLimitMiB: 1024,
        desiredCount: 3,
        taskImageOptions: {
          image: ecs.ContainerImage.fromEcrRepository(repo, 'latest'),
          containerPort: 8080,
          environment: { NODE_ENV: 'production' },
        },
        publicLoadBalancer: true,
        assignPublicIp: false,    // tasks in private subnets
        listenerPort: 443,
      });

    // Auto-scaling
    const scaling = service.service.autoScaleTaskCount({
      minCapacity: 2,
      maxCapacity: 20,
    });

    scaling.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 70,
      scaleInCooldown: Duration.seconds(60),
      scaleOutCooldown: Duration.seconds(30),
    });

    scaling.scaleOnRequestCount('RequestScaling', {
      requestsPerTarget: 1000,
      targetGroup: service.targetGroup,
    });

    // Health check tuning
    service.targetGroup.configureHealthCheck({
      path: '/health',
      interval: Duration.seconds(30),
      timeout: Duration.seconds(5),
      healthyThresholdCount: 2,
      unhealthyThresholdCount: 3,
    });
  }
}

Decision Criteria

Factor ECS Fargate EKS Fargate EKS EC2 Nodes
Operational overhead Lowest Medium Highest
Kubernetes ecosystem No Yes Yes
Cluster upgrades None Managed Manual
Cost at scale Medium Medium Lowest
Custom schedulers No Yes Yes
Service mesh App Mesh Istio or App Mesh Istio or App Mesh
Best for Greenfield, smaller teams Teams needing K8s APIs Large-scale, K8s expertise

Recommendation for most Ascendion projects: Start with ECS Fargate. Migrate to EKS only when you need a specific Kubernetes capability — custom operators, advanced scheduling, or client-required Kubernetes.

Cost Model

Component Cost Driver Optimisation
Fargate tasks vCPU and memory per second Right-size to actual utilisation
NAT Gateway Data processing and hourly Share across services in same VPC
ALB LCU hours and rule evaluations Consolidate services on one ALB
ECR Storage and data transfer Lifecycle policies remove old images

Fargate Spot reduces task cost by 70% for interruption-tolerant workloads. Use Spot for batch processing, non-production environments, and stateless services that restart gracefully.

Anti-Patterns to Avoid

⚠ 1. Lifting VMs into Containers Unchanged

Taking an application that was designed to run as a persistent server — holding in-memory sessions, writing to local disk, running background threads — and packaging it as a Docker image without changing the architecture. The application behaves unpredictably when Fargate replaces the task, local files disappear on restart, and sessions are lost on scale-in.

Hover to see the fix ↻
↺ Correct Approach

Containerise after making the application stateless. Move sessions to ElastiCache, move files to S3, move background jobs to SQS. Containers assume the task is replaceable at any time.

⚠ 2. Mutable Container Tags

Deploying images tagged as latest or with a branch name. Two deployments of the same tag can produce different behaviour if the image was rebuilt. Impossible to roll back to a specific version when latest points to the newest build.

Hover to see the fix ↻
↺ Correct Approach

Tag every image with the Git commit SHA. ECR immutable tags prevent overwriting. Deployments reference an exact image digest, making every deployment reproducible and rollback trivial.

Flowchart

%%{init:{'theme':'base','themeVariables':{'fontSize':'14px','fontFamily':'IBM Plex Sans, system-ui, sans-serif','primaryColor':'#DBEAFE','primaryTextColor':'#1e3a5f','primaryBorderColor':'#2563EB','lineColor':'#374151','clusterBkg':'#F9FAFB','clusterBorder':'#D1D5DB','edgeLabelBackground':'#FFFFFF'},'flowchart':{'curve':'orthogonal','padding':30,'nodeSpacing':65,'rankSpacing':75,'useMaxWidth':true}}}%% flowchart TD START([Code Commit to Main Branch]) START --> BUILD[Build Container Image\nDocker multi-stage build\nMinimise image layer size] BUILD --> SCAN_S{ECR Image Scan\nCritical CVEs found?} SCAN_S -->|Yes| BLOCK[Block Deployment\nAlert Security Hub\nRequires remediation] SCAN_S -->|No| PUSH[Push to ECR\nImmutable tag — Git SHA\nLifecycle policy active] PUSH --> DECIDE{Deployment Target} DECIDE -->|ECS Fargate| TASK[Update Task Definition\nNew image tag\nBlue/Green via CodeDeploy] DECIDE -->|EKS| HELM[Helm chart upgrade\nvalues.yaml image tag\nRolling update strategy] TASK --> ALB_D[ALB Target Group\nHealth checks pass\nOld tasks drained and stopped] HELM --> ALB_D ALB_D --> SCALE{Auto-scaling\nPolicy Triggered?} SCALE -->|CPU over 70 percent| OUT[Scale Out\nAdd tasks or pods\nWithin 30 seconds] SCALE -->|CPU under 30 percent| IN[Scale In\n60 second cooldown\nMinimum 2 tasks] SCALE -->|Within bounds| STEADY[Steady State\nMonitor with\nContainer Insights] OUT & IN & STEADY --> DONE([Application Running on Fargate]) style START fill:#4f8ef7,color:#fff style DONE fill:#10b981,color:#fff style BLOCK fill:#fef3c7 style SCAN_S fill:#e0f2fe

References

  1. AWS — Amazon ECS Developer Guide. docs.aws.amazon.com/ecs
  2. AWS — Amazon EKS Best Practices Guide. aws.github.io/aws-eks-best-practices
  3. AWS — Fargate pricing. aws.amazon.com/fargate/pricing
  4. CNCF — Cloud Native Landscape. landscape.cncf.io
Ascendion Engineering Knowledge Base ← Cloud