PHP
Article

How to Deploy Symfony Apps with Capifony

By Peter Nijssen

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. Are you using Capifony to deploy your Symfony applications? Let us know if you run into any difficulties or have any questions!

  • Michaël Perrin

    Very nice article, thank you! Very complete, with information about session handling and SSH forwarding which is something that’s too often forgotten in Capifony tutorials.

    I had the “logger.level = Logger::MAX_LEVEL” enabled until now, and I am now very happy to get much less verbose output when deploying, with these nice little “✔” signs.

    • Michaël Perrin

      Just one thing. It would be great to add information about multiple deployment stages.

      • https://www.peternijssen.nl/ Peter Nijssen

        Thanks for the compliment and for the suggestion. Will take that into account next time :)

  • David Romaní

    I usually advised that you need to trigger:
    after “deploy”, “deploy:cleanup”
    to apply keep_releases well

    • https://www.peternijssen.nl/ Peter Nijssen

      Thanks for the heads up!

  • http://www.kiwisoft.co.uk AndreFigueira

    Why would one use this instead of straight up Capistrano?

    • https://www.peternijssen.nl/ Peter Nijssen

      Good question. In this case, when working with a Symfony application, you already have some scripts ready to use. For the average person who doesn’t know anything about Capistrano, this is a simple way to get your app deployed.

      • http://www.kiwisoft.co.uk AndreFigueira

        Understood, so in essence you have Symphony specific tasks coded right into it for minimal setup.

        Cheers

        • https://www.peternijssen.nl/ Peter Nijssen

          Indeed. Also see the tools mentioned in the other comments. You can see other tools also provide you with this. Capifony is just one of those :)

  • http://www.frandieguez.com/ Fran Dieguez

    Why are you using capifony (that uses capistrano 2) instead of using capistrano 3 with some capistrano plugins?

    capistrano/symfony https://github.com/capistrano/symfony
    capistrano/composer https://github.com/capistrano/composer

    Capistrano 3 and its plugins work far better.

  • https://www.peternijssen.nl/ Peter Nijssen

    Good point. I was unaware of these scripts at the moment of writing. Capifony is still being improved every day and despite a bit older version of Capistrano, it still runs fine.

  • Elfet

    Take a look at http://deployer.in

  • Chris Emerson

    Having been to hell and back trying to get Capifony set up with a Symfony project at work, I never want to touch it again. Absolutely horrible – if you don’t want to, or can’t, do things its way, you are stuffed. The documentation is pretty poor too. Why not stick with something more flexible like Phing, which can do anything you want in the order you want, has good documentation, is consistent with industry standard tools like Ant, and doesn’t require you to learn an entire new language in order to use effectively, or hook into?

  • Alex Stansfield

    I’d like to recommend against sharing the vendors directory when doing a deployment in place on the remote server.

    Whilst deploying a new version of your site your current version is protected by the fact that the new code is being cloned into a different folder and will only have the “current” symlink pointed at it once everything is successful.

    However by sharing the vendors folder your vendors libraries are being updated as the deployment is happening. This could have negative effects on your running site. Especially if there is a problem and composer bugs out half way. Also a deploy:rollback won’t run the composer install on the rolled back version (afaik) so you could be left with incompatible libraries should you need to rollback.

    A better solution is to set the copy_vendors option to true. This will copy the vendors file from the previous release into the new deployment before running composer install. This way you’ll only be fetching the dependencies that have changed.

  • digitalbart
  • Moe

    Hey,

    in the moment capifony tries to download Composer on my webserver I get the following error: “sh: curl: command not found”.
    I don’t have root access, but composer is preinstalled on the server.
    Has anyone an idea how I can fix this?

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in PHP, once a week, for free.