Deploying a Symfony application with Capistrano

It’s been a while since I last wrote my “deploy with Magallanes” story. A lot of people is constantly reading that piece of news maybe finishing with a “well, I need a better tool to deploy my Symfony project”.

I wrote a whole — free — book about easy Symfony development that you can read on Leanpub (link at the end). But, for this 2017, I’m going to write a series of posts about web development that I hope you enjoy. This first one talks about the tool I use to deploy my Symfony projects: Capistrano.

Capistrano is a remote server automation tool written in Ruby. It supports the development and execution of tasks and it includes a great variety of them by default for deployment tasks. Since version 3, Capistrano does have a plugin called “capistrano-symfony” which automates the deployment task of that kind of projects.

First of all, create (or edit) a Gemfile in the root of the project with this content:

source 'https://rubygems.org'
#…
gem 'capistrano-symfony', '~> 0.4'

Then execute the bundle installcommand that install the needed gems (this works like a composer.json or package.json file, but you need bundler gem installed). After that, create a Capfile file in the root of your project with the following content:

# Capfile
require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/symfony'

Now, we just have to create the needed config files. Create a config folder in the root of the project, and inside it, create a deploy.rb file. In this file you will configure the whole deploy process: tasks, project SCM, number of releases, … Here you have a deploy.rb example file. Edit with your needs:

########################
Setup project
########################
set :application, "hello-world-medium"
set :repo_url, "https://github.com/groupname/repository.git"
set :scm, :git
#########################
Setup Capistrano
#########################
set :log_level, :info
set :use_sudo, false
set :ssh_options, {
forward_agent: true
}
set :keep_releases, 3
#######################################
Linked files and directories (symlinks)
#######################################
set :linked_files, ["app/config/parameters.yml"]
set :linked_dirs, [fetch(:log_path), fetch(:web_path) + "/uploads"] set :file_permissions_paths, [fetch(:log_path), fetch(:cache_path)] set :composer_install_flags, '--no-interaction --optimize-autoloader'
namespace :deploy do
after :updated, 'composer:install_executable'
end

Please, read carefully the following links in order to understand the Capistrano flow and variables:
* The Capistrano Flow, explained here
* The capistrano-symfony exposed variables, explained here

Last step: the stage configuration. We are allowed to create a file per stage. This means: a pro.rb file for production, a dev.rb file for a dev environment, etc. Create a folder called deploy inside the previously config created folder. There, create a pro.rb file with the following (edited for your needs) content:

#######################
Setup Server
########################
server "pro.company.com", user: "sshuser", roles: %w{web}
set :deploy_to, "/path/to/your/deployment/directory"
#########################
Capistrano Symfony
#########################
set :file_permissions_users, ['www-data']
set :webserver_user, "www-data"
#########################
Setup Git
#########################
set :branch, "master"

Now, you can run cap pro deploy and let the magic work automatically. Please, be sure to adapt your WebServer to the Capistrano folder structure.

Working with custom tasks

Ok, we’ve learn how to use the basic stuff, but what about custom tasks? My project does have YARN, how can I work with it?

I usually create a tasks folder inside the config one. There, I create a task.cap file for each diferent task. In order to get them loaded, you must edit your Capfile adding the following:

Dir.glob('config/tasks/*.cap').each { |r| import r }

That’s it! Here you have a simple task for YARN and Gulp that executes locally and then uploads the result to the needed server(s). The file is config/deploy/yarn.cap:

namespace :dependencies do
desc 'npm dependencies & gulp tasks'
task :yarn
run_locally do
execute "yarn install && gulp prod"
end
end
desc 'Upload compiled dependencies'
task :upload do
on roles(:all) do |host|
upload! "#{fetch(:web_path)}/css", "#{release_path}/#{fetch(:web_path)}", recursive: true
upload! "#{fetch(:web_path)}/js", "#{release_path}/#{fetch(:web_path)}", recursive: true
end
end

Remember this is Ruby, so indentation is a must. You must then edit your deploy.rb file and add this task whenever you want (do you remember the Capistrano flow?)

namespace :deploy do
after :starting, 'composer:install_executable'
after :updated, 'symfony:assets:install'
after :updated, 'dependencies:yarn'
after :updated, 'dependencies:upload
'
end

You can read more about this on my book: Learn Symfony.