Development and databases
Deploy WordPress and Laravel on cPanel with Deployer
Deployer automates deployments over SSH using a PHP recipe that you keep with the project. Each release can prepare a new folder and activate that version when it finishes. It is an alternative for people who want to maintain their own deployment process; if you would rather set it up from the client area, see Deploy+.
This guide uses Deployer 8 and suggests starting with a test subdomain. Separating releases shortens the code switch, but service continuity also depends on the application's migrations, shared files, caches and processes.
Prepare the environment
You need SSH access to the hosting, a Git repository reachable from the server and space for the code, the dependencies, the shared files and several releases. Set up a read-only key for the private repository if needed.
On your computer or CI environment, check the requirements of the Deployer 8 installation. That version needs PHP 8.3 or later to run the tool. Install it as a development dependency of the project and keep the lock file:
composer require --dev deployer/deployer:^8 vendor/bin/dep --version
The PHP that runs Deployer on your computer and the PHP that will run the application on the hosting are different environments. Check the version and extensions of both, as well as Composer, Git and the tools your build needs.
Choose the location and the public root
In the recipes, {{deploy_path}}/current stands for the link to the published release inside the deployment path. Deployer replaces {{deploy_path}} with the value you configure; it is not a path you should copy literally into cPanel.
We will use /home/usuario_cpanel/apps/miapp as the private deployment directory. Replace the user, server, SSH port and repository in the examples with the real details.
The structure will be:
miapp/ releases/ versiones de código shared/ archivos que persisten current enlace a la versión publicada
The test domain's document root must serve current/public for Laravel or current for a traditional WordPress. Confirm that your domain's configuration allows that path and symbolic links before the first deployment. cPanel restrictions may prevent a document root outside public_html; the main domain is not always configured like an addon domain either.
Do not move or delete public_html to test this structure: it may contain other websites. If the domain does not accept the intended path, agree a compatible configuration with support or use Deploy+. Do not publish the whole Laravel folder, which contains private files.
WordPress example
This example assumes the repository includes the code of a traditional WordPress, its plugins and its theme. If you use Bedrock or another structure, adapt the recipe and the public root.
Create deploy.php in the project:
<?php namespace Deployer; require 'recipe/wordpress.php'; set('repository', 'git@github.com:organizacion/wordpress.git'); set('branch', 'main'); set('keep_releases', 3); set('shared_files', ['wp-config.php']); set('shared_dirs', ['wp-content/uploads']); set('writable_mode', 'chmod'); set('writable_chmod_mode', '0755'); set('writable_dirs', ['wp-content/uploads']); host('pruebas') ->setHostname('servidor.example.com') ->setRemoteUser('usuario_cpanel') ->setPort(93) ->set('deploy_path', '/home/usuario_cpanel/apps/miapp'); after('deploy:failed', 'deploy:unlock');
Prepare shared/wp-config.php beforehand with the full configuration of the test WordPress: database, table prefix, keys and other site settings. Keep those values when moving an existing installation and check any inherited absolute paths. Do not upload the credentials to the repository.
Copy the existing images to shared/wp-content/uploads and check their permissions with the hosting user. The shared folder must contain the right data before publishing; do not expect the deployment to import another installation's files on its own.
The plugins and themes in this example are published from Git. An update made only from the WordPress dashboard may disappear with the next release. Decide on an update workflow that keeps the repository up to date. Sharing the whole of wp-content is not an automatic solution either: it would stop you from reliably versioning that code.
Laravel example
The official Laravel recipe includes migration and service reload tasks. Before adopting it, check which ones your application needs and allows. The following example defines an explicit workflow without migrations or process restarts, to get publishing working in testing first:
<?php namespace Deployer; require 'recipe/laravel.php'; set('repository', 'git@github.com:organizacion/laravel.git'); set('branch', 'main'); set('keep_releases', 3); set('shared_files', ['.env']); set('shared_dirs', ['storage']); set('writable_mode', 'chmod'); set('writable_chmod_mode', '0755'); set('bin/php', '/opt/alt/php83/usr/bin/php'); host('pruebas') ->setHostname('servidor.example.com') ->setRemoteUser('usuario_cpanel') ->setPort(93) ->set('deploy_path', '/home/usuario_cpanel/apps/miapp'); task('deploy', [ 'deploy:prepare', 'deploy:vendors', 'artisan:storage:link', 'artisan:config:cache', 'artisan:route:cache', 'artisan:view:cache', 'deploy:publish', ]); after('deploy:failed', 'deploy:unlock');
The PHP 8.3 path is an example: check that it exists and satisfies composer.lock. Set another available version if the project requires it. Also check how the recipe runs Composer on that server.
Before the first deployment, prepare shared/.env outside the public root and with permissions restricted to the account's user. Use the test database, turn off public debugging and keep an existing application's APP_KEY: regenerating it can make data impossible to decrypt and log users out. Only generate a key for a new installation that does not have one yet.
Prepare the folders the application uses inside shared/storage, including the sessions, views and cache directories if applicable. bootstrap/cache belongs to each release; it is not a subfolder of storage. Check that the user running PHP can write where it needs to, without 777 permissions.
If the project needs built assets, include the output in the artefact or add a build task before publishing. The tasks shown do not run npm. If a cache is not compatible with your application, fix its configuration or adapt the task after testing it.
Review and run the deployment
From the computer where you installed Deployer:
vendor/bin/dep deploy --plan pruebas
Review the order of tasks and the hooks of the installed version. The plan helps spot unexpected operations, but it does not check that the server or the application works.
When the test environment is ready:
vendor/bin/dep deploy pruebas
Check the result in the output and open the website. Test logging in, an internal page, forms, image uploads and database operations. In Laravel, also check the scheduled tasks and background processes you actually use.
If a run fails, look at the first task with an error. Do not unlock the deployment while another process is still working, and do not delete releases to free up space without first identifying which one is active.
Migrations and going to production
A new release may require schema changes. Plan migrations that are compatible with the code still serving requests during the switch. If that is not possible, use a maintenance window and a tested recovery procedure.
Ordering "code first, then database" is not enough: it depends on the change. Nor should you add migrate:fresh, which drops tables, to a deployment of an installation with data.
Before going to production, keep a recoverable copy of files and database, test the recipe and prepare the change of the domain's document root. The first release has no previous release to go back to.
Go back to a previous release
If you keep a compatible previous release, you can trigger the rollback:
vendor/bin/dep rollback pruebas
Check the website and its main operations afterwards. Rollback changes the published code; it does not recover the database or return the shared files to their previous state. A migration or a script that changed them needs its own recovery procedure.
Saved releases use space and are not a substitute for backups. Adjust their retention to the size of the project and the room available to build the next release.
To learn more about the available tasks, see the WordPress recipe and the Laravel recipe for the version you have installed. On shared hosting, use deployment tasks with your user; the server provisioning recipes require privileges that account does not have.
You may also find useful
- Automating Git deployment tasks with the .cpanel.yml file Define deployment tasks in .cpanel.yml, run them after a push or pull and learn how to trigger the p...
- Clone and deploy from a private remote repository Authorise a hosting SSH key on GitHub, clone a private repository, check the connection and apply ch...
- Automatic deployment from GitHub, GitLab or Bitbucket with instant rollback Connect a Git repository to your hosting, run per-release scripts with deployment and .env variables...
- Host React, Vue and static websites connected to external APIs Check whether your JavaScript frontend fits on shared hosting, connect it to Supabase, Firebase or o...
Something wrong or out of date? Tell us and we will review it.