Deploying Jekyll blog automatically using Bitbucket Pipelines
So.
For my 50th note, I though I’d finally spend some time trying to figure out how to deploy this blog automatically instead of manually running an Ansible script every time. After an hour or so, I came up with a pretty simple solution.
Have you heard of Bitbucket Pipelines? It’s a pretty great CI/CD pipeline by Atlassian that’s integrated into Bitbucket. The idea is very simple. You just add a bitbucket-pipelines.yml
file in the root of your git repo, place some deployment instructions in it, and Bitbucket will launch a new Docker container on the next push
and run those instructions. These instructions could be pretty much anything, from running test cases to deploying code to remote servers, etc.
Pipelines has a very basic feature set, but they’re pretty useful. Here’s some of the basic stuff we’ll be using:
- It allows you to specify SSH keys and Known Hosts for the docker container Pipelines launches. Bitbucket will place these credentials in the new container, which makes doing stuff over SSH really easy. Let’s say you want Pipelines to deploy code to a remote server. You can create a new SSH key pair, place the public key on the remote host, and fetch the host address, which Pipelines will place in the Known Hosts file on the new Docker container it launches. It will also place the private key you generated within the new container.
- There is also a provision to define encrypted environment variables that you can use in the Pipelines file.
So let’s get started.
Enabling Pipelines in Bitbucket
First, go to the Settings
page of your repo in Bitbucket. The link should be somewhere on the left sidebar. Once there, find Settings
under the Pipelines
section in the sub-menu that opens up. On the new page, you’ll find just one button which says Enable Pipelines
. Let’s activate that.
Setting Environment Variables
Next, head to the Environment variables
section in the same sub-menu. Here you can add key-value pairs that are going to be available for use in the Docker container using the $
symbol. Let’s define a variable with the name PRODUCTION_HOST
and the value of the domain/IP of the machine we want to deploy our code on. Make sure to check Secure
to ensure the variable is encrypted. Hit Add
when you’re done.
Configuring SSH Keys
Let’s head to the SSH Keys
section. The link is in the same sub-menu. On this page we’ll do two things: create a new SSH key-pair to use for deployment, and add the host address and fingerprint of our target machine so we can deploy securely. The process is pretty straight-forward. You can use the interface to generate a new key-pair. Bitbucket will place the new private key in the container it launches for deployment, and you should place the public key on the remote machine. That way we’ll be able to securely establish an SSH connection between our Docker container and the remote machine without any additional configuration in the container itself. Also, add the host address of the target machine in the Host address
section below. It will fetch the fingerprint of the target machine and load it in the known_hosts
file in the container, to verify that the container knows the target machine. Note that the domain/IP should be the same as the one you defined in PRODUCTION_HOST
variable above, since that’s the variable we’ll use to establish a connection.
Creating the bitbucket-pipelines.yml file
Once the above configuration is done, go ahead and create a bitbucket-pipelines.yml
file in the root of your git repo. Place the following content in it:
image: jekyll/builder:latest
pipelines:
default:
- step:
script:
- jekyll build
- rsync -a _site/ root@$PRODUCTION_HOST:/var/www/ --exclude=bitbucket-pipelines.yml --exclude=deploy --exclude=Vagrantfile --chown=www-data:www-data
Save the file and commit
, but don’t push
yet. Let’s go over the file before we do that.
- The
image:
section contains the image name of the Docker container you want Pipelines to launch. You can specify the name of any public container available on DockerHub. Pipelines will download and launch that container, and run the commands we specify in the rest of our file. We’re using thejekyll/builder
container, since it already has everything that we’ll need to build and deploy our Jekyll blog. - The
script:
section contains the actual commands we want Pipelines to run after the container launches. The commands here are pretty basic: we’re building our blog usingjekyll build
, and then wersync
-ing the_site
directory, which is where Jekyll will place our built blog, to the target machine. Note that we’re using the$PRODUCTION_HOST
variable, which Pipelines will populate in the container. I won’t go over the syntax for thersync
command, but remember to exclude files you don’t want and setting the right permissions on those files after upload.
Pipelines has syntax available for running different commands on the push
of different branches. So depending on whether staging
, production
, or feature_x
branch was push
-ed, you could run different commands and deploy in different ways. Since we haven’t specified anything, the above file will run commands when we push
any and all branches.
Watching It Work
Once the above is done, push
your branch to Bitbucket. Head to the Pipelines
link of your repo in the sub-menu, and you should see it all in action. Once successfully deployed, you’ll see a page like this:
And that’s it! What we just did was automate the build and deploy process of our website. Pipelines has a lot of other interesting options, and can do a lot more than the simple task we accomplished. Please remember that Pipelines offers 50 minutes of build time on the free account, but even after that its not very expensive. Pipelines is a good way to get a CI/CD setup for free and have an easier life by letting Bitbucket take care of deployments for you. Pipelines will also send you emails when deployments fail.
I’m writing this note in Evernote right now. I’ll be commit
-ing and push
-ing it soon. If you’re reading this right now, it means it all went fine. For some reason I’m getting a message-in-a-bottle feeling right now. Fingers crossed 😃