Adam Reeve

Continuous delivery isn't just for web applications, it's valuable to be able to quickly get feedback from real users for any kind of software project. Also, I'm lazy, so automating the release process for any software to the point that I only have to merge a branch into master is great.

I recently set up continuous deployment of my npTDMS Python package to PyPI (the Python package index) using Wercker. Wercker is a continuous integration and deployment solution built on Docker. You build your code inside a Docker container and can specify a Docker image to use that contains the build dependencies for your project. This Docker image can come from any public or private registry, so you can use your own Docker image and have complete control over the build environment. I created a Python image containing everything needed to build and test my library and hosted it on Docker Hub. The Dockerfile looks like this:

FROM ubuntu:14.04

RUN sudo apt-get update && \
        sudo apt-get install -y \
            python3 python3-numpy python3-setuptools python3-nose python3-pandas \
            python python-numpy python-setuptools python-nose python-pandas \
            pep8 python-pip python-wheel

This Dockerfile lives in a git repository on GitHub. Any commits to this repository trigger a new Docker image build using Docker Hub's auto-build feature.

In wercker.yml I set the Docker image to use, pointing it at my image on Docker Hub:

box: adreeve/python-numpy

The test section in my wercker.yml defines what to run to build and test my code, and looks like:

build:
  steps:
    - script:
        name: PEP8 Check
        code: |
           pep8 ./nptdms
    - script:
        name: Test on Python 2.7
        code: |
          python2.7 setup.py install
          nosetests
    - script:
        name: Test on Python 3
        code: |
          python3 setup.py install
          nosetests3

This tells Wercker to run pep8 to check code style, then run tests on Python 2.7 and Python 3 using nose.

Wercker lets you define deployment steps and you can optionally enable automatic deployment from specific branches after successful builds. The deployment step in my wercker.yml file is pretty simple, it looks like this:

deploy:
  steps:
    - script:
        name: Deploy to PyPI
        code: |
          echo "[pypirc]
          servers = pypi
          [server-login]
          username:$PYPI_USER
          password:$PYPI_PASSWORD" > ~/.pypirc
          python setup.py sdist upload
          python setup.py bdist_wheel upload

This simply configures my PyPI credentials then runs the commands to upload a new release as a source package and a Python wheel. The PYPI_USER and PYPI_PASSWORD variables are configured in the Wercker UI and are kept secret. Now after any successful build on master, Wercker will automatically deploy a new release to PyPI!

One thing to note with this approach is that PyPI will reject an upload of a new release if the version number has not changed, so if you enable automatic deployment from your master branch, you should ensure any pull request into that branch increments the version number. Remember to follow semantic versioning!

The complete wercker.yml file for npTDMS can be found in the GitHub repository.