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:

  • bitlbee: the packaged version was outdated
  • alice: not available in official repos at all

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).

View the Dockerfile on GitHub

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-bitlbee
    
  • Detach with -d. Runs the container in the background.

  • Attach to a running container for debugging:

    docker exec -ti bitlbee bash
    
  • Clean up regularly. List images and containers:

    docker images
    docker ps -a
    

    Remove them with docker rmi and docker rm.

  • Set --restart=always for 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-bitlbee
    

    This maps port 16667 on the host to port 6667 inside the container.

  • Use volumes with -v for 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 RUN instruction 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 VOLUME and permissions carefully. When a VOLUME is declared, Docker creates the mount point before running subsequent commands. If you need correct ownership regardless of whether the user mounts an external volume, chown the directory twice: once before and once after the VOLUME declaration:

    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.