Building and Pushing Images using Docker and Makefiles

Walter Code
5 min readMar 23, 2023
Building and Pushing Images (illustrated)

In our last blog post, we covered Building and Orchestrating Containers with Docker Compose, and now we’re continuing the knowledge-sharing by discussing Docker and Makefiles!

We love this practice of sharing some of the topics we mentioned in our internal workshops, hoping that they bring value to some of you.

In this article, we will be covering the basics of how Makefiles work, and how they optimize and accelerate our work in Docker. Again, our colleague Rade Mihajlović did his part in helping us get to the bottom of it.

Makefiles

Makefiles are text files that contain a set of rules and dependencies for building and managing software projects. They are used by the Unix tool called Make, a build automation tool for compiling code, generating documentation, and performing other tasks related to software development. In essence, Makefiles are a special format file with rules that tell Make how to execute commands that run on Unix.

Makefiles consist of a set of rules that describe how to build specific targets (such as executable files, libraries, or documentation) from source files. Each rule specifies a target, the dependencies required to build the target, and the commands to be executed to build the target.

Makefile is commonly used to compile C or C++, but it is NOT limited to any particular programming language.

You can use Make for all sorts of stuff:

  • Execute a chain of commands to set up your dev environment
  • Automate build
  • Run test suites
  • Deployments, etc.

Makefile Rules

Basic Makefile rules are set, and they generally look like this:

# run `make <target>` to run this rule

<target>: <prerequisite 1> <prerequisite 2> <prerequisite N>

<command 1>

<command 2>

<command N>

where:

  • <target> — name of an action
  • <command> — commands/steps used to make the <target>. These MUST start with a TAB character
  • <prerequisite> — optional. This tells Make that each prerequisite must exist before the commands are run. Therefore, prerequisites run in order of 1 to N (in the example above).

Makefile Variables

A variable is a name defined in a makefile to represent a string of text known as the variable’s value. These values are explicitly requested to be substituted into targets, prerequisites, recipes, and other areas of the makefile.

To declare a Makefile variable, we use =, := or ?= syntax.

The differences between them are the use cases, and so we use:

:= (simply expanded variables ) — used to execute a statement once

= (recursively expanded variables)—used to execute a statement every time

?=(conditional variable assignment operator) — set the Makefile variable only if it’s not set or doesn’t have a value

And, to reference the variables, we use either ${} or $().

Now that we’ve explained the basics of Makefiles, let’s see how we would run the Makefiles.

You don’t tend to execute the make file itself, rather you execute make, giving it the make file as an argument:

  • make
  • make –f filename — if the name of your file is not “makefile” or “Makefile”
  • make target_name — if you want to make a target that is not the first one

Using Makefiles to build and push Docker images

Here is an example Makefile that builds and pushes a Docker image:

In this Makefile, we define two targets — build and push. The build target executes the docker build command to build the Docker image, and the push target executes the docker push command to push the image to a Docker registry.

To build the Docker image, we can execute the following command:

make build

To push the Docker image, we can execute the following command:

make push

How do Makefiles help?

Using a Makefile provides several benefits when building and pushing Docker images.

Firstly, Makefiles enable automation of the build and push process. Instead of executing each command separately, we can execute a single command to build and push the image. This saves time and reduces the risk of human error.

Secondly, Makefiles enable easy management of dependencies between images. For example, if we have multiple images that depend on each other, we can use a Makefile to ensure that the dependencies are built and pushed in the correct order.

Here is an example Makefile that demonstrates this:

In this Makefile, we define three targets — “database”, “web”, and “all”. The “database” and “web” targets execute the Makefile in the corresponding directories to build and push the images. The “all” target specifies that the database and web targets should be executed, and ensures that the dependencies between the images are handled correctly.

Finally, using Makefiles makes the build and push process more portable. By specifying the build and push steps in a Makefile, we can easily share the file with others and ensure that the build and push process is consistent across different environments.

In summary, using Makefiles when building and pushing Docker images provides several benefits, including automation of the build and push process, management of dependencies between images, and portability of the build and push process.

By defining targets and dependencies in a Makefile, we can easily build and push Docker images with a single command, manage dependencies between images, and ensure that the build and push process is consistent across different environments.

The benefits that using Makefiles brings are evident, it’s up to you to choose to optimize your work. Additionally, if you want to learn more about Makefiles, here’s another source of information.

In our future blog posts, we’ll see how Kubernetes stacks up against Docker, so stay tuned!

--

--