• 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 » Beyond the Basics: Implementing Advanced CI/CD Pipelines for Laravel with Docker, GitHub Actions, and AWS ECS

Beyond the Basics: Implementing Advanced CI/CD Pipelines for Laravel with Docker, GitHub Actions, and AWS ECS

Dockerizing the Laravel Application

The foundation of our advanced CI/CD pipeline is a robust Docker setup for the Laravel application. This ensures consistency across development, staging, and production environments. We’ll define a multi-stage Dockerfile to optimize image size and security.

First, let’s define the base PHP image with necessary extensions. We’ll use an official PHP image and install Composer, Node.js for asset compilation, and common PHP extensions.

# Stage 1: Builder
FROM php:8.2-fpm AS builder

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

# Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) gd \
    && docker-php-ext-install pdo pdo_mysql zip exif pcntl opcache

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Install Node.js and npm (for asset compilation)
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
    && apt-get install -y nodejs \
    && npm install -g npm@latest

# Set working directory
WORKDIR /var/www/html

# Copy application files
COPY . .

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

# Compile frontend assets
RUN npm install && npm run build

# Stage 2: Production
FROM php:8.2-fpm-alpine

# Install system dependencies for production
RUN apk add --no-cache \
    libzip \
    freetype \
    libjpeg-turbo \
    libpng \
    oniguruma \
    libxml2

# Install PHP extensions
RUN docker-php-ext-install -j$(nproc) pdo pdo_mysql zip exif pcntl opcache

# Copy application files from builder stage
COPY --from=builder /var/www/html /var/www/html

# Copy compiled assets from builder stage
COPY --from=builder /var/www/html/public/build /var/www/html/public/build

# Set permissions for storage and bootstrap/cache
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache

# Expose port
EXPOSE 9000

# Set entrypoint (optional, can be overridden by ECS task definition)
ENTRYPOINT ["php-fpm"]

We also need a separate Dockerfile for Nginx, which will serve our Laravel application.

FROM nginx:alpine

# Remove default Nginx configuration
RUN rm /etc/nginx/conf.d/default.conf

# Copy custom Nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Copy application files (or mount them later)
# COPY --from=builder /var/www/html /var/www/html

# Expose port
EXPOSE 80

And the corresponding Nginx configuration file (nginx.conf):

server {
    listen 80;
    index index.php index.html index.htm;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/html/public;

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

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php-fpm:9000; # Assuming php-fpm service is available
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }

    # Serve static assets directly
    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|webp)$ {
        expires 1y;
        log_not_found off;
    }
}

Orchestration with AWS ECS and Fargate

AWS Elastic Container Service (ECS) with Fargate provides a serverless compute engine for containers. This abstracts away the underlying infrastructure, allowing us to focus on deploying and scaling our application.

We’ll define an ECS Task Definition that specifies the Docker images to use, CPU and memory requirements, environment variables, and port mappings. This definition will be used by GitHub Actions to launch new tasks.

{
  "family": "laravel-app",
  "networkMode": "awsvpc",
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "cpu": "1024",
  "memory": "2048",
  "executionRoleArn": "arn:aws:iam::YOUR_ACCOUNT_ID:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::YOUR_ACCOUNT_ID:role/ecsTaskRole",
  "containerDefinitions": [
    {
      "name": "laravel-app-php",
      "image": "YOUR_ECR_REPO_URI:latest",
      "essential": true,
      "portMappings": [
        {
          "containerPort": 9000,
          "protocol": "tcp"
        }
      ],
      "environment": [
        {
          "name": "APP_ENV",
          "value": "production"
        },
        {
          "name": "APP_URL",
          "value": "https://your-domain.com"
        },
        {
          "name": "DB_HOST",
          "value": "your-db-host"
        },
        {
          "name": "DB_PORT",
          "value": "3306"
        },
        {
          "name": "DB_DATABASE",
          "value": "your-db-name"
        },
        {
          "name": "DB_USERNAME",
          "value": "your-db-user"
        },
        {
          "name": "DB_PASSWORD",
          "value": "your-db-password"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/laravel-app",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "php"
        }
      }
    },
    {
      "name": "laravel-app-nginx",
      "image": "YOUR_ECR_REPO_URI_NGINX:latest",
      "essential": true,
      "portMappings": [
        {
          "containerPort": 80,
          "protocol": "tcp"
        }
      ],
      "links": [
        "laravel-app-php"
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/laravel-app",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "nginx"
        }
      }
    }
  ]
}

Important Notes:

  • Replace YOUR_ACCOUNT_ID, YOUR_ECR_REPO_URI, YOUR_ECR_REPO_URI_NGINX, and database credentials with your actual values.
  • Ensure the IAM roles ecsTaskExecutionRole and ecsTaskRole are properly configured with necessary permissions (e.g., ECR pull, CloudWatch Logs, Secrets Manager if used).
  • The links directive is for older ECS configurations; for newer ones, use service discovery or task networking. In this Fargate example, we’ll rely on the default VPC networking and assume the Nginx container can reach the PHP-FPM container via its service name (e.g., php-fpm if using ECS Service Discovery or a task definition where containers share the same network namespace). For simplicity in this example, we’ll assume the Nginx container can reach the PHP-FPM container by its container name laravel-app-php if they are in the same task.
  • Environment variables for sensitive data (like database passwords) should ideally be managed via AWS Secrets Manager or Parameter Store and injected into the task definition.

Automating with GitHub Actions

GitHub Actions will be our CI/CD orchestrator. We’ll create workflows to build Docker images, push them to Amazon Elastic Container Registry (ECR), and deploy to AWS ECS.

First, set up AWS credentials as a GitHub Secret (e.g., AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY). Also, configure ECR repository URIs and ECS cluster/service names as secrets (e.g., AWS_ECR_REPO_URI, AWS_ECS_CLUSTER_NAME, AWS_ECS_SERVICE_NAME).

Here’s a workflow for building and pushing Docker images:

name: Build and Push Docker Images

on:
  push:
    branches:
      - main # Or your primary development branch

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1 # Your AWS region

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build and push Laravel PHP image
        id: build-php
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPO_URI_PHP }} # e.g., 123456789012.dkr.ecr.us-east-1.amazonaws.com/laravel-app-php
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -f Dockerfile -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

      - name: Build and push Nginx image
        id: build-nginx
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPO_URI_NGINX }} # e.g., 123456789012.dkr.ecr.us-east-1.amazonaws.com/laravel-app-nginx
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -f Dockerfile.nginx -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

Next, a workflow to deploy to ECS. This workflow will trigger after a successful build and push.

name: Deploy to AWS ECS

on:
  push:
    branches:
      - main # Trigger deployment on push to main

jobs:
  deploy:
    runs-on: ubuntu-latest
    needs: build-and-push # Ensure build is complete
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1 # Your AWS region

      - name: Get ECR image tags
        id: image-tags
        run: |
          echo "::set-output name=php_image::${{ secrets.AWS_ECR_REPO_URI_PHP }}:${{ github.sha }}"
          echo "::set-output name=nginx_image::${{ secrets.AWS_ECR_REPO_URI_NGINX }}:${{ github.sha }}"

      - name: Update ECS Task Definition
        id: update-task-def
        uses: aws-actions/amazon-ecs-update-task-definition@v1
        with:
          cluster-name: ${{ secrets.AWS_ECS_CLUSTER_NAME }}
          service-name: ${{ secrets.AWS_ECS_SERVICE_NAME }}
          task-definition: task-definition.json # Path to your task definition file
          container-name-php: laravel-app-php # Name of the PHP container in task-def
          container-name-nginx: laravel-app-nginx # Name of the Nginx container in task-def
          image-php: ${{ steps.image-tags.outputs.php_image }}
          image-nginx: ${{ steps.image-tags.outputs.nginx_image }}

      - name: Deploy to Amazon ECS
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          cluster-name: ${{ secrets.AWS_ECS_CLUSTER_NAME }}
          service-name: ${{ secrets.AWS_ECS_SERVICE_NAME }}
          task-definition: ${{ steps.update-task-def.outputs.task-definition }}
          wait-for-service-stability: true

In the deployment workflow, we use the amazon-ecs-update-task-definition action to dynamically update the image tags in our task-definition.json file with the newly built images from ECR. Then, amazon-ecs-deploy-task-definition registers the new task definition and updates the ECS service to use it, triggering a rolling deployment.

Database Migrations and Seeding

Handling database migrations in a containerized, automated deployment requires careful consideration. We don’t want migrations to run on every container start, nor do we want them to conflict if multiple instances start simultaneously.

A common strategy is to run migrations as a separate, one-off task before deploying the new application version. This can be integrated into the deployment workflow.

We can create a separate Docker image or a command within the existing workflow to execute migrations. For simplicity, let’s assume we’ll run migrations using the existing Laravel PHP image as a one-off task.

# Add this job to your deploy workflow
  run-migrations:
    runs-on: ubuntu-latest
    needs: build-and-push # Ensure build is complete
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1

      - name: Set up PHP and Composer
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
          extensions: mbstring, xml, ctype, tokenizer, json, dom, fileinfo, redis
          coverage: none

      - name: Install Composer dependencies
        run: |
          composer install --no-dev --optimize-autoloader --no-interaction

      - name: Run Laravel Migrations
        env:
          APP_ENV: production
          APP_URL: https://your-domain.com
          DB_HOST: your-db-host
          DB_PORT: 3306
          DB_DATABASE: your-db-name
          DB_USERNAME: your-db-user
          DB_PASSWORD: ${{ secrets.DB_PASSWORD }} # Use a secret for DB password
          REDIS_HOST: your-redis-host # Example for Redis
          REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD }} # Example for Redis
        run: |
          php artisan migrate --force
          # Optionally run seeding
          # php artisan db:seed --force

This migration job should run before the deployment job updates the ECS service. The --force flag is necessary for production environments to bypass the confirmation prompt. It’s crucial to manage the database password securely using GitHub Secrets.

Environment Management and Secrets

Managing environments (development, staging, production) and sensitive credentials is a critical aspect of any CI/CD pipeline. For this setup, we’ll leverage GitHub Actions secrets and AWS services.

GitHub Secrets:

  • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY: For authenticating with AWS.
  • AWS_ECR_REPO_URI_PHP, AWS_ECR_REPO_URI_NGINX: ECR repository URIs.
  • AWS_ECS_CLUSTER_NAME, AWS_ECS_SERVICE_NAME: ECS cluster and service identifiers.
  • DB_PASSWORD, REDIS_PASSWORD (and any other sensitive credentials): For database and other service connections.

AWS Secrets Manager / Parameter Store:

For production, it’s highly recommended to store sensitive information like database passwords, API keys, and JWT secrets in AWS Secrets Manager or AWS Systems Manager Parameter Store. The ECS task definition can then be configured to inject these secrets as environment variables into the containers.

{
  "family": "laravel-app",
  "networkMode": "awsvpc",
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "cpu": "1024",
  "memory": "2048",
  "executionRoleArn": "arn:aws:iam::YOUR_ACCOUNT_ID:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::YOUR_ACCOUNT_ID:role/ecsTaskRole",
  "containerDefinitions": [
    {
      "name": "laravel-app-php",
      "image": "YOUR_ECR_REPO_URI:latest",
      "essential": true,
      "portMappings": [
        {
          "containerPort": 9000,
          "protocol": "tcp"
        }
      ],
      "environment": [
        {
          "name": "APP_ENV",
          "value": "production"
        },
        {
          "name": "APP_URL",
          "value": "https://your-domain.com"
        }
      ],
      "secrets": [
        {
          "name": "DB_HOST",
          "valueFrom": "arn:aws:secretsmanager:us-east-1:YOUR_ACCOUNT_ID:secret:your-db-host-secret-xxxxx"
        },
        {
          "name": "DB_PORT",
          "valueFrom": "arn:aws:secretsmanager:us-east-1:YOUR_ACCOUNT_ID:secret:your-db-port-secret-xxxxx"
        },
        {
          "name": "DB_DATABASE",
          "valueFrom": "arn:aws:secretsmanager:us-east-1:YOUR_ACCOUNT_ID:secret:your-db-name-secret-xxxxx"
        },
        {
          "name": "DB_USERNAME",
          "valueFrom": "arn:aws:secretsmanager:us-east-1:YOUR_ACCOUNT_ID:secret:your-db-user-secret-xxxxx"
        },
        {
          "name": "DB_PASSWORD",
          "valueFrom": "arn:aws:secretsmanager:us-east-1:YOUR_ACCOUNT_ID:secret:your-db-password-secret-xxxxx"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/laravel-app",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "php"
        }
      }
    },
    // ... Nginx container definition
  ]
}

The taskRoleArn must have permissions to access Secrets Manager (e.g., secretsmanager:GetSecretValue). The application code in Laravel will then access these values as standard environment variables.

Monitoring and Logging

Effective monitoring and logging are crucial for maintaining a healthy production environment. Our setup leverages AWS CloudWatch for centralized logging.

As configured in the ECS task definition, we are using the awslogs log driver. This automatically streams container logs to CloudWatch Logs. You can then create log groups and streams for your Laravel application and Nginx containers.

Within CloudWatch Logs, you can:

  • View real-time logs from your containers.
  • Create metric filters to track specific events (e.g., errors, request counts).
  • Set up alarms based on these metrics to notify you of potential issues.
  • Use CloudWatch Logs Insights for advanced log querying and analysis.

For application performance monitoring (APM), consider integrating tools like AWS X-Ray, Datadog, or New Relic. These can provide deeper insights into application performance, trace requests across services, and help pinpoint bottlenecks.

Additionally, set up health checks within your ECS service. This allows ECS to automatically detect unhealthy tasks and replace them, ensuring high availability.

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

  • Beyond the Basics: Implementing Advanced CI/CD Pipelines for Laravel with Docker, GitHub Actions, and AWS ECS
  • Orchestrating Microservices with Docker Swarm: A Scalable and Resilient Architecture for Modern PHP Applications
  • Leveraging PHP 9’s JIT Compiler and Ahead-of-Time Compilation for Unprecedented Laravel Performance: A Deep Dive into Micro-optimizations and Deployment Strategies
  • Achieving Sub-Millisecond API Response Times with Laravel 11, Swoole, and Advanced Caching Strategies on AWS ECS
  • Orchestrating Microservices with PHP 8/9 and Laravel: A Deep Dive into Docker Swarm & AWS ECS for High Availability

Categories

  • apache (1)
  • AWS (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (664)
  • Desktop Applications (14)
  • DevOps (39)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (38)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (873)
  • Performance & Security Optimization (7)
  • PHP (132)
  • PHP Development (49)
  • Plugins & Themes (244)
  • Programming Languages (10)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (650)
  • SEO & Growth (492)
  • Server (118)
  • Softwares (1)
  • Ubuntu (9)
  • Uncategorized (263)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (84)
  • WordPress Plugin Development (728)
  • WordPress Theme Development (357)

Recent Posts

  • Beyond the Basics: Implementing Advanced CI/CD Pipelines for Laravel with Docker, GitHub Actions, and AWS ECS
  • Orchestrating Microservices with Docker Swarm: A Scalable and Resilient Architecture for Modern PHP Applications
  • Leveraging PHP 9's JIT Compiler and Ahead-of-Time Compilation for Unprecedented Laravel Performance: A Deep Dive into Micro-optimizations and Deployment Strategies

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (728)
  • Debugging & Troubleshooting (664)
  • Security & Compliance (650)
  • SEO & Growth (492)

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