Docs

Deployment Guide

Deployment Guide

1 · Production Checklist

Item

Target

PHP version

8.4 with OPcache enabled, memory_limit >= 256 M

Extensions

pdo_mysql (or pgsql), openssl, json, mbstring

Web server

Nginx 1.20 + PHP-FPM or Apache 2.4 mod_php/FPM

Database

MySQL 8.0 / MariaDB 10.6

Filesystem

Writable: storage/, public/uploads/, cache dirs

Env vars

APP_ENV=prod, APP_DEBUG=false, JWT_SECRET=…

Secrets

JWT RSA keys (storage/keys/), DB password, Redis URL

2 · Build the release

# 1. Pull code
git clone git@github.com:acme/myapp.git /var/www/myapp
cd /var/www/myapp

# 2. Install deps (no dev)
composer install --no-dev --optimize-autoloader --apcu-autoloader

# 3. Cache config & compile views
php vendor/bin/ml config:cache
php vendor/bin/ml view:clear   # no-op if nothing cached yet

# 4. Generate RSA keys if fresh server
php vendor/bin/ml key:generate --force

Tip: commit the public key; keep the private key out of VCS.

3 · Database migrations & seeders

php vendor/bin/ml migrate --env=prod
php vendor/bin/ml db:seed  --class=InitialUserSeeder

Each batch is wrapped in a transaction; failures roll back automatically.

4 · Nginx + PHP-FPM config

/etc/nginx/sites-available/myapp.conf

server {
    listen 443 ssl http2;
    server_name app.example.com;

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

    ssl_certificate     /etc/letsencrypt/live/app/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app/privkey.pem;

    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;

    # Static assets – 365 d
    location ~* \.(css|js|png|jpe?g|svg|woff2?)$ {
        expires 365d;
        access_log off;
        try_files $uri =404;
    }

    # Healthcheck
    location = /healthz { access_log off; return 200 'OK'; }

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

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

Enable and reload:

ln -s /etc/nginx/sites-available/myapp.conf /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

5 · Environment variables

Create /etc/myapp.env (or use a secrets manager):

APP_ENV=prod
APP_DEBUG=false
DB_HOST=127.0.0.1
DB_DATABASE=myapp
DB_USER=myapp
DB_PASS=SuperSecret
JWT_SECRET="file:///var/www/myapp/storage/keys/private.pem"
REDIS_URL=redis://127.0.0.1:6379

Add to FPM pool config:

; /etc/php/8.4/fpm/pool.d/myapp.conf
[myapp]
user = www-data
group = www-data
listen = /run/php/php8.4-fpm.sock
env[APP_ENV] = prod
env[APP_DEBUG] = false
env[DB_HOST]   = 127.0.0.1
...

Reload FPM: systemctl reload php8.4-fpm.

6 · Queue / cron workers (optional)

# Run queue every minute via systemd or supervisor
* * * * *  www-data php /var/www/myapp/artisan queue:work --stop-when-empty

# Schedule commands
* * * * *  www-data php /var/www/myapp/artisan schedule:run >> /dev/null 2>&1

7 · Zero-downtime deploys (blue-green)

  1. Deploy to /var/www/myapp-<hash>

  2. Run composer, migrations, cache-clear

  3. ln -sfn myapp-<hash> /var/www/myapp_current

  4. Point Nginx root to /var/www/myapp_current/public and reload

8 · Container / Kubernetes

  • Dockerfile (slim):

FROM php:8.4-fpm-alpine

RUN apk add --no-cache nginx supervisor \
    && docker-php-ext-install pdo_mysql

WORKDIR /var/www/html
COPY . .
RUN composer install --no-dev --optimize-autoloader

EXPOSE 80
CMD ["php-fpm"]
  • Helm chart sample values:

image:
  repository: ghcr.io/acme/myapp
  tag: v1.2.0
env:
  - name: APP_ENV
    value: prod
  - name: DB_HOST
    valueFrom:
      secretKeyRef:
        name: db-creds
        key: host
probes:
  liveness:  /healthz
  readiness: /healthz

9 · Monitoring & logging

Concern

Solution

Metrics

Expose /metrics (Prometheus) via Telemetry package

Logs

Monolog → syslog or JSON → Loki/Elastic

Tracing

Wrap Connection + HTTP client with an OpenTelemetry decorator

Alerts

Subscribe to MigrationEvent, RequestEvent, push to Slack

10 · Hardening tips

  • Disable app.debug in production (config/app.mlc).

  • Serve uploads from a separate sub-domain if possible (files.example.com).

  • Run php -d detect_unicode=0 vendor/bin/ml security:audit monthly (upcoming CLI command).

  • Keep Composer up-to-date (composer self-update && composer outdated --direct).

Next steps

  • Autoscaling on GCP – see the Google Cloud guide.

  • CI/CD pipeline – follow our GitHub Actions template.

  • Blue/green database migrations – planned for Migration 2.0.

Happy shipping! 🚀