Automating Django deployment and updates with make

2017-09-11 by Senko Rašić

Nowadays there are myriad build automation tools. At GoodCode we prefer make, the tool that first appeared way back in '67, and is nowadays available on every operating system. There are a few popular variants (GNU make, BSD make), but for the simple use cases like the ones we're going to explore here, they all behave the same.

A make primer

Make is great at specifying inter-dependent tasks. Each task has a target (either a file that should be produced, or just a task name that can be referenced elsewhere), list of dependencies (what files or other tasks need to be done before it can start), and a series of commands. We can also define variables that we can use throughout the code.

The tasks are defined in one or more Makefiles. The first defined task is the one that will be started by default (unless we manually specify the task we want). Here's an example that invokes the SASS compiler to produce the CSS for the project:

static/css/style.css: static/scss/style.scss
        sassc --style 'compressed' $< > $@

Here we've defined a single target, file static/css/style.css that will be the result of the task. The task depends on static/scss/style.scss, and the command to execute is the sassc tool passing it in the source (dependency, that's the $<), and storing the result to the desired location (that's the $@ referencing the target name).

Also note that Makefile, being an artefact from the bygone era, is picky about spacing. The rules must start with a tab, not spaces!

We can start the target with:

make

The automatic dependency tracking is at work here: make will only rebuild style.css if the corresponding dependency style.scss changed.

Let's spice things up a bit:

SASSC=sassc
SASS_OPTS=--style 'compressed'
STYLES=static/scss/style.scss static/scss/base.scss static/scss/variables.scss ... static/scss/components.scss

.PHONY: all clean

all: static/css/style.css

static/css/style.css: $(STYLES)
        $(SASSC) $(STYLE_OPTS) $(STYLES) > $@

clean:
        rm -f static/css/style.css

Here we turned the SASS compiler name and options into variables so we can easily change them or override from the command line. We've also dealing with a more realistic scenario where the CSS depends on more than just one file. We also added two new targets, all (which does everything for a complete build - in this case, just ensures the CSS file is built) and clean that cleans up the directory. As these two don't represent files in the file system (but are virtual names), we've indicated that to make using .PHONY directive.

Making a Django deployment

Armed with this knowledge, let's automate some tedious Django deployment tasks. Here's a typical Makefile we use at GoodCode, slightly simplified:

MANAGE=python manage.py
TEST_SETTINGS=project.settings.test

.PHONY: all help deps static migrate restart update deploy

all: help

help:
        @echo "Usage:"
        @echo "  make update - update application"
        @echo "  make deploy - pull and deploy the update"
        @echo "  make test - run automated tests"

staticfiles/css/style.css:
        sassc --style 'compressed' staticfiles/sass/style.scss > $@

deps:
        pip install -r requirements.txt

static:
        $(MAKE) staticfiles/css/style.css
        $(MANAGE) collectstatic --noinput

migrate:
        $(MANAGE) migrate

restart:
        sudo restart app

update: migrate static

deploy:
        git pull --ff-only
        $(MAKE) deps
        $(MAKE) update
        $(MAKE) restart

test:
        $(MANAGE) test $(TEST_SETTINGS)

Here we define a few low-level tasks: updating dependencies, compiling the SASS files, collecting the static files, running the migrations, restarting the server. These are things that we usually don't need to run manually, but part as more complex tasks (but we can run them manually if we want to).

We also define the higher-level tasks: updating the application and deploying it (which combines the update with pulling from the remote git repository and restarting the server instead). Running the tests is not a complex task, but if we need to pass on test-specific arguments (like the test settings) it's easier to put it in a Makefile than to copy-paste the test command from README.

The weird $(MAKE) ... invocations are a way to run the specified task at the exact order we want. So with deploy, we first want to make a git pull (which is not a separate task itself), then deps, and only then update. To do this, we rerun make itself (the $(MAKE) variable is defined by default) with the specified target.

Finally, a note that our restart task makes an assumption on how the app can be restarted. The exact details will vary from system to system, and based on how you've configured your server.

Benefits of make

A great thing about using make vs some other tool is that is's as usable for Django projects as it is with C, Go, JavaScript or CSS build tasks. In practice, a project is likely to have a mix of these, so having a single tool that can combine all of them is pretty useful.

Author
Senko Rašić
We’re small, experienced and passionate team of web developers, doing custom app development and web consulting.