How to Deploy Symfony Apps with Capifony

on

This article is written for and published on SitePoint.

Say you have a Symfony application. At some point, you would like to deploy it to your server and show it to the world. Of course, you can do it all manually, but these days you can also choose to use a tool like Capifony.

If you have developed Ruby applications in the past, you are perhaps familiar with Capistrano. Capistrano is a tool to deploy your Ruby application to your server. Capifony has been created on top of Capistrano, and is basically a collection of deployment recipes.

In this article, we are going to deploy a Symfony application to a server with Capifony.

How does Capifony work?

Before we start, it’s important to understand how Capifony works. By running the deploy command, Capifony runs certain commands performing different tasks. For example, it will download composer, install the dependencies and clear the cache.

The directory structure is very important. Capifony needs two directories and one symlink. The first directory it needs is called releases. Every time you deploy, a new directory is created within this directory. Capifony pulls in your git repository and runs all commands on this newly created directory.

The second directory is named shared. You can imagine that some directories are shared between releases. For instance, if you allow people to upload images, you want to make sure that these files are shared between releases. These directories and files are typically stored in the shared directory.

Next to these two directories, we have a symlink called current. This symlink points to the latest successful release. So, when you deploy a new version, a new directory will be created within the releases directory. If all tasks succeed on this directory, the current symlink will point to this new version.
You should point your web server to read from this symlink so it always uses the correct, latest version.

Installing Capifony

Let’s cut the theoretic part and dive into deployment. For that, we need to install Capifony. Make sure Ruby is installed on your system before proceeding. You can install the Capifony gem by running gem install capifony.

Within your application directory, run the command capifony .. This command will create a file named Capfile in your root directory and a deploy.rb in your configuration directory. You will alter the deploy.rb file during this article, so make sure you have it open in your favorite editor.

Now you have to decide what your deploy strategy will be. Either you choose to let your production server access your SCM (Source Control Management) or your local computer pulls in your repository from the SCM and copies it to your production server.

Within this article, we will look at the first strategy. If you are interested in the second strategy, have a look at the official Capifony website for instructions.

Configure your project

I am going to use this project and deploy it to a production server. I got the application checked out on my machine, so it’s time to run capifony ..

$ capifony .
[add] writing './Capfile'
[add] writing './app/config/deploy.rb'
[done] symfony 2 project capifonied!

When you open up the deploy.rb script, you will see the following content.

set :application, "set your application name here"
set :domain,      "#{application}.com"
set :deploy_to,   "/var/www/#{domain}"
set :app_path,    "app"

set :repository,  "#{domain}:/var/repos/#{application}.git"
set :scm,         :git
# Or: `accurev`, `bzr`, `cvs`, `darcs`, `subversion`, `mercurial`, `perforce`, or `none`

set :model_manager, "doctrine"
# Or: `propel`

role :web,        domain                         # Your HTTP server, Apache/etc
role :app,        domain, :primary => true       # This may be the same as your `Web` server

set  :keep_releases,  3

# Be more verbose by uncommenting the following line
# logger.level = Logger::MAX_LEVEL

It’s time to change this configuration file. We start off with the top 4 parameters. First off, we define what the name of our application is, what the domain to deploy to is, what the directory will be and where the app path is. If you are using the default Symfony setup, the app path will already be configured correctly. So far my configuration looks like this.

set :application, "Jumph"
set :domain,      "peternijssen.nl"
set :deploy_to,   "/srv/www/jumph"
set :app_path,    "app"

Let’s configure our repository. Since we are using a git repository, we should set the SCM to git and point the repository to our Github repository.

set :repository,  "git@github.com:jumph-io/Jumph.git"
set :scm,         :git

Up next we define our model manager. In my case I am using Doctrine, but if you are using Propel, you should change the configuration value to “propel”.

We can skip the roles. They are just reusing your domain.

The last setting is the keep_releases setting. With this setting, you can define how many releases you may want to keep, allowing you to rollback to a previous version.

So far, we changed all the default config variables to the correct values. However, Symfony requires more configuration to be deployed. In my case, I am both using Assetic as well as Composer. This means I have to add the following settings to the file.

set :dump_assetic_assets, true
set :use_composer, true

Now we need to configure the shared files. For example, your parameters.yml should be shared between every release. Next to that, it’s wise to also share your uploads, your logs and your vendor between releases. If you are not sharing your vendor between every release, it means your deploy is installing all vendors every single time. To set these shared paths, we just add the following configuration.

set :shared_files,      ["app/config/parameters.yml"]
set :shared_children,     [app_path + "/logs", web_path + "/uploads", "vendor", app_path + "/sessions"]

Note that in my case I also moved the session directory outside the cache directory. This way I am able to share this directory between releases and nobody gets logged out on a new deploy. Do note you need to change the configuration within Symfony also to reflect this change.

session:
    save_path: "%kernel.root_dir%/sessions/"

Configure your server

So far everything is ready for our Symfony application. Now it’s time to configure everything for our server. We do this within the same config file as above.

When deploying, Capifony runs as root. If you prefer to run it as your own user, you can add the following lines to your configuration.

set :use_sudo,      false
set :user, "peter"

It’s also important to make sure your web server user is able to write to certain directories. This can be done by adding the following settings.

set :writable_dirs,       ["app/cache", "app/logs", "app/sessions"]
set :webserver_user,      "www-data"
set :permission_method,   :acl
set :use_set_permissions, true

Note: You might need to install certain packages on your server. For more information regarding permissions, please check this page.

Now we can tell Capifony to prepare the directories on your server. We can do this by running cap deploy:setup. Do make sure you have SSH access to the server and the directory is writable by your user of course.

Note: In my case I had to add default_run_options[:pty] = true to my configuration due to a known issue.

After the command has been run, you will notice it created the releases and shared directories on your server.

Now you should be able to deploy by running cap deploy on your command line. If you bump into any problems, you can add the following line to your configuration file, to get more information about the error.

logger.level = Logger::MAX_LEVEL

In my case, I was unable to access the git repository due to an invalid public key. Since my local computer can access the repository, I just had to forward my SSH agent to the server. This can be done by adding the following line to the deploy script.

ssh_options[:forward_agent] = true

Since it’s your first deployment, Capifony will ask you for the credentials of the parameters.yml file. You only have to fill them in once, since we configured the file to be shared across releases.

Adding additional commands

If you tried to deploy the repository I mentioned earlier, you will notice it fails when Assetic tries to dump it’s files. This is due to the fact that I am managing my JavaScipt and CSS dependencies through Bower. So before Assetic dumps the files, I should first run bower install.

Capifony by default has no support for bower, so we have to expand the list of tasks that Capifony performs. We add an additional task by adding it to the configuration file.

before 'symfony:assetic:dump', 'bower:install'

namespace :bower do
    desc 'Run bower install'
    task :install do
      capifony_pretty_print "--> Installing bower components"
      invoke_command "sh -c 'cd #{latest_release} && bower install'"
      capifony_puts_ok
    end
end

The task is quite easy to understand. First we define when the task should run. In this case, we want to run it before Assetic dumps its files. Next we define which task it should run.

The last thing we need to do is to define this new task. We do so by creating a task within a namespace and write down which command to run. In this task, we first make sure we are in the correct directory and then run bower install.

Additionally, I added some output before and after the command. This way, it will show up in the cap deploy command when running. It gives you some extra feedback as you can see below.

$ cap deploy
--> Updating code base with checkout strategy
--> Creating cache directory................................✔
--> Creating symlinks for shared directories................✔
--> Creating symlinks for shared files......................✔
--> Normalizing asset timestamps............................✔
--> Downloading Composer....................................✔
--> Installing Composer dependencies........................✔
--> Installing bower components.............................✔
--> Dumping all assets to the filesystem....................✔
--> Warming up cache........................................✔
--> Clear controllers.......................................✔
--> Setting permissions.....................................✔
--> Successfully deployed!

Additional commands

In the beginning, we decided to keep at least 3 releases. If something should go wrong in the new release, you can rollback by running the command cap deploy:rollback.

Additionally you can also activate or deactivate the Symfony maintenance page by either running cap deploy:web:disable or cap deploy:web:enable.

Capifony consists of more commands that might come in handy. For a full list you can run cap -vT.

Complete configuration

As a reference, this is the complete configuration file which we created through this article.

set :application, "Jumph"
set :domain, "peternijssen.nl"
set :deploy_to, "/srv/www/jumph"
set :app_path, "app"

set :repository, "git@github.com:jumph-io/Jumph.git"
set :scm, :git

set :model_manager, "doctrine"

role :web, domain
role :app, domain, :primary => true

set :use_sudo, false
set :user, "peter"

set  :keep_releases, 3

set :dump_assetic_assets, true
set :use_composer, true

set :shared_files, ["app/config/parameters.yml"]
set :shared_children, [app_path + "/logs", web_path + "/uploads", "vendor", app_path + "/sessions"]

set :writable_dirs, ["app/cache", "app/logs", "app/sessions"]
set :webserver_user, "www-data"
set :permission_method, :acl
set :use_set_permissions, true

ssh_options[:forward_agent] = true
default_run_options[:pty] = true

before 'symfony:assetic:dump', 'bower:install'

namespace :bower do
    desc 'Run bower install'
    task :install do
      capifony_pretty_print "--> Installing bower components"
      invoke_command "sh -c 'cd #{latest_release} && bower install'"
      capifony_puts_ok
    end
end

Conclusion

Capifony makes your life easier if it comes to deploying your Symfony application. Although we have already seen a lot of options Capifony offers, you might want to dig deeper. You can check out their website for more information.

Leave a Reply

Your email address will not be published. Required fields are marked *