Writing & building a dockerfile
If you have read the two earlier posts in this series, you should know, at least to some extent, what the difference between a container, image and docker-compose file is.
In this post I wish to talk about the
Dockerfile. The Dockerfile is a file which describes the image, it basically tells the docker engine how to build the image.
It contains a set of commands which the engine executes and puts into layers, which is what the final image consists of.
To start off, we can look at a very basic dockerfile.
LABEL maintainer="John Doe <[email protected]>"
ADD . /app
RUN npm install -g gulp \
&& cd /app/src \
&& npm install \
&& cd .. \
&& rm -r /app/src
CMD ['index.js', 'start']
The file above uses the most vital parts of the dockerfile syntax.
FROM indicates which image that the file is derived from. That means that you can use another dockerfile or image, even one you have not written yourself, to extend.
FROM command is always there, there is no way to get away from it! You can use
scratch though, if you wish to have a totally empty docker image to start off with.
LABEL is just a label for the image, image labels can later be viewed through the
docker inspect command. In this case it is used to set a
maintainer label, which consists of a name and a email address to the person maintaining the image. This is not required, but its a common practice.
ENV is used to add environment variables to the image. An environment variable that is defined in the file can be overriden by the
-e flag when running the container but will default to what it is set to in the file. Its also possible to use the defined env variable during build time.
There is another directive which is much used as the
ARG defines a build time variable which does not persist into the container.
It can be changed when the image is built by passing a new value in with a
To add data from the computer building the image, the
COPY directive is used. It takes the local data and copies it into the docker image at a the specified location. In the example image, we copy the whole directory that the docker image is in and put it in the
This is the best way to add data into the image, this way data is added to the image even before a container is built.
This can be useful to move install files or scripts or basically anything over from the repository or computer that the author works from. It can also sometimes be used to build a 100% static image without any outside data connected to it. This is sometimes a very good way to go, if you do not need to persist the data in a local directory on the computer.
RUN directive is used.
RUN is a way for the file to define what the image is supposed to do. All code written after
RUN (until end of line) will be executed in the images shell during build. This is used to install things, build things, well, basically anything you wish to do that is done before the image is used to create a container.
As you might see, mos of the lines in the
RUN command ends with a
\ indicates that the next line is part of the current
RUN command. That way its possible to split it up into multiple lines instead of one long line of text, which could easily be hard to manage when it grows too big. It also means that you do not have to run a whole lot of
RUN commands to split it up into more than one line, which is nice.
WORKDIR is set.
It’s possible to set a workdir multiple times in a dockerfile, each time it will set the shells starting directory and the last one will decide where the container will enter.
/app/src is set at the end, running the container will make the container start up at
/app/src, in the above example, we set it to
/app/dist, which is good, cause in the command above, we removed the
EXPOSE opens up a given port to the network that the container runs. This is very useful way to only expose the most executing ports outside of the network, but still let other containers access the container while they are running. Giving a extra layer of security!
ENTRYPOINT can be written in two forms. As a command:
command a b or as an array (aka exec form) which is the preferred way:
["command", "a", "b"]. This defines a command that will be appended after any command the user of the image sends into the container with the
docker run command.
If you set the entrypoint to
ls and the container is started as
docker run --rm container/name /app, the
/app directory will be printed out in the console as the final command will be
ls /app. The entrypoint can be overriden when running the container, which could be useful to for example enter the containers shell instead of executing a command.
ENTRYPOINT are often used together. In our case, we use the entrypoint to define which executable (node) we want to run and we use the
CMD to define which commands to pass in to the executable (in this case index.js and start).
If using a entrypoint and a cmd together as above, the
CMD should be written in exec form (
["command", "command"]) but if acting on its own, the “shell form” (command) can be used (
node index.js start).
If you define more than one
CMD in the file, the last one will be used and only the last one. Only one
CMD will be executed and it will only be executed if no command is sent in through the
docker run command. If any command is passed, the container will use that instead of
CMD. So in the above example, we could just as well pass in another file when we run the container:
docker run --rm container/name notindex.js and it would run the
node executable (due to the entrypoint) on the
notindex.js file instead.
CMD could easily be mistaken for the
RUN command, as cmd is a short form of
Command, but the
RUN command is used to tell the image what to do during build, while the
CMD command is used to tell the container what to do when it starts.
So, when we are ready to build the image, we just fire:
docker build -t myname/imagename and it will start the build and tag the image with
myname/imagename making it easy to run by
docker run --rm myname/imagename.
Publishing docker images
When we have our docker image built and ready we might wish to push it up to the docker hub registery (or another maybe?), this is easily done by logging in to docker hub through the terminal:
docker login [optional registry url if not docker hub, which else is default]
Write username and password. OBSERVE that the login asks for email, but it actually expects the username!
When we are logged in, we can push the image by using the
docker push command:
docker push myname/imagename:tag
myname is your docker hub username and
imagename is the name of the image you are pushing, the
tag is an optional tag, like
latest or something similar.
There are ways to automatically build images from repositories on docker hub, but I have decided to not go through this in this post, just check the docs and it should be quite easy to figure out!