📦 Marketplace⭐ GitHub
Getting Startedv2.0

Deployment Guide

MonkeysLegion v2 — Deployment Guide

Complete production checklist and environment-specific deployment instructions for MonkeysLegion v2 applications built on PHP 8.4+.


Table of Contents

  1. Production Checklist
  2. Shared VPS / Dedicated Server
  3. Docker / Container Orchestration
  4. Platform-as-a-Service (PaaS)
  5. Serverless (AWS Lambda / Bref)
  6. CI/CD Pipeline Reference
  7. Monitoring & Observability
  8. Rollback Strategy

1 · Production Checklist

Complete every item before going live — regardless of environment type.

1.1 Environment & Secrets

#ItemCommand / ActionNotes
1Set APP_ENV=production.env or host env varsNever development in prod
2Disable debug modeAPP_DEBUG=falseLeaks stack traces if true
3Generate application keyphp vendor/bin/ml key:generateUnique per environment
4Generate strong JWT secretphp -r "echo bin2hex(random_bytes(32));"Set in JWT_SECRET
5Set APP_URLAPP_URL=https://yourdomain.comUsed for URL generation
6Protect .env filechmod 600 .envMust never be web-accessible
7Remove .env.example from deployExclude in CI artifactContains default secrets

1.2 PHP Runtime

#ItemRecommended ValueNotes
1PHP version8.4+Property hooks, readonly classes
2OPcache enabledopcache.enable=1Mandatory for performance
3OPcache preloadingopcache.preload=preload.phpOptional, significant gains
4JIT compilationopcache.jit=1255Tracing JIT for max throughput
5realpath_cache_size4096KReduces filesystem stat calls
6memory_limit256MTune per workload
7max_execution_time30Prevent runaway requests
8display_errorsOffAlways off in production
9expose_phpOffHide PHP version header

Required PHP extensions:

pdo_mysql (or pdo_pgsql)  mbstring  openssl  json  ctype
tokenizer  fileinfo  bcmath  intl  redis (if using Redis)

1.3 Database

#ItemAction
1Use dedicated credentialsNever use root in production
2Restrict privilegesGrant only SELECT, INSERT, UPDATE, DELETE to app user
3Run migrationsphp vendor/bin/ml schema:update
4Enable SSL connectionsSet DB_SSL_CA / PDO SSL options
5Connection poolingUse ProxySQL or PgBouncer for high traffic
6Automated backupsDaily snapshots with point-in-time recovery
7Set DB_CHARSET=utf8mb4Full Unicode support

1.4 Security Hardening

#ItemConfiguration
1HTTPS onlyRedirect all HTTP → HTTPS
2HSTS headerStrict-Transport-Security: max-age=31536000; includeSubDomains
3Session cookiesSESSION_COOKIE_SECURE=true, SESSION_COOKIE_HTTPONLY=true, SESSION_COOKIE_SAMESITE=Lax
4CORS lockdownReplace allow_origin = ["*"] with exact domains in config/cors.mlc
5CSRF protectionEnabled by default — verify VerifyCsrfToken in middleware pipeline
6Rate limitingConfigure RateLimitMiddleware thresholds
7Auth public pathsRemove "*" from auth.public_paths in config/auth.mlc
8Content Security PolicyAdd CSP headers via middleware
9File upload limitsReview files.max_bytes and files.mime_allow in config/files.mlc
10Hide server tokensRemove X-Powered-By, set Server header to generic

1.5 Performance

#ItemConfiguration
1Cache driverCACHE_DRIVER=redis (never file in clustered setups)
2Session driverSESSION_DRIVER=redis or database for multi-server
3Queue driverQUEUE_CONNECTION=redis or database (never sync)
4Template cacheCACHE_VIEWS=true in config/cache.mlc
5Composer autoloadercomposer install --optimize-autoloader --no-dev
6Asset minificationMinify CSS/JS, enable gzip/brotli compression
7Static file cachingSet Cache-Control / Expires headers for /assets/

1.6 Logging & Monitoring

#ItemConfiguration
1Log levelLOG_LEVEL=warning (or error for minimal noise)
2Log rotationUse daily channel — rotate & compress old logs
3External loggingShip to ELK / Datadog / CloudWatch
4Error trackingIntegrate Sentry or Bugsnag
5Health endpointCreate /health route returning 200 + DB/Redis status
6Uptime monitoringExternal ping service (UptimeRobot, Pingdom)

1.7 Pre-Deploy Validation

# Run the full check suite before deploying
composer check          # PSR-12 lint + PHPStan level 9 + all tests

# Individual checks
composer cs             # Code style (PSR-12)
composer phpstan        # Static analysis (level 9)
composer test           # PHPUnit full suite
composer test:unit      # Unit tests only
composer test:feature   # Feature / HTTP tests

2 · Shared VPS / Dedicated Server

Classic deployment on Ubuntu/Debian with Nginx + PHP-FPM.

2.1 System Requirements

Ubuntu 24.04 LTS (or Debian 12+)
PHP 8.4-fpm
Nginx 1.24+
MySQL 8.4+ or PostgreSQL 16+
Redis 7+ (recommended)
Composer 2.7+
Git

2.2 Nginx Configuration

server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    root /var/www/monkeyslegion/public;
    index index.php;

    ssl_certificate     /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # Static assets with long cache
    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?|ttf|eot)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        try_files $uri =404;
    }

    # Block dotfiles (except .well-known)
    location ~ /\.(?!well-known) {
        deny all;
    }

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

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.4-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;

        fastcgi_buffer_size 16k;
        fastcgi_buffers 4 16k;
    }

    # Block access to sensitive files
    location ~ /\.(env|git|mlc) {
        deny all;
    }
}

server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://$host$request_uri;
}

2.3 PHP-FPM Pool (/etc/php/8.4/fpm/pool.d/monkeyslegion.conf)

[monkeyslegion]
user = www-data
group = www-data

listen = /run/php/php8.4-fpm.sock
listen.owner = www-data
listen.group = www-data

pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 1000

php_admin_value[display_errors] = Off
php_admin_value[expose_php] = Off
php_admin_value[opcache.enable] = 1
php_admin_value[opcache.memory_consumption] = 256
php_admin_value[opcache.max_accelerated_files] = 20000
php_admin_value[opcache.validate_timestamps] = 0
php_admin_value[realpath_cache_size] = 4096K
php_admin_value[realpath_cache_ttl] = 600

2.4 Deployment Script

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

DEPLOY_DIR="/var/www/monkeyslegion"
REPO="git@github.com:your-org/your-app.git"
BRANCH="main"

echo "▸ Pulling latest code…"
cd "$DEPLOY_DIR"
git fetch origin "$BRANCH"
git reset --hard "origin/$BRANCH"

echo "▸ Installing dependencies…"
composer install --no-dev --optimize-autoloader --no-interaction

echo "▸ Running migrations…"
php vendor/bin/ml schema:update --force

echo "▸ Clearing caches…"
rm -rf var/cache/*
rm -rf var/sessions/* 2>/dev/null || true

echo "▸ Setting permissions…"
chown -R www-data:www-data var/ storage/ public/files/
chmod -R 775 var/ storage/ public/files/
chmod 600 .env

echo "▸ Restarting PHP-FPM…"
sudo systemctl reload php8.4-fpm

echo "✔ Deployment complete!"

2.5 Directory Permissions

public/          → 755 (web root, read-only)
public/files/    → 775 (user uploads)
var/log/         → 775 (log files)
var/cache/       → 775 (compiled templates, cache)
var/sessions/    → 775 (file sessions)
storage/         → 775 (app storage)
.env             → 600 (secrets, owner-only)
config/          → 750 (config files)

3 · Docker / Container Orchestration

Docker Compose for staging, Kubernetes or ECS for production clusters.

3.1 Production Dockerfile

# ── Build Stage ──────────────────────────────────────────────
FROM composer:2 AS deps
WORKDIR /build
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-scripts --ignore-platform-reqs
COPY . .
RUN composer dump-autoload --optimize --classmap-authoritative

# ── Runtime Stage ────────────────────────────────────────────
FROM php:8.4-fpm-alpine AS runtime

# Install extensions
RUN apk add --no-cache icu-libs libpq \
    && docker-php-ext-install pdo_mysql intl opcache bcmath \
    && pecl install redis && docker-php-ext-enable redis

# OPcache tuning
RUN echo "opcache.enable=1\n\
opcache.memory_consumption=256\n\
opcache.max_accelerated_files=20000\n\
opcache.validate_timestamps=0\n\
opcache.jit=1255\n\
opcache.jit_buffer_size=128M\n\
expose_php=Off\n\
display_errors=Off" > /usr/local/etc/php/conf.d/production.ini

WORKDIR /app
COPY --from=deps /build /app

# Writable dirs
RUN mkdir -p var/log var/cache var/sessions storage public/files \
    && chown -R www-data:www-data var/ storage/ public/files/

USER www-data
EXPOSE 9000
CMD ["php-fpm"]

3.2 Docker Compose (Staging)

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    env_file: .env.staging
    volumes:
      - app-storage:/app/storage
      - app-logs:/app/var/log
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - backend

  nginx:
    image: nginx:1.27-alpine
    restart: unless-stopped
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
      - ./public:/app/public:ro
      - ./docker/nginx/certs:/etc/nginx/certs:ro
    depends_on:
      - app
    networks:
      - backend

  db:
    image: mysql:8.4
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_USER: ${DB_USERNAME}
      MYSQL_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      retries: 5
    networks:
      - backend

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --requirepass ${REDIS_PASSWORD:-}
    volumes:
      - redis-data:/data
    networks:
      - backend

  queue-worker:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    env_file: .env.staging
    command: php vendor/bin/ml queue:work --sleep=3 --tries=3 --max-jobs=1000
    depends_on:
      - db
      - redis
    networks:
      - backend

volumes:
  db-data:
  redis-data:
  app-storage:
  app-logs:

networks:
  backend:
    driver: bridge

3.3 Kubernetes Highlights

# Key points for K8s deployment (abbreviated)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: monkeyslegion-app
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    spec:
      containers:
        - name: php-fpm
          image: registry.example.com/ml-app:latest
          resources:
            requests: { cpu: "250m", memory: "256Mi" }
            limits:   { cpu: "1000m", memory: "512Mi" }
          readinessProbe:
            httpGet: { path: /health, port: 80 }
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet: { path: /health, port: 80 }
            initialDelaySeconds: 15
            periodSeconds: 30
          envFrom:
            - secretRef: { name: ml-app-secrets }
            - configMapRef: { name: ml-app-config }

Key Kubernetes considerations:

  • Use Secrets for all .env values — never bake into images
  • SESSION_DRIVER=redis — file sessions don't work across pods
  • CACHE_DRIVER=redis — shared cache across replicas
  • QUEUE_CONNECTION=redis — single queue worker deployment
  • Run migrations as a Job (not in the app container init)
  • Mount var/log/ to a persistent volume or ship to stdout

4 · Platform-as-a-Service (PaaS)

Deploy to Railway, Render, DigitalOcean App Platform, or similar.

4.1 Railway / Render

railway.json / render.yaml essentials:

{
  "build": {
    "builder": "DOCKERFILE",
    "dockerfilePath": "Dockerfile"
  },
  "deploy": {
    "startCommand": "php-fpm",
    "healthcheckPath": "/health",
    "restartPolicyType": "ON_FAILURE"
  }
}

Environment variables — set via the platform dashboard:

APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:...
APP_URL=https://your-app.railway.app
DB_HOST=<managed-db-host>
DB_DATABASE=<db-name>
DB_USERNAME=<db-user>
DB_PASSWORD=<db-pass>
CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
REDIS_HOST=<managed-redis-host>
LOG_LEVEL=warning
JWT_SECRET=<generated-secret>

4.2 DigitalOcean App Platform

# .do/app.yaml
name: monkeyslegion-app
services:
  - name: web
    dockerfile_path: Dockerfile
    http_port: 80
    instance_count: 2
    instance_size_slug: professional-xs
    routes:
      - path: /
    envs:
      - key: APP_ENV
        value: production
      - key: APP_KEY
        type: SECRET
        value: "base64:..."
    health_check:
      http_path: /health

databases:
  - name: db
    engine: MYSQL
    version: "8"
    size: db-s-1vcpu-1gb

4.3 PaaS Checklist

#ItemNotes
1Use managed databaseAuto-backups, failover, SSL
2Use managed RedisFor cache, sessions, and queues
3Set all env vars in dashboardNever commit .env to repo
4Configure custom domain + SSLPlatform usually provides free SSL
5Set up build hookcomposer install --no-dev --optimize-autoloader
6Set up release hookphp vendor/bin/ml schema:update --force
7Configure health checksPoint to /health endpoint
8Enable auto-scalingIf platform supports it

5 · Serverless (AWS Lambda / Bref)

Run MonkeysLegion on AWS Lambda using Bref.

5.1 serverless.yml

service: monkeyslegion-app

provider:
  name: aws
  region: us-east-1
  runtime: provided.al2
  environment:
    APP_ENV: production
    APP_DEBUG: "false"
    APP_KEY: ${ssm:/ml-app/app-key}
    DB_HOST: ${ssm:/ml-app/db-host}
    DB_DATABASE: ${ssm:/ml-app/db-name}
    DB_USERNAME: ${ssm:/ml-app/db-user}
    DB_PASSWORD: ${ssm:/ml-app/db-pass}
    CACHE_DRIVER: redis
    SESSION_DRIVER: redis
    QUEUE_CONNECTION: sqs
    LOG_CHANNEL: stderr

plugins:
  - ./vendor/bref/bref

functions:
  web:
    handler: public/index.php
    timeout: 28
    memorySize: 1024
    layers:
      - ${bref:layer.php-84-fpm}
    events:
      - httpApi: "*"

  queue:
    handler: worker.php
    timeout: 120
    memorySize: 512
    layers:
      - ${bref:layer.php-84}
    events:
      - sqs:
          arn: !GetAtt JobsQueue.Arn

resources:
  Resources:
    JobsQueue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: ml-jobs
        VisibilityTimeout: 120

5.2 Serverless Considerations

#ItemNotes
1No local filesystemUse S3 for uploads, DynamoDB/Redis for sessions
2Cold startsKeep memory ≥ 1024MB, use provisioned concurrency
3Timeout limitAPI Gateway = 29s max — offload long tasks to queue
4Logs go to CloudWatchSet LOG_CHANNEL=stderr
5Database connectionsUse RDS Proxy to pool connections
6Static assetsServe via CloudFront + S3, not Lambda
7MigrationsRun as a separate CLI Lambda invoke

6 · CI/CD Pipeline Reference

6.1 GitHub Actions

name: Deploy

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:8.4
        env:
          MYSQL_ROOT_PASSWORD: test
          MYSQL_DATABASE: ml_test
        ports: ["3306:3306"]
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-retries=5
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: "8.4"
          extensions: pdo_mysql, mbstring, intl, redis, bcmath
          coverage: xdebug
      - run: composer install --no-interaction
      - run: composer check
        env:
          DB_HOST: 127.0.0.1
          DB_DATABASE: ml_test
          DB_USERNAME: root
          DB_PASSWORD: test

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: success()
    steps:
      - uses: actions/checkout@v4
      # Add your deployment step here:
      # - SSH deploy, Docker push, serverless deploy, etc.

6.2 Pipeline Stages

┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│  Lint &  │───▸│  Test &  │───▸│  Build   │───▸│  Deploy  │
│  Analyse │    │ Coverage │    │ Artifact │    │  Release │
└──────────┘    └──────────┘    └──────────┘    └──────────┘
 PSR-12 CS       PHPUnit         composer        ssh / docker
 PHPStan L9      Coverage ≥80%   install         push / sls
                                 --no-dev        deploy

7 · Monitoring & Observability

7.1 Health Check Endpoint

Create a /health route in your application:

#[Route('GET', '/health', name: 'health.check')]
public function health(): Response
{
    $checks = [
        'status' => 'ok',
        'php'    => PHP_VERSION,
        'time'   => date('c'),
    ];

    // Optional: verify database connectivity
    try {
        $this->db->query('SELECT 1');
        $checks['database'] = 'connected';
    } catch (\Throwable) {
        $checks['database'] = 'error';
        $checks['status'] = 'degraded';
    }

    $code = $checks['status'] === 'ok' ? 200 : 503;
    return Response::json($checks, $code);
}
LayerTools
APMDatadog, New Relic, or Elastic APM
Error trackingSentry, Bugsnag, or Rollbar
Log aggregationELK Stack, Loki + Grafana, or CloudWatch
UptimeUptimeRobot, Pingdom, or Better Uptime
MetricsPrometheus + Grafana or Datadog
AlertingPagerDuty, Opsgenie, or Slack webhooks

8 · Rollback Strategy

8.1 Quick Rollback Procedures

VPS / Bare Metal:

cd /var/www/monkeyslegion
git log --oneline -5                    # Find previous commit
git reset --hard <previous-commit>      # Revert code
composer install --no-dev --optimize-autoloader
sudo systemctl reload php8.4-fpm

Docker:

docker pull registry.example.com/ml-app:<previous-tag>
docker compose up -d --no-deps app

Kubernetes:

kubectl rollout undo deployment/monkeyslegion-app
kubectl rollout status deployment/monkeyslegion-app

Serverless:

serverless rollback --timestamp <previous-timestamp>

8.2 Database Rollback

Warning: Always test migration rollbacks in staging first.

  • Keep migration files idempotent when possible
  • Maintain a down() method for every migration
  • Take a database snapshot before every deploy
  • For breaking schema changes, use expand-contract migration pattern

Quick Reference Card

┌─────────────────────────────────────────────────────────────┐
│                    DEPLOY QUICK REF                         │
├─────────────────────────────────────────────────────────────┤
│  APP_ENV=production    APP_DEBUG=false                      │
│  php vendor/bin/ml key:generate                             │
│  composer install --no-dev --optimize-autoloader            │
│  php vendor/bin/ml schema:update --force                    │
│  CACHE_DRIVER=redis  SESSION_DRIVER=redis                   │
│  QUEUE_CONNECTION=redis                                     │
│  LOG_LEVEL=warning                                          │
│  opcache.enable=1  opcache.validate_timestamps=0            │
│  chmod 600 .env                                             │
│  Verify /health returns 200                                 │
└─────────────────────────────────────────────────────────────┘

MonkeysLegion v2 — Built with 🐵 by MonkeysCloud Team