Docker - HowTo's about Docker

mail

How to remove containers and images ?

which ones ? containers images both
all
(details)
docker rm $(docker ps -a -q) docker rmi $(docker images -q) docker rm $(docker ps -a -q) && docker rmi $(docker images -q)
exited docker rm $(docker ps -a | awk '/Exited \(/ { print $1 }') Not applicable docker rm $(docker ps -a | awk '/Exited \(/ { print $1 }') && docker rmi $(docker images -f "dangling=true" -q)
dangling (untagged) Not applicable docker rmi $(docker images -f "dangling=true" -q)
unless you're working anywhere you can crash everything —which is not always true, even in test environments— this is probably a bad idea.
mail

How to get a container's IP address ?

Remember :

docker ps -q
is the easy way of doing
docker ps | awk '!/^CONTAINER/ {print $1}'

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

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

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

All methods below describe how to open a shell as root in a container.
The method to use depends on whether you want to log in a :

In a running 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:/# 
    This may output an error message like :
    OCI runtime exec failed: exec failed: container_linux.go:348: starting container process caused "exec: \"/bin/bash\": stat /bin/bash: no such file or directory": unknown
    This just means /bin/bash can't be found in the image (i.e. Bash is not installed). Workaround : try with /bin/sh.

In a non-running container

The idea is to overwrite a container's entrypoint while starting it :
docker run --rm -it --entrypoint /bin/bash myimage
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 !!!