Deploying a Vapor app to a remote server using Capistrano

timominous
Apr 19, 2017 · 8 min read

Great! You have a Vapor app running locally (or even on a remote machine). There’s just one problem. Before you can serve the latest changes from your remote machine, you’d have to go through a lot of steps which will most likely consist of the following:

  1. Pushing the changes to a remote repository
  2. Logging in to your remote machine
  3. Pulling the latest code from the remote repository
  4. Fetching the dependencies of your app
  5. Building the app using the latest code
  6. Running the built app from the background
  7. Logging out of your remote machine

The list could grow as you add more configurations into the mix and doing all of these manually would be tedious and time-consuming.

What you need to do is to streamline the deployment process so that you don’t have to go through all those steps every time you want to reflect the changes you’ve made to your app. There are a lot of tools that offer a set of functionalities that make deploying to your remote server a more enjoyable task but for now, we are going to discuss Capistrano.

Important notes

A working Git repository setup is also essential for this guide and process as the latest code will always be fetched from the remote Git repository. You can read up on how to get started with Git here.

This guide will be using the default Vapor app. The machines used were a local Macbook Pro with macOS 10.12.3 and a remote Ubuntu Server 16.04.2. Both machines have Swift 3.1. The latest version of the Vapor toolbox is being used on the remote machine. You can find guides on how to install the latest toolbox to your remote machine here.

Throughout this guide, take notice of values enclosed in angle brackets <> as you will have to replace them with the appropriate values from your setup. For example:

$ ssh <server_user>@<remote_server_address>:<port>// Will become
$ ssh ubuntu@127.0.0.1:22

What is Capistrano?

The set of features Capistrano offers is exactly what we need to streamline our process. But first we have to install the Capistrano gem to our local machine by running this command:

$ gem intstall capistrano -v 3.5.0

If you don’t know what RubyGems are, you can find guides on how to work with them here.

Getting to work with Capistrano

In order for Capistrano to generate the needed files we need to run the following commands:

$ cd <project_directory>
$ cap install

This will add all the files and directories needed to run Capistrano. Specifically, it will add the following:

├── Capfile
├── config
│ ├── deploy
│ │ ├── production.rb
│ │ └── staging.rb
│ └── deploy.rb
└── lib
└── capistrano
└── tasks

For this guide, we will be working with the basic provided configuration files only found under the config directory. We can safely ignore the Capfile and lib directory for now. Global configuration variables can be found and set in config/deploy.rb while target specific configuration variables can be found and set in config/deploy/<target>.rb.

Deployment Targets

We won’t have to modify most of these variables for now. What we will do is add a server-based configuration to specify our remote machine. You can add a new line or uncomment and modify the existing ones.

You need to fill in the variables above with the appropriate values so that Capistrano will know where to deploy the project. You can also to the same to staging.rb if you have a staging environment.

Deploy Script

For this guide, we can safely remove lines 40–47 from the :deploy namespace of deploy.rb since we are not going to use the method. From the code above, you can see the :application and :repo_url configuration variables. You should modify them to match the values from your setup.

The :application variable will be the name of your application. The source code of the project will be deployed (by default) to /var/www/<my_app_name>. You can change the deploy directory by setting the :deploy_to variable but for this guide, we will be using the default.

The :repo_url variable will the Git URL of your remote repository. If you looked at the other variables in the configuration file, you can see that both the source control management system :scm and the :branch are configurable but, again, for this guide, we will be using the default values. The latest source code from :repo_url will be the one used by Capistrano for publishing.

If you’re done modifying :application and :repo_url, we could test out our deploy script by running this command:

$ cap production deploy

For now, all it does is it publishes the latest source code to the /var/www/<my_app_name>/current directory of your machine. If you take note of the console logs produced by the command, you can see that it’s actually executing a lot of steps. For simplicity, the following steps are equivalent to what it does:

  1. Logs in through SSH to your remote machine
  2. Creates a new release directory in /var/www/<my_app_name>/releases/
  3. Clones the latest source code in the newly created release directory
  4. Creates a symlink from the release directory to /var/www/<my_app_name>/current
  5. Logs the revision to /var/www/<my_app_name>/revisions.log
  6. Logs out of the SSH session

It may look like a lot of steps but all of these are automated and it’s a great start since it already eliminates steps 2, 3, and 7 from the list earlier. What we’re going to do next is to eliminate the remaining steps in between.

Automated building

What these set of commands does is automate the fetching and building with the deploy process. The :deploy namespace should the be something similar to this:

Now if you try running the deploy command again, you will be able to see in the logs that, in addition to publishing our code, the script now fetches the dependencies and builds the app in release configuration.

Application service

What we need to do is create a systemd service that runs our Vapor app in the background. First, you need to create the service file:

$ cd /lib/systemd/system
$ [sudo] touch vaporapp.service

Open up vaporapp.service in your favorite terminal editor and modify its contents.

Now we have what we need to start our Vapor app as a service, we can run this command to start it up:

$ [sudo] systemctl start vaporapp.service

You can now access your app at <remote_server_address>:<port>. What we need to do next is to add this to our deploy script so that our app is automatically served using the latest build everytime we deploy changes. We can also run

$ [sudo] systemctl enable vaporapp.service

to start up the service every time the remote machine reboots.

If you want to learn more about systemd, DigitalOcean has a great guide that can be found here. If you are running a version of Ubuntu lower that 15.04, you can achieve the same functionality by using Upstart.

Automated serving

Notice that we added 3 methods to our build script: start, stop, and restart. We will be using the restart method but the other 2 are added for good measure and can be useful for future endeavors. Don’t forget to add the hook to call the restart method after the build hook.

You could try running the deploy command again and see that after publishing, the deploy script builds and serves your app. At this point, we already have most of what we need to streamline our deployment process. There’s just one more thing we need…

Persisting secrets

To achieve this, we have to manually upload our secrets directory to our remote machine initially and every time we make changes to the files within it. The easiest way to do this is through scp:

$ scp -r <project_directory>/Config/secrets server_user@remote_server_address:/var/www/<my_app_name>/shared/secrets

Our secrets folder should now be uploaded to /var/www/<my_app_name>/shared/secrets. Changes to this directory should be handled manually and not included in the deployment process. What we can do from our deploy script is to add a symlink to this directory from within our project directory so that our app can use it. You can add the method right below the :restart method in our :deploy namespace.

The method we added is named secrets and it handles the linking of our secrets folder. We also added a deploy hook that calls this method right after our source code is published and before the application starts fetching dependencies.

Finished!

While it may have taken a little while to setup, it pays off in the long run by reducing our deployment process to two steps!

  1. Pushing the latest source code to the remote repository; then
  2. Running our deploy script
$ cap production deploy

This is a great improvement over the 7-step process we described earlier. You can now reallocate your time to building world-changing important features for your app.

Links