Dockerfile cheat sheet

Omid Asadpour
15 min readJul 12, 2020

--

hello every body , i want to share common commands that i use in my Dockerfiles ! .

this is a quick cheat sheet for Dockerfile commands . in the next article we will learn about Dockerfile’s best practices .

so , let’s get started :

FROM

Usage:

  • FROM <image>
  • FROM <image>:<tag>
  • FROM <image>@<digest>

Information:

  • FROM must be the first non-comment instruction in the Dockerfile.
  • FROM can appear multiple times within a single Dockerfile in order to create multiple images. Simply make a note of the last image ID output by the commit before each new FROM command.
  • The tag or digest values are optional. If you omit either of them, the builder assumes a latest by default. The builder returns an error if it cannot match the tag value.

MAINTAINER

Usage:

  • MAINTAINER <name>

The MAINTAINER instruction allows you to set the Author field of the generated images.

RUN

Usage:

  • RUN <command> (shell form, the command is run in a shell, which by default is /bin/sh -c on Linux or cmd /S /C on Windows)
  • RUN ["<executable>", "<param1>", "<param2>"] (exec form)

Information:

  • The exec form makes it possible to avoid shell string munging, and to RUN commands using a base image that does not contain the specified shell executable.
  • The default shell for the shell form can be changed using the SHELL command.
  • Normal shell processing does not occur when using the exec form. For example, RUN ["echo", "$WORKDIR"] will not do variable substitution on $WORKDIR.

Shell vs. Exec

Both the ENTRYPOINT and CMD instructions support two different forms: the shell form and the exec form. In the example above, I used the shell form which looks like this:

CMD executable param1 param2

When using the shell form, the specified binary is executed with an invocation of the shell using /bin/sh -c. You can see this clearly if you run a container and then look at the docker ps output:

$ docker run -d demo
15bfcddb11b5cde0e230246f45ba6eeb1e6f56edb38a91626ab9c478408cb615
$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED
15bfcddb4312 demo:latest "/bin/sh -c 'ping localhost'" 2 seconds ago

Here I’ve run the “demo” image again and you can see that the command which was executed was /bin/sh -c 'ping localhost'.

This appears to work just fine, but there are some subtle issues that can occur when using the shell form of either the ENTRYPOINT or CMD instruction. If we peek inside our running container and look at the running processes we will see something like this:

$ docker exec 15bfcddb ps -f
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 20:14 ? 00:00:00 /bin/sh -c ping localhost
root 9 1 0 20:14 ? 00:00:00 ping localhost
root 49 0 0 20:15 ? 00:00:00 ps -f

Note how the process running as PID 1 is not our ping command, but is the /bin/sh executable. This can be problematic if we need to send any sort of POSIX signals to the container since /bin/sh won’t forward signals to child processes .

Beyond the PID 1 issue, you may also run into problems with the shell form if you’re building a minimal image which doesn’t even include a shell binary. When Docker is constructing the command to be run it doesn’t check to see if the shell is available inside the container — if you don’t have /bin/sh in your image, the container will simply fail to start.

A better option is to use the exec form of the ENTRYPOINT/CMD instructions which looks like this:

CMD ["executable","param1","param2"]

Note that the content appearing after the CMD instruction in this case is formatted as a JSON array.

When the exec form of the CMD instruction is used the command will be executed without a shell.

Let’s change our Dockerfile from the example above to see this in action:

FROM ubuntu:trusty
CMD ["/bin/ping","localhost"]

Rebuild the image and look at the command that is generated for the running container:

$ docker build -t demo .
[truncated]
$ docker run -d demo
90cd472887807467d699b55efaf2ee5c4c79eb74ed7849fc4d2dbfea31dce441
$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED
90cd47288780 demo:latest "/bin/ping localhost" 4 seconds ago

Now /bin/ping is being run directly without the intervening shell process (and, as a result, will end up as PID 1 inside the container).

Whether you’re using ENTRYPOINT or CMD (or both) the recommendation is to always use the exec form so that’s it’s obvious which command is running as PID 1 inside your container.

CMD

Usage:

  • CMD ["<executable>","<param1>","<param2>"] (exec form, this is the preferred form)
  • CMD ["<param1>","<param2>"] (as default parameters to ENTRYPOINT)
  • CMD <command> <param1> <param2> (shell form)

Information:

  • The main purpose of a CMD is to provide defaults for an executing container. These defaults can include an executable, or they can omit the executable, in which case you must specify an ENTRYPOINT instruction as well.
  • There can only be one CMD instruction in a Dockerfile. If you list more than one CMD then only the last CMD will take effect.
  • If CMD is used to provide default arguments for the ENTRYPOINT instruction, both the CMD and ENTRYPOINT instructions should be specified with the JSON array format.
  • If the user specifies arguments to docker run then they will override the default specified in CMD.
  • Normal shell processing does not occur when using the exec form. For example, CMD ["echo", "$WORKDIR"] will not do variable substitution on $WORKDIR.

ENTRYPOINT

Usage:

  • ENTRYPOINT ["<executable>", "<param1>", "<param2>"] (exec form, preferred)
  • ENTRYPOINT <command> <param1> <param2> (shell form)

Information:

  • Allows you to configure a container that will run as an executable.
  • Command line arguments to docker run <image> will be appended after all elements in an exec form ENTRYPOINT and will override all elements specified using CMD.
  • The shell form prevents any CMD or run command line arguments from being used, but the ENTRYPOINT will start via the shell. This means the executable will not be PID 1 nor will it receive UNIX signals. Prepend exec to get around this drawback.
  • Only the last ENTRYPOINT instruction in the Dockerfile will have an effect.

Notice about some differences between ENTRYPOINT and CMD :

Both ENTRYPOINT and CMD allow you to specify the startup command for an image, but there are subtle differences between them. There are many times where you’ll want to choose one or the other, but they can also be used together. We’ll explore all these scenarios in the sections below.

ENTRYPOINT or CMD

Ultimately, both ENTRYPOINT and CMD give you a way to identify which executable should be run when a container is started from your image. In fact, if you want your image to be runnable (without additional docker run command line arguments) you must specify an ENTRYPOINT or CMD.

Trying to run an image which doesn’t have an ENTRYPOINT or CMD declared will result in an error

$ docker run alpine
FATA[0000] Error response from daemon: No command specified

Many of the Linux distro base images that you find on the Docker Hub will use a shell like /bin/sh or /bin/bash as the the CMD executable. This means that anyone who runs those images will get dropped into an interactive shell by default (assuming, of course, that they used the -i and -t flags with the docker run command). This makes sense for a general-purpose base image, but you will probably want to pick a more specific CMD or ENTRYPOINT for your own images.

The ENTRYPOINT or CMD that you specify in your Dockerfile identify the default executable for your image. However, the user has the option to override either of these values at run time.

For example, let’s say that we have the following Dockerfile

FROM ubuntu:trusty
CMD ping localhost

If we build this image (with tag “demo”) and run it we would see the following output:

$ docker run -t demo
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.051 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.038 ms
^C
--- localhost ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.026/0.032/0.039/0.008 ms

You can see that the ping executable was run automatically when the container was started. However, we can override the default CMD by specifying an argument after the image name when starting the container:

$ docker run demo hostname
6c1573c0d4c0

In this case, hostname was run in place of ping

The default ENTRYPOINT can be similarly overridden but it requires the use of the --entrypoint flag:

$ docker run --entrypoint hostname demo
075a2fa95ab7

Given how much easier it is to override the CMD, the recommendation is use CMD in your Dockerfile when you want the user of your image to have the flexibility to run whichever executable they choose when starting the container. For example, maybe you have a general Ruby image that will start-up an interactive irb session by default (CMD irb) but you also want to give the user the option to run an arbitrary Ruby script (docker run ruby ruby -e 'puts "Hello"')

In contrast, ENTRYPOINT should be used in scenarios where you want the container to behave exclusively as if it were the executable it’s wrapping. That is, when you don’t want or expect the user to override the executable you’ve specified.

There are many situations where it may be convenient to use Docker as portable packaging for a specific executable. Imagine you have a utility implemented as a Python script you need to distribute but don’t want to burden the end-user with installation of the correct interpreter version and dependencies. You could package everything in a Docker image with an ENTRYPOINT referencing your script. Now the user can simply docker run your image and it will behave as if they are running your script directly.

Of course you can achieve this same thing with CMD, but the use of ENTRYPOINT sends a strong message that this container is only intended to run this one command. The utility of ENTRYPOINT will become clearer when i show how you can combine ENTRYPOINT and CMD together .

ENTRYPOINT and CMD

Up to this point, we’ve discussed how to use ENTRYPOINT or CMD to specify your image’s default executable. However, there are some cases where it makes sense to use ENTRYPOINT and CMD together.

Combining ENTRYPOINT and CMD allows you to specify the default executable for your image while also providing default arguments to that executable which may be overridden by the user. Let’s look at an example:

FROM ubuntu:trusty
ENTRYPOINT ["/bin/ping","-c","3"]
CMD ["localhost"]

Let’s build and run this image without any additional docker run arguments:

$ docker build -t ping .
[truncated]
$ docker run ping
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.025 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.038 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.051 ms
--- localhost ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.025/0.038/0.051/0.010 ms
$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED
82df66a2a9f1 ping:latest "/bin/ping -c 3 localhost" 6 seconds ago

Note that the command which was executed is a combination of the ENTRYPOINT and CMD values that were specified in the Dockerfile. When both an ENTRYPOINT and CMD are specified, the CMD string(s) will be appended to the ENTRYPOINT in order to generate the container’s command string. Remember that the CMD value can be easily overridden by supplying one or more arguments to `docker run` after the name of the image. In this case we could direct our ping to a different host by doing something like this:

$ docker run ping docker.io
PING docker.io (162.242.195.84) 56(84) bytes of data.
64 bytes from 162.242.195.84: icmp_seq=1 ttl=61 time=76.7 ms
64 bytes from 162.242.195.84: icmp_seq=2 ttl=61 time=81.5 ms
64 bytes from 162.242.195.84: icmp_seq=3 ttl=61 time=77.8 ms
--- docker.io ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 76.722/78.695/81.533/2.057 ms
$ docker ps -l --no-trunc
CONTAINER ID IMAGE COMMAND CREATED
0d739d5ea4e5 ping:latest "/bin/ping -c 3 docker.io" 51 seconds ago

Running the image starts to feel like running any other executable — you specify the name of the command you want to run followed by the arguments you want to pass to that command.

Note how the -c 3 argument that was included as part of the ENTRYPOINT essentially becomes a "hard-coded" argument for the ping command (the -c flag is used to limit the ping count to the specified number). It's included in each invocation of the image and can't be overridden in the same way as the CMD parameter.

If you want your image to actually do anything when it is run, you should definitely configure some sort of ENTRYPOINT or CMD in you Dockerfile. However, remember that they aren’t mutually exclusive. In many cases you can improve the user experience of your image by using them in combination.

No matter how you use these instructions you should always default to using the exec form.

EXPOSE

Usage:

  • EXPOSE <port> [<port> ...]

Information:

  • Informs Docker that the container listens on the specified network port(s) at runtime.
  • EXPOSE does not make the ports of the container accessible to the host.

ENV

Usage:

  • ENV <key> <value>
  • ENV <key>=<value> [<key>=<value> ...]

Information:

  • The ENV instruction sets the environment variable <key> to the value <value>.
  • The value will be in the environment of all “descendant” Dockerfile commands and can be replaced inline as well.
  • The environment variables set using ENV will persist when a container is run from the resulting image.
  • The first form will set a single variable to a value with the entire string after the first space being treated as the <value> - including characters such as spaces and quotes.

ADD

Usage:

  • ADD <src> [<src> ...] <dest>
  • ADD ["<src>", ... "<dest>"] (this form is required for paths containing whitespace)

Information:

  • Copies new files, directories, or remote file URLs from <src> and adds them to the filesystem of the image at the path <dest>.
  • <src> may contain wildcards and matching will be done using Go’s filepath.Match rules.
  • If <src> is a file or directory, then they must be relative to the source directory that is being built (the context of the build).
  • <dest> is an absolute path, or a path relative to WORKDIR.
  • If <dest> doesn’t exist, it is created along with all missing directories in its path.

COPY

Usage:

  • COPY <src> [<src> ...] <dest>
  • COPY ["<src>", ... "<dest>"] (this form is required for paths containing whitespace)

Information:

  • Copies new files or directories from <src> and adds them to the filesystem of the image at the path <dest>.
  • <src> may contain wildcards and matching will be done using Go’s filepath.Match rules.
  • <src> must be relative to the source directory that is being built (the context of the build).
  • <dest> is an absolute path, or a path relative to WORKDIR.
  • If <dest> doesn’t exist, it is created along with all missing directories in its path.

ADD vs COPY

If you’re not interested in the nuances of ADD and COPY and just want an answer to “which one should I use?”, all you need to know is: use COPY !!!! .

ADD : one of features of ADD is the ability to automatically unpack compressed files. If the <src> argument is a local file in a recognized compression format (tar, gzip, bzip2, etc) then it is unpacked at the specified <dest> in the container's filesystem.

ADD /foo.tar.gz /tmp/

The command above would result in the contents of the foo.tar.gz archive being unpacked into the container’s /tmp directory.

Interestingly, the URL download and archive unpacking features cannot be used together. Any archives copied via URL will NOT be automatically unpacked.

COPY : doesn’t support URLs as a <src> argument so it can't be used to download files from remote locations. Anything that you want to COPY into the container must be present in the local build context.

Also, COPY doesn’t give any special treatment to archives. If you COPY an archive file it will land in the container exactly as it appears in the build context without any attempt to unpack it.

COPY is really just a stripped-down version of ADD that aims to meet the majority of the “copy-files-to-container” use cases without any surprises.

Which to Use?

In case it isn’t obvious by now, the recommendation from the Docker team is to use COPY in almost all cases.

Really, the only reason to use ADD is when you have an archive file that you definitely want to have auto-extracted into the image. Ideally, ADD would be renamed to something like EXTRACT to really drive this point home (again, for backward-compatibility reasons, this is unlikely to happen).

OK, but what about fetching packages from remote URLs, isn’t ADD still useful for that? Technically, yes, but in most cases you’re probably better off RUNning a curl or wget. Consider the following example:

ADD http://package.tar.bz /tmp/
RUN tar -xjf /tmp/package.tar.bz2 \
&& make -C /tmp/package \
&& rm /tmp/package.tar.bz2

Here we have an ADD instruction which retrieves a package from a URL followed by a RUN instruction which unpacks it, builds it and then attempts to clean-up the downloaded archive.

Unfortunately, since the package retrieval and the rm command are in separate image layers we don't actually save any space in our final image

In this case you’re better off doing something like this:

RUN curl http://package.tar.bz2 \
| tar -xjC /tmp/package \
&& make -C /tmp/package

Here we curl the package and pipe it right into the tar command for extraction. This way we aren't left with an archive file on the filesystem that we need to clean-up.

There may still be valid reasons to ADD a remote file to your image, but that should be an explicit decision and not your default choice.

Ultimately, the rule is this: use COPY (unless you’re absolutely sure you need ADD).

VOLUME

Usage:

  • VOLUME ["<path>", ...]
  • VOLUME <path> [<path> ...]

Creates a mount point with the specified name and marks it as holding externally mounted volumes from native host or other containers.

VOLUME

Usage:

  • VOLUME ["<path>", ...]
  • VOLUME <path> [<path> ...]

Creates a mount point with the specified name and marks it as holding externally mounted volumes from native host or other containers.

WORKDIR

Usage:

  • WORKDIR </path/to/workdir>

Information:

  • Sets the working directory for any RUN, CMD, ENTRYPOINT, COPY, and ADD instructions that follow it.
  • It can be used multiple times in the one Dockerfile. If a relative path is provided, it will be relative to the path of the previous WORKDIR instruction.

ARG

Usage:

  • ARG <name>[=<default value>]

Information:

  • Defines a variable that users can pass at build-time to the builder with the docker build command using the --build-arg <varname>=<value> flag.
  • Multiple variables may be defined by specifying ARG multiple times.
  • It is not recommended to use build-time variables for passing secrets like github keys, user credentials, etc. Build-time variable values are visible to any user of the image with the docker history command.
  • Environment variables defined using the ENV instruction always override an ARG instruction of the same name.
  • Docker has a set of predefined ARG variables that you can use without a corresponding ARG instruction in the Dockerfile.
  • HTTP_PROXY and http_proxy
  • HTTPS_PROXY and https_proxy
  • FTP_PROXY and ftp_proxy
  • NO_PROXY and no_proxy

ONBUILD

Usage:

  • ONBUILD <Dockerfile INSTRUCTION>

Information:

  • Adds to the image a trigger instruction to be executed at a later time, when the image is used as the base for another build. The trigger will be executed in the context of the downstream build, as if it had been inserted immediately after the FROM instruction in the downstream Dockerfile.
  • Any build instruction can be registered as a trigger.
  • Triggers are inherited by the “child” build only. In other words, they are not inherited by “grand-children” builds.
  • The ONBUILD instruction may not trigger FROM, MAINTAINER, or ONBUILD instructions.

STOPSIGNAL

Usage:

  • STOPSIGNAL <signal>

The STOPSIGNAL instruction sets the system call signal that will be sent to the container to exit. This signal can be a valid unsigned number that matches a position in the kernel’s syscall table, for instance 9, or a signal name in the format SIGNAME, for instance SIGKILL.

HEALTHCHECK

Usage:

  • HEALTHCHECK [<options>] CMD <command> (check container health by running a command inside the container)
  • HEALTHCHECK NONE (disable any healthcheck inherited from the base image)

Information:

  • Tells Docker how to test a container to check that it is still working
  • Whenever a health check passes, it becomes healthy. After a certain number of consecutive failures, it becomes unhealthy.
  • The <options> that can appear are...
  • --interval=<duration> (default: 30s)
  • --timeout=<duration> (default: 30s)
  • --retries=<number> (default: 3)
  • The health check will first run interval seconds after the container is started, and then again interval seconds after each previous check completes. If a single run of the check takes longer than timeout seconds then the check is considered to have failed. It takes retries consecutive failures of the health check for the container to be considered unhealthy.
  • There can only be one HEALTHCHECK instruction in a Dockerfile. If you list more than one then only the last HEALTHCHECK will take effect.
  • <command> can be either a shell command or an exec JSON array.
  • The command’s exit status indicates the health status of the container.
  • 0: success - the container is healthy and ready for use
  • 1: unhealthy - the container is not working correctly
  • 2: reserved - do not use this exit code
  • The first 4096 bytes of stdout and stderr from the <command> are stored and can be queried with docker inspect.
  • When the health status of a container changes, a health_status event is generated with the new status.

SHELL

Usage:

  • SHELL ["<executable>", "<param1>", "<param2>"]

Information:

  • Allows the default shell used for the shell form of commands to be overridden.
  • Each SHELL instruction overrides all previous SHELL instructions, and affects all subsequent instructions.
  • Allows an alternate shell be used such as zsh, csh, tcsh, powershell, and others.

so , this is our first story about Dockerfile , in the next article I will introduce some best practices to write our own Dockerfile .

hope this article will help you .

stay in touch with me in Linkedin : https://www.linkedin.com/in/omid-asadpoor-423370172/

--

--