• 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 » Leveraging Docker’s Multi-Stage Builds and BuildKit for Optimized Laravel CI/CD Pipelines

Leveraging Docker’s Multi-Stage Builds and BuildKit for Optimized Laravel CI/CD Pipelines

Optimizing Laravel Docker Images with Multi-Stage Builds and BuildKit

Modern CI/CD pipelines for PHP applications, particularly those built with frameworks like Laravel, often struggle with bloated Docker images. These large images lead to slower build times, increased storage consumption, and longer deployment cycles. This article details how to leverage Docker’s multi-stage builds and the advanced features of BuildKit to create lean, production-ready Docker images for your Laravel applications.

The Problem: Monolithic Dockerfiles

A common approach to Dockerizing a Laravel application involves a single Dockerfile that installs dependencies, compiles assets, and sets up the runtime environment. While functional, this often results in an image containing development tools, build artifacts, and unnecessary packages that are not required in production. Consider a typical Dockerfile:

This approach leads to an image that includes the PHP development environment, Composer dependencies, Node.js and npm for asset compilation, and potentially other build tools. The final production image, therefore, carries the overhead of all these intermediate build steps.

The Solution: Multi-Stage Builds

Multi-stage builds allow you to use multiple FROM instructions in a single Dockerfile. Each FROM instruction begins a new build stage, which can use a different base image. You can selectively copy artifacts from one stage to another, discarding everything you don’t need in the final image. This is crucial for separating build dependencies from runtime dependencies.

Stage 1: The Builder Environment

The first stage will be dedicated to installing all necessary build tools and dependencies. This includes Composer for PHP dependencies and Node.js/npm for frontend asset compilation. We’ll use a PHP base image that includes common extensions and a recent Node.js version.

Here’s a sample Dockerfile snippet for the builder stage:

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

# Install system dependencies for PHP extensions and Node.js
RUN apt-get update \
    && apt-get install -y \
        git \
        unzip \
        libzip-dev \
        libpng-dev \
        libjpeg-dev \
        libfreetype6-dev \
        libonig-dev \
        libxml2-dev \
        libssl-dev \
        libcurl4-openssl-dev \
        libzip-dev \
        vim \
        curl \
        # Node.js and npm installation (using nodesource repository)
        && curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
        && apt-get install -y nodejs \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install pdo pdo_mysql zip gd exif intl opcache \
    && pecl install redis \
    && docker-php-ext-enable redis

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

# Set working directory
WORKDIR /app

# Copy application files
COPY . /app

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

# Install Node.js dependencies and compile assets
RUN npm install \
    && npm run build -- --prod

Stage 2: The Production Environment

The second stage will use a minimal PHP base image (e.g., php:8.2-fpm-alpine) and copy only the necessary artifacts from the builder stage. This includes the compiled application code, vendor directory, and public assets. We will exclude development dependencies and build tools.

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

# Install production dependencies
RUN apk add --no-cache \
    libzip \
    libpng \
    libjpeg-turbo \
    freetype \
    oniguruma \
    libxml2 \
    openssl \
    curl \
    # Install PHP extensions required for runtime
    && docker-php-ext-install pdo pdo_mysql zip gd exif intl opcache \
    && pecl install redis \
    && docker-php-ext-enable redis

# Set working directory
WORKDIR /app

# Copy only necessary files from the builder stage
COPY --from=builder /app/vendor /app/vendor
COPY --from=builder /app/public /app/public
COPY --from=builder /app/bootstrap /app/bootstrap
COPY --from=builder /app/config /app/config
COPY --from=builder /app/resources /app/resources
COPY --from=builder /app/routes /app/routes
COPY --from=builder /app/storage /app/storage
COPY --from=builder /app/.env.example /app/.env.example
COPY --from=builder /app/artisan /app/artisan
COPY --from=builder /app/composer.json /app/composer.json
COPY --from=builder /app/composer.lock /app/composer.lock

# Clean up unnecessary files from copied directories
RUN rm -rf /app/storage/debugbar \
    && rm -rf /app/storage/logs/* \
    && rm -rf /app/storage/framework/sessions/* \
    && rm -rf /app/storage/framework/cache/* \
    && rm -rf /app/storage/framework/views/*

# Expose port
EXPOSE 9000

# Command to run PHP-FPM
CMD ["php-fpm"]

Leveraging BuildKit for Enhanced Performance

Docker’s BuildKit is an advanced builder engine that significantly improves build performance and efficiency. It offers features like:

  • Parallel build execution: BuildKit can execute independent build steps in parallel.
  • Improved caching: More granular caching of build layers, reducing redundant work.
  • Remote caching: Support for remote cache backends (e.g., S3, GCS) for shared caches across CI agents.
  • Secret management: Securely inject secrets into the build process.
  • Export cache: Export build cache to a remote location.

To enable BuildKit, ensure your Docker daemon is configured to use it. This is often the default in newer Docker versions. You can explicitly enable it by setting the DOCKER_BUILDKIT=1 environment variable in your CI environment or by configuring the Docker daemon.

BuildKit in Action: Caching and Parallelism

When building the multi-stage Dockerfile with BuildKit enabled, you’ll notice several improvements:

  • The RUN apt-get update && apt-get install ... command in the builder stage will be cached effectively. Subsequent builds that don’t change the base image or the package list will reuse the cached layer.
  • Composer and npm dependency installations are also cached. If your composer.lock or package-lock.json (or yarn.lock) files haven’t changed, these layers will be reused.
  • BuildKit’s parallel execution can speed up independent commands within a stage, though in this specific Laravel example, many commands are sequential due to dependencies.

Configuring Remote Caching with BuildKit

For CI/CD pipelines, a shared remote cache is invaluable. This allows different CI agents to benefit from builds performed by others. You can configure BuildKit to use an S3 bucket as a cache backend.

First, ensure your CI environment has AWS credentials configured (e.g., via environment variables AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_REGION). Then, you can specify the cache configuration during the build command:

DOCKER_BUILDKIT=1 docker build \
  --cache-from "type=s3,bucket=your-ci-cache-bucket-name" \
  --build-arg AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
  --build-arg AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
  --build-arg AWS_REGION=$AWS_REGION \
  -t your-dockerhub-username/your-laravel-app:latest .

To push the cache back to S3 after a successful build, you can use the --push flag with BuildKit. This is typically done when building and pushing the final image to a registry:

DOCKER_BUILDKIT=1 docker build \
  --cache-from "type=s3,bucket=your-ci-cache-bucket-name" \
  --build-arg AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
  --build-arg AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
  --build-arg AWS_REGION=$AWS_REGION \
  --push \
  -t your-dockerhub-username/your-laravel-app:latest .

Integrating into a CI/CD Pipeline (Example: GitLab CI)

Here’s a simplified example of how this could look in a .gitlab-ci.yml file:

variables:
  DOCKER_BUILDKIT: "on"
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  LATEST_TAG: $CI_REGISTRY_IMAGE:latest
  S3_CACHE_BUCKET: "your-ci-cache-bucket-name"

stages:
  - build
  - deploy

build_docker_image:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  before_script:
    # Login to Docker registry
    - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
    # Configure AWS credentials for S3 cache
    - export AWS_ACCESS_KEY_ID=$CI_AWS_ACCESS_KEY_ID
    - export AWS_SECRET_ACCESS_KEY=$CI_AWS_SECRET_ACCESS_KEY
    - export AWS_REGION=$CI_AWS_REGION
  script:
    - echo "Building Docker image with BuildKit and S3 cache..."
    - docker build \
        --cache-from "type=s3,bucket=$S3_CACHE_BUCKET" \
        --build-arg AWS_ACCESS_KEY_ID=$CI_AWS_ACCESS_KEY_ID \
        --build-arg AWS_SECRET_ACCESS_KEY=$CI_AWS_SECRET_ACCESS_KEY \
        --build-arg AWS_REGION=$CI_AWS_REGION \
        --tag $IMAGE_TAG \
        --tag $LATEST_TAG \
        --push \
        .
  only:
    - main # Or your production branch

Further Optimizations and Considerations

  • Alpine Linux Base Images: Using -alpine variants of base images (e.g., php:8.2-fpm-alpine) significantly reduces the final image size due to Alpine’s minimal package manager and smaller footprint.
  • Specific PHP Extensions: Only install the PHP extensions that are strictly required for your application’s runtime. Avoid installing development-only extensions in the production stage.
  • `.dockerignore` File: Use a robust `.dockerignore` file to prevent unnecessary files (like .git, node_modules from the builder stage if not needed, local development logs, etc.) from being copied into the build context, which can slow down the build and increase image size.
  • Entrypoint Scripts: For more complex startup routines (e.g., running database migrations), consider using an entrypoint script that is copied into the final image.
  • Security Scanning: Integrate Docker image security scanning tools into your CI pipeline to identify vulnerabilities in your base images and installed packages.
  • Layer Pruning: Regularly prune unused Docker images and build cache on your CI runners to free up disk space.

Conclusion

By adopting multi-stage builds and leveraging the power of Docker’s BuildKit, you can dramatically reduce the size and build time of your Laravel application’s Docker images. This leads to faster CI/CD cycles, more efficient resource utilization, and ultimately, quicker deployments. The separation of build and runtime environments is a fundamental practice for creating lean, secure, and performant containerized applications.

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

  • Unlocking High-Performance WordPress: A Deep Dive into Headless CMS Architecture with Laravel as the Backend
  • Leveraging Docker’s Multi-Stage Builds and BuildKit for Optimized Laravel CI/CD Pipelines
  • Beyond the Monolith: Architecting Microservices with Laravel Octane and Docker Swarm for High-Performance WordPress Headless Deployments
  • Demystifying Kubernetes for WordPress: Advanced Headless Deployments and High Availability
  • Mastering Serverless PHP 9 with AWS Lambda: A Deep Dive into Performance and Cost Optimization

Categories

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

Recent Posts

  • Unlocking High-Performance WordPress: A Deep Dive into Headless CMS Architecture with Laravel as the Backend
  • Leveraging Docker's Multi-Stage Builds and BuildKit for Optimized Laravel CI/CD Pipelines
  • Beyond the Monolith: Architecting Microservices with Laravel Octane and Docker Swarm for High-Performance WordPress Headless Deployments

Top Categories

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

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