CLI tooling for deploying a PHP application to shared hosting with something better than "upload the ZIP and hope." Preflight checks before you deploy, verification after, a manifest recording what shipped, and a safe cleanup utility for the debris previous uploads leave behind.
Shared hosting has no pipeline. No CI runner, no blue/green, no rollback button — deployment is an overlay upload over a live directory, and the failure modes are quiet ones:
- a required SQL migration silently never ran
- the last upload left
v2.0.17/andv2.0.18/documentation folders side by side, and the app now reads one and opens the other - a version number is hardcoded in three tools, two of which still say
v2.0.1 - an overlay half-uploaded, and nobody can tell which files are current
None of these throw an error. They just make the platform quietly wrong until a customer finds it.
This toolkit turns those into checks that fail loudly at deploy time.
| Script | When you run it | What it does |
|---|---|---|
preflight-v2.php |
Before deploying | Verifies environment, extensions, writable paths, config presence and that the SQL files the release expects are actually in the package |
apply-migration-v2.php |
During deploy | Applies the release's schema migration |
verify-release.php |
After upload | Confirms the deployed tree matches the release manifest and the version the application reports |
post-deploy-check-v2.php |
After upload | Smoke-checks the running application — version, schema state, critical paths |
platform-hotfix-self-check.php |
After a hotfix | Verifies the specific fixes in a hotfix actually landed, rather than trusting the upload |
release-cleanup.php |
After upload | Removes obsolete release and documentation artifacts left by previous overlays — --dry-run first, --confirm to act |
disable-maintenance.php |
Last step | Takes the app out of maintenance mode |
_cli_bootstrap.php |
— | Shared CLI bootstrap for all of the above |
src/Release.php resolves the current version from a single root VERSION file, so no tool carries its own hardcoded copy to go stale. That sounds trivial; it is the fix for an entire class of "the verifier says 2.0.1" bugs.
RELEASE-MANIFEST.example.json is what a release records about itself:
{
"release": "Production v2.0.32",
"generated_at_utc": "2026-08-19T19:33:56+00:00",
"php_files_linted": 312,
"validation_errors": [],
"validation_warnings": [],
"documentation": "Documentation/current",
"release_sql": "sql/HOTFIX-v2.0.32-RELEASE-CLEANUP.sql"
}Every PHP file linted before packaging, errors and warnings recorded, and the exact SQL that must run named in the artifact. If the manifest says 312 files linted with zero errors, you know a syntax error did not ship — and verify-release.php can check the deployed tree against it.
# 1. Back up first — files and database, verified
# 2. Preserve the live config; the release package ships none
php bin/preflight-v2.php # abort here if anything fails
# 3. Upload the overlay, preserving .htaccess
php bin/release-cleanup.php --dry-run # read the output
php bin/release-cleanup.php --confirm
php bin/apply-migration-v2.php
php bin/verify-release.php
php bin/post-deploy-check-v2.php
# 4. Clear OPcache, run smoke tests
php bin/disable-maintenance.phpThe ordering is the point: preflight can abort before anything changes, cleanup is dry-run first, and the app stays in maintenance mode until the checks have passed.
Requirements: PHP 8.0+ CLI, PDO MySQL.
These scripts assume a specific application layout — a root VERSION file, a config/ directory, a sql/ directory of versioned hotfixes, and Documentation/current. Read them before running them anywhere; they are a worked pattern to adapt, not a drop-in package.
release-cleanup.php deletes files. Run --dry-run and read every line before --confirm.
Extracted and generalised from the release infrastructure of a production platform built under contract. Application code, business logic, schema and client branding are excluded; namespaces were rebased to App\. What remains is the deployment tooling.
MIT — see LICENSE.