Recently I have been playing with Docker containers, and I am sure you already know what Docker is. In this post I will describe what I have learnt while using Docker containers and preparing Dockerfiles.
What is Docker?
In a few words: Docker is a software to manage and run Linux containers in which you can deploy an application using Dockerfiles
. The main concept here is divide-et-impera
concept: a Docker container is just like a virtual machine, except that is very lightweight (it is only a container, so it is not virtualizing an entire machine).
How do I run a Docker container?
- Install Docker
docker run container command
(e.g.docker run -ti fedora bash
)
Docker will fetch the container (from DockerHub, a central repo for Docker containers) and run the specified command.
What is a Dockerfile?
A Dockerfile is a set of instructions to prepare a Docker container on your own. It basically declare a base image (like a Linux distribution: for example Fedora, Ubuntu, etc.) and apply modifications on that container (fetch the software you want to run on container, compile it, run, etc). There is a basic set of instruction for Dockerfiles. For all Vagrant users: that’s right, a Dockerfile is just like a Vagrantfile: it states the steps to prepare a machine (except that in this case we are preparing a container).
How do I use a Dockerfile?
- Install Docker
- Download a Dockerfile
- (if applicable): edit any files that will be copied from host to container. This is useful for configuration files if you want to customize your Dockerized application
docker build -t containername .
Docker will reproduce the steps to create the container, using the instructions found on the Dockerfile. After it has finished, you can run the Docker container as specified above.
Docker: from theory to practice
Ok, theory aside. I decided to create a Dockerfile for two applications because:
- the application was not available from the official repos (e.g. alice)
- the version in the official repos is outdated (e.g. bitlbee)
Basically, we will declare two Docker containers in which we fetch our software, customize to our needs and run it inside the container. Both of them will declare a service, and the container will serve as a server for the application (alice/http and bitlbee/irc).
bitlbee Dockerfile
In this case we are using my preferred base image which is Fedora, we customize it to be able to fetch and compile the source code of bitlbee and then proceed to compile it. In this Dockerfile we also ADD
two configuration files from the host to the Dockerfile. Again, we launch the service as daemon
user and expose the 6667/tcp port. The final size of the Docker container image is 359MB.
To use it, connect your IRC client to localhost:6667 (remember to map the correct port, see below).
Tips and caveats
Docker
First of all, some tips I learnt:
- When running a container, it is always best to launch it with a name (it easier to reference the container afterwards):
docker run --name bitlbee_container mbologna/docker/bitlbee
- If you want to detach a container, supply
-d
option when running - You can inspect a running container by attaching to it:
docker exec -ti bitlbee bash
- Remember to clean up docker images and docker containers: show them with
docker images
anddocker ps -a
. Remove them withdocker rmi
anddocker rm
- If you are running Docker containers as a service (like in this example), you should remember to set the option
--restart=always
to make sure that your Docker container is started at boot and whenever it exits abnormally
Everything on the docker container makes it apart from the host machine under all points of view (network, fs, etc.). Thus:
- When using Docker containers (in particular you are running a service inside a Docker container), you can access your container ports by mapping the ports on the containers to ports on your host using the
-p
option:docker run -p 16667:6667 mbologna/docker-bitlbee
(container maps 16667 port on the host machine to 6667 port on the container, so it can be accessed at 16667/tcp on the host machine) - When a container is restarted, everything on the container is reset (speaking of file-system too). In order to write non-volatile files, you should supply
-v
option that declares a volume; as with ports we have seen above, you specify first the directory on host and then the corresponding directory on the container. This is useful for config files (you want to keep them, right?):docker run -v /home/mbologna/docker-bitlee/var/lib/bitlbee:/var/lib/bitlbee mbologna/docker-bitlbee
Dockerfiles
- If you define a
VOLUME
in the Dockerfile:- if user is launching Docker container without specifying a volume,
VOLUME
directory will typically resides under/var/lib/docker/volumes
(you can discover it usingdocker inspect <container>
) - otherwise,
VOLUME
directory will resides on the specified directory using-v
option.
This exposes an issue of permissions on the
VOLUME
directory. I basically solved it bychown
ing twice the volume directory, otherwise either one of the two cases described above wouldn’t have the correct permissions:chown -R daemon:daemon /var/lib/bitlbee* # dup: otherwise it won't be chown'ed when using volumes
VOLUME ["/var/lib/bitlbee"]
chown -R daemon:daemon /var/lib/bitlbee* # dup: otherwise it won't be chown'ed when using volumes - if user is launching Docker container without specifying a volume,
- When a final user
pull
s a container, it basically downloads your container from DockerHub. That’s why we want to minimize Docker container size. How can we do that when preparing a Dockerfile?- Every command you launch on a Dockerfile creates a new (intermediate) Docker container (the final result will be the application of every instruction on top of the instruction above it!) => minimize steps and group commands under
RUN
commands using &&. E.g.:RUN touch /var/run/bitlbee.pid && \
chown daemon:daemon /var/run/bitlbee.pid && \
chown -R daemon:daemon /usr/local/etc/* && \
chown -R daemon:daemon /var/lib/bitlbee* After you compiled/installed your software, be sure to remove it if unnecessary to clean up space:
apt-get clean && \
apt-get autoremove -y --purge make \
rm -fr /var/lib/apt/lists/*
- Every command you launch on a Dockerfile creates a new (intermediate) Docker container (the final result will be the application of every instruction on top of the instruction above it!) => minimize steps and group commands under
Every command you launch on the Docker container is run as root: be sure, before launching your software, to launch it with as minimal privileges as possible Principle of least privilege. For example, I launch
alice
andbitlbee
daemons with thedaemon
user:USER daemon
EXPOSE 8080
CMD ["/home/alice/alice/bin/alice", "-a", "0.0.0.0"]
Contributing
You can pull my Docker containers on DockerHub:
You can browse my Dockerfiles on GitHub:
Future work
Two interesting concepts I came across during my research and I will investigate in the future:
- CoreOS, a Linux distribution in which every application is launched on a separate Docker container
- Kubernetes, an orchestration layer for Docker containers