Skip to content

Patterns

In a nutshell, Make calls either Docker or Compose which then runs a Command inside a container. A project does not need to follow only 1 pattern. For instance, make targetA can call Compose and make targetB, Docker.

pattern-overview

In this pattern, Make calls Compose which then runs the Command inside a container.

pattern-compose

Here are some examples illustrating Compose with different commands.

Make calls Compose which then calls another Make target inside a Docker container. This requires the Docker image (that runs the command) to have Make installed.

compose.yml
version: "3"
services:
alpine:
image: alpine
volumes:
- .:/opt/app
working_dir: /opt/app
Makefile
echo:
docker compose run --rm alpine make _echo
_echo:
echo 'Hello, World!'
Terminal window
make echo

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

compose.yml
version: "3"
services:
alpine:
image: alpine
Makefile
echo:
docker compose run --rm alpine sh -c 'echo Hello, World!'
Terminal window
make echo

Make calls Compose which executes a shell/bash command inside a Docker container. The following example uses a shell file that mimics Make:

make.sh
#!/usr/bin/env sh
deps(){
printf "deps\n"
}
test(){
printf "test\n"
}
for target in "$@"
do
case "$target" in
(deps)
deps
;;
(test)
test
;;
(*)
printf "Usage: $0 {deps|test}\n"
exit 2
;;
esac
done
Set executable permission
chmod +x make.sh
compose.yml
version: "3.8"
services:
alpine:
image: alpine
volumes:
- .:/opt/app
working_dir: /opt/app
Makefile
test:
docker compose run --rm alpine make.sh deps test
Terminal window
make test

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

main.go
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
compose.yml
version: "3"
services:
golang:
image: golang:alpine
volumes:
- .:/opt/app
working_dir: /opt/app
Makefile
echo:
docker compose run --rm golang go run main.go
Terminal window
make echo

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.

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

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!'
Terminal window
make echo

There are many articles and videos talking about Docker-in-Docker (DinD) and Docker-outside-of-Docker (DooD) with pros and cons. This section describes a pattern that can be applied to both. A Docker container contains Make, Docker, and Compose which communicates to a Docker daemon, whether it is on a host (DooD) or inside another container (DinD). In such case, an image like flemay/musketeers can be used.

pattern-dind

An example of DooD is the 3 Musketeers demo generated with VHS.

Another example is GitLab CI which allows access to Docker daemon within a Docker container. A pipeline configuration would look like the following:

.gitlab-ci.yml
image: flemay/musketeers:latest
services:
- docker:dind
variables:
DOCKER_HOST: "tcp://docker:2375"
stages:
- test
test:
stage: test
script:
- make test

Some references: