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

Vengala Vinay

Having 9+ Years of Experience in Software Development

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

Dockerizing and Orchestrating Legacy Laravel Systems on Modern Google Cloud Infrastructure

Containerizing the Laravel Application

The first step in modernizing a legacy Laravel application for cloud deployment is to containerize it. This involves creating a Dockerfile that encapsulates the application’s environment, dependencies, and runtime. For a typical Laravel application, this means including PHP, Composer, and any necessary PHP extensions, along with the application code itself.

We’ll start with a multi-stage build to keep our final image lean. The first stage will handle dependency installation, and the second will copy only the necessary artifacts to a minimal base image.

Dockerfile for Laravel

# Stage 1: Build dependencies
FROM composer:latest as builder

WORKDIR /app

COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-interaction

COPY . .
RUN composer dump-autoload --optimize --no-dev

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

# Install necessary PHP extensions
RUN apk add --no-cache \
    acl \
    file \
    fpm \
    git \
    icu-dev \
    libzip-dev \
    libpng-dev \
    libjpeg-turbo-dev \
    oniguruma-dev \
    postgresql-dev \
    supervisor \
    zip \
    && docker-php-ext-configure gd --with-jpeg --with-freetype \
    && docker-php-ext-install -j$(nproc) gd \
    && docker-php-ext-install -j$(nproc) opcache \
    && docker-php-ext-install -j$(nproc) pdo pdo_pgsql zip

# Copy application code and dependencies from builder stage
COPY --from=builder /app /app

# Set working directory
WORKDIR /app

# Copy the application's .env.example to .env if it doesn't exist
RUN cp .env.example .env

# Install Node.js and npm for asset compilation (if needed)
# This can be a separate stage if you prefer to keep the final image smaller
RUN apk add --no-cache nodejs npm
RUN npm install
RUN npm run build

# Clean up npm cache
RUN npm cache clean --force

# Configure Supervisor for running background processes (e.g., queue workers)
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# Expose port 9000 for PHP-FPM
EXPOSE 9000

# Start Supervisor to manage PHP-FPM and other processes
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf"]

Supervisord Configuration

To manage PHP-FPM and potential background workers (like queue workers), we’ll use Supervisor. This ensures that critical processes are automatically restarted if they fail.

[supervisord]
nodaemon=true
user=root

[program:php-fpm]
command=/usr/local/sbin/php-fpm --nodaemonize --fpm-config /usr/local/etc/php-fpm.conf
autostart=true
autorestart=true
priority=10
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:queue-worker]
command=php artisan queue:work --tries=3 --timeout=60
process_name=%(program_name)s_%(process_num)02d
numprocs=2
autostart=true
autorestart=true
priority=20
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
user=www-data
directory=/app

Database and Cache Services on Google Cloud

Legacy applications often rely on traditional database setups. For modernization on Google Cloud, we’ll leverage managed services like Cloud SQL for PostgreSQL and Memorystore for Redis. This offloads operational burden and provides scalability and reliability.

Cloud SQL for PostgreSQL Configuration

When deploying to Google Cloud, your Laravel application will need to connect to a Cloud SQL instance. The most secure and recommended method is to use the Cloud SQL Auth Proxy. This proxy handles secure, encrypted connections to your database without requiring you to manage SSL certificates directly within your application’s Docker container.

First, ensure you have the Cloud SQL Auth Proxy binary available. You can download it or, more practically for containerization, include it in your Docker image. For simplicity in this example, we’ll assume it’s available in the environment where the container runs, or you can add it to your Dockerfile.

Your Laravel application’s database configuration (`config/database.php`) will need to be updated to point to the proxy. The proxy typically runs on `127.0.0.1:5432` (or a different port if configured) and connects to your Cloud SQL instance via its instance connection name.

'pgsql' => [
    'driver' => 'pgsql',
    'host' => env('DB_HOST', '127.0.0.1'), // This will be the proxy's host
    'port' => env('DB_PORT', '5432'),     // This will be the proxy's port
    'database' => env('DB_DATABASE', 'forge'),
    'username' => env('DB_USERNAME', 'forge'),
    'password' => env('DB_PASSWORD', ''),
    'charset' => 'utf8',
    'prefix' => '',
    'prefix_indexes' => true,
    'schema' => 'public',
    'sslmode' => 'prefer',
],

In your `.env` file, you’ll set the connection details to point to the proxy:

DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=your_database_name
DB_USERNAME=your_db_user
DB_PASSWORD=your_db_password

Memorystore for Redis Configuration

For caching and session management, Redis is a common choice. Google Cloud’s Memorystore provides a managed Redis service. Your Laravel application will connect to the Memorystore instance’s IP address.

Update your `.env` file with the Memorystore instance details:

REDIS_HOST=your-memorystore-instance-ip
REDIS_PASSWORD=null
REDIS_PORT=6379

And in `config/cache.php` and `config/session.php`, ensure you’re using the Redis driver and that the configuration correctly picks up these environment variables.

'default' => env('CACHE_DRIVER', 'file'),

'stores' => [
    // ... other stores
    'redis' => [
        'driver' => 'redis',
        'connection' => 'cache',
    ],
    // ...
],

'redis' => [
    'client' => 'phpredis', // Or 'predis' if you prefer
    'default' => [
        'host' => env('REDIS_HOST'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT'),
        'database' => 0,
    ],
],

Orchestration with Google Kubernetes Engine (GKE)

To deploy and manage your containerized Laravel application at scale, Google Kubernetes Engine (GKE) is the ideal choice. It provides a robust platform for orchestration, scaling, and self-healing.

Kubernetes Deployment Manifests

We’ll define Kubernetes resources using YAML manifests. This includes Deployments for managing application pods, Services for exposing the application, and potentially Ingress for external access.

First, a Deployment to manage your Laravel application pods. This will specify the Docker image to use, the number of replicas, and how to perform rolling updates.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: laravel-app
  labels:
    app: laravel
spec:
  replicas: 3 # Adjust based on your needs
  selector:
    matchLabels:
      app: laravel
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  template:
    metadata:
      labels:
        app: laravel
    spec:
      containers:
      - name: laravel-app
        image: gcr.io/your-gcp-project-id/your-laravel-image:latest # Replace with your image
        ports:
        - containerPort: 80 # Or the port your web server inside the container listens on
        env:
        - name: APP_ENV
          value: "production"
        - name: APP_DEBUG
          value: "false"
        - name: APP_URL
          value: "https://your-domain.com" # Set your application URL
        - name: DB_CONNECTION
          value: "pgsql"
        - name: DB_HOST
          value: "127.0.0.1" # Points to the Cloud SQL Auth Proxy
        - name: DB_PORT
          value: "5432"
        - name: DB_DATABASE
          valueFrom:
            secretKeyRef:
              name: db-secrets
              key: db-name
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: db-secrets
              key: db-user
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secrets
              key: db-password
        - name: REDIS_HOST
          value: "your-memorystore-instance-ip" # Direct IP for Memorystore
        - name: REDIS_PORT
          value: "6379"
        # Add other necessary environment variables
        livenessProbe:
          httpGet:
            path: /healthz # Create a health check endpoint in your Laravel app
            port: 80
          initialDelaySeconds: 15
          periodSeconds: 20
        readinessProbe:
          httpGet:
            path: /healthz
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 10
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "500m"
            memory: "512Mi"
      # If using Cloud SQL Auth Proxy as a sidecar
      # - name: cloud-sql-proxy
      #   image: gcr.io/cloudsql-docker/gce-proxy:1.33.0 # Use the latest version
      #   command:
      #     - "/cloud_sql_proxy"
      #     - "-instances=your-gcp-project-id:your-region:your-cloudsql-instance-name=tcp:5432"
      #   resources:
      #     requests:
      #       cpu: "50m"
      #       memory: "64Mi"
      #     limits:
      #       cpu: "100m"
      #       memory: "128Mi"
      #   securityContext:
      #     runAsNonRoot: true

Next, a Service to expose your application within the cluster. This provides a stable IP address and DNS name for your pods.

apiVersion: v1
kind: Service
metadata:
  name: laravel-app-service
spec:
  selector:
    app: laravel
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80 # The port your application container listens on
  type: ClusterIP # Or LoadBalancer if you want a direct external IP

For external access, an Ingress resource is typically used. This routes external HTTP(S) traffic to your Service. You’ll need to have an Ingress controller (like GKE’s built-in Load Balancer or Nginx Ingress Controller) installed.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: laravel-ingress
  annotations:
    # For GKE Load Balancer:
    kubernetes.io/ingress.class: "gce"
    # For Nginx Ingress Controller:
    # nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: your-domain.com # Replace with your domain
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: laravel-app-service
            port:
              number: 80

Secrets Management

Sensitive information like database credentials should not be hardcoded in your manifests. Kubernetes Secrets are the standard way to manage these. You can create secrets manually or use tools like Google Secret Manager integrated with GKE.

kubectl create secret generic db-secrets \
  --from-literal=db-name='your_database_name' \
  --from-literal=db-user='your_db_user' \
  --from-literal=db-password='your_db_password'

CI/CD Pipeline for Automated Deployments

A robust CI/CD pipeline is crucial for modernizing legacy systems. We’ll outline a workflow using Cloud Build, which integrates seamlessly with Google Cloud services.

Cloud Build Configuration

Create a `cloudbuild.yaml` file in your project’s root directory. This file defines the build steps.

steps:
# 1. Build the Docker image
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-t', 'gcr.io/$PROJECT_ID/your-laravel-image:$COMMIT_SHA', '.']

# 2. Push the Docker image to Google Container Registry
- name: 'gcr.io/cloud-builders/docker'
  args: ['push', 'gcr.io/$PROJECT_ID/your-laravel-image:$COMMIT_SHA']

# 3. Deploy to GKE
- name: 'gcr.io/cloud-builders/kubectl'
  args:
  - 'apply'
  - '-f'
  - 'kubernetes/deployment.yaml' # Path to your deployment manifest
  env:
  - 'CLOUDSDK_COMPUTE_ZONE=your-gke-zone' # e.g., us-central1-a
  - 'CLOUDSDK_CONTAINER_CLUSTER=your-gke-cluster-name' # Your GKE cluster name
  - 'PROJECT_ID=$PROJECT_ID' # Pass project ID to the build step

# 4. (Optional) Update the deployment with the new image tag
# This step is useful if your deployment.yaml uses a fixed tag like 'latest'
# and you want to ensure it picks up the specific commit SHA.
# Alternatively, you can use kustomize or Helm for more advanced deployments.
- name: 'gcr.io/cloud-builders/kubectl'
  args:
  - 'set'
  - 'image'
  - 'deployment/laravel-app' # Name of your deployment
  - 'laravel-app=gcr.io/$PROJECT_ID/your-laravel-image:$COMMIT_SHA'
  env:
  - 'CLOUDSDK_COMPUTE_ZONE=your-gke-zone'
  - 'CLOUDSDK_CONTAINER_CLUSTER=your-gke-cluster-name'

images:
- 'gcr.io/$PROJECT_ID/your-laravel-image:$COMMIT_SHA'

options:
  logging: CLOUD_LOGGING_ONLY

To trigger this build, you can set up a webhook in Cloud Build to listen for pushes to your Git repository (e.g., Cloud Source Repositories, GitHub, Bitbucket). When a push occurs, Cloud Build will execute the steps defined in `cloudbuild.yaml`, building your Docker image, pushing it to GCR, and deploying the new version to your GKE cluster.

Monitoring and Logging

Effective monitoring and logging are critical for maintaining production systems. Google Cloud offers integrated solutions for this.

Cloud Logging and Cloud Monitoring

By default, containers running on GKE will have their stdout and stderr streams sent to Cloud Logging. You can access these logs via the Google Cloud Console or the `gcloud` CLI.

gcloud logging read "resource.type=k8s_container AND resource.labels.cluster_name=your-gke-cluster-name AND resource.labels.namespace_name=default AND resource.labels.pod_name=laravel-app-*" --limit=100

For more advanced logging, consider configuring your application to write logs to files within the container and then using a logging agent (like Fluentd or Fluent Bit) deployed as a DaemonSet in GKE to collect and forward these logs to Cloud Logging with structured metadata. This is especially useful for application-specific logs.

Cloud Monitoring can be used to set up dashboards and alerts based on metrics from your GKE cluster, pods, and managed services like Cloud SQL and Memorystore. You can monitor CPU usage, memory, network traffic, database connections, and more. Set up alerts for critical thresholds to proactively address potential issues.

Primary Sidebar

A little about the Author

Having 9+ Years of Experience in Software Development.
Expertised in Php Development, WordPress Custom Theme Development (From scratch using underscores or Genesis Framework or using any blank theme or Premium Theme), Custom Plugin Development. Hands on Experience on 3rd Party Php Extension like Chilkat, nSoftware.

Recent Posts

  • Step-by-Step: Diagnosing thread pools deadlock during concurrent ActiveRecord transaction processing on Linode Servers
  • Securing Your E-commerce APIs: Preventing SQL Injection (SQLi) in customized checkout queries in WooCommerce Implementations
  • Disaster Recovery 101: Architecting Auto-Failovers for MySQL and Ruby Deployments on Linode
  • High-Throughput Caching Strategies: Scaling MySQL for Perl Application APIs
  • Disaster Recovery 101: Architecting Auto-Failovers for DynamoDB and Laravel Deployments on DigitalOcean

Copyright © 2026 · Vinay Vengala