While developing salt formulas I was looking for a simple and reproducible environment to allow faster development, less bugs and more fun. The formula must handle multiple target OS (Debian and Centos).

The first barrier is the master/minion installation of Salt, but fortunately Salt has a masterless mode. The idea is quite simple, bring up a virtual machine, install a Salt minion on it, expose the code inside the VM and call Salt states.

At Logilab we like to work with docker, a lightweight OS-level virtualization solution. One of the key features is docker volumes to share local files inside the container. So I started to write a simple Python script to build a container with a Salt minion installed and run it with formula states and a few config files shared inside the VM.

The formula I was working on is used to deploy the saemref project, which is a Cubicweb based application:

% cat test/centos7.Dockerfile
FROM centos:7
RUN yum -y install epel-release && \
    yum -y install && \
    yum clean expire-cache && \
    yum -y install salt-minion

% cat test/jessie.Dockerfile
FROM debian:jessie
RUN apt-get update && apt-get -y install wget
RUN wget -O - | apt-key add -
RUN echo "deb jessie main" > /etc/apt/sources.list.d/saltstack.list
RUN apt-get update && apt-get -y install salt-minion

% cat test/minion.conf
file_client: local
    - /srv/salt
    - /srv/formula

And finally the file, using the beautiful click module

#!/usr/bin/env python
import os
import subprocess

import click
def cli():

formula = "saemref"
BASEDIR = os.path.abspath(os.path.dirname(__file__))

image_choice = click.argument("image", type=click.Choice(["centos7", "jessie"]))

@cli.command(help="Build an image")
def build(image):
    dockerfile = "test/{0}.Dockerfile".format(image)
    tag = "{0}-formula:{1}".format(formula, image)
    subprocess.check_call(["docker", "build", "-t", tag, "-f", dockerfile, "."])
    return tag

@cli.command(help="Spawn an interactive shell in a new container")
def dev(ctx, image):
    tag = ctx.invoke(build, image=image)[
        "docker", "run", "-i", "-t", "--rm", "--hostname", image,
        "-v", "{0}/test/minion.conf:/etc/salt/minion.d/minion.conf".format(BASEDIR),
        "-v", "{0}/{1}:/srv/formula/{1}".format(BASEDIR, formula),
        tag, "/bin/bash",

if __name__ == "__main__":

Now I can run quickly multiple containers and test my Salt states inside the containers while editing the code locally:

% ./ dev centos7
[root@centos7 /]# salt-call state.sls saemref

[ ... ]

[root@centos7 /]# ^D
% # The container is destroyed when it exits

Notice that we could add some custom pillars and state files simply by adding specific docker shared volumes.

With a few lines we created a lightweight vagrant like, but faster, with docker instead of virtualbox and it remain fully customizable for future needs.

blog entry of