EDIT: this blog is now deployed and hosted via Firebase.
Lately, I’ve been looking for a way to replace my dirty bash scripts used to deploy this blog on my VPS. A few years ago, I gave a shot to Capistrano and Fabric. They are great tools but written in languages I don’t master (that’s especially true for Capistrano). I also remember that I had problems with them when upgrading the Ruby and Python packages of my machine. This time, I wanted to try something else, something that could respect my expectations:
- supports rollbacks
- backups the last n releases
- hides the host information
- easy to understand and debug
- allows to build the application locally (I don’t want to install
composer
andnpm
on my server) - allows to upload the sources by myself (I don’t want to install
git
on my server) - with a good documentation
- bonus: supports Symfony out of the box
At first glance Deployer seemed to answer my needs.
Deployer, a deployment tool for PHP
Apart from all the features I’ve already listed above, Deployer is easy to install (either by composer
or with a phar
package), highly configurable and very modular. It supports natively a lot of PHP frameworks and applications. A deployment is as easy as executing dep deploy
and using the option -v
lists all the underlying system commands that are actually launched. Last but no least, it’s written in PHP. It may seem to be a detail, but it’s very useful to dig into the code and to understand exactly what a recipe does.
In short, Deployer is simple, efficient and crystal clear.
Symfony works out of the box
Deployer ships with recipes for Symfony 2, 3 and 4. By default, a Symfony4 deployment performs the following actions on the server:
- creates necessary directories
- checkouts the sources from the repository
- makes the shared files available to the new release
- installs vendors
- ensures writable files and directories are OK
- removes the cache
- warmups the cache
- handles Doctrine migrations
All those actions work out of the box as long as the recipe is correctly configured. Also, using an inventory file, allows to have a solid deployment script that can be versioned alongside the code.
That’s great! But it’s not yet what I need :)
Building a Symfony application locally
The first thing I needed was to create a custom task to build my project locally:
desc('Build project');
task(
'build',
function () {
// build frontend
run('sh bin/build-front');
// build backend
run('composer install --no-ansi --no-dev --no-interaction --no-progress --no-scripts --optimize-autoloader');
}
)->local();
As I don’t checkout the sources from the repository, I need to be able to upload them:
desc('Upload project');
task('upload', function () {
upload(__DIR__ . '/*', '{{release_path}}', ['options' =>
[
"--exclude='.env'",
"--exclude='.git/'",
"--exclude='.gitignore'",
"--exclude='.idea/'",
"--exclude='.*.xml.dist'",
"--exclude='.*.yml'",
"--exclude='.*.yml.dist'",
"--exclude='phpstan.neon'",
"--exclude='semantic.json'",
"--exclude='symfony.lock'",
"--exclude='node_modules/'",
"--exclude='semantic/'",
"--exclude='tests/'",
"--exclude='var/'",
]
]);
});
It’s very important to use the variable {{release_path}}
here (and not the variable {{deploy_path}}
). For instance, if {{deploy_path}}
relates to path/to/my/app/
, then {{release_path}}
would be path/to/my/app/releases/X/
(where X is the Xth deployment).
Then, the last thing to do is to replace the default Symfony4 deploy
task by what I need:
- task('deploy', [
+ task('deploy', [ // override the default Symfony4 deploy task
+ 'build',
'deploy:info',
'deploy:prepare',
'deploy:lock',
'deploy:release',
- 'deploy:update_code',
+ 'upload',
'deploy:shared',
- 'deploy:vendors',
- 'deploy:writable', // I don't need any writable file or directory
'deploy:cache:clear',
'deploy:cache:warmup',
'deploy:symlink',
'deploy:unlock',
'cleanup',
]);
That’s simple and works like a charm. The whole script is available here.
Next step will be to setup continuous delivery on Gitlab with Deployer. Maybe I’ll have to change a few things on this deployment script.