Making docker-compose easier with wdocker

Published on:

Introduction

wdocker is a little utility written by a friend and former colleague of mine. It allows you to define commands for it in a Dockerfile. He wrote it because he used a lot of composite commands when writing docker images like:

docker stop CONTAINER && docker rm CONTAINER && docker rmi IMAGE && \
    docker build -t IMAGE && docker run --name CONTAINER IMAGE

By using wdocker to define a command he can greatly simplify his own workflow. Let's call it rebuild:

#wd# container = CONTAINER
#wd# image = IMAGE
#wd# stop = docker stop {container}
#wd# rm = docker rm {container}
#wd# rmi = docker rmi {container}
#wd# build = docker build -t {image}
#wd# run = docker run --name {container} {image}

#wd# rebuild: {stop} && {rm} && {rmi} && {build} && {run}

FROM ubuntu

# ...

Now he can use the following command instead of the list presented before:

wdocker rebuild

Syntax

wdocker has very simple syntax. You can define variables and commands:

#wd# variable = value
#wd# command: program

Variables can be used by putting them in braces, including in other variables, as you've seen in the first example.

#wd# variable = -l
#wd# list: ls {variable}

This would run ls -l when the command wdocker list is called.

As you can see you're not limited to using docker in your wdocker commands. This property is what allows me to use wdocker in my workflow.

Combining with docker-compose

I started using docker not too long ago at work to develop our projects in. This is nice because it allows me to completely isolate my development environments. Since we have a few processes running together a single docker image isn't a great option, so I use docker-compose to define and combine the containers I need.

As a side-effect this requires me to write long commands to do something like run rspec tests:

docker-compose run --rm -e RACK_ENV=test -e RAILS_ENV=test \
    container bundle exec rspec

The alternative is defining a specialized test container with a bogus entry command (such as true) and use that, which would still make the command:

docker-compose run --rm test-container bundle exec rspec

Instead I can define a wdocker command in the Dockerfile used to build the containers used:

#wd# rspec: docker-compose run --rm -e RACK_ENV=test -e RAILS_ENV=test container bundle exec rspec

FROM ruby

#...

Now I can run the following, much shorter, command to run the rspec tests:

wdocker rspec

We also use cucumber for some other tests, which is even longer to type in, adding the cucumber command is easy:

#wd# rspec: docker-compose run --rm -e RACK_ENV=test -e RAILS_ENV=test container bundle exec rspec
#wd# cucumber: docker-compose run --rm -e RACK_ENV=test -e RAILS_ENV=test container bundle exec cucumber

FROM ruby

# ...

Now I can run wdocker cucumber as well.

The latest git version of wdocker passes any arguments after the command name directly to the command to be executed. So if I need to run tests in a single spec file I can just do:

wdocker rspec spec/models/mymodel_spec.rb

We have two commands defined now that are 90% the same. I always use the --rm switch to remove the started container after it's done, I don't want a lot of containers piling up. I also always have to use bundle exec to run commands, since the containers don't use rvm or add the script directories to $PATH. We can extract them to some variables:

#wd# run = docker-compose run --rm
#wd# exec = bundle exec
#wd# test = -e RACK_ENV=test -e RAILS_ENV=test

#wd# rspec: {run} {test} container {exec} rspec
#wd# cucumber: {run} {test} container {exec} cucumber

FROM ruby

# ...

Right now these commands always use the container service defined in docker-compose.yml. I could add it to the run command, but I might need to run some commands on another container, but I can define another variable:

#wd# run = docker-compose run --rm
#wd# test = -e RACK_ENV=test -e RAILS_ENV=test
#wd# run-test-container = {run} {test} container
#wd# exec = bundle exec

#wd# rspec: {run-test-container} {exec} rspec
#wd# cucumber: {run-test-container} {exec} cucumber

FROM ruby

# ...

Now you also see that variables can be nested in other variables.

If you ever forget what you defined or if the mix of commands and variables becomes too much for you, you can call the wdocker command without arguments to see the commands you defined and the shell commands they'll run.