25 min read Grand Capstone Project Part 4 (16 / 16 — Course Finale)
Part 4 — Grand Capstone Project (Final Lesson)

Lesson 12.16 — Capstone: Deploying & Operating a Real-World Cloud Application

From Localhost to Global Cloud: Docker Compose, Nginx SSL, GitHub Actions CI/CD, Prometheus Monitoring & Automated Backups

What will you build in this Grand Capstone?

Over the past 15 lessons, we studied cloud architecture, Linux systems, Docker containers, reverse proxies, CI/CD pipelines, Infrastructure as Code, secrets management, monitoring, scaling, and backups in isolation. Now, we unify all these concepts into a production-grade web application platform:

  • Preparing a complete Laravel 11 + MySQL 8.4 + Redis stack for production
  • Ubuntu LTS Cloud VM provisioning, SSH hardening & UFW firewall configuration
  • Multi-container Docker Compose (App, Web, Worker, DB, Redis) with isolated networks
  • Domain DNS routing, Host Nginx Reverse Proxy & automated Let's Encrypt HTTPS
  • Zero-touch GitHub Actions CI/CD with automated tests, image builds & SSH deployments
  • Production health monitoring, off-site encrypted MySQL backups, and rollback runbooks

1 A Real-World Cloud Deployment Project

We have covered the theory of cloud computing, datacenters, virtualization, Docker containers, reverse proxies, and backup strategies. The crowning test of a Cloud & DevOps engineer is to synthesize these individual building blocks into an unshakeable, automated production deployment.

Imagine your engineering team has developed a modern web application using Laravel 11, MySQL 8.4, Redis, and Nginx. It works flawlessly on your local laptop at localhost:8000. Now, you must publish it to the internet so thousands of global customers can access it securely, reliably, and with lightning speed.

Why Cloning Code onto a Server is NOT a Deployment:

Novice developers often believe that running git clone on a server completes the job. In production engineering, a robust deployment requires:

  • Hardening OS ports and host firewalls so internal databases are never exposed to the public internet.
  • Containerizing all runtimes with Docker to eliminate "Works on my machine" inconsistencies.
  • Securing web endpoints with Let's Encrypt TLS 1.3 certificates and automated renewal.
  • Setting up a GitHub Actions CI/CD pipeline so pushing code triggers tests and deploys zero-downtime updates.
  • Decoupling long-running tasks into background asynchronous queue workers.
  • Implementing telemetry health checks, automated alerts, and off-site encrypted database backups.

2 Project Technology Stack

Our capstone architecture utilizes industry-standard, battle-tested open-source components:

Technology Version / Platform Role in Architecture
Laravel 11.x (PHP 8.3-FPM) Core backend web application, business domain logic & RESTful APIs
MySQL 8.4 LTS (Official) Primary relational transactional database (with named persistent Docker volumes)
Redis 7.x Alpine In-memory caching and persistent asynchronous background queue broker
Docker & Compose v26.x / Compose v2 Application, web server, database, and background worker containerization
Ubuntu Server 24.04 LTS (Noble) Cloud Virtual Machine host operating system
Nginx Reverse Proxy 1.24+ (Host & Container) TLS/SSL termination, edge routing, static asset caching, and FastCGI bridging
Let's Encrypt / Certbot Snap Classic Automated free 90-day SSL/TLS certificate generation and renewal
GitHub Actions & GHCR CI/CD Runner Continuous testing, multi-stage Docker image builds, and automated SSH deployment

3 Master Production Deployment Architecture Blueprint

The complete journey of user requests through the host firewall, reverse proxy, and container network is mapped out below:

# Master Production Deployment Architecture
[Users on Internet] (Browser / Mobile App) | v HTTPS (Port 443) via DNS (example.com) +-----------------------------------------------------------------------+ | Cloud Virtual Machine (Ubuntu 24.04 LTS) | | | | [Host Nginx Reverse Proxy] <--- Certbot SSL Auto-Renew (/etc/letsencrypt) | | | | v Reverse Proxy (127.0.0.1:8080) | | +-----------------------------------------------------------------+ | | | Docker Compose Network (Isolated Bridge: backend) | | | | | | | | +------------------+ +-------------------------------+ | | | | | Container: web | --TCP-> | Container: app | | | | | | (Nginx Frontend) | :9000 | (PHP-FPM Laravel 11 Core) | | | | | +------------------+ +-------------------------------+ | | | | | | | | | +-------------+-------------+ | | | | v v | | | | +-----------------------+ +-------------+ | | | | | Container: db | | Container: | | | | | | (MySQL 8.4 Engine) | | redis (7) | | | | | | Vol: mysql_data | | Vol: r_data | | | | | +-----------------------+ +-------------+ | | | | ^ | | | | +-----------------------------+ | | | | | | Container: worker | -----------------------+ | | | | | (php artisan queue:work) | (Consumes async jobs) | | | | +-----------------------------+ | | | +-----------------------------------------------------------------+ | +-----------------------------------------------------------------------+ ^ | SSH Deploy over Key (Strict Known Hosts) [GitHub Actions CI/CD Pipeline] (Builds & pushes image to GHCR)

Key Security Guarantee: Notice that internal database and runtime ports (MySQL 3306, Redis 6379, PHP-FPM 9000) are never exposed to the public internet. Only the Host Nginx daemon listens on public ports 80 and 443, proxying valid web traffic to Docker's loopback port 127.0.0.1:8080.

4 Step 1: Preparing the Application for Production

Before provisioning cloud hardware, validate application health and enforce production configurations on your local environment:

4.1 Validating Dependencies and Test Suites

# 1. Validate Composer dependencies & optimize class autoloader:
composer validate
composer install --no-dev --prefer-dist --optimize-autoloader
# 2. Run full test suite (100% assertions must pass):
php artisan test
# 3. Clean install and bundle frontend assets:
npm ci
npm run build

4.2 Hardening Production Environment (.env.production)

Leaving APP_DEBUG=true in production is a fatal vulnerability that exposes database credentials and secrets upon any uncaught exception.

APP_NAME=ByteionApp
APP_ENV=production
APP_DEBUG=false # Mandatory: Never set to true in production!
APP_URL=https://example.com
APP_KEY=base64:YOUR_GENERATED_APP_KEY_HERE

LOG_CHANNEL=stderr
LOG_LEVEL=info

DB_CONNECTION=mysql
DB_HOST=db # Service name inside Docker Compose bridge network
DB_PORT=3306
DB_DATABASE=myapp_prod
DB_USERNAME=myapp_user
DB_PASSWORD=SuperSecureRandomPassword#2026

CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
REDIS_HOST=redis
REDIS_PORT=6379

5 Step 2: Provisioning Cloud Server & SSH Authentication

Provision an Ubuntu Virtual Machine on AWS EC2, DigitalOcean, Hetzner, or Google Compute Engine:

Recommended Baseline Specs: Ubuntu 24.04 LTS • 2 vCPU • 4 GB RAM • 50 GB NVMe SSD
Networking & Auth: Static Public IPv4 • Ed25519 Asymmetric SSH Keypair

Connecting and Updating System Packages

# 1. Connect via SSH key:
ssh -i ~/.ssh/id_ed25519 ubuntu@YOUR_SERVER_PUBLIC_IP
# 2. Update package indexes and install base admin utilities:
sudo apt update && sudo apt upgrade -y
sudo apt install -y git curl ufw ca-certificates gnupg lsb-release

6 Step 3: Server Security & UFW Firewall Hardening

Before running any workloads, configure the host firewall to enforce a default-deny policy:

# 1. Establish default baseline policies:
sudo ufw default deny incoming
sudo ufw default allow outgoing
# 2. Allow strictly required ports:
sudo ufw allow OpenSSH # Port 22
sudo ufw allow 80/tcp # HTTP (ACME Let's Encrypt validation)
sudo ufw allow 443/tcp # HTTPS (Encrypted client web traffic)
# 3. Enable firewall and verify status:
sudo ufw enable
sudo ufw status verbose
Critical Warning: The Docker UFW Bypass Vulnerability

By default, Docker manipulates Linux iptables directly. If you specify ports: "3306:3306" in Docker Compose, Docker injects rules that bypass UFW, exposing your database to the world! Never publish database ports publicly; always bind web containers strictly to 127.0.0.1:8080:80.

7 Step 4: Installing Docker Engine & Docker Compose

Install the official Docker Engine and Docker Compose v2 plugin on Ubuntu:

# 1. Add Docker's official GPG key:
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# 2. Set up apt repository and install:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 3. Add deploy user to docker group:
sudo usermod -aG docker $USER
docker compose version # Verify Compose v2 is active

Creating Production Application Directory

sudo mkdir -p /opt/myapp
sudo chown -R $USER:$USER /opt/myapp
cd /opt/myapp

8 Step 5: Production Docker Compose Configuration

Our production /opt/myapp/compose.yaml configures five orchestrated microservices:

services:
  # 1. Laravel PHP 8.3-FPM Application Core
  app:
    image: ghcr.io/byteionlab/myapp-php:${IMAGE_TAG}
    restart: unless-stopped
    env_file:
      - .env.production
    expose:
      - "9000"
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    volumes:
      - laravel_storage:/var/www/html/storage
    networks:
      - backend

  # 2. Container Nginx Web Server
  web:
    image: ghcr.io/byteionlab/myapp-nginx:${IMAGE_TAG}
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"  # Bound exclusively to host loopback interface
    depends_on:
      - app
    networks:
      - backend

  # 3. Laravel Background Queue Worker
  worker:
    image: ghcr.io/byteionlab/myapp-php:${IMAGE_TAG}
    restart: unless-stopped
    env_file:
      - .env.production
    command: ["php", "artisan", "queue:work", "--sleep=3", "--tries=3", "--timeout=90"]
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    volumes:
      - laravel_storage:/var/www/html/storage
    networks:
      - backend

  # 4. MySQL 8.4 Relational Database Server
  db:
    image: mysql:8.4
    restart: unless-stopped
    env_file:
      - .env.mysql
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u root -p$$MYSQL_ROOT_PASSWORD --silent"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - backend

  # 5. Redis In-Memory Cache & Queue Store
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    networks:
      - backend

volumes:
  mysql_data:
  redis_data:
  laravel_storage:

networks:
  backend:
    driver: bridge
Locking Down Production Secrets:

Restrict permissions on .env.production and .env.mysql so only the deployer user can read them:

chmod 600 .env.production .env.mysql

9 Step 6: Container Nginx Configuration

The Nginx container handles static assets directly from public/ and forwards PHP requests over FastCGI to app:9000:

server {
    listen 80;
    server_name _;
    root /var/www/html/public;
    index index.php index.html;

    client_max_body_size 64M;

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

    location ~ \.php$ {
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_read_timeout 60;
    }

    # Deny access to hidden dotfiles (.env, .git)
    location ~ /\.(?!well-known).* {
        deny all;
    }
}

10 Step 7: Domain, DNS, Host Reverse Proxy & HTTPS SSL

Point your public domain (e.g., example.com) to the cloud VM's static IP and provision automated Let's Encrypt TLS 1.3 certificates.

10.1 DNS Record Configuration

Record Type Host / Name Value TTL
A @ YOUR_SERVER_PUBLIC_IP 300 (Auto)
A www YOUR_SERVER_PUBLIC_IP 300 (Auto)

10.2 Host Nginx Reverse Proxy Configuration

Install Nginx on the host and create /etc/nginx/sites-available/example.com.conf:

server {
    listen 80;
    server_name example.com www.example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

10.3 Automated SSL Certificate with Certbot

# 1. Enable host site configuration:
sudo ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
# 2. Acquire and bind Let's Encrypt TLS certificate:
sudo snap install --classic certbot
sudo certbot --nginx -d example.com -d www.example.com
# 3. Verify automated renewal dry-run:
sudo certbot renew --dry-run

11 Step 8: First Live Deployment (Initial Run)

Execute the initial release sequence to bootstrap database storage and start services:

# 1. Define release image tag:
export IMAGE_TAG=v1.0.0
# 2. Start database and cache dependencies:
docker compose up -d db redis
# 3. Execute database migrations safely:
docker compose run --rm --no-deps app php artisan migrate --force
# 4. Launch all application and worker containers:
docker compose up -d
# 5. Optimize Laravel route and configuration caches:
docker compose exec -T app php artisan optimize
# 6. Verify service health:
docker compose ps
curl -fsS https://example.com/up

12 Step 9: Automated CI/CD Deployment with GitHub Actions

Eliminate manual server deployments with a fully automated push-to-deploy workflow:

# .github/workflows/deploy.yml
name: Build, Test & Deploy to Production

on:
  push:
    branches: [ main ]

jobs:
  # 1. Automated Test Quality Gate
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup PHP & Composer
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
          extensions: mbstring, pdo_mysql, redis
      - run: composer install --no-progress --prefer-dist
      - run: php artisan test

  # 2. Multi-stage Docker Build & GHCR Publish
  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build & Push App Image
        uses: docker/build-push-action@v6
        with:
          context: .
          file: docker/php/Dockerfile
          push: true
          tags: ghcr.io/byteionlab/myapp-php:${{ github.sha }}

      - name: Build & Push Nginx Image
        uses: docker/build-push-action@v6
        with:
          context: .
          file: docker/nginx/Dockerfile
          push: true
          tags: ghcr.io/byteionlab/myapp-nginx:${{ github.sha }}

  # 3. Zero-Touch SSH Remote Deploy
  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - name: Execute Remote SSH Deployment
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /opt/myapp
            ./deploy.sh ${{ github.sha }}

Production Deployment Script (/opt/myapp/deploy.sh)

#!/usr/bin/env bash
set -euo pipefail

RELEASE_TAG="${1:?Release SHA required}"
export IMAGE_TAG="$RELEASE_TAG"

echo "=== [Deploying Release: $IMAGE_TAG] ==="

# 1. Pull new immutable image tags
docker compose pull app web worker

# 2. Run safe database migrations
docker compose run --rm --no-deps app php artisan migrate --force

# 3. Update active containers
docker compose up -d --remove-orphans

# 4. Warm caches and restart queue workers
docker compose exec -T app php artisan optimize
docker compose exec -T app php artisan queue:restart

# 5. Live smoke test
echo "Checking Application Health..."
curl -fsS --retry 3 --retry-delay 2 http://127.0.0.1:8080/up

echo "=== [Deployment Succeeded Seamlessly!] ==="

13 Step 10: Production Monitoring, Logging & Alerting

Maintain full observability across three operational layers:

1. Health Probe

External uptime monitors poll /up every 30 seconds, verifying HTTP 200 responses.

2. Resource Metrics

docker stats and Prometheus track CPU, memory saturation, and NVMe disk utilization.

3. Alert Routing

CPU > 90% (5 min), 5xx error rate > 5%, or disk > 85% immediately notify on-call Slack channels.

14 Step 11: Automated Backups & Disaster Recovery

Ensure rapid recovery from catastrophic VM failures with an automated off-site S3 backup cron job:

# /opt/myapp/backup-db.sh (Runs automatically every midnight via cron):
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
BACKUP_FILE="/tmp/db_backup_$TIMESTAMP.sql.gz"

# Dump live database from Docker container:
docker compose exec -T db mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" myapp_prod | gzip > "$BACKUP_FILE"

# Upload encrypted archive to isolated AWS S3 bucket:
aws s3 cp "$BACKUP_FILE" s3://my-secure-backup-vault/daily-mysql/ --sse AES256
rm -f "$BACKUP_FILE"

15 Step 12: Deployment Troubleshooting & Rollback Runbook

When production incidents strike, execute this standardized diagnostic playbook:

Incident Probable Cause Diagnostic Action & Remediation
HTTP 502 Bad Gateway Nginx unable to connect to PHP-FPM socket/port 9000 Run docker compose ps; if app is unhealthy, restart via docker compose restart app.
HTTP 500 Server Error Missing APP_KEY, filesystem permission failure, or PHP fatal error Inspect logs: docker compose logs --tail=50 app; verify storage permissions and APP_KEY existence.
Database Connection Refused MySQL container down or credential mismatch Check docker compose logs db; verify DB_HOST=db and synchronize passwords in .env files.
Emergency Instant Rollback Critical bug discovered in latest release Execute: export IMAGE_TAG=PREVIOUS_SHA && docker compose up -d to roll back immediately.
Production Gate

Production Deployment Checklist

Verify every security, infrastructure, and operational checkpoint before opening the application to public traffic.

0 / 17 Completed
1. Application Readiness
2. Infrastructure & Security
3. Domain & Network Security
4. Operations & Continuity
Hands-On Lab & Simulator

Interactive Cloud Deployment & Ops Workbench

Zero quizzes! Run automated CI/CD pipelines, troubleshoot live simulated production failures, and inspect container topologies interactively.

GitHub Actions Automated Deploy Simulator

Simulate pushing a commit to main and watch the pipeline test, build, push, and deploy live.

Step 1 Unit & Feature Tests Pending
Step 2 Docker Multi-stage Build Pending
Step 3 Push to GHCR Pending
Step 4 SSH Deploy & Migrate Pending
# GitHub Actions Runner on standby. Click 'Git Push & Run Pipeline!' above.

What Did We Learn Across the Course & Capstone?

1. Complete Environment Isolation Docker containers ensure that whatever ran on your local machine executes identically in production.
2. Zero Public Database Ports MySQL and Redis ports must never be exposed publicly; communication occurs strictly over isolated Docker bridge networks.
3. Dual-Layer Firewall Defense Combining cloud security groups with host UFW firewalls strictly limits ingress to ports 22, 80, and 443.
4. Automated SSL Encryption Certbot paired with an Nginx reverse proxy provides automated, lifetime Let's Encrypt HTTPS at zero cost.
5. Automated CI/CD Pipelines Pushing code to GitHub triggers automated tests, multi-stage Docker builds, and zero-touch remote SSH deployments.
6. Asynchronous Background Queues Offloading CPU-intensive jobs and emails to dedicated queue worker containers ensures lightning-fast HTTP response times.
7. Configuration Lockdown Enforcing APP_DEBUG=false and restricting .env file permissions to 600 prevents catastrophic secret leaks.
8. Off-Site Backup Verification Daily automated database dumps must be encrypted, uploaded to isolated S3 storage, and validated with periodic restore tests.
9. Rapid Rollback Readiness Containerized releases allow instant rollbacks to previous Git SHA image tags within seconds if a release exhibits critical bugs.
10. 24/7 Production Observability Monitoring built-in health routes (/up) and CPU/RAM saturation enables proactive remediation before users notice outages.

Congratulations! You Have Completed All 16 Lessons of Cloud & DevOps!

From foundational cloud concepts and datacenters to Docker containerization, networking, CI/CD, Infrastructure as Code, security hardening, observability, auto-scaling, disaster recovery, and an end-to-end production capstone—you are now empowered to design, deploy, and operate resilient production cloud systems.

References & Industry Standards

  • Laravel Documentation: Official Deployment Guide & Health Checks
  • Docker Documentation: Best Practices for Using Compose in Production
  • Ubuntu Server Guide: Security, UFW & TLS Certificate Management
  • GitHub Actions Documentation: Continuous Deployment to Cloud VM
  • Let's Encrypt / Certbot: Automated Certificate Management Environment (ACME)