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
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-clusterfrom 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
- Create Security Groups
- Create Application Load Balancer
- Create Target Group
- Create IAM Roles
- Create CloudWatch Log Group
- Create Task Definition
- Create ECS Service with ALB
- 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
Navigate to EC2 → Security Groups
Click Create security group
Enter security group name:
dev-alb-sgEnter description: “Security group for Application Load Balancer”
Select your default VPC
Under Inbound rules, click Add rule:
- Type:
HTTP - Source:
0.0.0.0/0 - Description:
Allow HTTP from internet
- Type:
Verify Outbound rules has all traffic allowed
Click Create security group
Note down the security group ID (e.g.,
sg-abc12345)
Create ECS Task Security Group
Click Create security group again
Enter security group name:
dev-ecs-tasks-alb-sgEnter description: “Security group for ECS tasks behind ALB”
Select your default VPC
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
- Type:
Verify Outbound rules has all traffic allowed
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/0for 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.
Navigate to EC2 → Load Balancers
Click Create load balancer
Select Application Load Balancer
Click Create
Enter load balancer name:
dev-php-app-albSelect Scheme:
Internet-facingUnder Network mapping:
- VPC: Select your default VPC
- Select at least 2 availability zones with public subnets
Under Security groups:
- Remove default security group
- Select
dev-alb-sg(from Step 1)
Skip the Listeners section for now (we’ll configure after creating target group)
Click Next
Skip creating target group here - click “Create load balancer” at the bottom
Wait for load balancer to become active (2-3 minutes)
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.
Navigate to EC2 → Target Groups
Click Create target group
Select target type:
IP addresses(required for Fargate)Enter target group name:
dev-php-tgSelect Protocol:
HTTP, Port:8000Select your default VPC
Select Protocol version:
HTTP1Under 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
- Healthy threshold:
- Health check protocol:
Click Next
Skip registering targets (ECS will do this automatically)
Click Create target group
Configure ALB Listener
Navigate to EC2 → Load Balancers
Click on
dev-php-app-albClick Listeners tab
Click Add listener
Configure listener:
- Protocol:
HTTP - Port:
80 - Default action:
Forward to→ selectdev-php-tg
- Protocol:
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
Navigate to IAM → Roles → Create role
Select “AWS service” → “Elastic Container Service” → “Elastic Container Service Task”
Click Next
Select
AmazonECSTaskExecutionRolePolicyClick Next
Enter role name:
dev-php-app-container-execution-roleClick Create role
Create Task Role
Click Create role again
Select “AWS service” → “Elastic Container Service” → “Elastic Container Service Task”
Click Next
Skip adding policies
Click Next
Enter role name:
dev-php-app-container-task-roleClick 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-roleanddev-php-app-container-task-role
Step 5: Create CloudWatch Log Group
Create a log group for the PHP application container logs.
Navigate to CloudWatch → Log groups
Click Create log group
Enter log group name:
/ecs/dev-php-app-albSelect retention: “7 days”
Click Create
✓ Quick check: Log group
/ecs/dev-php-app-albvisible with 7-day retention
Step 6: Create Task Definition
Create the task definition with container configuration, health checks, and resource limits.
Navigate to ECS → Task definitions → Create new task definition
Enter task definition family:
dev-php-app-albUnder Infrastructure:
- Launch type:
AWS Fargate - OS/Architecture:
Linux/X86_64 - CPU:
.25 vCPU - Memory:
0.5 GB
- Launch type:
Under Task roles:
- Task execution role:
dev-php-app-container-execution-role - Task role:
dev-php-app-container-task-role
- Task execution role:
Under Container - 1:
- Name:
php-app-container - Image URI: (your ECR image URI or Docker Hub image)
- Name:
Under Port mappings, click Add:
- Container port:
8000 - Protocol:
TCP - App protocol:
HTTP
- Container port:
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
Expand HealthCheck (Container level):
- Command:
CMD-SHELL, curl -f http://localhost:8000/ || exit 1 - Interval:
30 - Timeout:
5 - Start period:
60 - Retries:
3
- Command:
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:latestornginx: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:1shows 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.
Navigate to Clusters →
dev-cluster→ Services tabClick Create
Under Environment:
- Compute options:
Launch type - Launch type:
FARGATE
- Compute options:
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)
- Application type:
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
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:
60seconds
Verify Service auto scaling is “Do not use”
Click Create
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
Navigate to Clusters →
dev-cluster→ Services →dev-php-app-alb-serviceVerify Running tasks:
2/2Click Tasks tab
Verify both tasks show “RUNNING” status
Check Events tab for any errors
Check Target Group Health
Navigate to EC2 → Target Groups →
dev-php-tgClick Targets tab
Verify both targets show “healthy” status
If unhealthy, wait 2-3 minutes for health checks to complete
Test Application
Navigate to EC2 → Load Balancers →
dev-php-app-albCopy the DNS name
Open a browser tab
Navigate to
http://<alb-dns-name>Verify your PHP application loads correctly
Refresh several times to see different task IPs (if your app shows them)
Check Logs
Navigate to CloudWatch → Log groups →
/ecs/dev-php-app-albClick a log stream (one per task)
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:
- ✓ Application Load Balancer (fixed DNS endpoint)
- ✓ Target Group (health checks and routing)
- ✓ Multiple ECS tasks (high availability)
- ✓ Security group layers (defense in depth)
- ✓ Container health checks (automatic recovery)
- ✓ Zero-downtime deployments (rolling updates)
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:
- Delete ECS service (Clusters → Services → Delete)
- Delete ALB (EC2 → Load Balancers → Delete)
- Delete Target Group (EC2 → Target Groups → Delete)
- Delete Security Groups (EC2 → Security Groups → Delete both)
- Delete Task Definition (Task definitions → Deregister)
- Delete Log Group (CloudWatch → Log groups → Delete)
- Delete IAM Roles (IAM → Roles → Delete both)