Updated 2026: Docker has matured significantly since 2015. Many of the patterns here are still valid, but the ecosystem has evolved: multi-stage builds are now the standard way to reduce image size, BuildKit is the default builder, and Docker Compose (v2) has replaced standalone
docker-compose. The concepts below remain a useful foundation.
I’ve been spending time with Docker containers, building Dockerfiles for a couple of real applications. Here’s what I learned.
What is Docker? Link to heading
Docker runs Linux containers: lightweight, isolated environments for deploying applications. Unlike a full virtual machine, a container shares the host kernel, so it starts fast and uses minimal resources. You describe what goes inside using a Dockerfile.
Running a container Link to heading
docker run -ti fedora bash
Docker pulls the image from Docker Hub if it’s not cached locally, then runs the specified command inside the container.
What is a Dockerfile? Link to heading
A Dockerfile is a script that builds a container image. It starts from a base image (such as a Linux distribution), applies a sequence of instructions (installing packages, copying files, setting environment variables) and produces a final image you can run anywhere.
Building from a Dockerfile Link to heading
docker build -t myapp .
Docker executes each instruction in order, creating an intermediate layer for each one. The final image is the cumulative result.
From theory to practice Link to heading
I built Dockerfiles for two applications:
Both run as persistent services inside containers, exposing a network port to the host.
The bitlbee Dockerfile Link to heading
The image starts from Fedora, installs build dependencies, compiles bitlbee from source, copies in two configuration files, drops privileges to the daemon user, and exposes port 6667/tcp. The final image came out to 359 MB.
To use it, connect your IRC client to localhost:6667 (with the port mapping described below).
Tips and caveats Link to heading
Running containers Link to heading
Give containers names. It’s much easier to reference them later:
docker run --name bitlbee_container mbologna/docker-bitlbeeDetach with
-d. Runs the container in the background.Attach to a running container for debugging:
docker exec -ti bitlbee bashClean up regularly. List images and containers:
docker images docker ps -aRemove them with
docker rmianddocker rm.Set
--restart=alwaysfor service containers so they restart on boot and on failure.Map ports with
-p. Containers are isolated from the host network by default:docker run -p 16667:6667 mbologna/docker-bitlbeeThis maps port 16667 on the host to port 6667 inside the container.
Use volumes with
-vfor persistent data. Container filesystems reset on restart:docker run -v /home/mbologna/bitlbee/var/lib/bitlbee:/var/lib/bitlbee mbologna/docker-bitlbee
Writing Dockerfiles Link to heading
Minimize layers. Each
RUNinstruction creates a new layer. Chain related commands with&&to keep the layer count low: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*Clean up after installing. Remove build tools and package caches to reduce image size:
RUN apt-get clean && \ apt-get autoremove -y --purge make && \ rm -rf /var/lib/apt/lists/*Handle
VOLUMEand permissions carefully. When aVOLUMEis declared, Docker creates the mount point before running subsequent commands. If you need correct ownership regardless of whether the user mounts an external volume,chownthe directory twice: once before and once after theVOLUMEdeclaration:RUN chown -R daemon:daemon /var/lib/bitlbee* VOLUME ["/var/lib/bitlbee"] RUN chown -R daemon:daemon /var/lib/bitlbee*Drop privileges before
CMD. Everything in a Dockerfile runs as root by default. Always switch to a lower-privileged user before launching your application (principle of least privilege):USER daemon EXPOSE 8080 CMD ["/home/alice/alice/bin/alice", "-a", "0.0.0.0"]
The images Link to heading
Pull from Docker Hub:
Browse the source on GitHub:
Updated 2026: CoreOS Container Linux (mentioned below as future work) reached end-of-life in May 2020. Its successor for container-focused Linux is Flatcar Container Linux. Kubernetes, on the other hand, has become the de facto standard for container orchestration, no longer “future work” for most production setups.