Deploying PHP applications with zero downtime
Deploying software is moving the recent changes of code or features to the production environment to have it available to the users. With products that require constant changes and improvements, chunks of code are moved almost all the time into production. These code might be integrated immediately into the current release or await feature toggling.
Have you ever faced a deployment hell? In this article, I am talking on the problems of deployment and how to use PHP Deployer to have a zero downtime deployment.
"We have a commitment to deliver quality results to our customers. Contact us and receive a personalised quote."
The problem:
Imagine a scenario where you decide to implement a new feature or update an entity with new fields or change the display of a particular screen in the software or what not. Then you test it locally and everything works as expected. You decide to deploy those changes. After the deployment, you receive a phone call from a user whose business is based on it telling you he/she cannot access the software because there is some server errors he/she cannot understand or from a colleague who is navigating around the software or yourself who try to see what you have deployed. You are hit with 500 server error for example. While users were navigating around the app, they all have the same error. Panic hits in. You are trying to figure out what the issue might be. You might have forgotten to run the migration? Then you do it immediately so as not to frustrate your users. That's not the problem. You rush to check the logs to see what is failing. Then you realise you did not install a dependency the new changes are based on or that you did not check if the new field you added in an entity is null when trying to display it or the problem might be something else?
No way to do an immediate rollback to keep it running on the previous state. You are devastated. This might be your business, or a client software the company you work for develops. Things like this happen a lot and can lead to disastrous consequences.
The solution:
Deployment tools.
Deployment pipelines are a must have for every software project. Deployment pipeline automates the process of moving new code additions and updates to production from version control. Without a deployment pipeline, taking code from local to provide might be entails: pushing to a remote repository, merging or rebasing, pulling the code in production, then doing the necessary work like migrations, cache warmup, dependencies update etc.
In PHP, there is a popular deployment tool https://deployer.org that automates the deployment process. This takes care of everything for you and you can create tasks and decide what task to run and in what order. Fortunately enough, PHP Deployer has recipes for nearly all the major PHP Frameworks and CMS such as Symfony, Laravel, Sulu, CakePHP. Let's say we want to deploy a PHP Symfony application.
If you already have composer, cd into the project directory and install Deployer with composer require --dev deployer/deployer
This will install Deployer and it'll be ready to be used.
Run vendor/bin/dep init
to choose a recipe language. This will be Yaml
or PHP
. Let's go with PHP. Enter 0 and press return/enter.
Tip: You can alias Deployer by adding this to your .bashrc file alias dep='vendor/bin/dep
- Then select the recipe of your choice. For our project, we are using Symfony and the version of Deployer at the time of this writing has Symfony recipe as entry 21. So, select that. Deployer will detect your git repository. If you are okay with that repo, hit return/enter
- Then it'll suggest your project name based on the git repository.
- Enter the host name which will be the domain name or ip address of your server.
This will create a deploy.php
file in the root directory of your project.
your hosts section can look like this
host('hey.cm')
->set('remote_user', 'deployer')
->set('deploy_path', '~/hey');
Set the identity file to enable ssh connection ->setIdentityFile('/pathTo/.ssh/private_key')
then the corresponding public key can be added to the authorized_keys
of your production server.
Since my deploy path is /hey
, I will point my virtual host to /hey/current/public
. The current
directory is a symlink of that last successful release in the releases directory.
Doing 'dep deploy
will start the deployment process. This entails pulling the code from git repository, installing composer dependencies, checking for migrations and migration, clearing cache, symlinking. You can define any task you want and decide when to run it during the deployment process. This of course does not affect the running of your software application.
If there's a deployment failure, Deployer will rollback and point to the last successful deployment. You can also rollback a particular deployment if things go wrong.
This is one of the battle-tested solution to deployment hell developers face.