• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Dockerizing and Orchestrating Legacy Laravel Systems on Modern AWS Infrastructure

Dockerizing and Orchestrating Legacy Laravel Systems on Modern AWS Infrastructure

Containerizing the Legacy Laravel Application

The first step in modernizing a legacy Laravel application for AWS is to containerize it. This involves creating a Dockerfile that accurately reflects the application’s runtime dependencies and build process. For older Laravel versions, common challenges include specific PHP version requirements, non-standard extensions, and manual dependency management.

Let’s assume a hypothetical legacy Laravel 5.x application with PHP 7.2, Redis, and a MySQL database. The Dockerfile will need to install these components and configure the web server (e.g., Nginx).

Dockerfile for Laravel 5.x

# Use an official PHP runtime as a parent image
FROM php:7.2-fpm

# Set the working directory in the container
WORKDIR /var/www/html

# Install system dependencies
RUN apt-get update && apt-get install -y \
    git \
    unzip \
    libzip-dev \
    libpng-dev \
    libjpeg-dev \
    libfreetype6-dev \
    libssl-dev \
    libonig-dev \
    libxml2-dev \
    zip \
    && rm -rf /var/lib/apt/lists/*

# Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype-dir=/usr --with-jpeg-dir=/usr \
    && docker-php-ext-install -j$(nproc) gd \
    && docker-php-ext-install pdo pdo_mysql zip exif pcntl opcache sockets \
    && pecl install redis \
    && docker-php-ext-enable redis

# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Copy the application code
COPY . /var/www/html

# Install Composer dependencies
RUN composer install --no-dev --prefer-dist --optimize-autoloader

# Configure PHP-FPM
COPY docker/php-fpm/zz-laravel.ini /usr/local/etc/php-fpm.d/zz-laravel.ini
COPY docker/php-fpm/www.conf /usr/local/etc/php-fpm.d/www.conf

# Expose port 9000 and start php-fpm server
EXPOSE 9000
CMD ["php-fpm"]

The accompanying zz-laravel.ini might look like this, optimizing for production:

memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300
date.timezone = UTC
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
display_errors = Off
log_errors = On
error_log = /var/log/php/error.log
session.save_path = /var/lib/php/sessions
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2
opcache.validate_timestamps=0
opcache.fast_shutdown=1

And the www.conf for PHP-FPM pool configuration:

[www]
user = www-data
group = www-data
listen = /var/run/php/php7.2-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 50
pm.min_spare_servers = 5
pm.max_spare_servers = 10
pm.start_servers = 2
pm.slowlog_timeout = 30s
pm.request_terminate_timeout = 120s
pm.process_idle_timeout = 10s
request_terminate_timeout = 120s
request_slowlog_timeout = 30s
catch_workers_output = yes
clear_env = no
env[PATH] = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
env[PHPRC] = /usr/local/etc/php/conf.d/zz-laravel.ini

Next, we need a web server configuration. Nginx is a common choice for serving Laravel applications.

Nginx Configuration for Laravel

server {
    listen 80;
    server_name your-legacy-app.com;
    root /var/www/html/public;

    index index.php index.html index.htm;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php:9000; # Assuming a service named 'php' in docker-compose
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }

    # Optional: Caching for static assets
    location ~* \.(css|js|jpg|jpeg|gif|png|ico|svg|webp)$ {
        expires 1y;
        add_header Cache-Control "public";
    }
}

To tie these together for local development and testing, a docker-compose.yml file is essential.

Docker Compose for Local Orchestration

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: legacy_laravel_app
    ports:
      - "8000:80" # Map host port 8000 to container port 80
    volumes:
      - .:/var/www/html # Mount current directory for development
      - ./docker/php/custom.ini:/usr/local/etc/php/conf.d/custom.ini # Optional custom php.ini
    depends_on:
      - db
      - redis
    environment:
      APP_ENV: local
      APP_DEBUG: true
      DB_HOST: db
      DB_PORT: 3306
      DB_DATABASE: legacy_db
      DB_USERNAME: user
      DB_PASSWORD: password
      REDIS_HOST: redis
      REDIS_PORT: 6379

  nginx:
    image: nginx:alpine
    container_name: legacy_laravel_nginx
    ports:
      - "80:80"
    volumes:
      - .:/var/www/html # Mount application code
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf # Mount Nginx config
    depends_on:
      - app

  db:
    image: mysql:5.7
    container_name: legacy_laravel_db
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root_password
      MYSQL_DATABASE: legacy_db
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql

  redis:
    image: redis:alpine
    container_name: legacy_laravel_redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

volumes:
  db_data:
  redis_data:

With these files in place, you can build and run the application locally using docker-compose up --build. This provides a self-contained environment for development and initial testing.

Migrating to AWS Infrastructure

Once the application is containerized, we can leverage AWS services for orchestration and scaling. For legacy applications, a common and robust approach is to use Amazon Elastic Container Service (ECS) with the EC2 launch type. This offers more control over the underlying instances compared to Fargate, which can be beneficial for applications with specific resource requirements or complex networking needs.

Setting up ECS Cluster and Task Definitions

First, we need to push our Docker image to Amazon Elastic Container Registry (ECR). Assuming you have the AWS CLI configured:

# Create an ECR repository
aws ecr create-repository --repository-name legacy-laravel-app

# Authenticate Docker to your ECR registry
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin [YOUR_AWS_ACCOUNT_ID].dkr.ecr.us-east-1.amazonaws.com

# Build your Docker image (ensure you are in the project root)
docker build -t legacy-laravel-app .

# Tag the image for ECR
docker tag legacy-laravel-app:latest [YOUR_AWS_ACCOUNT_ID].dkr.ecr.us-east-1.amazonaws.com/legacy-laravel-app:latest

# Push the image to ECR
docker push [YOUR_AWS_ACCOUNT_ID].dkr.ecr.us-east-1.amazonaws.com/legacy-laravel-app:latest

Next, create an ECS Cluster. This can be done via the AWS Console or AWS CLI. We’ll choose the EC2 launch type for more control.

Then, define an ECS Task Definition. This JSON document describes your application’s containers, including the Docker image to use, CPU and memory requirements, environment variables, and port mappings. For our legacy Laravel app, we’ll need at least two containers: one for the Laravel application (PHP-FPM) and one for Nginx.

{
    "family": "legacy-laravel-task",
    "networkMode": "awsvpc",
    "requiresCompatibilities": [
        "EC2"
    ],
    "cpu": "1024",
    "memory": "2048",
    "executionRoleArn": "arn:aws:iam::[YOUR_AWS_ACCOUNT_ID]:role/ecsTaskExecutionRole",
    "containerDefinitions": [
        {
            "name": "laravel-app",
            "image": "[YOUR_AWS_ACCOUNT_ID].dkr.ecr.us-east-1.amazonaws.com/legacy-laravel-app:latest",
            "cpu": 512,
            "memory": 1024,
            "essential": true,
            "portMappings": [
                {
                    "containerPort": 9000,
                    "protocol": "tcp"
                }
            ],
            "environment": [
                {
                    "name": "APP_ENV",
                    "value": "production"
                },
                {
                    "name": "APP_DEBUG",
                    "value": "false"
                },
                {
                    "name": "DB_HOST",
                    "value": "your-rds-endpoint.rds.amazonaws.com"
                },
                {
                    "name": "DB_PORT",
                    "value": "3306"
                },
                {
                    "name": "DB_DATABASE",
                    "value": "legacy_db"
                },
                {
                    "name": "DB_USERNAME",
                    "value": "admin"
                },
                {
                    "name": "DB_PASSWORD",
                    "value": "your_db_password"
                },
                {
                    "name": "REDIS_HOST",
                    "value": "your-elasticache-redis-endpoint.cache.amazonaws.com"
                },
                {
                    "name": "REDIS_PORT",
                    "value": "6379"
                }
            ],
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-group": "/ecs/legacy-laravel-task",
                    "awslogs-region": "us-east-1",
                    "awslogs-stream-prefix": "laravel-app"
                }
            }
        },
        {
            "name": "nginx",
            "image": "nginx:alpine",
            "cpu": 512,
            "memory": 512,
            "essential": true,
            "portMappings": [
                {
                    "containerPort": 80,
                    "protocol": "tcp"
                }
            ],
            "mountPoints": [
                {
                    "sourceVolume": "app-code",
                    "containerPath": "/var/www/html"
                },
                {
                    "sourceVolume": "nginx-config",
                    "containerPath": "/etc/nginx/conf.d/default.conf"
                }
            ],
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-group": "/ecs/legacy-laravel-task",
                    "awslogs-region": "us-east-1",
                    "awslogs-stream-prefix": "nginx"
                }
            }
        }
    ],
    "volumes": [
        {
            "name": "app-code",
            "host": {
                "sourcePath": "/path/to/your/app/code"
            }
        },
        {
            "name": "nginx-config",
            "host": {
                "sourcePath": "/path/to/your/nginx/config/default.conf"
            }
        }
    ]
}

Note: For EC2 launch type, the volumes section in the task definition refers to paths on the EC2 host. A more robust approach for production would be to use Amazon Elastic File System (EFS) for shared code or to bake the code directly into the Docker image and avoid host volume mounts for production deployments.

The ecsTaskExecutionRole needs permissions to pull images from ECR and send logs to CloudWatch Logs. The Nginx container will need access to the application code. In a production EC2 setup, this often involves provisioning EC2 instances with an IAM role that allows them to access EFS or using a shared volume strategy. For simplicity in this example, we’re referencing host paths, which is more typical for development or simpler setups.

Database and Cache Services

For the database, Amazon Relational Database Service (RDS) is the standard choice. Provision a MySQL instance (e.g., MySQL 5.7 or 8.0) and configure its security group to allow inbound traffic from your ECS cluster’s security group on port 3306. For caching, Amazon ElastiCache for Redis is ideal. Configure its security group similarly.

Ensure your ECS task definition’s environment variables correctly point to the RDS and ElastiCache endpoints.

Setting up Load Balancing and Service Discovery

To distribute traffic to your ECS tasks and provide a stable endpoint, an Application Load Balancer (ALB) is recommended. Create an ALB with a listener on port 80 (and optionally 443 for HTTPS). Configure a Target Group that points to your ECS cluster and the port your Nginx container is exposing (port 80 in our case).

Create an ECS Service. This service will manage the desired number of tasks running your task definition. Configure the service to use the ALB’s Target Group. The service will automatically register and deregister tasks with the ALB as they are launched or stopped.

For service discovery within the cluster (e.g., if other microservices needed to talk to your Laravel app), ECS provides built-in service discovery using AWS Cloud Map, or you can rely on DNS resolution if using the awsvpc network mode and assigning ENIs to tasks.

Deployment Strategies and CI/CD

For production deployments, manual pushes to ECR and updates to ECS task definitions are not scalable. Implement a CI/CD pipeline using AWS CodePipeline, AWS CodeBuild, and AWS CodeDeploy (or a third-party tool like GitLab CI, GitHub Actions, Jenkins).

  • CodeCommit/GitHub/Bitbucket: Source code repository.
  • CodeBuild: Builds the Docker image, pushes it to ECR, and can update the ECS task definition.
  • CodePipeline: Orchestrates the build and deployment process.
  • CodeDeploy: Manages the deployment of new task definitions to the ECS service, allowing for rolling updates or blue/green deployments.

A typical pipeline would trigger on a code commit, build the Docker image, tag it with a unique identifier (e.g., Git commit hash or build number), push to ECR, and then trigger a deployment to ECS. CodeDeploy can be configured to perform rolling updates, replacing old tasks with new ones gradually to minimize downtime.

Monitoring and Logging

Leverage AWS CloudWatch for monitoring and logging. Ensure your task definitions are configured with logConfiguration to send container logs to CloudWatch Logs. Create CloudWatch Alarms based on metrics like CPU utilization, memory utilization, request counts, and error rates from your ALB and ECS tasks. This is crucial for identifying performance bottlenecks and application errors in a production environment.

For legacy applications, specific error patterns or performance issues might only surface under load. Comprehensive logging and monitoring are key to diagnosing these issues in the containerized environment.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability
  • Scala Pekko vs. Go Goroutines: Actor Model vs. CSP for Event-Driven Reactive Systems
  • Java Loom Virtual Threads vs. Go Goroutines: Under-the-Hood Scheduler and Thread Overhead Comparison

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (584)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (806)
  • PHP (5)
  • PHP Development (21)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (19)
  • Ruby on Rails (1)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (7)
  • WordPress Theme Development (357)

Recent Posts

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (806)
  • Debugging & Troubleshooting (584)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala