Deployment Notes

Getting PHP code from a development machine to a production server is the kind of work that separates hobbyist projects from professional ones. A bad deployment process means broken releases, frantic rollbacks, and the creeping dread that every deploy might be the one that takes the site down. A good one means you ship with confidence, multiple times a day if the work calls for it. This page covers the deployment patterns, environment configuration, and production settings that apply to PHP applications, whether they run on Zend Framework, Laminas, Laravel, Symfony, or plain PHP. Below you will find sections on deployment strategies, CI/CD pipeline fundamentals, environment configuration management, production PHP settings, pre-deployment checklists, database migration handling, rollback planning, and monitoring basics.

For the broader set of resources that complement the book and guides, see the Resources hub.

Deployment Strategies

Not all deployments are equal. The strategy you choose depends on your uptime requirements, infrastructure, and how much risk you can absorb per release.

Rolling deployments update servers one at a time (or in small batches) behind a load balancer. At any given moment during the deployment, some servers run the old version and some run the new version. This works well when your application can tolerate both versions running simultaneously, which means your database schema changes and API contracts must be backward compatible during the transition window.

Blue-green deployments maintain two identical environments: blue (current production) and green (staging the new release). You deploy to green, run smoke tests against it, then switch the load balancer to point traffic at green. If something goes wrong, you switch back to blue. The cost is maintaining two environments, but the benefit is near-instant rollback with zero mixed-version traffic.

Canary deployments route a small percentage of traffic to the new version while the majority stays on the old version. You monitor error rates, response times, and business metrics on the canary. If everything looks healthy, you gradually increase the percentage until all traffic hits the new version. This is the most conservative strategy and works well for applications where a subtle bug in the new release could have significant financial impact.

For most PHP applications, especially those covered in the Zend Framework Book, a blue-green or simple rolling approach covers the requirements. The important thing is that your deployment is automated, repeatable, and reversible.

CI/CD Pipeline Fundamentals

A CI/CD pipeline automates the steps between committing code and running it in production. At minimum, a PHP pipeline includes:

  1. Install dependencies: composer install --no-dev --optimize-autoloader
  2. Run static analysis: PHPStan, Psalm, or both at the level your project enforces
  3. Run tests: PHPUnit or Pest with the full test suite
  4. Check code style: PHP-CS-Fixer or PHP_CodeSniffer in dry-run mode
  5. Build artifacts: compile frontend assets if applicable, generate optimised autoloader
  6. Deploy: push the built artifact to the target environment

Each step must pass before the next one starts. If static analysis finds a new error, the pipeline stops. If a test fails, no deployment happens. This is the gate that keeps broken code out of production.

The tooling for running these pipelines varies. GitHub Actions, GitLab CI, Jenkins, and Bitbucket Pipelines all handle PHP projects. The principles are the same regardless of platform: define the steps, define the triggers (push to main, tag creation, manual approval), and let the pipeline run.

A typical pipeline configuration installs the correct PHP version, enables required extensions (like pdo_mysql, intl, or mbstring), caches the Composer vendor directory between runs to speed up builds, and runs each step in sequence. The PHP Tooling page covers the individual tools that these pipeline steps invoke.

Environment Configuration

PHP applications need different configuration in development, staging, and production. Database credentials, API keys, cache backends, error reporting levels, and mail transport settings all vary between environments. Managing these differences safely is a deployment concern.

The .env file pattern stores environment-specific values in a file that is not committed to version control. Libraries like vlucas/phpdotenv load these values into $_ENV and getenv() at application boot. Your application code reads configuration from environment variables, never from hardcoded values.

1
2
3
4
5
6
7
APP_ENV=production
DB_HOST=db.internal.example
DB_NAME=app_production
DB_USER=app
DB_PASS=secure-password-here
CACHE_DRIVER=redis
REDIS_HOST=redis.internal.example

Critical rule: the .env file never goes into version control. A .env.example file with placeholder values does. Each environment (developer machine, CI server, staging, production) has its own .env file with real values.

For Zend Framework 1 applications that use application.ini with environment sections, the same principle applies: keep sensitive values out of the repository. One approach is to have application.ini reference environment variables using a custom config loader, or to use a separate local.ini file that is excluded from version control. The Bootstrap chapter covers the configuration loading process in ZF1.

Secrets management goes further for production environments. Services like HashiCorp Vault, AWS Secrets Manager, and Azure Key Vault store sensitive values encrypted and provide them to your application at runtime. This is the right approach when your .env file would contain credentials that could cause significant damage if leaked.

Production PHP Settings

PHP’s default configuration is tuned for development, not production. Several settings must change before you go live.

Error display: set display_errors = Off and log_errors = On in production. Displayed errors leak internal paths, class names, and sometimes database credentials to anyone who triggers an error. Log errors to a file or logging service instead.

OPcache: enable it and tune it properly. These settings work well for most PHP 8.x applications:

1
2
3
4
5
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.save_comments=1

Setting validate_timestamps=0 means OPcache never checks whether source files have changed. This is correct for production because files only change during deployments. After each deployment, restart PHP-FPM or call opcache_reset() to load the new code. The PHP Performance Playbook covers OPcache tuning in detail.

Memory limit: set memory_limit to a value appropriate for your application. 128M is a reasonable starting point for web requests. CLI scripts that process large datasets may need more. Do not set it to -1 (unlimited) in production.

Execution time: max_execution_time should be set to a value that matches your expected maximum request duration. 30 seconds is the default. If a request takes longer than this, something is wrong, and it is better to let PHP terminate it than to let it consume resources indefinitely.

Session configuration: use a centralised session store (Redis, Memcached, or database) in production rather than the default file-based handler. File-based sessions do not work across multiple application servers and create filesystem I/O under load.

Pre-Deployment Checklist

Before every production deployment, verify the following:

  • All tests pass on the exact commit being deployed
  • Static analysis reports no new errors above the baseline
  • composer.lock is committed and up to date
  • Database migrations have been reviewed and tested against a copy of production data
  • Environment configuration for the target environment is complete and verified
  • The rollback procedure has been documented and tested
  • Monitoring and alerting are in place for the target environment
  • The team knows the deployment is happening and who to contact if something goes wrong
  • Frontend assets are built and minified
  • The deployment artifact does not include development dependencies, test files, or .env files

This checklist is not a formality. Every item on it exists because skipping it has caused a production incident in a real PHP project. Print it, pin it to the wall next to your deployment terminal, and go through it every time.

Database Migrations

Schema changes are the riskiest part of most deployments. A missing column or a dropped index can take an application down in ways that are not immediately obvious.

Principles for safe migrations:

  • Every schema change is a versioned migration file that can be run forward and rolled back
  • Migrations run before the new application code is deployed, not after
  • Additive changes (new columns, new tables, new indexes) are safe to deploy independently of the application code
  • Destructive changes (dropping columns, changing types, removing indexes) require a two-phase deployment: first deploy code that stops using the column, then deploy the migration that removes it
  • Always test migrations against a recent copy of production data, not against an empty database

For Zend Framework 1 applications that do not use a migration tool, introducing one is a worthwhile investment. Phinx and Doctrine Migrations both work independently of the application framework. A migrations/ directory at the project root with numbered migration files and a simple runner script is better than applying SQL changes by hand.

Rollback Planning

Every deployment needs a rollback plan, written down before the deployment starts, not improvised after something breaks.

For a blue-green deployment, rollback means switching the load balancer back to the previous environment. For a rolling deployment, it means redeploying the previous version. For a deployment that includes database migrations, rollback is more complex: you need to determine whether the migration can be reversed and whether any data written by the new code is compatible with the old version.

Document three things for every deployment:

  1. How to roll back: the exact commands or steps
  2. When to roll back: the specific error conditions, metric thresholds, or time limits that trigger a rollback decision
  3. Who decides: a named person with the authority and context to make the call

Monitoring Basics

Deployment does not end when the code is live. You need to know whether the release is healthy, and you need to know fast.

Application-level monitoring tracks error rates, response times, and throughput. A spike in 500 errors immediately after a deployment is a strong signal that something went wrong. Tools in this space include application performance monitoring services and simple log aggregation.

Infrastructure monitoring tracks CPU usage, memory consumption, disk I/O, and network throughput on your servers. A deployment that causes memory usage to climb steadily suggests a leak in the new code.

Uptime monitoring hits your application from an external network and alerts you if it stops responding. This catches problems that internal monitoring might miss, like a misconfigured firewall rule or a DNS change gone wrong.

Log aggregation collects logs from all application servers into a single searchable interface. When a user reports a problem, you need to find the relevant log entries without SSH-ing into each server individually.

Set up a deployment dashboard that shows the key metrics for the first 30 minutes after each release. Watch it. If the metrics look wrong, roll back first and investigate second. The cost of a fast rollback is always lower than the cost of debugging a broken production environment under pressure.

For performance-specific monitoring concerns, the Performance topic hub and the PHP Performance Playbook cover profiling and measurement techniques that complement deployment monitoring. The Application Architecture topic hub covers the structural decisions that affect how deployable your code is in the first place.