9 minutes read

This topic will describe a very common instruction that is bound to be useful when working with Docker. It is the RUN instruction. You'll learn how to apply it in practice and explore its options with examples. In addition to this, you'll learn how to change the default shell that some instructions use to execute commands.

RUN instruction

The Docker RUN instruction allows you to execute commands or a set of commands during an image build time. You can use this instruction to install packages, set environment variables, or make various configurations. You can use it to execute either a single command or multiple commands using the && operator. Let's start by reviewing it with a single command:

FROM ubuntu:22.04

LABEL author=HyperUser

RUN mkdir /tmp/demo

ENTRYPOINT ["ls", "/tmp"]

Just like CMD and ENTRYPOINT instructions, this one also has shell and exec forms. But in general, you'll use the shell form. So, you won't see the exec form in this topic.

Here, you'll create a demo directory inside the /tmp directory and the ls command output will list it inside the /tmp directory. Now, let's try this instruction with two commands. The Dockerfile below will install the iputils-ping package to your image. You can use it to ping any IP address.

FROM ubuntu:22.04

LABEL author=HyperUser

RUN apt-get update -y && apt-get install -y iputils-ping

ENTRYPOINT ["/bin/bash"]

Now, let's check if we can ping the desired IP address using an image based on the above Dockerfile:

$ docker run -it --name hs-ubuntu-v1 ubuntu:v1
root@2cedd094f3e4:/# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=44 time=1.46 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=44 time=1.57 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=44 time=1.46 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=44 time=1.28 ms
64 bytes from 8.8.8.8: icmp_seq=5 ttl=44 time=1.33 ms
64 bytes from 8.8.8.8: icmp_seq=6 ttl=44 time=1.63 ms
^C
--- 8.8.8.8 ping statistics ---
6 packets transmitted, 6 received, 0% packet loss, time 5005ms
rtt min/avg/max/mdev = 1.281/1.456/1.632/0.121 ms
root@2cedd094f3e4:/# exit

If the RUN instruction contains several commands, you can use \ to transfer the text to the next line.

FROM ubuntu:22.04

LABEL author=HyperUser

RUN apt-get update -y \
  && apt-get upgrade -y \
  && apt-get install iputils-ping -y \
  && apt-get install net-tools -y

ENTRYPOINT ["/bin/bash"]

Let's move to the next section and explore the --mount option for the RUN instruction.

RUN instruction mounts

The RUN instruction provides us with the --mount option to create mounts that you can access at the build time to bind files, store cache, etc. This option has several types and each provides a specific feature. Among those types are:

  • bind: binds files or directories to the build container

  • cache: caches directories for compilers and package managers

  • tmpfs: for mounting tmpfs in the build container

  • secret: gives access to secure files

  • ssh: gives access to SSH keys via SSH agents

Let's explore cache type since this feature can have some common use cases. It's used to cache the results of the command on the OS level so that the data isn't downloaded again.

If you want to apply this feature, you must enable the BuildKit engine. It's a newer build engine shipped with new features that increase the performance capabilities of Docker. It's enabled by default on Docker Desktop. But if you want to use it on Linux, you need to enable it yourself.

To build the image with the required mount option, let's apply the DOCKER_BUILDKIT=1 docker build -t ubuntu:v1 . command.

FROM ubuntu:22.04

LABEL author=HyperUser

RUN --mount=type=cache,target=/var/cache/apt/archives \
  apt-get update -y \
  && apt-get upgrade -y \
  && apt-get install iputils-ping -y \
  && apt-get install net-tools -y

ENTRYPOINT ["/bin/bash"]

This configuration can speed up the build process because Docker can use cache from the target directory in case it needs to rebuild the RUN layer in later builds.

SHELL instruction

As you already know, several instructions have shell and exec forms. When applying the shell form, you run commands in the default /bin/sh -c shell for Linux and ["cmd", "/S", "/C"] for Windows. Docker lets us change this default configuration. The SHELL instruction sets the default shell for instructions operating with the shell form. You can use the SHELL ["executable", "parameters"] command to change the default shell. You can use it multiple times but each SHELL instruction overrides the previous one. To see it in action, let's run a container without this instruction on the Dockerfile below.

FROM ubuntu:22.04

LABEL author=HyperUser

# shell form
CMD echo Hello, Students.

This is what you'll see in the COMMAND column when listing containers.

$ docker ps -a --format "table {{.Names}}\\t{{.Command}}" --last 1
NAMES        COMMAND ...
hs-ubuntu-1   "/bin/sh -c 'echo He…"

Now, let's change the shell to /bin/bash.

FROM ubuntu:22.04

LABEL author=HyperUser

SHELL ["/bin/bash", "-c"]

# shell form
CMD echo Hello, Students.

This time the output of the docker ps -a command indicates that the shell has been changed.

$ docker ps -a --format "table {{.Names}}\\t{{.Command}}" --last 1
NAMES        COMMAND ...
hs-ubuntu-2   "/bin/bash -c 'echo He…"

Now, let's look at another Dockerfile shown below.

FROM ubuntu:22.04

LABEL author=HyperUser

RUN echo Hello, World.

SHELL ["/bin/bash", "-c"]

RUN echo Hello, World.

CMD echo Hello, Students.

The first RUN instruction will execute the /bin/sh -c 'Hello, World.' command. After this, Docker will switch to the bash shell and execute /bin/bash -c 'Hello, World.' and /bin/bash -c 'Hello, Students.' commands accordingly.

Conclusion

The RUN instruction is extremely useful when you need to execute commands in a Docker image. You know how to execute single and multiple commands using this instruction. You also learned about the --mount option and its various types. This topic also introduced to you BuildKit which is a build engine necessary to operate with this option. Also, now you know how to work with the SHELL instruction that can set the desired shell to run commands. It will be especially beneficial when you need to operate with a more advanced shell rather than sh.

30 learners liked this piece of theory. 2 didn't like it. What about you?
Report a typo