Deployer is a powerful tool to automate PHP deployments. The documentation may feel overwhelming. This article aims to fill the gaps.
Note: This article contains affiliate links. If you feel that my article has helped you and the linked services are useful, I would appreciate if you used my referral link.
Deployer
Deployer is a “PHP deployment tool with support for popular frameworks out of the box”. I’m not going to copy the official documentation here, there are plenty of blog posts around who do that already. When starting with Deployer I didn’t understand how the documentation was built, what options I had and how Deployer generally worked. I’m documenting my steps here in the hopes that it may be useful to others.
Once I got Deployer running, the time it took me to make a deployment decreased from about 20-30 minutes of carefully and manually executing commands in the right order to executing dep deploy and waiting 30 seconds.
I’m using Laravel as my go-to PHP framework.
Deployer provides recipes for popular PHP frameworks, such as Laravel, out of the box.
These recipes are a great starting point.
Everything you need can be defined in the deploy.php file that is generated at initialization.
However you may still need some customization, especially if you are on shared hosting like me.
Structure of Documentation
Every task in Deployer is defined in a recipe. The Recipes section in the documentation provides groups of common tasks for popular frameworks (e.g. Laravel, Symfony, Codeigniter)1.
If your deployment fails, check which task failed specifically, and see if you need to adjust the configuration options. Also don’t be afraid to look at the source code as it’s pure PHP and Linux commands.
Configuration values are enclosed in curly brackets, e.g. {{keep_releases}}.
These can be overwritten in your recipe with
set('config_key', 'value'); // sets {{config_key}} = value
Deploying on Shared Hosting
I’m using cyon.ch for their excellent customer support and documentation. They provide a flexible shared web hosting service with functionality geared toward professionals. While not the cheapest provider, I have found that even their most basic package lasts a long way in terms of resources. Unfortunately their services are only available in German so far.
Deployer’s documentation and some of its defaults assumes that you have full control over the server you are going to deploy to. This is usually not the case when you use a shared hosting provider that already provides standard PHP and MySQL.
Deployer defaults sometimes use functions that are not available on shared hostings. Luckily these defaults can be overridden.
For instance, cyon.ch provides multiple versions of PHP. If you want to use a specific version of PHP, because a package requires it, you can set it with:
set('bin/php', 'php81'); // sets PHP version to 8.1
By default Deployer uses the acl package (access control list) to make folders writable in the recipe recipe/deploy/writable.php.
However acl is not available on shared hosting, but chmod is.
By checking the documentation for the writable task, we see that we can configure the command used as such
set('writable_mode', 'chmod');
Any other configuration value that is missing (for me http_user was missing), you can add to your deploy.php the same way.
For a shared hosting, there is also no need to run dep provision as PHP, MySQL and firewall rules have already been set up.
SSH Agent Forwarding is a Super Power
What I didn’t realize at first is just how powerful deployments through SSH are.
Deployer uses the forward_agent option by default.
This allows you to use your local SSH keys to authenticate on remote servers (e.g. your git repository).
My code is hosted on GitLab, my server is at cyon.ch.
When Deployer is ssh’ing into the server with forward_agent enabled, it uses the same ssh key to checkout code from GitLab.
This means there is no need to add a private key on the server. As long as I add the ssh public key to GitLab and the server, I can use the same key for
- pushing code to the repository,
- ssh’ing into the shared hosting with Deployer, and
- checking out the git repository to the server.
To copy your ssh public key to the server, use:
ssh-copy-id user@hostname.example.com -i ~/.ssh/identity_file
If you have trouble accessing the git repository, you can find out which user is trying to authenticate with:
ssh -T git@gitlab.com
// [example.com] Welcome to GitLab, @USERNAME.
Adding Custom Tasks
I’m using Laravel as my go-to framework. Deployer provides a full-fledged deployment recipe for Laravel out of the box. Sometimes you will still need to add a custom task.
For example, I’m using the Laravel Jetstream starter kit. For this, I need to build CSS assets with NPM.
I created the following task
// Tasks
task('npm_build', function () {
run('cd {{release_or_current_path}} && npm install');
run('cd {{release_or_current_path}} && npm run build');
});
and hook it into the standard Laravel recipe to execute before the database migration:
// Hooks
before('artisan:migrate', 'npm_build');
Additional Tips and Tricks
For easier debugging (e.g. to find out where deployment gets stuck) you can use the verbose option: dep deploy -vvvv.
This will output every command that’s being executed.
Point your domain to the folder {{deploy_path}}/current (in my case ~/public_html/my_app/current).
With dep deploy you should have zero downtime deployments.
During development the dep push task may be interesting to push local changes to the server.
If you want to roll back a release because you made an error, use dep rollback.
Conclusion
Deployer is a powerful tool that does most of the heavy lifting. However its documentation may at first overwhelm you just like it did for me. It took me some time to fully grasp it, which is why I’m documenting these steps here.
Until now deployments were my biggest pain when developing. Even before starting development of a new feature, I was already dreading the deployment. With this automation tool, I can finally release my side projects faster. Thank you, Deployer.org!
-
Side Note: Irritatingly theDone ↩︎recipe/common.phpdoes not define thedeploytask. I might open a pull request for that.