4. Deploy PHP Application with Application Load Balancer

Goal

Deploy a production-ready PHP application on AWS ECS Fargate with an Application Load Balancer for traffic distribution and high availability.

What you’ll learn:

  • How to configure Application Load Balancer with target groups
  • When to use ALB security groups vs ECS task security groups
  • How to integrate ECS services with load balancers
  • Best practices for container health checks and zero-downtime deployments

Table of Contents

  1. Prerequisites
  2. Exercise Steps
  3. Common Issues
  4. Summary
  5. Done! 🎉

Prerequisites

Before starting, ensure you have:

  • ✓ AWS account with administrative access
  • ✓ AWS region selected (e.g., us-east-1 or eu-west-1)
  • ✓ ECS cluster created (e.g., dev-cluster from previous exercise)
  • ✓ Container image in ECR or Docker Hub (e.g., your PHP app image)
  • ✓ Default VPC with public subnets in at least 2 availability zones

Exercise Steps

Overview

  1. Create Security Groups
  2. Create Application Load Balancer
  3. Create Target Group
  4. Create IAM Roles
  5. Create CloudWatch Log Group
  6. Create Task Definition
  7. Create ECS Service with ALB
  8. Verify Deployment

Step 1: Create Security Groups

Create two security groups: one for the ALB (accepts HTTP from internet) and one for ECS tasks (accepts traffic only from ALB).

Create ALB Security Group

  1. Navigate to EC2 → Security Groups

  2. Click Create security group

  3. Enter security group name: dev-alb-sg

  4. Enter description: “Security group for Application Load Balancer”

  5. Select your default VPC

  6. Under Inbound rules, click Add rule:

    • Type: HTTP
    • Source: 0.0.0.0/0
    • Description: Allow HTTP from internet
  7. Verify Outbound rules has all traffic allowed

  8. Click Create security group

  9. Note down the security group ID (e.g., sg-abc12345)

Create ECS Task Security Group

  1. Click Create security group again

  2. Enter security group name: dev-ecs-tasks-alb-sg

  3. Enter description: “Security group for ECS tasks behind ALB”

  4. Select your default VPC

  5. Under Inbound rules, click Add rule:

    • Type: Custom TCP
    • Port range: 8000 (your container port)
    • Source: Custom → select the ALB security group ID from step 9 above
    • Description: Allow traffic from ALB only
  6. Verify Outbound rules has all traffic allowed

  7. Click Create security group

ℹ Concept Deep Dive

Security group layering follows defense in depth:

  • ALB security group accepts HTTP (port 80) from anywhere
  • ECS security group accepts traffic only from ALB on container port (8000)
  • This prevents direct access to containers - all traffic must go through the load balancer

Source security group means “allow traffic from any resource that has this security group attached.” When you specify the ALB security group as source, only the ALB can reach your containers.

âš  Common Mistakes

  • Using 0.0.0.0/0 for ECS security group - this defeats the purpose of having an ALB
  • Wrong port number in ECS security group - must match your container port
  • Creating security groups in different VPCs - they must be in the same VPC

✓ Quick check: Two security groups created - ALB allows port 80 from internet, ECS allows container port from ALB only

Step 2: Create Application Load Balancer

Create an internet-facing ALB in public subnets to distribute traffic across ECS tasks.

  1. Navigate to EC2 → Load Balancers

  2. Click Create load balancer

  3. Select Application Load Balancer

  4. Click Create

  5. Enter load balancer name: dev-php-app-alb

  6. Select Scheme: Internet-facing

  7. Under Network mapping:

    • VPC: Select your default VPC
    • Select at least 2 availability zones with public subnets
  8. Under Security groups:

    • Remove default security group
    • Select dev-alb-sg (from Step 1)
  9. Skip the Listeners section for now (we’ll configure after creating target group)

  10. Click Next

  11. Skip creating target group here - click “Create load balancer” at the bottom

  12. Wait for load balancer to become active (2-3 minutes)

  13. Note down the DNS name (e.g., dev-php-app-alb-123456.us-east-1.elb.amazonaws.com)

ℹ Concept Deep Dive

Internet-facing vs internal: Internet-facing ALBs get public DNS names and can receive traffic from the internet. Internal ALBs only receive traffic from within the VPC.

Multiple availability zones are required for high availability. The ALB automatically distributes traffic across AZs and fails over if one AZ becomes unavailable.

ALB DNS name is permanent - it doesn’t change when you update tasks or deployments. This is the stable endpoint you’ll use to access your application.

âš  Common Mistakes

  • Selecting only one AZ - ALB requires minimum 2 AZs
  • Selecting private subnets - ALB needs public subnets for internet-facing configuration
  • Not noting the DNS name - you’ll need it for testing

✓ Quick check: ALB status is “Active” and has a DNS name

Step 3: Create Target Group

Create a target group that defines how the ALB routes traffic and performs health checks on your containers.

  1. Navigate to EC2 → Target Groups

  2. Click Create target group

  3. Select target type: IP addresses (required for Fargate)

  4. Enter target group name: dev-php-tg

  5. Select Protocol: HTTP, Port: 8000

  6. Select your default VPC

  7. Select Protocol version: HTTP1

  8. Under Health checks:

    • Health check protocol: HTTP
    • Health check path: /
    • Expand Advanced health check settings:
      • Healthy threshold: 2
      • Unhealthy threshold: 3
      • Timeout: 5
      • Interval: 30
      • Success codes: 200-299
  9. Click Next

  10. Skip registering targets (ECS will do this automatically)

  11. Click Create target group

Configure ALB Listener

  1. Navigate to EC2 → Load Balancers

  2. Click on dev-php-app-alb

  3. Click Listeners tab

  4. Click Add listener

  5. Configure listener:

    • Protocol: HTTP
    • Port: 80
    • Default action: Forward to → select dev-php-tg
  6. Click Add

ℹ Concept Deep Dive

Target type IP is required for Fargate because each task gets its own elastic network interface with a unique IP address. EC2 launch type can use instance IDs.

Health check configuration:

  • Path / must return HTTP 200 for the app to be healthy
  • Interval 30 seconds means ALB checks every 30 seconds
  • 2 consecutive successes mark target as healthy
  • 3 consecutive failures mark target as unhealthy (removes from rotation)

Listener is what actually receives traffic on port 80 and forwards it to the target group.

âš  Common Mistakes

  • Using “Instance” target type - Fargate requires “IP addresses”
  • Wrong health check path - must be a route your app responds to with 200
  • Port mismatch - target group port must match container port
  • Forgetting to add listener - ALB won’t route traffic without it

✓ Quick check: Target group created with IP target type and listener configured on ALB port 80

Step 4: Create IAM Roles

Create IAM roles for task execution (ECS infrastructure) and task role (application permissions).

Create Task Execution Role

  1. Navigate to IAM → Roles → Create role

  2. Select “AWS service” → “Elastic Container Service” → “Elastic Container Service Task”

  3. Click Next

  4. Select AmazonECSTaskExecutionRolePolicy

  5. Click Next

  6. Enter role name: dev-php-app-container-execution-role

  7. Click Create role

Create Task Role

  1. Click Create role again

  2. Select “AWS service” → “Elastic Container Service” → “Elastic Container Service Task”

  3. Click Next

  4. Skip adding policies

  5. Click Next

  6. Enter role name: dev-php-app-container-task-role

  7. Click Create role

ℹ Concept Deep Dive

These are the same role types as the nginx exercise, but we’re creating new ones specific to the PHP app for separation of concerns. In production, you might share the execution role but keep task roles separate per application.

✓ Quick check: Two roles created with names matching dev-php-app-container-execution-role and dev-php-app-container-task-role

Step 5: Create CloudWatch Log Group

Create a log group for the PHP application container logs.

  1. Navigate to CloudWatch → Log groups

  2. Click Create log group

  3. Enter log group name: /ecs/dev-php-app-alb

  4. Select retention: “7 days”

  5. Click Create

✓ Quick check: Log group /ecs/dev-php-app-alb visible with 7-day retention

Step 6: Create Task Definition

Create the task definition with container configuration, health checks, and resource limits.

  1. Navigate to ECS → Task definitions → Create new task definition

  2. Enter task definition family: dev-php-app-alb

  3. Under Infrastructure:

    • Launch type: AWS Fargate
    • OS/Architecture: Linux/X86_64
    • CPU: .25 vCPU
    • Memory: 0.5 GB
  4. Under Task roles:

    • Task execution role: dev-php-app-container-execution-role
    • Task role: dev-php-app-container-task-role
  5. Under Container - 1:

    • Name: php-app-container
    • Image URI: (your ECR image URI or Docker Hub image)
  6. Under Port mappings, click Add:

    • Container port: 8000
    • Protocol: TCP
    • App protocol: HTTP
  7. Expand Log collection:

    • Check “Use log collection”
    • Log driver: AWS CloudWatch
    • awslogs-group: /ecs/dev-php-app-alb
    • awslogs-region: (your region)
    • awslogs-stream-prefix: ecs
  8. Expand HealthCheck (Container level):

    • Command: CMD-SHELL, curl -f http://localhost:8000/ || exit 1
    • Interval: 30
    • Timeout: 5
    • Start period: 60
    • Retries: 3
  9. Click Create

ℹ Concept Deep Dive

Container health check is separate from ALB health check:

  • Container health check determines if the container itself is healthy (ECS may restart it)
  • ALB health check determines if traffic should be routed to the task
  • Start period of 60 seconds gives the app time to initialize before health checks count

Image URI format:

  • ECR: 123456789.dkr.ecr.us-east-1.amazonaws.com/my-php-app:latest
  • Docker Hub: username/my-php-app:latest or nginx:latest

âš  Common Mistakes

  • Wrong container port - must match what your app listens on (8000 for PHP built-in server)
  • Missing curl in health check - container needs curl installed
  • Start period too short - app fails health checks during startup

✓ Quick check: Task definition dev-php-app-alb:1 shows ACTIVE with Fargate compatibility

Step 7: Create ECS Service with ALB

Create the ECS service that maintains running tasks and integrates with the load balancer.

  1. Navigate to Clusters → dev-cluster → Services tab

  2. Click Create

  3. Under Environment:

    • Compute options: Launch type
    • Launch type: FARGATE
  4. Under Deployment configuration:

    • Application type: Service
    • Task definition family: dev-php-app-alb
    • Revision: 1 (latest)
    • Service name: dev-php-app-alb-service
    • Desired tasks: 2 (for high availability)
  5. Under Networking:

    • VPC: Select your default VPC
    • Subnets: Select all available subnets
    • Security group: Select “Use an existing security group”
    • Choose dev-ecs-tasks-alb-sg
    • Remove any default security groups
    • Public IP: Turn ON
  6. Under Load balancing:

    • Load balancer type: Select “Application Load Balancer”
    • Choose “Use an existing load balancer”
    • Load balancer: Select dev-php-app-alb
    • Choose “Use an existing listener”
    • Listener: Select 80:HTTP
    • Choose “Use an existing target group”
    • Target group: Select dev-php-tg
    • Health check grace period: 60 seconds
  7. Verify Service auto scaling is “Do not use”

  8. Click Create

  9. Wait for service to become stable (3-5 minutes)

ℹ Concept Deep Dive

Desired count of 2 provides high availability - if one task fails, traffic continues through the other while ECS starts a replacement.

Health check grace period of 60 seconds tells ECS to ignore ALB health check failures during startup. Without this, ECS might kill and restart tasks that are still initializing.

Load balancer integration automatically:

  • Registers new tasks with the target group
  • Deregisters old tasks during deployments
  • Waits for tasks to pass health checks before routing traffic
  • Enables zero-downtime deployments

Deployment configuration:

  • Minimum healthy 100% + Maximum 200% = rolling deployment
  • ECS starts new tasks first, waits for them to be healthy, then stops old tasks
  • This ensures zero downtime during updates

âš  Common Mistakes

  • Forgetting to enable Public IP - tasks won’t be able to pull images or access internet
  • Not selecting the ALB - service won’t register with load balancer
  • Wrong target group - traffic won’t route correctly
  • Desired count of 1 - no high availability, and deployments have brief downtime
  • Setting grace period too short - healthy tasks get killed during deployment

✓ Quick check: Service shows “ACTIVE” with “Running count: 2/2”

Step 8: Verify Deployment

Verify the PHP application is accessible through the load balancer and tasks are healthy.

Check Service Status

  1. Navigate to Clusters → dev-cluster → Services → dev-php-app-alb-service

  2. Verify Running tasks: 2/2

  3. Click Tasks tab

  4. Verify both tasks show “RUNNING” status

  5. Check Events tab for any errors

Check Target Group Health

  1. Navigate to EC2 → Target Groups → dev-php-tg

  2. Click Targets tab

  3. Verify both targets show “healthy” status

  4. If unhealthy, wait 2-3 minutes for health checks to complete

Test Application

  1. Navigate to EC2 → Load Balancers → dev-php-app-alb

  2. Copy the DNS name

  3. Open a browser tab

  4. Navigate to http://<alb-dns-name>

  5. Verify your PHP application loads correctly

  6. Refresh several times to see different task IPs (if your app shows them)

Check Logs

  1. Navigate to CloudWatch → Log groups → /ecs/dev-php-app-alb

  2. Click a log stream (one per task)

  3. Verify application logs and HTTP access logs appear

ℹ Concept Deep Dive

Target health states:

  • Initial: Target just registered, first health check not yet performed
  • Healthy: Passing health checks, receiving traffic
  • Unhealthy: Failing health checks, not receiving traffic
  • Draining: Being deregistered, finishing existing connections

Load balancer distributes traffic using round-robin by default. Each request might go to a different task.

DNS propagation: The ALB DNS name is immediately available, but DNS propagation globally can take a few minutes.

âš  Common Mistakes

  • Testing before targets are healthy - wait for “healthy” status
  • Using https:// - we haven’t configured SSL yet
  • Expecting consistent responses - ALB distributes across tasks
  • Checking logs immediately - wait 30-60 seconds after deployment

✓ Success indicators:

  • ✓ Service shows 2/2 running tasks
  • ✓ Both targets healthy in target group
  • ✓ Application loads via ALB DNS name
  • ✓ CloudWatch logs show access logs from both tasks
  • ✓ Refreshing page may show different task responses

Common Issues

“Targets are unhealthy”:

  • Check ECS task Events tab for errors
  • Verify container is listening on port 8000
  • Check health check path returns HTTP 200
  • Ensure container has curl installed for health checks
  • Wait full interval × unhealthy threshold (30s × 3 = 90s minimum)

“Service won’t start tasks”:

  • Check service Events tab for error messages
  • Verify subnets have internet access (for image pull)
  • Ensure task execution role has correct permissions
  • Check container image URI is correct

“Cannot access via ALB DNS”:

  • Verify ALB status is “Active”
  • Check listener is configured on port 80
  • Ensure ALB security group allows port 80 from 0.0.0.0/0
  • Verify targets are healthy in target group
  • Check DNS resolution (may take a few minutes)

“Tasks start but immediately stop”:

  • Check CloudWatch logs for application errors
  • Verify container health check command is correct
  • Ensure health check start period is long enough (60s)
  • Check container has required dependencies

“Zero-downtime deployment fails”:

  • Verify minimum healthy percent is 100%
  • Check health check grace period is sufficient (60s)
  • Ensure new tasks pass health checks before old ones stop
  • Review deployment circuit breaker settings

Summary

You’ve successfully deployed a production-ready PHP application with:

Key takeaway:

Application Load Balancer integration provides production-ready features: fixed DNS endpoints, automatic target registration, health-based routing, and zero-downtime deployments. This is the foundation for scalable, highly available container applications.

Done! 🎉

You’ve learned how to deploy containerized applications behind an Application Load Balancer on ECS Fargate. This architecture supports auto-scaling, blue/green deployments, and HTTPS termination (future enhancement).

To clean up resources:

  1. Delete ECS service (Clusters → Services → Delete)
  2. Delete ALB (EC2 → Load Balancers → Delete)
  3. Delete Target Group (EC2 → Target Groups → Delete)
  4. Delete Security Groups (EC2 → Security Groups → Delete both)
  5. Delete Task Definition (Task definitions → Deregister)
  6. Delete Log Group (CloudWatch → Log groups → Delete)
  7. Delete IAM Roles (IAM → Roles → Delete both)