Image for post
Image for post

How to build a modern CI/CD pipeline

Using free and hosted services

Rob van der Leek
Apr 9, 2017 · 11 min read
Image for post
Image for post

Software developers are problem solvers. Although they can be very tenacious about finding solutions, the moment they have one they want to share it with the world; the feeling of shipping code is great. Code that is never executed for users is no more than a digital waste product. To prevent building waste, modern software developers ship functionality to their users in short iterations and small increments.

A way to ship code in small increments and iterations is by using a Continuous Integration and Continuous Deployment, or CI/CD, pipeline. In this tutorial we’ll go through all the steps in setting up such a pipeline using free and hosted services. From start to end this tutorial shows you in 9 steps how to:

  1. Write a little Python program (not Hello World)
  2. Add some automated tests for the program
  3. Push your code to GitHub
  4. Setup Travis CI to continuously run your automated tests
  5. Setup Better Code Hub to continuously check your code quality
  6. Turn the Python program into a web app
  7. Create a Docker image for the web app
  8. Push the Docker image to Docker Hub
  9. Deploy the Docker image to Heroku

The goal of this tutorial is to show you the essential building blocks of a modern software development pipeline, and how to assemble those blocks. It’s not about any programming language or software development methodology in particular. Maybe some of the building building blocks do not match your technology stack of choice. Even if that’s the case I still advice you to go through all the steps. Once you have a basic understanding of the complete end-to-end pipeline you can make it easily fit your specific needs.

Full disclosure: I’m one of the developers on the Better Code Hub team.

Update August 2017: Eran Barlev from published a short video on LinkedIn demonstrating how Codefresh can replace some of the building blocks described in this tutorial. Since no two software products are the same, development pipelines can vary too. Be sure to check out Eran’s video to see an alternative implementation of a modern CI/CD pipeline.

Step 1: Write a little buzz generator

We need a small piece of software that will travel through all phases of the pipeline, from your laptop to the cloud. In our case this piece of software is a little CI/CD buzz generator program written in Python.

Create a new directory (let’s say we call it ‘cicd-buzz’). Inside this directory create another directory called ‘buzz’ and in this directory save the snippet below to a file called ‘’.

Also create a new empty file called ‘’ in the same directory. Your project structure should look like this now:


You should be able to run this Python script from the command line inside the ‘buzz’ directory:

[cicd-buzz/buzz] $ python
End-To-End Devops Enormously Boosts Continuous Testing

Try it a couple of times, it’s fun:

[cicd-buzz/buzz] $ python
Complete Continuous Improvement Enormously Improves Devops
[cicd-buzz/buzz] $ python
Modern Devops Remarkably Improves Continuous Testing

Step 2: Add automated tests

Continuous delivery pipelines only makes sense when you have a significant amount of automated tests that prevents you from continuously shipping broken software. To get a proper set of unit-tests for our buzz generator create a new directory called ‘tests’ in the root of your project directory and save the the snippet below to a new file in the ‘tests’ directory. Call this file ‘’:

To run the tests we’ll be using the ‘pytest’ framework. To install pytest we’ll be using a Python Virtual Environment (‘virtualenv’). Don’t worry, that’s easier done than said. First, make sure you have virtualenv installed so you can execute the following command inside your project directory:

[cicd-buzz] $ virtualenv venv

This should create a new directory venv. To start using this environment type:

[cicd-buzz] $ source venv/bin/activate
(venv) [cicd-buzz] $

Next, create a new file called ‘requirements.txt’ that lists the pytest dependency:


To download dependencies listed in the requirements file you’ll need to execute the ‘pip’ command:

(venv) [cicd-buzz] $ pip install -r requirements.txt

After you’ve completed all steps above, the root of your project directory should look like this:


Inside the virtual environment you can now run the unit-tests in the ‘’ file:

(venv) [cicd-buzz] $ python -m pytest -v tests/

The output should look something like:

========== test session starts ==========
platform darwin -- Python 2.7.10, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- /Users/rob/projects/workspace/cicd-buzz/venv/bin/python
cachedir: .cache
rootdir: /Users/rob/projects/workspace/cicd-buzz, inifile:
collected 3 items
tests/ PASSED
tests/ PASSED
tests/ PASSED
========== 3 passed in 0.02 seconds ==========

Step 3: Put the code on GitHub

Login to GitHub (get an account first if you don’t have one already) and create a new public repository called ‘cicd-buzz’.

Inside your project directory create a new file called ‘.gitignore’ containing only a single line:


This will prevent git from adding the virtualenv to our repo. Now it’s time to initialize Git locally and push your code to GitHub:

[cicd-buzz] $ git init
[cicd-buzz] $ git add *
[cicd-buzz] $ git commit -m "Initial commit"
[cicd-buzz] $ git remote add origin<YOUR_GITHUB_USERNAME>/cicd-buzz.git
[cicd-buzz] $ git push -u origin master

If the last command above complains about access rights, make sure you’ve added your SSH key to your GitHub account.

Step 4: Connect Travis CI to run the tests on every commit

Travis CI is a hosted service for Continuous Integration work. It’s free for public GitHub repositories and getting a Travis CI account is just a matter of visiting and logging in with your GitHub credentials.

Enabling Travis CI to start a build at each Push and Pull Request for your repository is as easy as flipping the switch in front of your GitHub cicd-buzz repository (click the ‘Sync account’ button in case your repository is not yet visible) :

Image for post
Image for post

The last step in activating Travis CI is to add a ‘.travis.yml’ file in the root of your project directory. For our buzz generator this file should contain:

language: python
- python -m pytest -v

Add the file to Git, then commit and Push your changes:

[cicd-buzz] $ git add .travis.yml
[cicd-buzz] $ git commit -m "Add Travis CI configuration"
[cicd-buzz] $ git push

Go to the Travis CI dashboard. After a short amount of time Travis should notice your code changes and start the build/test process. The output log shows the execution of the unit-tests:

Image for post
Image for post

Step 5: Add Better Code Hub to your pipeline

Now that we have a well oiled pipeline that continuously checks the functionality of our code with automated tests, the temptation is strong to focus on functionality and forget about quality. Better Code Hub is a hosted platform that checks the quality of your code according to the 10 guidelines for maintainable, future proof code. Better Code Hub is a watchdog that continuously monitors our development work (literally every push to GitHub) and notifies you when the quality is at risk.

Better Code Hub is, like Travis CI, a service that seamlessly integrates with GitHub. To attach it to our repo, go to and choose the login button that says Free.

After logging in with your GitHub credentials, the next page lists all your GitHub repositories. Find the repository called ‘cicd-buzz’ and press the play button. Better Code Hub will then ask you if it’s fine to run the analysis with the default configuration. Click ‘Go’ and wait a few seconds, the analysis report should now be on your screen.

Image for post
Image for post

If you want Better Code Hub to run for every Push and Pull Request on your repo (just like Travis CI), toggle the pull-request icon that is displayed on the bottom-left part of the repo card:

Image for post
Image for post

Step 6: Turn the buzz generator into a simple web app

Nice job! You already have a continuous integration pipeline that checks for functionality and quality at this point. Next step is to continuously deploy your software whenever all tests pass.

Since we will deploy the software to Heroku as a web app, we first need to write a little Python Flask wrapper around our buzz generator to make the program respond to HTTP requests and output HTML. Add the code below in a file called ‘’ in the root of your project directory:

Also add another line to your ‘requirements.txt’ file for the Flask framework:


And install the new dependency:

(venv) [cicd-buzz] $ pip install -r requirements.txt

You can now run the web app on your laptop:

[cicd-buzz] $ python
* Running on (Press CTRL+C to quit)

Open the location http://localhost:5000 in a browser and admire your achievement. Hit refresh a couple of times, just for fun.

Image for post
Image for post

Finally, don’t forget to add your commit and push your changes:

[cicd-buzz] $ git add
[cicd-buzz] $ git add requirements.txt
[cicd-buzz] $ git commit -m "Step 6"
[cicd-buzz] $ git push

And enjoy watching Travis CI and Better Code Hub picking up this push!

Step 7: Containerize your web app with docker

We’ll use Docker to create a single self-contained, deployable unit of our web app. For a simple Python Flask app this may look like a lot of overhead but deploying different versions of your code base as a small, self-contained unit has a lot of benefits when your system grows over time.

Assuming you have Docker up and running, add the following to a new file called ‘Dockerfile’ in the root of your project directory:

The above tells docker to pick the alpine base image, install Python and pip, and also install our web app. The last line tells docker to launch the web app whenever the container is launched.

You should be able to build an image of this Docker configuration and launch it (depending on your OS configuration you might need to put sudo in front of the commands below):

[cicd-buzz] $ docker build -t cicd-buzz .
[cicd-buzz] $ docker run -p 5000:5000 --rm -it cicd-buzz

Admire the result in a browser:

Image for post
Image for post

Again, don’t forget to add your commit and push your changes:

[cicd-buzz] $ git add Dockerfile
[cicd-buzz] $ git commit -m "Step 7"
[cicd-buzz] $ git push

Step 8: Deploy to Docker Hub

Deploying your containers to a central Docker image registry, such as Docker Hub makes it much easier to share your containers in different environments or to go back to a previous version. To complete this step you’ll need to sign up at and add the following to a file called ‘’ in a new directory called ‘.travis’ in your project directory:

The script above will be called by Travis CI at the end of each pipeline build and will create a new deployable Docker image for that pipeline build. The script requires 3 environment variables that you can set under the ‘settings’ view of your cicd-buzz repo at Travis CI:

Image for post
Image for post

To have Travis CI deploy you Docker image to Docker Hub for each code push to your GitHub repository, modify your ‘.travis.yml’ file so that it looks like:

After committing and pushing these changes (and waiting for Travis CI to complete the full pipeline) you should be able to launch your Docker image straight from Docker Hub:

[cicd-buzz] $ docker run -p5000:5000 --rm -it <YOUR_DOCKER_USERNAME>/cicd-buzz:latest

Step 9: Deploy to Heroku

Heroku is a cloud platform for hosting small and scalable web applications. It offers a free plan so go to and sign up if you haven’t already done so.

Install the Heroku command-line toolbelt and from the root of your project directory execute the following commands:

[cicd-buzz] $ heroku login
[cicd-buzz] $ heroku create
Creating app… done, ⬢ fathomless-inlet-53225 |
[cicd-buzz] $ heroku plugins:install @heroku-cli/plugin-container-registry
[cicd-buzz] $ heroku container:login
[cicd-buzz] $ heroku container:push web
[cicd-buzz] $ heroku container:release web

After these commands you should be able to access the app at the url reported by the heroku create command (or alternatively run $ heroku open).

Image for post
Image for post

Note that the heroku container:push web command, pushes the same container to the Heroku platform as you’ve pushed to the Docker Hub registry.

To automate the process of deploying each build of the master branch of our project, add the following to a file called ‘’ in the directory ‘.travis’:

Also, add the following line to your ‘.travis.yml’ file:

- sh .travis/
- test "$TRAVIS_BRANCH" = "master" && sh .travis/

And finally add 2 more environment variables to your repo at Travis CI. You can find the Heroku API key under your Heroku ‘Account Settings’. The Heroku App name is the name reported by the heroku create command.

Image for post
Image for post

Commit and push these changes to GitHub. A new Docker image should now be pushed to both Docker Hub and Heroku after the build succeeds.

Step 10: CI/CD FTW!

Now that we have a modern development pipeline up and running, the fun of shipping functionality in short iterations and small increments starts. Let’s say we want to make the landing a bit more attractive. A typical workflow to do that looks like:

  1. Start by creating a new issue for the feature:
  2. Create a Git feature-branch for this ticket:
  3. *Coding magic happens here*
  4. Keep an eye on the feedback from Travis CI and Better Code Hub:
  5. Check a running instance of your app by locally running the latest Docker image:docker run --rm -p5000:5000 -it robvanderleek/cicd-buzz:issue-1 You can also share this container with others.
  6. If you’re satisfied with the new feature, open a Pull Request and your code is ready to be shipped by the CI/CD pipeline to production:
Image for post
Image for post

Happy coding & shipping!

Write BetterCode

The Definition of Done for Product Quality: Improve what…

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store