mail

docker-compose.yml directives

ports
expose ports :
ports:
  - "hostPort:containerPort"
mail

How to instantiate an image (aka start a container) ?

initial status

  • docker ps -a
    CONTAINER ID		IMAGE		COMMAND		CREATED		STATUS		PORTS		NAMES
    no running container
  • docker images
    REPOSITORY	TAG			IMAGE ID	CREATED		SIZE
    tomcat		latest			142fe91d8add	19 minutes ago	723MB
    tomcat		9.0.39-jdk11-openjdk	2703bbe9e9d4	6 days ago	648MB

create + start a container

  • docker run 142fe91d8add
    (starts, pretty verbose, does not give the shell prompt back)
  • in another shell :
    docker ps
    CONTAINER ID	IMAGE		COMMAND			CREATED		STATUS		PORTS			NAMES
    8e0f14c049b5	142fe91d8add	"catalina.sh run"	46 seconds ago	Up 44 seconds	8009/tcp, 8080/tcp	amazing_satoshi

stop it

  • docker stop 8e0f14c049b5
    8e0f14c049b5
  • docker ps
    CONTAINER ID		IMAGE		COMMAND		CREATED		STATUS		PORTS		NAMES
    not running anymore
    I also get the shell prompt back at this moment in the terminal I used to start the container.
  • but the container is still there :
    docker ps -a
    CONTAINER ID	IMAGE		COMMAND			CREATED		STATUS					PORTS		NAMES
    8e0f14c049b5	142fe91d8add	"catalina.sh run"	4 minutes ago	Exited (143) About a minute ago				amazing_satoshi
    

restart it

  • docker start 8e0f14c049b5
    8e0f14c049b5
    and this time I get the shell prompt back !
  • docker ps
    CONTAINER ID	IMAGE		COMMAND			CREATED		STATUS		PORTS			NAMES
    8e0f14c049b5	142fe91d8add	"catalina.sh run"	6 minutes ago	Up 17 seconds	8009/tcp, 8080/tcp	amazing_satoshi

stop it again + delete it

  • docker stop 8e0f14c049b5
    8e0f14c049b5
  • docker rm 8e0f14c049b5
    8e0f14c049b5
  • docker ps -a
    CONTAINER ID		IMAGE		COMMAND		CREATED		STATUS		PORTS		NAMES
    it's gone!

start a named container

  • docker run --name myContainer 142fe91d8add
    (starts and doesn't give the shell prompt back)
  • in another shell :
    docker ps
    CONTAINER ID	IMAGE		COMMAND			CREATED		STATUS		PORTS			NAMES
    31316d067d12	142fe91d8add	"catalina.sh run"	35 seconds ago	Up 33 seconds	8009/tcp, 8080/tcp	myContainer

log into a running container

See my dedicated article
mail

Dockerfile directives

ARG
  • defines a variable that users can pass at build-time (example) :
    • Dockerfile :
      
      ARG myVariable
      
    • build command :
      docker build --build-arg myVariable=value
  • it is possible to define an optional default value, which will be used when no value is provided at build-time :
    ARG MYVARIABLE=42
  • the variable name is often written uppercase, but I've found no rule enforcing this.
  • the defined variable lives (details) :
    • from the line it is defined (it can overwrite an existing variable)
    • until the end of the build
EXPOSE
  • informs Docker that the container listens on the specified network ports at runtime
  • you can specify whether the port listens on TCP (default) or UDP
  • EXPOSE :
    • does not actually publish the port
    • is just a kind of "documentation" between the person who builds the image and the person who runs the container about which ports are intended to be published
    To actually publish the port(s), use the docker run -P / -p flags
mail

Docker and AppArmor

Looks like both would fit pretty well with each other. So what's the problem ?

Containers are NOT VMs : the kernel is shared between the container and its host.
This means :
  • kernel options enabled on the host are/may be enabled on the container as well
  • you can not enable a kernel option in a container if it's not already enabled in the host
Generally speaking, fiddling with kernel options in the context of containers goes beyond what containers are for.
mail

Docker Compose

Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application's services. Then, with a single command, you create and start all the services from your configuration.

Setup :

  1. make sure you have installed the Docker engine
  2. As root :
    • find the latest Docker Compose version number
    • edit and run :
      dockerComposeVersion='1.27.4'; localDockerComposeBinary='/usr/local/bin/docker-compose'; curl -L "https://github.com/docker/compose/releases/download/$dockerComposeVersion/docker-compose-$(uname -s)-$(uname -m)" -o "$localDockerComposeBinary" && chmod +x "$localDockerComposeBinary"; curl -L https://raw.githubusercontent.com/docker/compose/dockerComposeVersion/contrib/completion/bash/docker-compose -o /etc/bash_completion.d/docker-compose
    • if you plan to run Docker Compose as a non-root user :
      chmod 755 "$localDockerComposeBinary"
  3. check installation :
    docker-compose --version
    docker-compose version 1.27.4, build 40524192

Let's build things with it (source) :

Target : have 4 containers (based on this) : 2 web and 2 DB. We'll use these with Ansible later.

Our working environment (source) :

workDir='./dockerCompose'; mkdir -p "$workDir" && cd "$workDir"

The Dockerfile : ./workDir/Dockerfile (source) :

We'll actually re-use this one.

The Compose file : ./workDir/docker-compose.yml (source) :

version: '3.7'
services:

  ubuntu_web1:
    hostname: web1
    build: .				this service uses the image built from the Dockerfile found in this directory (details)
    container_name: c_ubuntu_web1
    tty: true

  ubuntu_web2:
    hostname: web2
    build: .
    container_name: c_ubuntu_web2
    tty: true

  ubuntu_db1:
    hostname: db1
    build: .
    container_name: c_ubuntu_db1
    tty: true

  ubuntu_db2:
    hostname: db2
    build: .
    container_name: c_ubuntu_db2
    tty: true

Build and run (source) :

docker-compose up
These 11 steps are actually repeated for each configured service :
Building ubuntu_db2
Step 1/11 : FROM ubuntu:16.04
 ---> 5e13f8dd4c1a
Step 2/11 : ARG userName='ansible'
 ---> Using cache
 ---> 7f163145edfd
Step 3/11 : ARG homeDir="/home/$userName"
 ---> Using cache
 ---> 9025b899c1df
Step 4/11 : ARG sshDir="$homeDir/.ssh"
 ---> Using cache
 ---> b32d4b7d1abf
Step 5/11 : ARG authorizedKeysFile="$sshDir/authorized_keys"
 ---> Using cache
 ---> 8477ab5bcac4
Step 6/11 : ARG publicSshKey='./ansible.pub'
 ---> Using cache
 ---> 5b2235eb9fbf
Step 7/11 : RUN apt-get update &&       apt-get install -y iproute2 iputils-ping openssh-server &&      apt-get clean &&        useradd -d "$homeDir" -s /bin/bash -m "$userName" &&    mkdir -p "$sshDir"
 ---> Using cache
 ---> 41cfeaaa7dda
Step 8/11 : COPY "$publicSshKey" "$authorizedKeysFile"		this step doesn't like it when source files are symlinks
 ---> Using cache
 ---> 3dcab2010a2b
Step 9/11 : RUN chown -R "$userName":"$userName" "$sshDir" &&   chmod 700 "$sshDir" &&  chmod 600 "$authorizedKeysFile"
 ---> Using cache
 ---> 1f511354cd72
Step 10/11 : EXPOSE 22
 ---> Using cache
 ---> deb5199624a4
Step 11/11 : CMD [ "sh", "-c", "service ssh start; bash"]
 ---> Using cache
 ---> 3bb266505c4c

Successfully built 3bb266505c4c
Successfully tagged dockercompose_ubuntu_db2:latest
WARNING: Image for service ubuntu_db2 was built because it did not already exist. To rebuild this image you must use docker-compose build or docker-compose up --build.
Then :
Creating c_ubuntu_web1 ... done
Creating c_ubuntu_db1  ... done
Creating c_ubuntu_web2 ... done
Creating c_ubuntu_db2  ... done
Attaching to c_ubuntu_db1, c_ubuntu_web1, c_ubuntu_web2, c_ubuntu_db2
c_ubuntu_db1   |  * Starting OpenBSD Secure Shell server sshd            [ OK ]
c_ubuntu_web1  |  * Starting OpenBSD Secure Shell server sshd            [ OK ]
c_ubuntu_web2  |  * Starting OpenBSD Secure Shell server sshd            [ OK ]
c_ubuntu_db2   |  * Starting OpenBSD Secure Shell server sshd            [ OK ]
(No prompt given at that time, so Ctrl-z + bg to get it back)
docker ps
CONTAINER ID        IMAGE                       COMMAND                  CREATED             STATUS              PORTS               NAMES
76f413236d70        dockercompose_ubuntu_db2    "sh -c 'service ssh …"   3 minutes ago      Up 3 minutes       22/tcp              c_ubuntu_db2
240e1505313e        dockercompose_ubuntu_web2   "sh -c 'service ssh …"   3 minutes ago      Up 3 minutes       22/tcp              c_ubuntu_web2
b5b6887d2b02        dockercompose_ubuntu_db1    "sh -c 'service ssh …"   3 minutes ago      Up 3 minutes       22/tcp              c_ubuntu_db1
95f988a02ff2        dockercompose_ubuntu_web1   "sh -c 'service ssh …"   3 minutes ago      Up 3 minutes       22/tcp              c_ubuntu_web1
docker image ls
REPOSITORY                  TAG                 IMAGE ID            CREATED            SIZE
dockercompose_ubuntu_db1    latest              3bb266505c4c        4 minutes ago      205MB
dockercompose_ubuntu_db2    latest              3bb266505c4c        4 minutes ago      205MB
dockercompose_ubuntu_web1   latest              3bb266505c4c        4 minutes ago      205MB
dockercompose_ubuntu_web2   latest              3bb266505c4c        4 minutes ago      205MB
Maybe it was not necessary to build 4 distinct images that are actually identical...

Further steps :

  • To stop them all :
    docker-compose stop
  • To remove all containers and all images (source) :
    docker rm $(docker ps -a -q) && docker rmi $(docker images -q) --force
    --force will remove everything, including images that may not be related with your current work.
  • You may now alter the Dockerfile or the Compose file and rebuild :
    docker-compose up --build
  • To start everything again AND still get the shell prompt back :
    docker-compose up -d
    Creating c_ubuntu_db1  ... done
    Creating c_ubuntu_web1 ... done
    Creating c_ubuntu_web2 ... done
    Creating c_ubuntu_db2  ... done

Now let's use this "virtualized infrastructure" :

mail

How to get a container's IP address ?

mail

How to simulate a VM with a container ?

  • This may not be the best —or even a good— approach : Docker newbie here. Feel free to send feedback or advice using the envelope on the top-right corner of this article .
  • Everything below is made without security in mind, this shouldn't go further than dev/testing environments.

Target : an Ubuntu machine on which I can log in via ssh to play with Ansible.

There's an advanced version of this procedure describing the setup with SSH keys.

Build a custom Docker image (source) :

  1. cd my/working/directory; touch Dockerfile
  2. edit Dockerfile :
    FROM ubuntu:16.04
    
    RUN apt-get update && \
    	apt-get install -y iproute2 iputils-ping openssh-server && \
    	apt-get clean && \
    	useradd -d /home/ansible -s /bin/bash -m ansible && \
    	echo ansible:elbisna | chpasswd
    
    EXPOSE 22
    
    CMD [ "sh", "-c", "service ssh start; bash"]
  3. build the image (source) :
    • docker build -t imagename .
      imagename must be all lowercase
    • docker build -t myubuntu .
      Sending build context to Docker daemon   2.56kB
      Step 1/4 : FROM ubuntu:16.04
       ---> 5e13f8dd4c1a
      Step 2/4 : RUN apt-get update &&     apt-get install -y iproute2 iputils-ping openssh-server &&     apt-get clean &&     useradd -d /home/ansible -s /bin/bash -m ansible &&    echo ansible:elbisna | chpasswd
       ---> Running in 9f0ef385f89b
      Get:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]
      Get:2 http://security.ubuntu.com/ubuntu xenial-security InRelease [109 kB]
      ...
      ...
      ...
      Step 4/4 : CMD [ "sh", "-c", "service ssh start; bash"]
       ---> Running in a377195f387b
      Removing intermediate container a377195f387b
       ---> 65887834663f
      Successfully built 65887834663f
      Successfully tagged myubuntu:latest
  4. check it :
    docker image ls
    REPOSITORY	TAG	IMAGE ID	CREATED		SIZE
    myubuntu	latest	65887834663f	2 minutes ago	205MB

Run this image (i.e. create a container) :

A few things I learnt while experimenting this part :

  • You can't create an image with an already running process (such as sshd) since an image is just a static file. Instead, you'll have to "instantiate" that image (i.e. create a container) and start a process within this container.
  • You could do so with :
    docker run [options] imagename service ssh start
    but ...
  • A Docker container exits when its main process finishes (details). So if you're running something like :
    docker run [options] imagename command
    the container will start, run command and exit. (This is why I used this hack)

Actually run the image :

  1. create and start a container :
    docker run -dit --name myubuntu1 myubuntu
    docker run -dit --name myubuntu1 --hostname hostname myubuntu
    a914ca30d7181e7d39a272f633a8e180151c2b441845d0eae882bd7d8b16fdff
  2. log into it :
    docker attach myubuntu1
    root@a914ca30d718:/#
    root@hostname:/#
  3. get the container's IP address (there is a simpler solution) :
    root@a914ca30d718:/# ip a
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    	link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    	inet 127.0.0.1/8 scope host lo
    	   valid_lft forever preferred_lft forever
    141: eth0@if142: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    	link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    	inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
    	   valid_lft forever preferred_lft forever
  4. make sure sshd is running :
    root@a914ca30d718:/# service ssh status
     * sshd is running
    root@a914ca30d718:/# ss -punta
    Netid	State	Recv-Q	Send-Q	Local Address:Port	Peer Address:Port
    tcp	LISTEN	0	128		    *:22		   *:*		users:(("sshd",pid=31,fd=3))
    tcp	LISTEN	0	128		   :::22		  :::*		users:(("sshd",pid=31,fd=4))
  5. disconnect from the container while keeping it alive : as seen above, ending the main process of the container also "exits" the container. So, once logged in, exit (or any equivalent shortcut such as Ctrl-d) ends Bash, which ends the container.
    • Do : Ctrl-p-q (source)
      root@a914ca30d718:/# read escape sequence
      then back to your system prompt
    • If you did the wrong thing () :
      1. see the mess you've created :
        docker ps -a
        CONTAINER ID	IMAGE		COMMAND			CREATED		STATUS				PORTS	NAMES
        a914ca30d718	myubuntu	"sh -c 'service ssh …"	43 minutes ago	Exited (0) 6 seconds ago		myubuntu1
      2. start it again :
        docker start myubuntu1
        myubuntu1
      3. docker ps
        CONTAINER ID	IMAGE		COMMAND			CREATED		STATUS		PORTS	NAMES
        a914ca30d718	myubuntu	"sh -c 'service ssh …"	45 minutes ago	Up 24 seconds	22/tcp	myubuntu1
      4. re-attach to it :
        docker attach myubuntu1
        root@a914ca30d718:/#
      5. you can now leave nicely
  6. connect via SSH into the container :
    ssh ansible@172.17.0.3
    ansible@a914ca30d718:~$
    This time, since you're connecting through SSH, you can leave with exit.

Advanced version : let's use SSH keys rather than passwords :

Since the general procedure is pretty similar, I won't give full details. Please refer to the steps above.
  1. edit Dockerfile :
    FROM ubuntu:16.04
    
    ARG userName='ansible'
    ARG homeDir="/home/$userName"
    ARG sshDir="$homeDir/.ssh"
    ARG authorizedKeysFile="$sshDir/authorized_keys"
    ARG publicSshKey='./ansible.pub'
    
    RUN apt-get update && \
    	apt-get install -y iproute2 iputils-ping openssh-server && \
    	apt-get clean && \
    	useradd -d "$homeDir" -s /bin/bash -m "$userName" && \
    	mkdir -p "$sshDir"
    
    COPY "$publicSshKey" "$authorizedKeysFile"
    
    RUN chown -R "$userName":"$userName" "$sshDir" && \
    	chmod 700 "$sshDir" && \
    	chmod 600 "$authorizedKeysFile"
    
    EXPOSE 22
    
    CMD [ "sh", "-c", "service ssh start; bash"]
  2. build the image
  3. start a container
  4. log in via SSH :
    ssh -i ansible ansible@$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' myubuntu1)
mail

Install Docker CE (Community Edition) on Debian

Procedure (source) :

As root :
  1. apt update && apt upgrade
  2. The official install procedure lists packages that are dummy / transitional. The Debian packages site suggests replacements for some of these, as shown in the command below.
  3. Add Docker's official GPG key:
    curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
    OK
    Check :
    apt-key fingerprint 0EBFCD88
    pub   rsa4096 2017-02-22 [SCEA]
          9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
    uid           [ unknown] Docker Release (CE deb) <docker@docker.com>
    sub   rsa4096 2017-02-22 [S]
  4. Add the stable repository :
    add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"
  5. install Docker Engine :
    apt update && apt install docker-ce docker-ce-cli containerd.io
  6. At this step :
    • Docker Engine is installed and running.
    • The docker group is created but still empty.
    • You'll need to use sudo to run Docker commands.
    • You may configure this (and other things) right now by jumping to the postinstall steps or continue and check your installation below.
  7. check installation :
    docker run hello-world
    • if you get this error message :
      docker: Error response from daemon: Get https://registry-1.docker.io/v2/: proxyconnect tcp: dial tcp 127.0.0.1:8080: connect: connection refused
      read this article
    • otherwise, you should get :
      Unable to find image 'hello-world:latest' locally		you can safely ignore this line since it's the 1st launch
      latest: Pulling from library/hello-world
      0e03bdcc26d7: Pull complete
      Digest: sha256:8c5aeeb6a5f3ba4883347d3747a7249f491766ca1caa47e5da5dfcf6b9b717c0
      Status: Downloaded newer image for hello-world:latest
      
      Hello from Docker!
      This message shows that your installation appears to be working correctly.		
      
      To generate this message, Docker took the following steps:
       1. The Docker client contacted the Docker daemon.
       2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
          (amd64)
       3. The Docker daemon created a new container from that image which runs the
          executable that produces the output you are currently reading.
       4. The Docker daemon streamed that output to the Docker client, which sent it
          to your terminal.
      
      

Post-install steps (source) :

  1. As root (this is the For the impatients version. See the source documentation for details) :
    dockerGroupName='docker'; nonRootUser='bob'; getent group "$dockerGroupName" || groupapp "$dockerGroupName"; usermod -aG "$dockerGroupName" "$nonRootUser"; echo "User '$nonRootUser' must log out+in for changes to take effect."
  2. check installation, as the non-root user defined above :
    docker run hello-world
    Hello from Docker!
    This message shows that your installation appears to be working correctly.
    
    
mail

How to log (i.e. open a shell) into a container ?

All methods below describe root login to a container.

  • assuming there is only one running container :
    docker exec -it $(docker ps | awk '!/^CONTAINER ID/ {print $1}') bash
  • referring to the last created container :
    docker exec -it $(docker ps -q -l) bash
  • referring to the container by name :
    1. Add to ~/.bash_aliases :
      # "Log As root In Container"
      laric() {
      	containerName=$1
      	[ -z "$containerName" ] && {
      		cat <<-EOF
      			No container name specified. Must be one of :
      			$(docker ps | awk '!/NAMES/ {print "\t"$NF}' | sort)
      			EOF
      		return
      		}
      	containerId=$(docker ps | awk -v containerName="$containerName" '$NF==containerName {print $1}')
      	docker exec -it "$containerId" bash
      	}
    2. reload aliases :
      source ~/.bash_aliases
    3. log into myContainer :
      laric myContainer

Detailed procedure :

  1. get the container ID :
    docker ps
    CONTAINER ID	IMAGE				COMMAND			CREATED		STATUS			PORTS		NAMES
    4bac82b3ba41	molecule_local/ubuntu:18.04	"bash -c 'while true…"	28 minutes ago	Up 28 minutes				ubuntu1804
  2. run a single command inside the container :
    docker exec -it 4bac82b3ba41 hostname
    ubuntu1804
  3. to actually log into the container, just run a shell :
    docker exec -it 4bac82b3ba41 bash
    root@ubuntu1804:/# 
mail

How to start / stop / restart / pause / ... a container ?

  1. list running containers
    docker ps
    CONTAINER ID	IMAGE				COMMAND			CREATED		STATUS			PORTS		NAMES
    4bac82b3ba41	molecule_local/ubuntu:18.04	"bash -c 'while true…"	28 minutes ago	Up 28 minutes				ubuntu1804
    51a02953b18a	molecule_local/centos:7		"bash -c 'while true…"	4 hours ago	Up 4 hours				instance
  2. pause a container
    docker pause 51a02953b18a
    51a02953b18a
    docker ps
    CONTAINER ID	IMAGE				COMMAND			CREATED		STATUS			PORTS		NAMES
    4bac82b3ba41	molecule_local/ubuntu:18.04	"bash -c 'while true…"	28 minutes ago	Up 28 minutes				ubuntu1804
    51a02953b18a	molecule_local/centos:7		"bash -c 'while true…"	4 hours ago	Up 4 hours (Paused)			instance
  3. resume it
    docker unpause 51a02953b18a
    51a02953b18a
    docker ps
    CONTAINER ID	IMAGE				COMMAND			CREATED		STATUS			PORTS		NAMES
    4bac82b3ba41	molecule_local/ubuntu:18.04	"bash -c 'while true…"	28 minutes ago	Up 28 minutes				ubuntu1804
    51a02953b18a	molecule_local/centos:7		"bash -c 'while true…"	4 hours ago	Up 4 hours				instance
  4. stop it
    docker stop 51a02953b18a
    51a02953b18a
    docker ps
    CONTAINER ID	IMAGE				COMMAND			CREATED		STATUS			PORTS		NAMES
    4bac82b3ba41	molecule_local/ubuntu:18.04	"bash -c 'while true…"	28 minutes ago	Up 28 minutes				ubuntu1804
    Not listed anymore because docker ps only lists running containers.
    docker ps -a
    CONTAINER ID	IMAGE				COMMAND			CREATED		STATUS			PORTS		NAMES
    4bac82b3ba41	molecule_local/ubuntu:18.04	"bash -c 'while true…"	28 minutes ago	Up 28 minutes				ubuntu1804
    51a02953b18a	molecule_local/centos:7		"bash -c 'while true…"	Exited (137) 13 minutes ago				instance
mail

How to backup + restore all Docker data ?

Situation :

Docker is running fine, except that _AFTER_ putting it in production (i.e. once there's data on it), we've detected that the LVM configuration has problems (something Red Hat-specific related to Docker and its storage driver). We had other hosts configured the same (wrong) way, but since these had no Docker data, we've been able to simply delete + re-create the storage with the right settings.
As for hosts already having Docker data, there is no simple "export + restore" functionality, hence this procedure.
  • Docker newbie here so there _may_ be errors in this procedure or in the way I use some commands
  • Some settings / usages _may_ be specific to my company / tech. leader "suggestions" (I'm open to comments, use the envelope icon above )
  • The steps describing how to properly configure the storage to make Docker happy are not covered here.

Solution :

Preliminary steps :

  1. If running a virtual machine, take a snapshot, just in case...
  2. Fix NFS issues :
    Not enough local storage, so I wanted to copy data to a network share, but had some difficulties :

Backup data :

Steps below are run as root.

  1. declare some shell variables once for all (so that you'll be able to copy-paste further steps as-is ) :

    nfsServer='10.27.25.5'; nfsExportPath="/filer/projects/$HOSTNAME"; nfsMountPoint='/mnt/nfs'; dockerDataDir='/var/lib/docker'; dockerDataDir_backupDir="$nfsMountPoint/varLibDocker"; backupDirVolumes='backupDockerVolumes'; volumesToBackup='volume1Name;volume1Dir volume2Name;volume2Dir volume3Name;volume3Dir'; backupDirContainers='backupDockerContainers'; containerData='container1Name;container1ServiceName container2Name;container2ServiceName'

    • volumesToBackup and containerData are actually shell tuples
    • what volumenDir relates to is not (yet) clear to me. Looks like it's something "inside" the volume itself, or some kind of mount point...
    • containernServiceName will be used to build commands such as : systemctl start containernServiceName
  2. declare a utility function :

    getContainerIdByName() { imageName=$1; docker ps -a | awk -v needle="$imageName" '$0 ~ needle {print $1}'; }

  3. make a backup of dockerDataDir (/var/lib/docker) via rsync :

    systemctl stop docker.service; mkdir -p "$dockerDataDir_backupDir"; umount "$nfsMountPoint"; mount -t nfs -o v3,async "$nfsServer:$nfsExportPath" "$nfsMountPoint" && time rsync -avz --delete "$dockerDataDir/" "$dockerDataDir_backupDir"; systemctl start docker.service

    This step provides double security for the data (snapshot + file copy), but is not mandatory. It can take a long time, depending on the amount of data and the network/disks performance.
    Restarting Docker causes some files to change (containers/<hash>/..., devicemapper/devicemapper/data/...) and rsync will need a looong time to resynchronize files again. Don't run this command twice, it will only stress disks and network.
  4. backup Docker volumes :

    cd "$nfsMountPoint"; mkdir -p "$backupDirVolumes"; for tuple in $volumesToBackup; do volumeName=$(echo $tuple | cut -d ';' -f 1); volumeDir=$(echo $tuple | cut -d ';' -f 2); archiveName="$backupDirVolumes/$volumeName.tar"; echo -e "\n######## WORKING ON '$volumeName' '$volumeDir' ########"; docker run -it --rm -v "$volumeName":"$volumeDir" -v "$PWD/$backupDirVolumes":"/$backupDirVolumes" alpine tar -cf "$archiveName" "$volumeDir"; ls -lh "$archiveName"; done

  5. backup containers (images, actually) :

    absolutePathToBackupDir="$nfsMountPoint/$backupDirContainers"; mkdir -p "$absolutePathToBackupDir"; for tuple in $containerData; do containerName=$(echo $tuple | cut -d ';' -f 1); containerId=$(getContainerIdByName "$containerName"); echo -e "\n######## WORKING ON '$containerName' ########"; imageName="$containerName:$(date +%F_%H-%M-%S)"; archiveName="$absolutePathToBackupDir/$containerName.tar"; echo ' stopping...'; docker stop "$containerId"; echo ' committing...'; docker commit "$containerId" "$imageName"; echo ' saving...'; docker save "$containerName" -o "$archiveName"; ls -lh "$archiveName"; done

Configure the storage :

Not described here, but we did it with Ansible.

Restore the data :

  • Steps below are run as root.
  • If you left the shell session where you defined the variables before the backup, load those variables again.
  1. re-create the Docker volumes (more on the 'tuple' hack) :

    for tuple in $volumesToBackup; do volumeName=$(echo $tuple | cut -d ';' -f 1); echo -e "\nCreating volume '$volumeName' : "; docker volume create "$volumeName" && echo 'OK' || echo 'KO'; done

  2. extract the Docker volume archives we created earlier :

    cd "$nfsMountPoint"; for tuple in $volumesToBackup; do volumeName=$(echo $tuple | cut -d ';' -f 1); volumeDir=$(echo $tuple | cut -d ';' -f 2); echo -e "\n######## RESTORING '$volumeName' '$volumeDir' ########"; archiveName="$backupDirVolumes/$volumeName.tar"; docker run -it --rm -v "$volumeName":"$volumeDir" -v $PWD"/$backupDirVolumes":"/$backupDirVolumes" alpine tar -xf "$archiveName"; done

  3. restore containers (images, actually) :

    for tuple in $containerData; do containerName=$(echo $tuple | cut -d ';' -f 1); echo -e "\n######## RESTORING CONTAINER '$containerName' ########"; containerToRestore="$nfsMountPoint/$backupDirContainers/$containerName.tar"; [ -f "$containerToRestore" ] && docker load --input "$containerToRestore" || echo "file '$containerToRestore' not found"; done

  4. start the containers :

    for tuple in $containerData; do containerName=$(echo $tuple | cut -d ';' -f 1); containerServiceName=$(echo $tuple | cut -d ';' -f 2); echo -e "\nStarting container '$containerName' ('$containerServiceName') :"; systemctl start "$containerServiceName" && echo 'OK' || echo 'KO'; done

  5. Enjoy !!!
mail

Docker glossary

container (source)
  • runtime instance of an image (i.e. what the image becomes in memory when actually executed).
  • It runs completely isolated from the host environment by default, only accessing host files and ports if configured to do so via a Dockerfile.
  • Containers run apps natively on the host machine's kernel. They have better performance characteristics than virtual machines that only get virtual access to host resources through a hypervisor. Containers can get native access, each one running in a discrete process, taking no more memory than any other executable.
  • Container != VM
Dockerfile (source, directives reference)
text file containing all the commands you would normally execute manually in order to build a Docker image :
  • what's inside / outside of the image
  • from what + how the image is built
  • how the app inside the image interacts with the rest of the world (i.e. access to resources such as network, storage, etc)
  • some environment variables
  • what to do when the image launches
  • ...
image
    An image is static and lives only on disk (source).
  • An image is an executable package that includes everything needed to run an application (source) :
    • the code of the application
    • a runtime
    • libraries
    • environment variables
    • config files
  • An image typically contains a union of layered filesystems stacked on top of each other. An image does not have state and it never changes.
Union file system (source)
  • Docker images are stored as series of read-only layers. When we start a container, Docker takes the read-only image and adds a read-write layer on top. If the running container modifies an existing file, the file is copied out of the underlying read-only layer and into the top-most read-write layer where the changes are applied. The version in the read-write layer hides the underlying file, but does not destroy it : it still exists in the underlying layer.
  • When a Docker container is deleted, relaunching the image will start a fresh container without any of the changes made in the previously running container. Those changes are lost.
  • Docker calls this combination of read-only layers with a read-write layer on top a Union File System. (more about union mount)
volume
  • a special directory within one or more containers that bypasses the Union File System. Volumes are designed to persist data, independent of the container's life cycle.
  • a volume is a directory of the host filesystem that is mounted into a container.
  • More explanations :
  • Let's play with volumes :
    • List the running containers :
      docker ps
      CONTAINER ID	IMAGE			COMMAND			CREATED		STATUS		PORTS					NAMES
      
      f64391d2ae1b	atlassian_confluence	"./entrypoint.sh"	5 days ago	Up 4 days	0.0.0.0:8090-8091->8090-8091/tcp	atlassian_confluence_1
      
      
    • Get details about their volumes :
      docker inspect -f "" atlassian_confluence_1 | grep -A7 '"Type": "volume"'
      
      	"Type": "volume",
      	"Name": "atlassian_confluence_tmp",
      	"Source": "/var/lib/docker/volumes/atlassian_confluence_tmp/_data",
      	"Destination": "/tmp",
      	"Driver": "local",
      	"Mode": "rw",
      	"RW": true,
      	"Propagation": ""
      --
      	"Type": "volume",
      	"Name": "atlassian_confluence_data",
      	"Source": "/var/lib/docker/volumes/atlassian_confluence_data/_data",	the Source is inside the container (see Mountpoint below)
      	"Destination": "/var/atlassian/application-data/confluence",		the Destination is on the host
      	"Driver": "local",
      	"Mode": "rw",
      	"RW": true,
      	"Propagation": ""
    • More details about a single volume :
      docker volume inspect atlassian_confluence_data
      [
      	{
      		"CreatedAt": "2020-10-08T23:09:51+02:00",
      		"Driver": "local",
      		"Labels": {
      			"com.docker.compose.project": "atlassian",
      			"com.docker.compose.version": "1.21.2",
      			"com.docker.compose.volume": "confluence_data"
      		},
      		"Mountpoint": "/var/lib/docker/volumes/atlassian_confluence_data/_data",
      		"Name": "atlassian_confluence_data",
      		"Options": null,
      		"Scope": "local"
      	}
      ]

Container != VM (source) :

The underlying kernel is shared by all containers on the same host. The container-specific stuff is implemented via kernel namespaces. Each container has its own :
  • process tree (pid namespace)
  • filesystem (mnt namespace)
  • network namespace
  • RAM allocation + CPU time
Everything else is shared with the host : in general the host machine sees / contains everything inside the container from file system to processes etc. You can issue a ps on the host and see processes running inside the container (source).
Docker containers are not VMs hence everything is actually running natively on the host and is using the host kernel directly. Each container has its own user namespace (like good ol' chroot jails). There are tools / features which make sure containers only see their own processes, have they own file-system layered onto host file system and a networking stack which pipes to the host networking stack.
mail

Docker random notes

About Docker itself :

  • Since March 2014, Docker does not rely anymore on LXC but on libcontainer as its default execution driver (source).
  • libcontainer wraps around existing Linux container APIs, particularly cgroups and namespaces.

Handle images :

List images :
  • docker images
  • docker image ls
Both commands look equivalent (source).
Delete the image imageId :
docker rmi imageId
Where are stored images on the filesystem of the machine running Docker ?
docker info | grep 'Docker Root Dir'
Docker Root Dir: /var/lib/docker
  • This is the default path.
  • There is not a lot —if anything— that can be done directly at the filesystem level with images, this is mostly FYI.
Full details about a given image (source) :
  1. docker images
    REPOSITORY		TAG		IMAGE ID		CREATED			SIZE
    whatever/myImage	latest		e00a21e210f9		22 months ago		19.2MB
  2. docker image inspect e00a21e210f9 | grep -E '(Lower|Merged|Upper|Work)Dir' | sed 's|/var|\n/var|g'
    			"LowerDir": "
    /var/lib/docker/overlay2/9ceb16316d05119812856edda1c772b3680ff20336859cb79e8d58df8abf787a/diff:
    /var/lib/docker/overlay2/ac535e1ef985bec3d5bc90a5124f4ca14a610b9f007966f7521496aa6b6866ac/diff:
    /var/lib/docker/overlay2/4eeff3b3fc7a14b197827ffae0cab33c0df6a15b08b2f45895c6e987a6f3013a/diff",
    			"MergedDir": "
    /var/lib/docker/overlay2/6923b6f343bd98e0a05826de6794dcb1756b4eb5fad9811fcad773e76b66a737/merged",
    			"UpperDir": "
    /var/lib/docker/overlay2/6923b6f343bd98e0a05826de6794dcb1756b4eb5fad9811fcad773e76b66a737/diff",
    			"WorkDir": "
    /var/lib/docker/overlay2/6923b6f343bd98e0a05826de6794dcb1756b4eb5fad9811fcad773e76b66a737/work"
  3. These are the different layers an image is made of :
    • LowerDir : read-only layers of the image
    • UpperDir : read-write layer that represents changes
    • MergedDir : result of LowerDir + UpperDir that is used by Docker to run the container
    • WorkDir : internal directory used by the overlay2 storage driver, should be empty

Handle containers :

List running containers :
docker ps
List all containers (running + non-running) :
docker ps -a
Delete the container containerId :
docker rm containerId

Miscellaneous :

Start the Docker daemon (see also) :
systemctl start docker.service
Get system-wide information :
docker info
Main configuration file (source) :
/etc/docker/daemon.json, but since options can be passed from the command line, this file looks optional.