Building a Scalable WordPress Platform on AWS ECS Fargate: Architecture Decisions and Implementation

Traditional WordPress hosting on shared servers or single instances has limitations when it comes to scalability, reliability, and performance. Modern content management platforms need to handle traffic spikes, ensure high availability, and provide seamless scaling. This post explores building a production-ready WordPress platform using AWS ECS Fargate, diving into architectural decisions, tradeoffs, and implementation details.

The Challenge: WordPress at Scale

WordPress powers over 40% of websites globally, but scaling it beyond a single server presents several challenges:

  • Traffic Variability: Content sites experience unpredictable traffic patterns
  • Database Bottlenecks: MySQL becomes the limiting factor at scale
  • File Storage: Shared file systems for uploads and media
  • Session Management: Handling user sessions across multiple instances
  • Plugin Compatibility: Ensuring third-party plugins work in containerized environments
  • Security: Protecting against vulnerabilities and attacks
  • Cost Optimization: Balancing performance with operational costs

Let’s explore how containerized WordPress on ECS Fargate addresses these challenges.

Architecture Overview

Our scalable WordPress platform uses a microservices approach with these AWS components:

┌─────────────┐    ┌──────────────┐    ┌─────────────┐    ┌─────────────┐
│ CloudFront  │    │     ALB      │    │ECS Fargate  │    │     RDS     │
│   (CDN)     │───▶│ (Load Bal.)  │───▶│ WordPress   │───▶│   MySQL     │
└─────────────┘    └──────────────┘    │ Container   │    │  (Multi-AZ) │
                                       └─────────────┘    └─────────────┘
                                              │
                                              ▼
                                       ┌─────────────┐
                                       │     EFS     │
                                       │(File System)│
                                       └─────────────┘

Core Components

  1. ECS Fargate: Serverless containers for WordPress application
  2. Application Load Balancer: Distributes traffic across multiple containers
  3. RDS MySQL: Managed database with Multi-AZ deployment
  4. EFS: Shared file system for WordPress uploads and plugins
  5. CloudFront: Global CDN for static content delivery
  6. Secrets Manager: Secure storage for database credentials

CDK Infrastructure Implementation

VPC and Network Setup

The foundation starts with a properly configured network:

 1// lib/ecs-wordpress-stack.ts
 2export class EcsWordpressStack extends cdk.Stack {
 3  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
 4    super(scope, id, props);
 5
 6    // VPC with public and private subnets across multiple AZs
 7    const vpc = new ec2.Vpc(this, 'WordPressVpc', {
 8      maxAzs: 2,
 9      natGateways: 2, // One per AZ for high availability
10      subnetConfiguration: [
11        {
12          cidrMask: 24,
13          name: 'PublicSubnet',
14          subnetType: ec2.SubnetType.PUBLIC,
15        },
16        {
17          cidrMask: 24,
18          name: 'PrivateSubnet',
19          subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
20        },
21        {
22          cidrMask: 28,
23          name: 'IsolatedSubnet',
24          subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
25        },
26      ],
27    });
28
29    // Security group for ECS tasks
30    const ecsSecurityGroup = new ec2.SecurityGroup(this, 'EcsSecurityGroup', {
31      vpc,
32      description: 'Security group for ECS WordPress tasks',
33      allowAllOutbound: true,
34    });
35
36    // Security group for RDS
37    const rdsSecurityGroup = new ec2.SecurityGroup(this, 'RdsSecurityGroup', {
38      vpc,
39      description: 'Security group for RDS MySQL instance',
40      allowAllOutbound: false,
41    });
42
43    // Allow ECS to connect to RDS on MySQL port
44    rdsSecurityGroup.addIngressRule(
45      ecsSecurityGroup,
46      ec2.Port.tcp(3306),
47      'Allow ECS tasks to connect to MySQL'
48    );
49
50    // Security group for EFS
51    const efsSecurityGroup = new ec2.SecurityGroup(this, 'EfsSecurityGroup', {
52      vpc,
53      description: 'Security group for EFS file system',
54      allowAllOutbound: false,
55    });
56
57    // Allow ECS to connect to EFS on NFS port
58    efsSecurityGroup.addIngressRule(
59      ecsSecurityGroup,
60      ec2.Port.tcp(2049),
61      'Allow ECS tasks to connect to EFS'
62    );
63  }
64}

Key Design Decisions:

  • Multi-AZ deployment ensures high availability
  • Private subnets for ECS tasks provide security isolation
  • Isolated subnets for RDS eliminate internet access
  • Security groups implement least-privilege access

EFS File System for Persistent Storage

WordPress requires shared storage for uploads, themes, and plugins:

 1// EFS File System with encryption
 2const fileSystem = new efs.FileSystem(this, 'WordPressFileSystem', {
 3  vpc,
 4  securityGroup: efsSecurityGroup,
 5  encrypted: true,
 6  lifecyclePolicy: efs.LifecyclePolicy.AFTER_30_DAYS,
 7  performanceMode: efs.PerformanceMode.GENERAL_PURPOSE,
 8  throughputMode: efs.ThroughputMode.BURSTING,
 9  removalPolicy: cdk.RemovalPolicy.DESTROY,
10});
11
12// EFS Access Points for better security and performance
13const accessPoint = new efs.AccessPoint(this, 'WordPressAccessPoint', {
14  fileSystem,
15  path: '/var/www/html',
16  creationInfo: {
17    ownerGid: '33',    // www-data group
18    ownerUid: '33',    // www-data user
19    permissions: '755',
20  },
21  posixUser: {
22    gid: 33,
23    uid: 33,
24  },
25});
26
27// Create mount targets in each AZ
28const mountTargets = vpc.privateSubnets.map((subnet, index) => {
29  return new efs.MountTarget(this, `MountTarget${index}`, {
30    fileSystem,
31    subnet,
32    securityGroup: efsSecurityGroup,
33  });
34});

RDS MySQL Database

Managed MySQL database with high availability:

 1// Database subnet group for RDS
 2const dbSubnetGroup = new rds.SubnetGroup(this, 'DbSubnetGroup', {
 3  vpc,
 4  description: 'Subnet group for WordPress RDS instance',
 5  subnets: {
 6    subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
 7  },
 8});
 9
10// RDS MySQL instance
11const database = new rds.DatabaseInstance(this, 'WordPressDatabase', {
12  engine: rds.DatabaseInstanceEngine.mysql({
13    version: rds.MysqlEngineVersion.VER_8_0,
14  }),
15  instanceType: ec2.InstanceType.of(
16    ec2.InstanceClass.T3,
17    ec2.InstanceSize.MICRO
18  ),
19  vpc,
20  subnetGroup: dbSubnetGroup,
21  securityGroups: [rdsSecurityGroup],
22  
23  // Database configuration
24  databaseName: 'wordpress',
25  credentials: rds.Credentials.fromGeneratedSecret('wordpressadmin', {
26    excludeCharacters: '"@/\\\'',
27  }),
28  
29  // Storage configuration
30  allocatedStorage: 20,
31  maxAllocatedStorage: 100,
32  storageEncrypted: true,
33  storageType: rds.StorageType.GP2,
34  
35  // Backup and maintenance
36  backupRetention: cdk.Duration.days(7),
37  deleteAutomatedBackups: true,
38  deletionProtection: false,
39  
40  // Multi-AZ for production
41  multiAz: true,
42  
43  // Monitoring
44  monitoringInterval: cdk.Duration.seconds(60),
45  enablePerformanceInsights: true,
46  
47  removalPolicy: cdk.RemovalPolicy.DESTROY,
48});

ECS Fargate Configuration

Cluster and Task Definition

Setting up the ECS cluster and WordPress container:

 1// ECS Cluster
 2const cluster = new ecs.Cluster(this, 'WordPressCluster', {
 3  vpc,
 4  containerInsights: true,
 5});
 6
 7// Task Definition
 8const taskDefinition = new ecs.FargateTaskDefinition(this, 'WordPressTaskDef', {
 9  memoryLimitMiB: 2048,
10  cpu: 1024,
11});
12
13// WordPress container
14const wordpressContainer = taskDefinition.addContainer('wordpress', {
15  image: ecs.ContainerImage.fromRegistry('wordpress:latest'),
16  memoryReservationMiB: 1024,
17  
18  // Environment variables
19  environment: {
20    WORDPRESS_DB_HOST: database.instanceEndpoint.hostname,
21    WORDPRESS_DB_NAME: 'wordpress',
22    WORDPRESS_TABLE_PREFIX: 'wp_',
23    
24    // WordPress configuration
25    WORDPRESS_CONFIG_EXTRA: `
26      define('WP_REDIS_HOST', 'localhost');
27      define('FORCE_SSL_ADMIN', true);
28      define('WP_DEBUG', false);
29      define('WP_DEBUG_LOG', false);
30      define('WP_DEBUG_DISPLAY', false);
31    `,
32  },
33  
34  // Secrets from AWS Secrets Manager
35  secrets: {
36    WORDPRESS_DB_USER: ecs.Secret.fromSecretsManager(
37      database.secret!,
38      'username'
39    ),
40    WORDPRESS_DB_PASSWORD: ecs.Secret.fromSecretsManager(
41      database.secret!,
42      'password'
43    ),
44  },
45  
46  // Logging
47  logging: ecs.LogDrivers.awsLogs({
48    streamPrefix: 'wordpress',
49    logRetention: logs.RetentionDays.ONE_WEEK,
50  }),
51});
52
53// Configure container ports
54wordpressContainer.addPortMappings({
55  containerPort: 80,
56  protocol: ecs.Protocol.TCP,
57});
58
59// EFS volume mount
60taskDefinition.addVolume({
61  name: 'wordpress-data',
62  efsVolumeConfiguration: {
63    fileSystemId: fileSystem.fileSystemId,
64    transitEncryption: 'ENABLED',
65    accessPoint: accessPoint.accessPointId,
66  },
67});
68
69// Mount EFS volume in container
70wordpressContainer.addMountPoints({
71  sourceVolume: 'wordpress-data',
72  containerPath: '/var/www/html/wp-content',
73  readOnly: false,
74});

ECS Service with Auto Scaling

Configuring the service with automatic scaling:

 1// Application Load Balancer
 2const alb = new elbv2.ApplicationLoadBalancer(this, 'WordPressALB', {
 3  vpc,
 4  internetFacing: true,
 5  securityGroup: albSecurityGroup,
 6});
 7
 8// Target Group
 9const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WordPressTargets', {
10  vpc,
11  port: 80,
12  protocol: elbv2.ApplicationProtocol.HTTP,
13  targetType: elbv2.TargetType.IP,
14  
15  // Health check configuration
16  healthCheck: {
17    enabled: true,
18    healthyHttpCodes: '200,302',
19    interval: cdk.Duration.seconds(30),
20    path: '/wp-admin/install.php',
21    protocol: elbv2.Protocol.HTTP,
22    timeout: cdk.Duration.seconds(5),
23    unhealthyThresholdCount: 2,
24    healthyThresholdCount: 5,
25  },
26});
27
28// ALB Listener
29const listener = alb.addListener('WordPressListener', {
30  port: 80,
31  protocol: elbv2.ApplicationProtocol.HTTP,
32  defaultTargetGroups: [targetGroup],
33});
34
35// ECS Service
36const service = new ecs.FargateService(this, 'WordPressService', {
37  cluster,
38  taskDefinition,
39  serviceName: 'wordpress-service',
40  
41  // Desired count and capacity
42  desiredCount: 2,
43  minHealthyPercent: 50,
44  maxHealthyPercent: 200,
45  
46  // Network configuration
47  assignPublicIp: false,
48  securityGroups: [ecsSecurityGroup],
49  vpcSubnets: {
50    subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
51  },
52  
53  // Load balancer configuration
54  loadBalancers: [{
55    targetGroup,
56    containerName: 'wordpress',
57    containerPort: 80,
58  }],
59  
60  // Enable service discovery
61  cloudMapOptions: {
62    name: 'wordpress',
63    cloudMapNamespace: cluster.defaultCloudMapNamespace,
64    dnsRecordType: servicediscovery.DnsRecordType.A,
65  },
66});
67
68// Auto Scaling Configuration
69const scaling = service.autoScaleTaskCount({
70  minCapacity: 1,
71  maxCapacity: 10,
72});
73
74// CPU-based scaling
75scaling.scaleOnCpuUtilization('CpuScaling', {
76  targetUtilizationPercent: 70,
77  scaleInCooldown: cdk.Duration.minutes(5),
78  scaleOutCooldown: cdk.Duration.minutes(2),
79});
80
81// Memory-based scaling
82scaling.scaleOnMemoryUtilization('MemoryScaling', {
83  targetUtilizationPercent: 80,
84  scaleInCooldown: cdk.Duration.minutes(5),
85  scaleOutCooldown: cdk.Duration.minutes(2),
86});

Custom WordPress Container

Creating an optimized WordPress container:

 1# Dockerfile for optimized WordPress
 2FROM wordpress:6.4-php8.2-apache
 3
 4# Install additional PHP extensions
 5RUN apt-get update && apt-get install -y \
 6    libmemcached-dev \
 7    libzip-dev \
 8    libfreetype6-dev \
 9    libjpeg62-turbo-dev \
10    libpng-dev \
11    libwebp-dev \
12    && rm -rf /var/lib/apt/lists/*
13
14# Install PHP extensions for better performance
15RUN docker-php-ext-configure gd \
16    --with-freetype \
17    --with-jpeg \
18    --with-webp \
19    && docker-php-ext-install -j$(nproc) gd zip opcache
20
21# Install Redis extension for object caching
22RUN pecl install redis-5.3.7 \
23    && docker-php-ext-enable redis
24
25# Install APCu for additional caching
26RUN pecl install apcu \
27    && docker-php-ext-enable apcu
28
29# Copy custom PHP configuration
30COPY php.ini /usr/local/etc/php/conf.d/wordpress.ini
31
32# Copy custom WordPress configuration
33COPY wp-config-docker.php /usr/src/wordpress/
34
35# Copy custom .htaccess for better performance
36COPY .htaccess /var/www/html/
37
38# Set proper permissions
39RUN chown -R www-data:www-data /var/www/html \
40    && chmod -R 755 /var/www/html
41
42# Health check
43HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
44  CMD curl -f http://localhost/ || exit 1

PHP Optimization Configuration

 1; php.ini - Production optimizations
 2[PHP]
 3memory_limit = 256M
 4upload_max_filesize = 32M
 5post_max_size = 32M
 6max_execution_time = 300
 7max_input_vars = 3000
 8
 9; OPcache configuration
10opcache.enable=1
11opcache.memory_consumption=128
12opcache.interned_strings_buffer=8
13opcache.max_accelerated_files=4000
14opcache.revalidate_freq=2
15opcache.fast_shutdown=1
16opcache.save_comments=1
17
18; APCu configuration
19apc.enabled=1
20apc.shm_size=32M
21apc.ttl=7200
22apc.enable_cli=1
23
24; Session configuration
25session.cookie_httponly=1
26session.cookie_secure=1
27session.use_strict_mode=1

WordPress Configuration

 1<?php
 2// wp-config-docker.php - Optimized WordPress configuration
 3define('DB_NAME', getenv('WORDPRESS_DB_NAME'));
 4define('DB_USER', getenv('WORDPRESS_DB_USER'));
 5define('DB_PASSWORD', getenv('WORDPRESS_DB_PASSWORD'));
 6define('DB_HOST', getenv('WORDPRESS_DB_HOST'));
 7define('DB_CHARSET', 'utf8mb4');
 8define('DB_COLLATE', '');
 9
10// Security keys (should be generated for production)
11define('AUTH_KEY',         'your-auth-key');
12define('SECURE_AUTH_KEY',  'your-secure-auth-key');
13define('LOGGED_IN_KEY',    'your-logged-in-key');
14define('NONCE_KEY',        'your-nonce-key');
15define('AUTH_SALT',        'your-auth-salt');
16define('SECURE_AUTH_SALT', 'your-secure-auth-salt');
17define('LOGGED_IN_SALT',   'your-logged-in-salt');
18define('NONCE_SALT',       'your-nonce-salt');
19
20// WordPress Database Table prefix
21$table_prefix = getenv('WORDPRESS_TABLE_PREFIX') ?: 'wp_';
22
23// Redis Object Cache
24define('WP_REDIS_HOST', 'localhost');
25define('WP_REDIS_PORT', 6379);
26define('WP_REDIS_TIMEOUT', 1);
27define('WP_REDIS_READ_TIMEOUT', 1);
28
29// WordPress debugging
30define('WP_DEBUG', false);
31define('WP_DEBUG_LOG', false);
32define('WP_DEBUG_DISPLAY', false);
33
34// Security enhancements
35define('DISALLOW_FILE_EDIT', true);
36define('DISALLOW_FILE_MODS', true);
37define('FORCE_SSL_ADMIN', true);
38define('WP_POST_REVISIONS', 3);
39define('AUTOSAVE_INTERVAL', 300);
40
41// Performance optimizations
42define('WP_MEMORY_LIMIT', '256M');
43define('WP_MAX_MEMORY_LIMIT', '512M');
44define('COMPRESS_CSS', true);
45define('COMPRESS_SCRIPTS', true);
46define('CONCATENATE_SCRIPTS', true);
47
48// Multisite configuration (if needed)
49// define('WP_ALLOW_MULTISITE', true);
50
51// Custom content directory (if using EFS)
52define('WP_CONTENT_DIR', '/var/www/html/wp-content');
53define('WP_CONTENT_URL', 'http://' . $_SERVER['HTTP_HOST'] . '/wp-content');
54
55// Database repair
56define('WP_ALLOW_REPAIR', false);
57
58if (!defined('ABSPATH')) {
59    define('ABSPATH', __DIR__ . '/');
60}
61
62require_once ABSPATH . 'wp-settings.php';

CDN and Performance Optimization

CloudFront Distribution

Setting up global content delivery:

 1// CloudFront Origin Access Control for S3
 2const originAccessControl = new cloudfront.OriginAccessControl(this, 'OAC', {
 3  description: 'WordPress static content OAC',
 4});
 5
 6// S3 bucket for static assets
 7const assetsBucket = new s3.Bucket(this, 'WordPressAssets', {
 8  bucketName: `wordpress-assets-${cdk.Aws.ACCOUNT_ID}`,
 9  versioned: true,
10  encryption: s3.BucketEncryption.S3_MANAGED,
11  blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
12  removalPolicy: cdk.RemovalPolicy.DESTROY,
13});
14
15// CloudFront distribution
16const distribution = new cloudfront.Distribution(this, 'WordPressDistribution', {
17  defaultRootObject: 'index.php',
18  
19  // Origins
20  additionalBehaviors: {
21    // Static assets from S3
22    '/wp-content/uploads/*': {
23      origin: new origins.S3Origin(assetsBucket, {
24        originAccessControl,
25      }),
26      viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
27      cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
28      compress: true,
29    },
30    
31    // WordPress admin (no caching)
32    '/wp-admin/*': {
33      origin: new origins.LoadBalancerV2Origin(alb, {
34        protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY,
35      }),
36      viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
37      cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
38      allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
39    },
40    
41    // API endpoints (no caching)
42    '/wp-json/*': {
43      origin: new origins.LoadBalancerV2Origin(alb, {
44        protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY,
45      }),
46      viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
47      cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
48      allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
49    },
50  },
51  
52  // Default behavior for dynamic content
53  defaultBehavior: {
54    origin: new origins.LoadBalancerV2Origin(alb, {
55      protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY,
56    }),
57    viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
58    cachePolicy: new cloudfront.CachePolicy(this, 'WordPressCachePolicy', {
59      cachePolicyName: 'WordPress-Dynamic-Content',
60      comment: 'Cache policy for WordPress dynamic content',
61      defaultTtl: cdk.Duration.seconds(0),
62      maxTtl: cdk.Duration.days(1),
63      minTtl: cdk.Duration.seconds(0),
64      cookieBehavior: cloudfront.CacheCookieBehavior.allowList(
65        'wordpress_*', 'wp-*', 'comment_*'
66      ),
67      headerBehavior: cloudfront.CacheHeaderBehavior.allowList(
68        'Accept-Encoding', 'CloudFront-Viewer-Country', 'Host'
69      ),
70      queryStringBehavior: cloudfront.CacheQueryStringBehavior.all(),
71      enableAcceptEncodingGzip: true,
72      enableAcceptEncodingBrotli: true,
73    }),
74    allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
75    compress: true,
76  },
77  
78  // Geographic restrictions
79  geoRestriction: cloudfront.GeoRestriction.allowlist('US', 'CA', 'GB', 'DE', 'FR'),
80  
81  // Security headers
82  responseHeadersPolicy: new cloudfront.ResponseHeadersPolicy(this, 'SecurityHeaders', {
83    comment: 'Security headers for WordPress',
84    securityHeadersBehavior: {
85      contentTypeOptions: { override: true },
86      frameOptions: { frameOption: cloudfront.HeadersFrameOption.DENY, override: true },
87      referrerPolicy: { referrerPolicy: cloudfront.HeadersReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN, override: true },
88      strictTransportSecurity: {
89        accessControlMaxAge: cdk.Duration.seconds(31536000),
90        includeSubdomains: true,
91        preload: true,
92        override: true,
93      },
94    },
95  }),
96});

Architecture Tradeoffs Analysis

ECS Fargate vs EC2 Container Service

Advantages of Fargate:

  • Serverless containers: No EC2 instance management
  • Auto-scaling: Scales based on demand automatically
  • Cost efficiency: Pay only for running containers
  • Security: AWS manages underlying infrastructure

Disadvantages:

  • Cost at scale: More expensive than EC2 for consistent workloads
  • Limited customization: Less control over underlying OS
  • Cold starts: Slight delay when scaling up
  • Storage limitations: EFS required for persistent storage
 1// Cost comparison for different deployment options
 2const costAnalysis = {
 3  // Fargate (2 vCPU, 4GB RAM, running 24/7)
 4  fargate: {
 5    monthlyHours: 24 * 30,
 6    vCpuCostPerHour: 0.04048,
 7    memoryCostPerHour: 0.004445,
 8    monthlyCost: (24 * 30) * (2 * 0.04048 + 4 * 0.004445), // ~$90/month
 9  },
10  
11  // EC2 with ECS (t3.medium)
12  ec2: {
13    instanceCost: 41.61, // t3.medium monthly
14    storageGost: 10,     // EBS storage
15    monthlyCost: 51.61,  // ~$52/month
16  },
17  
18  // Managed services (WordPress.com, WP Engine)
19  managed: {
20    basicPlan: 25,       // Basic plan
21    businessPlan: 96,    // Business plan with CDN
22    enterprisePlan: 450, // Enterprise plan
23  }
24};

EFS vs S3 for File Storage

Why EFS for WordPress:

 1// EFS advantages for WordPress file storage
 2const efsAdvantages = {
 3  fileSystem: 'POSIX-compliant file system interface',
 4  concurrency: 'Multiple containers can read/write simultaneously',
 5  performance: 'Low-latency access for frequent file operations',
 6  plugins: 'WordPress plugins expect traditional file system',
 7  uploads: 'Direct file uploads without additional processing'
 8};
 9
10// S3 alternative considerations
11const s3Alternative = {
12  cost: 'Lower storage costs for large files',
13  durability: '99.999999999% (11 9s) durability',
14  cdn: 'Native CloudFront integration',
15  limitations: 'Requires plugin for WordPress integration'
16};

RDS vs Self-managed MySQL

RDS MySQL Benefits:

  • Automated backups and point-in-time recovery
  • Multi-AZ deployment for high availability
  • Automated patching and maintenance
  • Performance Insights for monitoring
  • Read replicas for read scaling

Cost vs Feature Comparison:

 1-- RDS pricing (us-east-1, t3.micro)
 2-- Instance: $0.017/hour = ~$12/month
 3-- Storage: $0.115/GB/month
 4-- Backup: Free for backup retention ≤ DB size
 5
 6-- Self-managed alternative
 7-- EC2 t3.micro: $8.5/month
 8-- EBS storage: $10/month
 9-- Manual maintenance: Engineering time
10-- Total: ~$18.5/month + operational overhead

Performance Optimization Strategies

Database Optimization

 1-- WordPress-specific MySQL optimizations
 2-- my.cnf configuration for RDS parameter group
 3
 4[mysqld]
 5# InnoDB settings for WordPress
 6innodb_buffer_pool_size = 512M
 7innodb_buffer_pool_instances = 2
 8innodb_flush_log_at_trx_commit = 2
 9innodb_flush_method = O_DIRECT
10innodb_log_file_size = 128M
11
12# Query cache (MySQL 5.7 and earlier)
13query_cache_type = 1
14query_cache_size = 64M
15query_cache_limit = 2M
16
17# Connection settings
18max_connections = 100
19max_connect_errors = 10000
20
21# Slow query logging
22slow_query_log = 1
23long_query_time = 2
24
25# WordPress-specific optimizations
26tmp_table_size = 64M
27max_heap_table_size = 64M
28join_buffer_size = 1M
29sort_buffer_size = 2M

Container Performance Tuning

 1// Task definition with performance optimizations
 2const optimizedTaskDefinition = new ecs.FargateTaskDefinition(this, 'OptimizedWordPressTask', {
 3  memoryLimitMiB: 4096,  // Increased memory
 4  cpu: 2048,             // Increased CPU
 5  
 6  // Platform version for better performance
 7  platformVersion: ecs.FargatePlatformVersion.LATEST,
 8  
 9  // Task role for AWS service access
10  taskRole: taskRole,
11  executionRole: executionRole,
12});
13
14// WordPress container with resource limits
15const optimizedContainer = optimizedTaskDefinition.addContainer('wordpress', {
16  image: ecs.ContainerImage.fromRegistry('your-account.dkr.ecr.region.amazonaws.com/wordpress:optimized'),
17  memoryReservationMiB: 3072,
18  memoryLimitMiB: 4096,
19  
20  // Resource allocation
21  cpu: 1536,
22  
23  // Environment variables for performance
24  environment: {
25    // PHP-FPM optimizations
26    PHP_FPM_PM: 'dynamic',
27    PHP_FPM_PM_MAX_CHILDREN: '50',
28    PHP_FPM_PM_START_SERVERS: '10',
29    PHP_FPM_PM_MIN_SPARE_SERVERS: '5',
30    PHP_FPM_PM_MAX_SPARE_SERVERS: '20',
31    PHP_FPM_PM_MAX_REQUESTS: '1000',
32    
33    // Apache optimizations
34    APACHE_MAX_REQUEST_WORKERS: '400',
35    APACHE_SERVER_LIMIT: '16',
36    APACHE_THREAD_LIMIT: '25',
37    APACHE_THREADS_PER_CHILD: '25',
38  },
39  
40  // Health check
41  healthCheck: {
42    command: ['CMD-SHELL', 'curl -f http://localhost/wp-admin/admin-ajax.php?action=health || exit 1'],
43    interval: cdk.Duration.seconds(30),
44    timeout: cdk.Duration.seconds(5),
45    retries: 3,
46    startPeriod: cdk.Duration.seconds(60),
47  },
48});

Security Implementation

Network Security

 1// Security group configurations with least privilege
 2const albSecurityGroup = new ec2.SecurityGroup(this, 'ALBSecurityGroup', {
 3  vpc,
 4  description: 'Security group for Application Load Balancer',
 5  allowAllOutbound: false,
 6});
 7
 8// Allow HTTP and HTTPS traffic
 9albSecurityGroup.addIngressRule(
10  ec2.Peer.anyIpv4(),
11  ec2.Port.tcp(80),
12  'Allow HTTP traffic'
13);
14albSecurityGroup.addIngressRule(
15  ec2.Peer.anyIpv4(),
16  ec2.Port.tcp(443),
17  'Allow HTTPS traffic'
18);
19
20// Allow outbound to ECS tasks
21albSecurityGroup.addEgressRule(
22  ecsSecurityGroup,
23  ec2.Port.tcp(80),
24  'Allow outbound to ECS tasks'
25);
26
27// ECS security group - only allow traffic from ALB
28ecsSecurityGroup.addIngressRule(
29  albSecurityGroup,
30  ec2.Port.tcp(80),
31  'Allow traffic from ALB'
32);
33
34// Allow outbound for database and EFS access
35ecsSecurityGroup.addEgressRule(
36  rdsSecurityGroup,
37  ec2.Port.tcp(3306),
38  'Allow database access'
39);
40ecsSecurityGroup.addEgressRule(
41  efsSecurityGroup,
42  ec2.Port.tcp(2049),
43  'Allow EFS access'
44);

Application Security

 1// wp-config.php security enhancements
 2// Disable file editing
 3define('DISALLOW_FILE_EDIT', true);
 4define('DISALLOW_FILE_MODS', true);
 5
 6// Security keys (use AWS Secrets Manager in production)
 7define('WP_DEBUG', false);
 8define('WP_DEBUG_LOG', false);
 9define('WP_DEBUG_DISPLAY', false);
10
11// Database security
12define('DB_CHARSET', 'utf8mb4');
13define('DB_COLLATE', 'utf8mb4_unicode_ci');
14
15// Force SSL
16define('FORCE_SSL_ADMIN', true);
17if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
18    $_SERVER['HTTPS'] = 'on';
19}
20
21// Limit post revisions
22define('WP_POST_REVISIONS', 3);
23
24// Increase memory limit securely
25define('WP_MEMORY_LIMIT', '256M');
26
27// Hide WordPress version
28function remove_wp_version() {
29    return '';
30}
31add_filter('the_generator', 'remove_wp_version');
32
33// Disable XML-RPC
34add_filter('xmlrpc_enabled', '__return_false');
35
36// Security headers
37function add_security_headers() {
38    header('X-Content-Type-Options: nosniff');
39    header('X-Frame-Options: DENY');
40    header('X-XSS-Protection: 1; mode=block');
41    header('Referrer-Policy: strict-origin-when-cross-origin');
42    header('Permissions-Policy: geolocation=(), microphone=(), camera=()');
43}
44add_action('send_headers', 'add_security_headers');

Monitoring and Observability

CloudWatch Metrics and Alarms

 1// Custom metrics for WordPress performance
 2const wordpressMetrics = {
 3  // Application Load Balancer metrics
 4  responseTime: new cloudwatch.Metric({
 5    namespace: 'AWS/ApplicationELB',
 6    metricName: 'TargetResponseTime',
 7    dimensionsMap: {
 8      LoadBalancer: alb.loadBalancerFullName,
 9    },
10    statistic: 'Average',
11  }),
12  
13  // ECS Service metrics
14  cpuUtilization: new cloudwatch.Metric({
15    namespace: 'AWS/ECS',
16    metricName: 'CPUUtilization',
17    dimensionsMap: {
18      ServiceName: service.serviceName,
19      ClusterName: cluster.clusterName,
20    },
21    statistic: 'Average',
22  }),
23  
24  // RDS metrics
25  databaseConnections: new cloudwatch.Metric({
26    namespace: 'AWS/RDS',
27    metricName: 'DatabaseConnections',
28    dimensionsMap: {
29      DBInstanceIdentifier: database.instanceIdentifier,
30    },
31    statistic: 'Average',
32  }),
33};
34
35// CloudWatch Alarms
36const highResponseTimeAlarm = new cloudwatch.Alarm(this, 'HighResponseTime', {
37  metric: wordpressMetrics.responseTime,
38  threshold: 2.0,
39  evaluationPeriods: 2,
40  comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
41  alarmDescription: 'WordPress response time is too high',
42});
43
44const highCpuAlarm = new cloudwatch.Alarm(this, 'HighCPUUtilization', {
45  metric: wordpressMetrics.cpuUtilization,
46  threshold: 80,
47  evaluationPeriods: 2,
48  comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
49  alarmDescription: 'ECS tasks CPU utilization is high',
50});
51
52const highDbConnectionsAlarm = new cloudwatch.Alarm(this, 'HighDatabaseConnections', {
53  metric: wordpressMetrics.databaseConnections,
54  threshold: 80,
55  evaluationPeriods: 2,
56  comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
57  alarmDescription: 'Database connection count is high',
58});

Application Performance Monitoring

 1// X-Ray tracing for request tracking
 2const xrayRole = new iam.Role(this, 'XRayTaskRole', {
 3  assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
 4  managedPolicies: [
 5    iam.ManagedPolicy.fromAwsManagedPolicyName('AWSXRayDaemonWriteAccess'),
 6  ],
 7});
 8
 9// Add X-Ray daemon sidecar container
10const xrayContainer = taskDefinition.addContainer('xray-daemon', {
11  image: ecs.ContainerImage.fromRegistry('amazon/aws-xray-daemon:latest'),
12  memoryReservationMiB: 32,
13  cpu: 32,
14  essential: false,
15  
16  environment: {
17    AWS_REGION: cdk.Aws.REGION,
18  },
19  
20  logging: ecs.LogDrivers.awsLogs({
21    streamPrefix: 'xray',
22    logRetention: logs.RetentionDays.ONE_WEEK,
23  }),
24});
25
26xrayContainer.addPortMappings({
27  containerPort: 2000,
28  protocol: ecs.Protocol.UDP,
29});

Backup and Disaster Recovery

Automated Backup Strategy

 1// RDS automated backups
 2const backupVault = new backup.BackupVault(this, 'WordPressBackupVault', {
 3  backupVaultName: 'wordpress-backup-vault',
 4  encryptionKey: kms.Alias.fromAliasName(this, 'BackupKey', 'alias/aws/backup'),
 5});
 6
 7// Backup plan for RDS
 8const backupPlan = new backup.BackupPlan(this, 'WordPressBackupPlan', {
 9  backupPlanName: 'wordpress-backup-plan',
10  backupPlanRules: [
11    new backup.BackupPlanRule({
12      ruleName: 'daily-backup',
13      targets: [
14        new backup.BackupTarget({
15          backupResource: backup.BackupResource.fromRdsDatabase(database),
16        }),
17      ],
18      scheduleExpression: events.Schedule.cron({ hour: '2', minute: '0' }),
19      deleteAfter: cdk.Duration.days(30),
20      moveToColdStorageAfter: cdk.Duration.days(7),
21    }),
22    new backup.BackupPlanRule({
23      ruleName: 'weekly-backup',
24      targets: [
25        new backup.BackupTarget({
26          backupResource: backup.BackupResource.fromRdsDatabase(database),
27        }),
28      ],
29      scheduleExpression: events.Schedule.cron({ weekDay: '1', hour: '1', minute: '0' }),
30      deleteAfter: cdk.Duration.days(90),
31      moveToColdStorageAfter: cdk.Duration.days(30),
32    }),
33  ],
34});
35
36// EFS backup
37const efsBackup = new backup.BackupPlanRule({
38  ruleName: 'efs-daily-backup',
39  targets: [
40    new backup.BackupTarget({
41      backupResource: backup.BackupResource.fromEfsFileSystem(fileSystem),
42    }),
43  ],
44  scheduleExpression: events.Schedule.cron({ hour: '3', minute: '0' }),
45  deleteAfter: cdk.Duration.days(14),
46});

Cost Optimization Strategies

Resource Right-sizing

 1// Cost optimization based on traffic patterns
 2const costOptimizedConfig = {
 3  // Development environment
 4  development: {
 5    rds: {
 6      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
 7      multiAz: false,
 8      backupRetention: cdk.Duration.days(1),
 9    },
10    ecs: {
11      cpu: 512,
12      memory: 1024,
13      desiredCount: 1,
14      minCapacity: 1,
15      maxCapacity: 2,
16    },
17  },
18  
19  // Production environment
20  production: {
21    rds: {
22      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL),
23      multiAz: true,
24      backupRetention: cdk.Duration.days(7),
25    },
26    ecs: {
27      cpu: 1024,
28      memory: 2048,
29      desiredCount: 2,
30      minCapacity: 2,
31      maxCapacity: 10,
32    },
33  },
34};
35
36// Scheduled scaling for predictable traffic patterns
37const scheduledScaling = service.autoScaleTaskCount({
38  minCapacity: 1,
39  maxCapacity: 10,
40});
41
42// Scale up during business hours
43scheduledScaling.scaleOnSchedule('ScaleUpMorning', {
44  schedule: autoscaling.Schedule.cron({ hour: '8', minute: '0' }),
45  minCapacity: 3,
46  maxCapacity: 10,
47});
48
49// Scale down during off hours
50scheduledScaling.scaleOnSchedule('ScaleDownEvening', {
51  schedule: autoscaling.Schedule.cron({ hour: '20', minute: '0' }),
52  minCapacity: 1,
53  maxCapacity: 5,
54});

Cost Monitoring

 1// Budget alerts for cost control
 2const budget = new budgets.CfnBudget(this, 'WordPressBudget', {
 3  budget: {
 4    budgetName: 'wordpress-monthly-budget',
 5    budgetLimit: {
 6      amount: 100,
 7      unit: 'USD',
 8    },
 9    timeUnit: 'MONTHLY',
10    budgetType: 'COST',
11    costFilters: {
12      service: ['Amazon Elastic Container Service', 'Amazon Relational Database Service'],
13    },
14  },
15  notificationsWithSubscribers: [
16    {
17      notification: {
18        notificationType: 'ACTUAL',
19        comparisonOperator: 'GREATER_THAN',
20        threshold: 80,
21      },
22      subscribers: [
23        {
24          subscriptionType: 'EMAIL',
25          address: 'devops@company.com',
26        },
27      ],
28    },
29  ],
30});

Deployment and CI/CD Pipeline

Multi-environment Deployment

 1// Environment-specific configurations
 2interface EnvironmentConfig {
 3  stackName: string;
 4  rdsInstanceType: ec2.InstanceType;
 5  ecsTaskCount: number;
 6  enableMultiAz: boolean;
 7  backupRetentionDays: number;
 8}
 9
10const environments: { [key: string]: EnvironmentConfig } = {
11  dev: {
12    stackName: 'wordpress-dev',
13    rdsInstanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
14    ecsTaskCount: 1,
15    enableMultiAz: false,
16    backupRetentionDays: 1,
17  },
18  staging: {
19    stackName: 'wordpress-staging',
20    rdsInstanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL),
21    ecsTaskCount: 2,
22    enableMultiAz: true,
23    backupRetentionDays: 3,
24  },
25  prod: {
26    stackName: 'wordpress-prod',
27    rdsInstanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MEDIUM),
28    ecsTaskCount: 3,
29    enableMultiAz: true,
30    backupRetentionDays: 7,
31  },
32};
33
34// Deploy stack for each environment
35Object.entries(environments).forEach(([env, config]) => {
36  new EcsWordpressStack(app, config.stackName, {
37    env: { region: 'us-east-1' },
38    config,
39  });
40});

CI/CD Pipeline with CodePipeline

 1// CI/CD Pipeline
 2const pipeline = new codepipeline.Pipeline(this, 'WordPressPipeline', {
 3  pipelineName: 'wordpress-ecs-pipeline',
 4  stages: [
 5    {
 6      stageName: 'Source',
 7      actions: [
 8        new codepipeline_actions.GitHubSourceAction({
 9          actionName: 'Source',
10          owner: 'yennanliu',
11          repo: 'cdk-playground',
12          branch: 'main',
13          oauthToken: cdk.SecretValue.secretsManager('github-token'),
14          output: sourceArtifact,
15        }),
16      ],
17    },
18    {
19      stageName: 'Build',
20      actions: [
21        new codepipeline_actions.CodeBuildAction({
22          actionName: 'Build',
23          project: new codebuild.Project(this, 'BuildProject', {
24            buildSpec: codebuild.BuildSpec.fromObject({
25              version: '0.2',
26              phases: {
27                pre_build: {
28                  commands: [
29                    'echo Logging in to Amazon ECR...',
30                    'aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com',
31                  ],
32                },
33                build: {
34                  commands: [
35                    'echo Build started on `date`',
36                    'echo Building the Docker image...',
37                    'cd ecs-wordpress-2',
38                    'docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .',
39                    'docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG',
40                  ],
41                },
42                post_build: {
43                  commands: [
44                    'echo Build completed on `date`',
45                    'echo Pushing the Docker image...',
46                    'docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG',
47                  ],
48                },
49              },
50            }),
51            environment: {
52              buildImage: codebuild.LinuxBuildImage.STANDARD_5_0,
53              privileged: true,
54            },
55          }),
56          input: sourceArtifact,
57        }),
58      ],
59    },
60    {
61      stageName: 'Deploy',
62      actions: [
63        new codepipeline_actions.EcsDeployAction({
64          actionName: 'Deploy',
65          service: service,
66          input: buildArtifact,
67        }),
68      ],
69    },
70  ],
71});

Lessons Learned and Best Practices

1. Container Design

 1# Multi-stage build for optimized images
 2FROM wordpress:6.4-php8.2-apache AS base
 3
 4# Development stage
 5FROM base AS development
 6RUN apt-get update && apt-get install -y \
 7    xdebug \
 8    && docker-php-ext-enable xdebug
 9
10# Production stage
11FROM base AS production
12# Remove unnecessary packages and files
13RUN apt-get autoremove -y \
14    && apt-get clean \
15    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
16
17# Copy production-only configurations
18COPY --from=development /usr/local/etc/php/conf.d/production.ini /usr/local/etc/php/conf.d/

2. Database Connection Management

 1// WordPress database connection optimization
 2// wp-config.php additions
 3define('WP_ALLOW_REPAIR', false);
 4
 5// Connection pooling for high traffic
 6define('DB_CONNECTION_TIMEOUT', 5);
 7define('DB_RETRY_INTERVAL', 1);
 8
 9// Custom database error handling
10function custom_db_error_handler($wp_error) {
11    error_log('WordPress database error: ' . $wp_error->get_error_message());
12    
13    // Implement exponential backoff for connection retries
14    $retry_count = get_transient('db_retry_count') ?: 0;
15    $backoff_time = min(pow(2, $retry_count), 60); // Max 60 seconds
16    
17    if ($retry_count < 5) {
18        set_transient('db_retry_count', $retry_count + 1, $backoff_time);
19        sleep($backoff_time);
20        return true; // Retry connection
21    }
22    
23    return false; // Give up
24}

3. Monitoring and Alerting

 1// Comprehensive monitoring dashboard
 2const dashboard = new cloudwatch.Dashboard(this, 'WordPressDashboard', {
 3  dashboardName: 'WordPress-ECS-Performance',
 4  widgets: [
 5    [
 6      new cloudwatch.GraphWidget({
 7        title: 'ECS Service Metrics',
 8        left: [wordpressMetrics.cpuUtilization, wordpressMetrics.memoryUtilization],
 9        right: [service.metricTaskCount()],
10      }),
11    ],
12    [
13      new cloudwatch.GraphWidget({
14        title: 'Application Load Balancer',
15        left: [wordpressMetrics.responseTime, alb.metricRequestCount()],
16        right: [alb.metricHttpCodeTarget(elbv2.HttpCodeTarget.TARGET_2XX_COUNT)],
17      }),
18    ],
19    [
20      new cloudwatch.GraphWidget({
21        title: 'RDS Performance',
22        left: [wordpressMetrics.databaseConnections, database.metricCPUUtilization()],
23        right: [database.metricReadLatency(), database.metricWriteLatency()],
24      }),
25    ],
26  ],
27});

Conclusion

Building a scalable WordPress platform on AWS ECS Fargate provides significant advantages for content-heavy applications:

  • Automatic scaling handles traffic fluctuations without manual intervention
  • Container isolation improves security and resource utilization
  • Managed services reduce operational overhead
  • High availability through multi-AZ deployments
  • Cost efficiency with pay-per-use pricing models

Key takeaways from this implementation:

  1. Container Optimization: Design lightweight, secure containers with proper resource allocation
  2. Storage Strategy: Use EFS for shared WordPress files and RDS for reliable database operations
  3. Security First: Implement network isolation, encryption, and least-privilege access
  4. Monitor Everything: Comprehensive observability is crucial for production WordPress deployments
  5. Cost Management: Right-size resources and implement scheduled scaling for predictable traffic patterns

The complete implementation is available in the CDK playground repository, demonstrating how modern containerization approaches can transform traditional WordPress hosting into a scalable, enterprise-grade platform.

Whether you’re migrating from traditional hosting or building a new WordPress platform, the patterns demonstrated here provide a solid foundation for scalable, secure, and cost-effective content management systems on AWS.

Yen

Yen

Yen