Donate Fork on GitHub Issues



1. Fundamentals

1.1. Concepts

  • Union file system (UFS): allows to overlay multiple file systems appearing as a single system whereby equal folders are merged and equally named files hide their previous versions
  • Image: a portable read-only file system layer optionally stacked on a parent image
  • Dockerfile: used to build an image and declare the command executed in the container
  • Registry: is the place where to push and pull from named / tagged images
  • Container: an instance of an image with a writable file system layer on top, virtual networking, ready to execute a single application
  • Volume: a directory outside the UFS that can be mounted inside containers for persistent and shared data
  • Network: acts as a namespace for containers
  • Service: a flexible number of container replicas running on a cluster of multiple hosts

1.2. Lifecycle

A typical docker workflow:

  • build an image based on a Dockerfile
  • tag and push the image to a registry
  • login to the registry from the runtime environment to pull the image
  • optionally create a volume or two to provide configuration files and hold data that needs to be persisted
  • run a container based on the image
  • stop and start the container if necessary
  • commit the container to turn it into an image (note: makes the image harder to reproduce)
  • in exceptional situations, exec additional commands inside the container
  • to replace a container with an updated version:
    • pull the new image from the registry
    • stop the running container
    • backup your volumes to be prepared for a potential rollback
    • run the newer one by specifying a temporary name
    • if successful, remove the old container and rename the new one accordingly

2. Recipes

2.1. Docker Engine

Show docker disk usage

docker system df

Remove unused data

docker system prune

This prompts for confirmation and will remove: all stopped containers, all volumes not used by at least one container, all networks not used by at least one container and all dangling images

2.1.1. Building Images

Debug image build

  • docker image build shows the IDs of all temporary containers and intermediate images
  • use docker container run -it IMAGE_ID with the ID of the image resulting from the last successful build step and try the next command manually

List all local tags for the same image

docker image ls --no-trunc | grep $(docker image inspect -f {{.Id}} IMAGE:TAG)

2.1.2. Running Containers

Start container and run command inside

docker container run -it ubuntu:14.04 /bin/bash

Start a shell in a running container

docker container exec -it CONTAINER /bin/bash

Start a container as another user

docker container run -u root IMAGE

List all existing containers

docker container ls -a

List running processes inside a container

docker container top CONTAINER

Follow the logs

docker container logs -f --tail=1000 CONTAINER

Stop all running containers

docker container stop $(docker container ls -q)

Remove all stopped containers, except those suffixed ‘-data’:

docker container ls -a -f status=exited | grep -v '\-data *$'| awk '{if(NR>1) print $1}' | xargs -r docker container rm

Remove all stopped containers (warning: removes data-only containers too)

docker container prune

List all images

docker image ls -a

Remove all unused images

docker image prune

Show image history of container

docker image history --no-trunc=true $(docker container inspect -f '{{.Image}}' CONTAINER)

Show file system changes compared to the original image

docker container diff CONTAINER

Backup directory content from container to host directory

docker container run --rm --volumes-from SOURCE_CONTAINER:ro -v $(pwd):/backup alpine \
 tar cvf /backup/backup_$(date +%Y-%m-%d_%H-%M).tar /data

Restore directory content to container from host directory

docker container run --rm --volumes-from TARGET_CONTAINER:ro -v $(pwd):/backup alpine \
 tar xvf /backup/backup.tar

Show names of volumes used by a container

docker container inspect -f '{{ range .Mounts }}{{ .Name }} {{ end }}' CONTAINER

Show names and mount point destinations of volumes used by a container

docker container inspect -f '{{ range .Mounts }}{{ .Name }}:{{ .Destination }} {{ end }}' CONTAINER

Start all paused / stopped containers

  • does not work together with container dependencies

Edit and update a file in a container

docker container cp CONTAINER:FILE /tmp/ && docker container run --name=nano -it --rm -v /tmp:/tmp \
 piegsaj/nano nano /tmp/FILE ; \
cat /tmp/FILE | docker container exec -i CONTAINER sh -c 'cat > FILE' ; \
rm /tmp/FILE

Deploy war file to Apache Tomcat server instantly

docker container run -i -t -p 80:8080 -e WAR_URL=“<>” \

Dump a Postgres database into current directory on the host

echo "postgres_password" | sudo docker container run -i --rm --link db:db -v $PWD:/tmp postgres:8 sh -c ' \
 pg_dump -h ocdb -p $OCDB_PORT_5432_TCP_PORT -U postgres -F tar -v openclinica \
 > /tmp/ocdb_pg_dump_$(date +%Y-%m-%d_%H-%M-%S).tar'

Backup data folder

docker container run --rm --volumes-from oc-data -v $PWD:/tmp piegsaj/openclinica \
 tar cvf /tmp/oc_data_backup_$(date +%Y-%m-%d_%H-%M-%S).tar /tomcat/

Restore volume from data-only container

docker container run --rm --volumes-from oc-data2 -v $pwd:/tmp piegsaj/openclinica \
 tar xvf /tmp/oc_data_backup_*.tar

Get the IP address of a container

docker container inspect -f '{{ .NetworkSettings.IPAddress }}' CONTAINER

2.1.3. Using Volumes

Declare a volume via Dockerfile

RUN mkdir /data && echo "some content" > /data/file && chown -R daemon:daemon /data
VOLUME /data

after the VOLUME directive, its content can not be changed within the Dockerfile

Create an anonymous volume at runtime

docker container run -it -v /data debian /bin/bash

Create a volume at runtime that is bound to a host directory

docker container run --rm -v /tmp:/data debian ls -RAlph /data

Create a named volume and use it

docker volume create --name=test
docker container run --rm -v test:/data alpine sh -c 'echo "Hello named volumes" > /data/hello.txt'
docker container run --rm -v test:/data alpine sh -c 'cat /data/hello.txt'

List the content of a volume

docker container run --rm -v data:/data alpine ls -RAlph /data

Copy a file from host to named volume

echo "debug=true" > test.cnf && \
docker volume create --name=conf && \
docker container run --rm -it -v $(pwd):/src -v conf:/dest alpine cp /src/test.cnf /dest/ && \
rm -f test.cnf && \
docker container run --rm -it -v conf:/data alpine cat /data/test.cnf

Copy content of existing volume to a new named volume

docker volume create --name VOL_B
  • than:
docker container run --rm -v VOL_A:/source/folder:ro -v VOL_B:/target/folder \
 alpine cp -r /source/folder /target

or without the need for an intermediate directory (cp implementations differ):

 docker container run --rm -v VOL_A:/source:ro -v VOL_B:/target debian cp -TR /source /target

List all orphaned volumes

docker volume ls -qf dangling=true

Remove all orphaned volumes

docker volume rm $(docker volume ls -qf dangling=true)

Caution, this also removes named volumes that are currently not mounted by any container!

2.2. Docker Machine

On a local VM

Get the IP address of the virtual machine for access from host

docker-machine ip default

Add persistent environment variable to boot2docker

echo 'echo '\''export ENVTEST="Hello Env!"'\'' > /etc/profile.d/' | \
sudo tee -a /var/lib/boot2docker/profile > /dev/null

and restart with docker-machine restart default

Install additional linux packages in boot2docker

  • create the file /var/lib/boot2docker/ with a content like:
sudo /bin/su - docker -c 'tce-load -wi nano'

Recreate any folders and files on boot2docker startup

  • store folders / files in /var/lib/boot2docker/restore-on-boot and
  • create the file /var/lib/boot2docker/ with a content like:
sudo mkdir -p /var/lib/boot2docker/restore-on-boot && \
sudo rsync -a /var/lib/boot2docker/restore-on-boot/ /

2.3. Dockerfile

Add a periodic health check

HEALTHCHECK --interval=1m --timeout=3s --retries=5 \
 CMD curl -f <http://localhost/> || exit 1

2.4. Logging

Enable log rotation for Docker

  • create the file /etc/logrotate.d/docker and insert:
/var/lib/docker/containers/*/*.log {
  rotate 14
  • check /etc/cron.daily/logrotate and /etc/crontab for general logrotate configuration.

This example will keep all container logs for 14 days.

3. Showcases

3.1. Private Docker Registry

Setup with boot2docker or natively on Linux

printf "\nPulling registry image ...\n" && \
docker image pull registry ; \

printf "\nPreparing registry-cert volume ...\n" && \
docker volume create --name=registry-cert && \
cd /tmp && \
openssl genrsa -out registry.key 4096 && \
openssl req -new -nodes -sha256 -subj '/CN=localhost' -key /tmp/registry.key -out /tmp/registry.csr && \
openssl x509 -req -days 3650 -signkey /tmp/registry.key -in /tmp/registry.csr -out /tmp/registry.pem && \
docker container run --rm -it -v /tmp:/from -v registry-cert:/to --entrypoint sh registry \
 -c 'cp /from/registry.key /to && cp /from/registry.pem /to' && \

printf "\nLetting docker client trust certificate ...\n" && \
if [ -d /var/lib/boot2docker ] ;
    sudo mkdir -p /var/lib/boot2docker/certs && \
    sudo cp /tmp/registry.pem /var/lib/boot2docker/certs
    sudo mkdir -p /etc/docker/certs.d/localhost && \
    sudo cp /tmp/registry.pem /etc/docker/certs.d/localhost
fi && \

printf "\nPreparing registry-auth volume (please change 'reg_user' and 'reg_password') ...\n" && \
docker volume create --name=registry-auth && \
docker container run --rm --entrypoint /bin/sh -v registry-auth:/auth registry \
 -c 'htpasswd -Bbn reg_user reg_password > /auth/htpasswd' && \

printf "\nPreparing registry-data volume ...\n" && \
docker volume create --name=registry-data && \

printf "\nRunning registry container ...\n" && \
docker container run --name registry -h registry -d \
-v registry-data:/var/lib/registry \
-v registry-auth:/auth:ro \
-v registry-cert:/certs:ro \
--restart=always \
-p 5000:5000 \
-e REGISTRY_HTTP_TLS_KEY=/certs/registry.key \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry.pem \
-e REGISTRY_AUTH=htpasswd \

Usage example

docker image pull alpine:latest && \
docker login -u reg_user -p reg_password localhost:5000 && \
docker image tag alpine:latest localhost:5000/alpine:private && \
docker image rm alpine:latest && \
docker image push localhost:5000/alpine:private && \
docker image rm -f localhost:5000/alpine:private && \
docker image pull localhost:5000/alpine:private && \
docker logout localhost:5000 && \
docker image ls | grep alpine && \
printf "Deleting image from registry ...\n" && \
curl -X DELETE -u reg_user:reg_password --insecure \
https://localhost:5000/v2/alpine/manifests/$(docker image ls --digests | grep localhost:5000/alpine | awk '{print $3}')


docker container rm -f registry && \
docker volume rm registry-data registry-cert registry-auth

Further Reading

3.2. Continuous Integration Tool Stack

Setup with docker-machine / boot2docker

  • make a directory called ci in your home directory on your host system and change into it

  • create a file named docker-compose.yml with the following content:

version: "2"


    image: jenkins
      - "8082:8082"
      - "50000:50000"
    restart: always
    env_file: .env
      - "JAVA_OPTS=-Dmail.smtp.starttls.enable=true -Dorg.apache.commons.jelly.tags.fmt.timeZone=Europe/Berlin"
      - "JENKINS_OPTS=--httpPort=8082"
      - jenkins_home:/var/jenkins_home
    image: sonatype/nexus3
      - "8081:8081"
    restart: always
    env_file: .env
      - nexus-data:/nexus-data
    image: sonarqube
      - "9000:9000"
    restart: always
      - .env
      - sonarqube.env
      - SONARQUBE_JDBC_URL=jdbc:postgresql://postgres:5432/sonar
      - sonarqube_conf:/opt/sonarqube/conf
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_bundled-plugins:/opt/sonarqube/lib/bundled-plugins
      - postgres

    image: postgres
      - "5432:5432"
    restart: always
      - .env
      - sonarqube.env
      - POSTGRES_USER=sonar
      - postgresql:/var/lib/postgresql
      - postgresql_data:/var/lib/postgresql/data

  • create a second file named .env that defines a timezone:
  • create a third file named sonarqube.env that holds the database passwords:


  • startup all containers:
docker-compose up -d
  • watch the logs, type docker-compose logs or docker logs -f ci_jenkins_1

  • access the web applications:

    • Jenkins on port 8082
    • Sonatype Nexus on port 8081 and
    • SonarQube on port 9000


  • to remove the tool stack (incl. data), use:
docker-compose down -v

4. Best Practices

Docker Engine

  • docker exec is your friend in development, but should be avoided in a production setup


  • use named volumes to simplify maintenance by separating persistent data from the container and communicating the structure of a project in a more transparent manner


  • always keep environment configuration and secrets out of deployments and images, for example by using environment variables (-e, --env-file)
  • always set the USER statement, otherwise the container will run as root user by default, which maps to the root user of the host machine
  • use ENTRYPOINT and CMD directives together to make container usage more convenient
  • coalesce consecutive RUN directives with && to reduce the costs of a build and to avoid caching of instructions like apt-get update
  • to reduce the size of an image, remove temporary resources in the same RUN statement that produces them (otherwise they are still present in an intermediate layer)
  • use EXPOSE to document all needed ports
  • introduce an additional build Dockerfile for your app, if you have a large set of compile-time dependencies (build container pattern)

5. Additional Material


Feel free to fork this project, send me pull requests, and issues through the project’s GitHub page.