Intro
Lumen is a micro-framework by Laravel. It is a perfect solution for building micro-services and APIs. Being powered by Laravel's components, Lumen lets you use all your favorite Laravel's features such as Eloquent, caching, queues, validation, routing, and middleware.
Deployer is a deployment tool written in PHP with support for popular frameworks out of the box. Although it doesn't come with a specific Lumen recipe, you may reuse its existing Laravel recipe and adjust it to your needs.
In this tutorial, we are going to create a simple Lumen API on the local machine and deploy it on the Ubuntu 18.04 server with Deployer.
Prerequisites
Lumen framework comes with a few Server Requirements. On top of that, we are going to configure two machines:
- A local machine which will be used to bundle the Lumen API code and upload it to the Ubuntu server using Deployer.
- A server machine which will become the deployment target, where the Lumen API will be running on.
Note
Instead of deploying the code from the local machine, we could use one of the existing CI solutions such as Travis, Github Actions or Gitlab Runners. However, in this tutorial, for simplicity's sake, we will use the local machine for both development and deployment.
Server machine prerequisites
The only prerequisite here is the Ubuntu 18.04 server, accessible with a root password via SSH. In this tutorial, we will go step by step through installing PHP 7.4 and Apache 2.4 with the mod_php on this server.
Local machine prerequisites
There are a few prerequisites here, including PHP, Composer, and Deployer. Some ways to get them running locally are:
- Installing a PHP web server on the operating system, i.e: MAMP
- Using hosted hypervisor like VirtualBox with Vagrant. Or even better, using an official, pre-packaged Vagrant box Laravel Homestead.
- Using a container technology like Docker with Docker Compose to define the application services in YAML format. Or even better, using Laradock, the Laravel community's project.
In this tutorial, we will not go into detail on how to create such a local environment. Instead, we will focus on creating a project with Composer, installing Deployer and configuring it to our needs. Without further ado, let's start with the server machine configuration.
Install PHP 7.4 and Apache2
Ubuntu 18.04 comes with PHP 7.2 version by default, so we will need to use deb repositories provided by Sury.org to get the newest PHP version.
For Ubuntu, Sury maintains the PHP Ubuntu Personal Package Archives, which we are going to install now:
apt install software-properties-common -y add-apt-repository ppa:ondrej/php -y apt update -y apt install php7.4
Because php7.4 depends on libapache2-mod-php7.4, which in turn recommends apache2 package, we will get PHP and Apache with mod_php up and running in one go.
Configure Apache2 to serve Lumen API
cd /etc/apache2/sites-available/ cat > 001-lumen-api.conf <<EOF
<VirtualHost *:80> ServerName lumen_api DocumentRoot /var/www/lumen_api/current/public DirectoryIndex /index.php <Directory /var/www/lumen_api/current/public> AllowOverride None Require all granted FallbackResource /index.php </Directory> ErrorLog /var/log/apache2/lumen_api_error.log CustomLog /var/log/apache2/lumen_api_access.log combined </VirtualHost> EOF
Let's pause here for a moment and try to understand why both DocumentRoot and Directory point to /var/www/lumen_api/current/public and not to /var/www/lumen_api/public, as Lumen's documentation recommends. Since we decided to use Deployer tool for deployments, it will create the following directories:
- releases that contains releases directories
- current which is a symlink to the current release directory - this is the directory which Apache2 should know about and read from.
- shared that contains shared files and directories
Knowing that, let's proceed to the last step, namely disabling the default Apache2 configuration and enabling ours.
a2dissite 000-default.conf a2ensite 001-lumen-api.conf
With all that, we have our Ubuntu Server up and running. It's high time to focus on the Lumen API codebase now.
Lumen API codebase on the local machine
composer create-project --prefer-dist laravel/lumen lumen-api
After we have run the Composer command, we should have the lumen-api directory with the Lumen project scaffolded, as shown below:
.
├── README.md
├── app
│ ├── Console
│ │ ├── Commands
│ │ └── Kernel.php
│ ├── Events
│ │ ├── Event.php
│ │ └── ExampleEvent.php
│ ├── Exceptions
│ │ └── Handler.php
│ ├── Http
│ │ ├── Controllers
│ │ └── Middleware
│ ├── Jobs
│ │ ├── ExampleJob.php
│ │ └── Job.php
│ ├── Listeners
│ │ └── ExampleListener.php
│ ├── Providers
│ │ ├── AppServiceProvider.php
│ │ ├── AuthServiceProvider.php
│ │ └── EventServiceProvider.php
│ └── User.php
├── artisan
├── bootstrap
│ └── app.php
├── composer.json
├── composer.lock
├── database
│ ├── factories
│ │ └── ModelFactory.php
│ ├── migrations
│ └── seeds
│ └── DatabaseSeeder.php
├── deploy.php
├── phpunit.xml
├── public
│ └── index.php
├── resources
│ └── views
├── routes
│ └── web.php
├── storage
│ ├── app
│ ├── framework
│ │ ├── cache
│ │ └── views
│ └── logs
│ └── lumen-2020-04-11.log
├── tests
│ ├── ExampleTest.php
│ └── TestCase.php
└── vendor
├── autoload.php
├── bin
/// ...
To keep things simple, we are going to add a /timezones GET route inside the routes/web.php file, as shown below:
<?php $router->get('/', function () use ($router) { return $router->app->version(); }); // Simple GET route to list all Time Zones Identifiers. $router->get('/timezones', function () { return DateTimeZone::listIdentifiers(); });
Our Lumen API is working locally, but we are still missing the final step - bundling and uploading the code to Ubuntu 18.04 server with Deployer. For that, we need to install and configure the Deployer tool.
Deployer on the local machine
There are three ways to install Deployer:
- PHAR archive
- Source composer installation
- Distribution composer installation
In this tutorial, we are going to use the second way, which is source composer installation
Install Deployer with Composer as a dev dependency
composer require deployer/deployer --dev
After we have required the Deployer dependency, our composer.json file should look similar to the following snippet:
{
"name": "laravel/lumen",
"description": "The Laravel Lumen Framework.",
"keywords": ["framework", "laravel", "lumen"],
"license": "MIT",
"type": "project",
"require": {
"php": "^7.2.5",
"laravel/lumen-framework": "^7.0"
},
"require-dev": {
"deployer/deployer": "^6.7",
"fzaninotto/faker": "^1.9.1",
"mockery/mockery": "^1.3.1",
"phpunit/phpunit": "^8.5"
},
...
}
Additionally, we should get the new dep executable inside the vendor/bin directory: ./vendor/bin/dep --version
Create Deployer recipe
dep init --template=Laravel
This command has created the basic deploy.php file and initialized it with the Laravel recipe template.
Note
If you want to learn more about what Laravel recipe template brings to the table, please study it yourself on GitHub.
Adjust Deployer recipe
<?php namespace Deployer; require 'recipe/laravel.php'; // Hosts host('IP_OR_DOMAIN_OF_YOUR_UBUNTU_SERVER') ->user('root') ->port(22) ->set('deploy_path', '/var/www/lumen_api'); // Main deploy task which consist of 4 other steps task('deploy', [ 'build', 'release', 'cleanup', 'success' ]); // Installs all Lumen API dependencies on local machine task('build', function () { run('composer install'); })->local(); task('release', [ 'deploy:prepare', 'deploy:release', 'upload', 'deploy:shared', 'deploy:writable', 'deploy:symlink', ]);
// Uploads current directory from local machine to the release path on the ubuntu server machine task('upload', function () { upload(__DIR__ . "/", '{{release_path}}'); });
Before we move forward, let's examine together what has just happened:
- We defined only one host with root password inside our recipe. However, we could use Inventory File and SSH keys instead.
- We used the Build Server Strategy and run Composer locally, to simplify the number of steps. However, we could use Single Server Strategy and run all of the steps on the server, including cloning the GIT repository.
- We overwrote the default deploy task and adjusted it to our needs. But there is much more to learn about the Deployer Flow for those who seek the knowledge.
Deployment
Before we proceed with the deployment, let's review what we have achieved so far:
- We successfully configured Ubuntu 18.04 Server with PHP 7.4 and Apache 2.4 running in mod_php mode.
- We scaffolded the simple Lumen API on our local machine.
- We installed Deployer and configured it to build our Lumen API with all its dependencies on our local machine.
It's high time for the final step, the deployment. Fortunately, this one is pretty simple:
dep deploy
The process of deployment should take around 1-2 minutes and look pretty similar to the snippet below.
✔ Executing task build
➤ Executing task deploy:prepare
✔ root@85.208.21.90's password:
✔ Executing task deploy:release
➤ Executing task upload
✔ root@85.208.21.90's password:
✔ Executing task deploy:shared
✔ Executing task deploy:writable
✔ Executing task deploy:symlink
✔ Executing task cleanup
Successfully deployed!
Note
As you see, Deployer will ask you twice for the password of your Ubuntu Server machine. First time for deploy:prepare task and the second time for upload task. If this bothers you, the easiest way to get around it is to use SSH keys and SSH config. You can read more about it from Deployer's Hosts documentation.
As soon as our deployment finishes successfully, we can open the web browser and type the URL http://your-server-ip - this should show us the default Lumen API route. We can also explore the http://your-server-ip/timezones URL, which we have created additionally earlier.
Moreover, if we log in to our Ubuntu server via SSH now, we can investigate the directory structure created by Deployer.
/var/www/lumen_api/ ├── current -> releases/2 ├── releases │ ├── 1 │ │ ├── app │ │ ├── artisan │ │ ├── bootstrap │ │ ├── composer.json │ │ ├── composer.lock │ │ ├── composer.phar │ │ ├── database │ │ ├── deploy.php │ │ ├── phpunit.xml │ │ ├── public │ │ ├── README.md │ │ ├── resources │ │ ├── routes │ │ ├── storage -> ../../shared/storage │ │ ├── tests │ │ └── vendor │ └── 2 │ ├── app │ ├── artisan │ ├── bootstrap │ ├── composer.json │ ├── composer.lock │ ├── database │ ├── deploy.php │ ├── phpunit.xml │ ├── public │ ├── README.md │ ├── resources │ ├── routes │ ├── storage -> ../../shared/storage │ ├── tests │ └── vendor └── shared └── storage ├── app ├── framework └── logs
We see here, as briefly mentioned before, that Deployer created the current symlink which currently points to the releases/2 directory.
Every time we run dep deploy successfully, we should see that the current symlink re-points itself to the latest release directory.
In case of any code issues after deployment, we can use dep rollback command that instructs Deployer to automatically re-point current symlink to the previous release.
Summary
- We have bootstrapped a simple Lumen API project together.
- On top of that, we installed and configured Deployer, a handy PHP tool that helped us with our deployment.
- Moreover, we have shown how to configure one simple Ubuntu Server.
If you don´t want to, you don´t have to stop here. You can configure a whole farm of servers and check how Deployer can help with deploying your code to all of them at once.
Isn't it nice to finally have a deployment tool that fits into the PHP ecosystem, similar to Ruby and its Capistrano? Leave a comment!