Deploying a Node.js web application with Capistrano
Capistrano is a Ruby-based server automaton and deployment tool that is primarily designed to deploy Ruby (on Rails) applications, but also works really well for deploying Node.js applications because of its sensible defaults.
I'm not going to explain exactly how Capistrano works, instead I'm just going to describe my Capistrano configuration for a simple Node.js application. To get started with Capistrano read through the documentation on their website and follow the instructions to set up Capistrano in your project.
Assuming you are using a single stage (e.g. 'production'), set up your stage file (probably config/deploy/production.rb
) so that your server is responsible for all app roles:
# config/deploy/production.rb
server 'my.server.com', user: 'me', roles: %w{app db web}
Then set up your config/deploy.rb
file to look something like this:
# config/deploy.rb
# config valid only for current version of Capistrano
lock '3.4.0'
set :application, 'my-application'
set :repo_url, 'git@github.com:me/my-application.git'
set :deploy_to, "/home/me/Applications/#{fetch :application}"
# Link node_modules so it is shared between deploys.
# If you also have a log folder, or other folders that
# should be shared, add them here
set :linked_dirs, %w(
node_modules
)
namespace :deploy do
desc 'Copy upstart script'
task :upstart do
on roles(:app) do
within release_path do
sudo :cp, "etc/#{fetch :application}.upstart.conf", "/etc/init/#{fetch :application}.conf"
end
end
end
desc 'Restart application'
task :restart do
on roles(:app) do
sudo :service, fetch(:application), 'restart'
end
end
desc 'Install node modules'
task :install_node_modules do
on roles(:app) do
within release_path do
execute :npm, 'install', '-s'
end
end
end
after :updated, :install_node_modules
after :updated, :upstart
after :publishing, :restart
end
- First, we specify that this configuration only works with the latest Capistrano version, which isn't strictly true, but is a safe default.
- Next, we specify the name of the application, the location of the git repository and where we want to deploy to.
- We instruct Capistrano to link
node_modules
between deploys. This means it will putnode_modules
in theshared
folder in the root of the deployment location and symlink it in to each deployed version so that all the modules don't have to be reinstalled every time we deploy.
The deploy
block describes the various tasks we want to run, and when to run them, you can also run them individually e.g. cap production deploy:restart
.
- Our
deploy:upstart
copies an upstart script from the deployed application directory into the correct place on the system using sudo for elevated permissions. This would require passwordless-sudo to be enabled. Capistrano has more information on how to do this. deploy:restart
is pretty self explanatory, using sudo again to restart the service with upstart.deploy:install_node_modules
does exactly what you would expect; runsnpm
on the server in the deployed directory.
Finally, we set up the deploy hooks to ensure these tasks run at the appropriate time.
Of course you would also need a web server (e.g. Nginx) to proxy to this Node.js application. You could keep the configuration for Nginx in the repository as well and copy it to the appropriate place much like the upstart script in the example above. You could even reload Nginx as part of the restart task.
This is the simplest configuration that can work, but it is easy to add additional functionality on top of this base configuration. Some examples of configurations I have used:
Locally building & uploading a set of static files
desc 'Locally build the static site'
task :build_static do
run_locally do
within 'static' do
execute :grunt, 'build'
end
end
end
desc 'Upload the static site'
task :upload_static do
on roles(:app) do
upload! 'static/dist', release_path.join('static'), recursive: true
end
end
before :check, :build_static
after :updated, :upload_static
Ensuring a file is present on the server
desc 'Ensure a file is present'
task :ensure_file do
on roles(:app) do
unless test '[ -f /srv/file ]'
error " Missing file in /srv directory."
exit 1
end
end
end
before :check, :ensure_file
Capistrano is a powerful, if slightly retro, tool. I hope this post is useful when you're next deploying a Node.js application :) let me know in the comments if you have a different preferred deployment method!