Low Orbit Flux Logo 2 D

Docker Tutorial

Docker whale

This is our brand new tutorial for Spring 2020. If you just want our quick cheat sheet, check here: Docker Cheat Sheet. If you want more detailed instructions, keep reading. We are going to cover all of the topics listed here and we are going to include video instrucions.

On this page we are going to show you how to setup and use Docker. We’re going to cover just about everything you might want to do while keeping it at least somewhat concise. We also may gloss over some of the things we don’t like, for example compose. We will try to note where we do this though.

The official Docker site can be found here: https://www.docker.com The Moby Project can be found here: https://mobyproject.org

NOTE - For most examples we use assume that you are running Linux, specifically Ubuntu 18.04. Other versions and distros should work almost the same. OSX and Windows are also an option, just not what we use for our examples. Many of the commands need root privileges. You will probably either need to either run as root or use sudo with many commands. Sudo is generally the ‘correct’ choice. We don’t always show the use of sudo in each of the example commands but you may need it.

What is Docker?

Docker is a system for containerization. A container is basically an isolated environment that behaves like a separate system. You could think of it kind of like a virtual machine except that it doesn’t require an entire copy of the operating system to be installed. Each container has much less overhead than a virtual machine. Docker makes it easy to package and ship applications.

Big Docker Cheat Sheet

We have an excellent cheat sheet that contains just about every command you might want to know. You can find it here: Docker Cheat Sheet.

We recommend that you use the docker cheat sheet as a reference along side this tutorial.

Install Docker on Ubuntu

We are installing Docker on Ubuntu. This has been tested on Feb 26, 2020 using Ubuntu 18.04.3 (both server and desktop) and it works great using the commands below. The Ubuntu package gave me Docker version 18.09.7. This is on the current long term support version of Ubuntu.

Easy Method

Super easy install on Ubuntu 18.04.3 with the Ubuntu provided packages:

sudo apt install docker.io

You might wanta newer version of Docker. The latest version on docker.com is 19.03.6. They have split this into three different packages: docker-ce, docker-ce-cli, and containerd.io. They halso have an enterprise edition of Docker that uses the following packages: docker-ee, docker-ee-cli, and containerd.io. The Ubuntu package should probably be fine but if you want to install a more up to date version you will have to add the docker.com repo.

Elaborate Method

More elaborate installation that gives you the newer packages from docker.com:

sudo apt-get update

sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo apt-key fingerprint 0EBFCD88

sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \

sudo apt-get install docker-ce docker-ce-cli containerd.io

Start / Stop Docker Daemon

Start / Stop the Docker Daemon:

dockerd                        # start docker directly (don't)
sudo start docker              # start using upstart (old)
sudo stop docker               # stop  using upstart (old)
sudo systemctl start docker    # start using systemd
sudo systemctl stop docker     # stop  using systemd

Basic Docker Usage

If you don’t name a container it will be given a goofy looking default name such as: “clever_lichterman” or “ecstatic_banach”.

Run a basic hello world docker container:

docker run hello-world

When a container finishes running it will stop. From there you can choose to start it again if you like. Using a command that doesn’t terminate will keep the container running.

Run a container in foreground mode with a shell. When you exit the shell, the container will also exit.

docker run -it ubuntu bash

You don’t need to specify the command bash.

docker run -it ubuntu

Run a container in detached mode. It won't connect until you explicitly attach (see below) and it won't exit when you exit the shell.

docker run -tid ubuntu

Create a container without starting it:

docker create -ti ubuntu     

Give your container a name:

docker run -tid --name my-container1 ubuntu

You can also specify a hostname:

docker run --hostname test1 --name test1 -tid ubuntu  

Show containers with the following commands:

docker ps                          # show running containers
docker ps -a                       # show all containers
docker ps --filter name=redis      # show matching containers
docker ps --filter name=redis -q   # show matching container ID

You can use the inspect command to view a huge dump of details about a particular container.

docker inspect clever_lichterman

You can start and stop a container like this:

docker start clever_lichterman
docker stop clever_lichterman

If you want to connect to a shell running container you can do it in a few different ways. You could use the attach command to attach to the container. You could also use the exec commmand to lauch a bash shell.

docker attach smelly-hippo         # connect to running container
                                   # ( login ) exit with CTRL-p CTRL-q.

docker exec -it smelly-hippo bash  # get shell on host

You can delete a container like this (stop it first):

docker stop clever_lichterman
docker rm clever_lichterman

We can make sure that the container is cleaned up after it finishes with the –rm option. There is a neat example image in the docker repo called whalesay that basically just includes what looks like a modified cowsay command. Both of these containers will be removed once they finish running.

docker run --rm hello-world
docker run --rm docker/whalesay cowsay "Hello World"   

You can copy files to a container and execute commands on that container with the following:

docker cp junk smelly-hippo:/opt/webapp   # copy file to container
docker exec smelly-hippo ls /opt/webapp   # run command on container1

You can rename a container like this:

docker rename epic_volhard smelly-hippo     

Container Lifecycle Stuff

Containers can be started, stopped, restarted, and more with the following commands:

docker start   smelly-hippo             # start
docker stop    smelly-hippo             # stop
docker stop    smelly-hippo funny-frog  # stop mutliple
docker restart smelly-hippo             # restart container
docker pause   smelly-hippo             # pauses a running container, freeze in place
docker unpause smelly-hippo             # unpause a container
docker wait    smelly-hippo             # blocks until running container stops
docker kill    smelly-hippo             # sends SIGKILL, faster than stop
docker rm      smelly-hippo             # remove
docker rm      smelly-hippo container5  # remove multiple
docker rm -f   smelly-hippo             # force remove

Remove all containers, running or stopped:

docker container rm -f $(docker ps -aq)

Resource Limits and Controls

Docker allows you to limit the resources used by a container. You can control how much memory and CPU is used. You can limit disk usage. You can even assign a container to use specific CPUs.

docker run -tid -c 512 ubuntu                     # 50% cpu
docker run -tid --cpuset-cpus=0,4,6 ubuntu         # use these cpus
docker run -tid -m 300M ubuntu                     # limit memory
docker create -ti --storage-opt size=120G ubuntu   # limit storage, not on aufs

Container Stats, Logs, and Events

The stats command will show resources used by currently running containers. It shows things like CPU, memory, and IO. You can also view the the running processes on a specific container with the top command. You can view the logs of a container with the logs command. This will include output to the console (what you see when you attach). The events command will show allow you to watch Docker events as they occur. You can see things like containers starting and stopping. The port command will show public facing ports for a specific container.

docker stats                   # resourse stats for all containers
docker stats container1        # resource stats for one container
docker top  nostalgic_colden   # shows processes in a container
docker logs web                # container logs
docker events                  # watch events in real time
docker port nostalgic_colden   # shows public facing port of container
docker diff practical_sinoussi   # show changes to a container's file system

Docker Images

Containers are launched from prebuilt imagaes. You can build and modify images yourself. You can also pull them down from Docker Hub.

You can aquire images from the following sources:


You can list the available images with the images command:

docker images

You can show an image’s history like this:

docker history ubuntu  

You can remove images like this:

docker image rm user1/funny-frog     # remove image
docker image remove 113a43faa138     # remove by id
docker image remove user1/funny-frog # remove image
docker rmi user1/funny-frog          # remove image
docker rmi $(docker images -q)   # remove all images

You generally need to stop all containers using an image before you can remove it. You can remove images with any of these commands. You can also force image removal. Note that you can use 'rm' or 'remove'. They both work in this case.

New Image From A Container

You can create an image from an existing container like this:

docker commit smelly-hippo                               # no repo name
docker commit smelly-hippo test1                         # repo name
docker commit smelly-hippo loworbitflux/test1            # repo name
docker commit smelly-hippo loworbitflux/test1:my-update  # tagged
docker commit smelly-hippo loworbitflux/test1:v1.2.3     # tagged

Export / Import / Save / Load

docker export    # export container to tarball archive stream
docker import    # create image from tarball, excludes history ( smaller image )
docker load      # load an image from tarball, includes history ( larger image )
docker save      # save image to tar archive stream ( includes parent layers )

docker load < my-image.tar.gz
docker save my_image:my_tag | gzip > my-image.tar.gz
cat my-container.tar.gz | docker import - my-image:my_tag
docker export my-container | gzip > my-container.tar.gz

A better way to create an image is to build it using a Dockerfile as we will cover further on in this guide.

Docker Hub / Registry

Docker Hub is kind of like GitHub except for Docker images. You don't need an account to pull images down. This is where they will be pulled from by default if they can't be found locally on your machine. If you want to upload images for others to use, you will need an account. You can sign up here: https://hub.docker.com.

Once you have an account you can log in and out like this. It will prompt you for a password.

docker login
docker logout

Before uploading you will need to tag your release using the tag command. Here we tag it with the image ID. Once you have got it tagged, you can upload it to DockerHub with the push command.

docker tag 7d9495d03763 loworbitflux/smelly-hippo:latest
docker push loworbitflux/smelly-hippo

You can search for images and pull them down like this:

docker search mysql    # search for an image
docker pull mysql      # pull it down

If you try to run a container using an image that isn't available on the local system, an automatic attempt will be made to find it and pull it down so that the container can be started.

docker run user1/funny-frog     # will be downloaded if it isn’t here

Building Docker Images From A Dockerfile

A Dockerfile can be used to create an image. You start out by creating a new directory and createing the file inside that directory. The file will need to be named "Dockerfile". Any other files you want included in your image should be placed here as well.

You can build an image from the docker file with the build command.

mkdir mydockerbuild               # create build dir
cd mydockerbuild                  # cd into build dir
vi Dockerfile                     # edit build instructions
docker build -t mydockerimage .   # build the image
docker images                     # show images
docker run mydockerimage          # run the new image

Here is a very basic example of what goes into a Dockerfile.

FROM docker/whalesay:latest
RUN apt-get -y update && apt-get install -y fortunes
CMD /usr/games/fortune -a | cowsay

Here is a real example that creates an NGINX web server based on the Ubuntu image:

FROM ubuntu

RUN apt update
RUN apt install nginx -y
CMD ["/usr/sbin/nginx"]

More Complete Dockerfile Example

Here is a more complex exampel of a Dockerfile. This one includes most of the options that are likely to be useful. You can do all sorts of things like exposing ports, copying in extra files, installing packages, adding storage, and more. Note that these commands don't all really fit together. I just tried to fit as much as possible in there.

WARNING - I just mixed a bunch of options together. Don’t actually use this example.

FROM ubuntu                         # base image
RUN apt update                      # run commands while building
RUN apt install nginx -y            # run commands while building
WORKDIR ~/                          # working dir that CMD is run from
ENTRYPOINT echo                     # default application
CMD "echo" "Hello docker!"          # main command / default application
CMD ["--port 27017"]                # params for ENTRYPOINT
CMD "Hello docker!"                 # params for ENTRYPOINT
ENV SERVER_WORKS 4                  # set env variable
EXPOSE 8080                         # expose a port, not published to the host
MAINTAINER authors_name             # deprecated
LABEL version="1.0"                 # add metadata
LABEL author="User One"             # add metadata
USER 751                            # UID (or username) to run as
VOLUME ["/my_files"]                # sets up a volume
COPY test relativeDir/              # copies "test" to `WORKDIR`/relativeDir/
COPY test /absoluteDir/             # copies "test" to /absoluteDir/
COPY ssh_config /etc/ssh/ssh_config     # copy over a vile
COPY --chown=user1:group1 files* /data/ # also changes ownership
ADD /dir1 /dir2                         # like copy but does more ...


The default value for ENTRYPOINT is ‘/bin/sh -c’. CMD just specifies parameters to this. CMD can be used to specify your main application and it’s paramters. You can also override ENTRYPOINT and just use CMD to hold the paramters.

Volumes / Storage

docker info | grep -i storage      #  check storage driver ( Ubuntu default: overlay2 )
docker inspect web    # look for “Mounts”
docker volume ls
docker volume create testvol1
docker volume inspect testvol1
:ro for read only
:z shared all containers can read/write
:Z private, unshared

docker run -d --name test1 -v /data ubuntu
docker run -d --name test2 -v vol1:/data  ubuntu       # named volume
docker run -d --name test3 -v /src/data:/data ubuntu     # bind mount
docker run -d --name test4 -v /src/data:/data:ro ubuntu  # RO

docker run -d --volumes-from test2 --name test5 ubuntu  # storage can be shared

docker rm -v  test1                 # remove container and unnamed volume
docker volume ls -f dangling=true   # find dangling ( unused ) volumes
docker volume rm volume1            # remove volume

dockerd --storage-driver=devicemapper &   # run docker with device mapper or set in /etc/default/docker

NOTE - On Linux volumes will be stored here by default: /var/lib/docker/overlay2. You can increase the storage space for this directory by mounting a large storage device here.

AUFS is a unification filesystem – fast, efficient, old, stable, copy-on-write technology (CoW)

Docker Networking

Expose Ports

Docker expose ports

docker run -tid -p 1234:80 nginx   # expose container port 80 on host port 1234

docker run -tid -p 80:5000            ubuntu  # bind port
docker run -tid -p 8000-9000:5000     ubuntu  # bind port to range
docker run -tid -p 80:5000/udp        ubuntu  # udp ports
docker run -tid -p  ubuntu  # bind port on an interface
docker run -tid -p    ubuntu  # bind any port, specific interface
docker run -tid -P                    ubuntu  # exposed ports to random ports


Network Info

You can view information about available networks with the following commands.

docker network ls               # show networks, bridge is default
docker network inspect bridge   # show network details and connected containers

Network Creation

You can create a new network with the 'network create' command. In this example we create a new bridge network with the bridge driver. The “-d bridge” is not really needed because that is the default.

docker network create -d bridge my-bridge-network  

Create a network with an explicit subnet. This also allows us to explicitly specify IPs for containers that are added to this network.

docker network create -d bridge --subnet my-network
docker network create --subnet --gateway my-network

The default binding IP address can be specified like this:

docker network create -o "com.docker.network.bridge.host_binding_ipv4"="" my-network

Connecting to a Network

You can specify which network to connect to when starting a container. A specific IP can be assigned as well. To assign an IP the subnet for the network will have to have been specified explicitly.

docker run -tid --net=my-bridge-network --name test1 ubuntu
docker run -tid --net=my-bridge-network --ip= --name=test1 ubuntu  

You can also connect a running container to a specific network. You can specify an IP if you want.

docker network connect my-bridge-network test1
docker network connect my-bridge-network test2  --ip  

You can inspect a container and grab only the network info. You can also grab just the IP address if you want.

docker inspect -f '{{json .NetworkSettings.Networks}}'  container1  
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container1

Disconect and Remove

You can disconnect and remove networks like this:

docker network disconnect bridge networktest       # disconnect container from this network  
docker network disconnect -f multihost redis_db    # force disconnect
docker network rm isolated_nw                      # remove network

Overlay Networks

Overlay Network ( Swarm Mode )

docker network create   --driver overlay   --subnet   my-multi-host-network   # create overlay network
docker service create --replicas 2 --network my-multi-host-network --name my-web nginx    # extend to  hosts that use it

Overlay Network ( No Swarm )

Configure each Docker Daemon with

--cluster-store=PROVIDER://URL                             Describes the location of the KV service.
--cluster-advertise=HOST_IP|HOST_IFACE:PORT      The IP address or interface of the HOST used for clustering.
--cluster-store-opt=KEY-VALUE OPTIONS       Options such as TLS certificate or tuning discovery Timers

To actually get the two damons working with Systemd on Ubuntu 16.04:

    "cluster-store": "consul://test1:8500",
    "cluster-advertise": "enp0s3:2376",
    "storage-driver": "aufs"

Create Keystore

docker-machine create -d virtualbox mh-keystore
eval "$(docker-machine env mh-keystore)"
docker run -d -p "8500:8500" -h "consul" progrium/consul -server -bootstrap

docker-machine create  -d virtualbox \
 --engine-opt="cluster-store=consul://$(docker-machine ip mh-keystore):8500" \
 --engine-opt="cluster-advertise=eth1:2376" mhs-demo0

docker-machine create -d virtualbox \
     --engine-opt="cluster-store=consul://$(docker-machine ip mh-keystore):8500" \
     --engine-opt="cluster-advertise=eth1:2376" mhs-demo1

eval $(docker-machine env mhs-demo0)
docker-machine ssh  mhs-demo0
    docker network create --driver overlay --subnet= my-multi-host-network  
    docker run -itd --network=my-multi-host-network busybox  # create conainer using it
docker-machine ssh  mhs-demo1
    docker run -itd --network=my-multi-host-network busybox  # create conainer using it

More Overlay Options

docker network create -d overlay \
  --subnet= \
  --subnet= \
  --gateway= \
  --gateway= \
  --ip-range= \
  --aux-address="my-router=" --aux-address="my-switch=" \
  --aux-address="my-printer=" --aux-address="my-nas=" \


Swarm Terms

node - docker engine instance
manager node - by default also work as worker nodes
worker node - each node has an agent
task - like a slot where a container is placed, atomic unit of scheduling
global service - on every node
replicated service - specify number of identical tasks

Active - can assign tasks to node
Pause - no new tasks
Drain - no new tasks, tasks moved off of node

no value - not a manager
Leader - the leader
Reachable - up but not leader
Unavailable - down

Setting up a Swarm:

docker-machine create --driver virtualbox manager
docker-machine create --driver virtualbox worker
docker-machine ssh manager1           # connect to manager
docker swarm init --advertise-addr <MANAGER-IP>    # create new swarm
docker swarm join-token worker       # get the join command and token
docker swarm join-token manager     # get the token to add a manager
docker swarm join-token --rotate       # rotate the join tokens ( every 6 months )
docker swarm join-token --rotate worker # only for worker

docker-machine ssh worker1           # connect to worker
docker swarm join --token  XXXXXXXXX

docker info                                              # see swarm state
docker node ls                                         # info about nodes
docker node update --availability drain worker1      # drain a node
docker node update --availability active worker1     # set it back to active
docker node inspect self --pretty                               # inspect a node     

docker node promote    # promote a worker to manager
docker node demote     # Demote one or more nodes from manager in the swarm
docker node inspect     # Display detailed information on one or more nodes
docker node ls	              # List nodes in the swarm
docker node promote	  # Promote one or more nodes to manager in the swarm
docker node ps             # List tasks running on one or more nodes, defaults to current node
docker node rm            # Remove one or more nodes from the swarm, run form a master
docker swarm leave     # leave the swarm, run on node
docker node update      # Update a node   

Create a service:

docker service create --replicas 1 --name helloworld alpine ping docker.com # name, image, command
docker service ls                                                # view services
docker service inspect --pretty helloworld        # inspect service, readable
docker service inspect helloworld                     # inspect service, json
docker service ps helloworld                             # see which nodes are running the service
docker service scale helloworld=5                    # scale a service
docker service update --replicas 3  helloworld  # alt way to scale
docker service rm helloworld                            # delete a service

Rolling updates with update delay:

docker service create --replicas 3 --name redis --update-delay 10s redis:3.0.6
docker service update --image redis:3.0.7 redis   

Swarm mode routing mesh:

Ports ( published port:target port -  target on all swarm nodes ):

docker service create --name my-web --publish 8080:80 --replicas 2 nginx
docker service update --publish-add 8080:80  my-web               # update existing
docker service inspect --format="" my-web    # view

docker service create --name dns-cache -p 53:53 dns-cache                   # TCP  
docker service create --name dns-cache -p 53:53/tcp dns-cache             # TCP
docker service create --name dns-cache -p 53:53/tcp -p 53:53/udp dns-cache    # TCP and UDP
docker service create --name dns-cache -p 53:53/udp dns-cache                        # UDP

--publish 8080:80    # publish on routing mesh:   8080 reachable on any node in the swarm
--publish mode=host,target=8080,published=8080   # single node only: only on this one node

Label a node in a swarm, different from docker daemon labels

docker node update --label-add foo --label-add bar=baz node-1   

Leave Swarm:

docker swarm leave        # leave a swarm, engine no longer in swarm mode
--force                             # override any warnings about quorum if leaving as a manager
docker node rm node-2   # from a manager,  remove a node after it has left the swarm

Replica or global:

docker service create --name="myservice" ubuntu:16.04             # image version
docker service create --name my_web --replicas 3 nginx            # replica by default
docker service create --name myservice --mode global alpine top   # specify global

Rollback service update:

docker service update --rollback --update-delay 0s my_we

Volume Mount:

docker service create --mount src=vol1,dst=/data1 --name myservice web

Secrets for Swarms:

docker secret create secret1 /etc/passwd
docker secret inspect  secret1
docker secret ls
docker secret rm secret1
echo "This is a secret" | docker secret create my_secret_data -
docker service  create --name="redis" --secret="my_secret_data" redis:alpine  
docker exec $(docker ps --filter name=redis -q) ls -l /run/secrets  
Overlay networks in a swarm:  
docker network create --driver overlay my-network          # Create overlay network
docker service create --replicas 3 --network my-network --name my-web nginx	# use it
docker network create --driver overlay --subnet --opt encrypted my-network # encrypt

View the service’s VIP ( other services in swarm can reach this ):

docker service inspect --format=''  my-web

Use dns round robin instead of normal VIP

docker service create --replicas 3 --name svcs1 --network net1 --endpoint-mode dnsrr nginx  


docker node inspect manager1 --format "{{ .ManagerStatus.Reachability }}" reachable

Troubleshoot a manager node

docker node demote <NODE>
docker node rm <NODE>
docker swarm join

    docker node rm --force node9    # if it is unreachable and you need to force remove it


NOTE - Our coverage of Compose is incomplete at best. This section is left over from our old tutorial and is kept just in case someone finds it useful. I would recommend using a different tool like Vagrant or Terraform.

Install Compose (optional):

curl -L "https://github.com/docker/compose/releases/download/1.11.2/docker-compose\
-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose --version

Web service is built using the docker file.
Redis image pulled from the Docker Hub registry.

mkdir composetest
cd composetest


version: '2'
    build: .
     - "5000:5000"
     - .:/code
    image: "redis:alpine"

docker-compose up
docker-compose ps
docker-compose run web env     # one off command,  run env on web service
docker-compose --help          # more commands
docker-compose stop            # stop services
docker-compose down            # stop, remove containers
docker-compose down --volumes  # stop, remove containers, remove data volumes
docker-compose -f docker-compose.yml -f docker-compose.admin.yml run backup_db

Docker Stack …..
docker stack deploy --compose-file docker-stack.yml vote    # deploy using compose
docker stack services vote                                  # show the services in this app

docker-compose bundle # create bundle from compose file
create stack ???  .dab files, json
docker deploy vossibility-stack     # deploy a stack

Version 3 example:

Docker Machine

NOTE - We recommend using another tool instead of Machine. Vagrant or Terraform are great tools depending on your needs.

Docker machine is a neat little tool used to manage docker engine instaces on hosts. You don’t really need it to work with Docker and you may end up using a completely different tool help with management. We show examples of some of the more important machine commands here but we aren’t going into any detail with them.

Install Docker Machine (optional):

curl -L https://github.com/docker/machine/releases/download/v0.10.0/docker-machine\
-`uname -s`-`uname -m` >/tmp/docker-machine &&
  chmod +x /tmp/docker-machine &&  sudo cp /tmp/docker-machine /usr/local/bin/docker-machine

Docker Machine Commands

docker-machine version
docker-machine ls
docker-machine create --driver virtualbox default
docker-machine ip box1
docker-machine inspect box1
docker-machine stop box1
docker-machine start box1
docker-machine env box1                # show env for box1
eval "$(docker-machine env box1)"      # switch to box1
docker-machine ssh manager                                   # ssh to the container
docker-machine scp docker-stack.yml manager:/home/docker/.   # scp

docker-machine create --driver amazonec2 --amazonec2-access-key AKI******* \
--amazonec2-secret-key 8T93C*******  aws-sandbox