Skip to content
On this page

Patterns

In a nutshell, a user calls a Make target which then delegates the Task to be executed in a Container to Docker or Compose.

pattern-overview

This section covers the following patterns:

TIP

A project does not need to follow only 1 pattern. For instance, a task A can use the pattern Shell command and task B, Shell file.

Make

INFO

This pattern is used to build and deploy this very website. See the code.

Make calls Compose which then calls another Make target inside a Docker container. This pattern requires the Docker image to have Make installed.

pattern-make

TIP

There are ways to add Make to your Docker image if it does not have it.

yaml
# docker-compose.yml
version: '3'
services:
  alpine:
    image: golang
    volumes:
      - .:/opt/app
    working_dir: /opt/app
makefile
# Makefile
echo:
	docker-compose run --rm alpine make _echo

_echo:
	echo 'Hello, World!'
bash
$ make echo

Shell command

Make calls Compose which executes a shell/bash command inside a Docker container.

pattern-shell

yaml
# docker-compose.yml
version: '3'
services:
  alpine:
    image: alpine
makefile
# Makefile
echo:
	docker-compose run --rm alpine sh -c 'echo Hello, World!'
bash
$ make echo

Shell file

Make calls Compose which executes a shell/bash command inside a Docker container. Also, an example of a shell file that mimics Make can be found here.

pattern-shell-file

bash
# echo.sh
#!/usr/bin/env sh
echo Hello, World!
bash
# set executable permission
$ chmod +x echo.sh
yaml
# docker-compose.yml
version: '3'
services:
  alpine:
    image: alpine
    volumes:
      - .:/opt/app
    working_dir: /opt/app
makefile
# Makefile
echo:
	docker-compose run --rm alpine sh echo.sh
bash
$ make echo

Languages

Languages like Python, JavaScript, Golang, Ruby, etc can be used as an alternative to shell/bash scripts. The following example uses JavaScript to echo 'Hello, World!'.

pattern-language

js
// helloworld.js
console.log('Hello, World!');
yaml
# docker-compose.yml
version: '3'
services:
  node:
    image: node:alpine
    volumes:
      - .:/opt/app
    working_dir: /opt/app
makefile
# Makefile
echo:
	docker-compose run --rm node node helloworld.js
bash
$ make echo

Task management tool

There are many languages and tools out there to make task implementation easy such as Gulp and Rake. Those tools can easily be integrated to the 3 Musketeers. The following is simply a NodeJS example which echos "Hello, World!" by invoking npm.

pattern-task-tool

json
// package.json

{
  "name": "helloworld",
  "description": "echos 'Hello, World!'",
  "scripts": {
    "echo": "echo 'Hello, World!'"
  },
}
yaml
# docker-compose.yml
version: '3'
services:
  node:
    image: node:alpine
    volumes:
      - .:/opt/app
    working_dir: /opt/app
makefile
# Makefile
echo:
	docker-compose run --rm node npm run echo
bash
$ make echo

Docker

Make calls directly Docker instead of Compose. Everything that is done with Compose can be done with Docker. Using Compose helps to keep the Makefile clean.

pattern-docker

makefile
echo:
	docker run --rm alpine echo 'Hello, World!'
bash
$ make echo

There are situations where calling Docker is required. For instance, if you generate a .env file from a container and that at least one of the Compose services uses .env file, then using the Compose command outputs an error like the following:

ERROR: Couldn't find env file: /github.com/flemay/3musketeers/.env

More details in Environment variables section.

Docker-in-Docker (DinD)

DIND EXPLAINED

Jérôme Petazzoni's excellent blog post on using Docker-in-Docker outlines some of the pros and cons of doing so (and some nasty gotchas you might run into). This 3 Musketeers pattern is about "The socket solution" described in his post.

So far, the patterns are for hosts (environments) that provide access to Make, Docker (and daemon), and Compose. However, there are times when the environment provided is Docker containers with no access to Make, Docker, or Compose. The 3 Musketeers can be applied by using a Docker image that provides Make, Docker (client), and Compose, such as flemay/musketeers, given it has access to the Docker socket which allows Docker (client) to connect to the Docker engine.

pattern-dind

An example is with GitLab CI. It allows you to access Docker within a Docker container. A pipeline configuration would look like the following:

yaml
# .gitlab-ci.yml
image: flemay/musketeers:latest
services:
  - docker:dind
variables:
  DOCKER_HOST: "tcp://docker:2375"

stages:
  - test

test:
  stage: test
  script:
    - make test

Released under the MIT license.