Super-simple Docker Tutorial
Tutorial guided by Creating your first Docker image.
Every Docker container is an instance of a Docker image. Docker images are built from layers, starting with a base layer. A common base layer is Ubuntu; another is CentOS. Note that CentOS is what ELK stack Docker images begin with. This example uses Ubuntu because Debian and because the Debian base layer is much smaller than others.
Start a Docker container based on something called Ubuntu:latest image. :latest is called the image tag and refers to the latest version of Ubuntu bash layer.
If you don't have the image locally, which will be the case the first time run, it will download automatically. Here we go...
[email protected]:~$ docker run --name my-redis -it ubuntu:latest bash Unable to find image 'ubuntu:latest' locally latest: Pulling from library/ubuntu 124c757242f8: Pull complete 9d866f8bde2a: Pull complete fa3f2f277e67: Pull complete 398d32b153e8: Pull complete afde35469481: Pull complete Digest: sha256:de774a3145f7ca4f0bd144c7d4ffb2931e06634f11529653b23eba85aef8e378 Status: Downloaded newer image for ubuntu:latest [email protected]:/#
Above, you see we started a Docker container...
At the end of this, we have a command prompt inside the Docker container (container doesn't mean it's invisible or that you can't see inside). We happen to be root, but, despite this awesome power, it's inside a container that protects the host we're running on—as well as any other containers running on our host—from ill effects including making mistakes using root and from what we'll do with root.
Since this demonstration uses Redis, we'll need a few things added into our container (which is Debian—Ubuntu). The first thing we need is a program called wget. To show that this container has nothing to do with the actual host, nargothrond, I'm running on, compare these two sessions. Throughout this tutorial, fluorescent green is the sign that we're doing something in our host operating environment.
[email protected]:~$ hostname nargothrond [email protected]:~$ sudo bash [email protected]:~# which wget /usr/bin/wget [email protected]:~# apt-get update Hit:1 http://archive.canonical.com/ubuntu bionic InRelease Ign:2 http://mirrors.xmission.com/linuxmint tara InRelease Get:3 http://security.ubuntu.com/ubuntu bionic-security InRelease [83.2 kB] Hit:4 http://mirrors.xmission.com/linuxmint tara Release Ign:6 http://dl.google.com/linux/chrome/deb stable InRelease Hit:7 http://dl.google.com/linux/chrome/deb stable Release Hit:9 http://ubuntu.cs.utah.edu/ubuntu bionic InRelease Get:10 http://ubuntu.cs.utah.edu/ubuntu bionic-updates InRelease [88.7 kB] Get:11 http://ubuntu.cs.utah.edu/ubuntu bionic-backports InRelease [74.6 kB] Fetched 247 kB in 2s (103 kB/s) Reading package lists... Done
[email protected]:/# hostname 178ff3c44fc2 [email protected]:/# which wget [email protected]:/# apt-get update Get:1 http://security.ubuntu.com/ubuntu bionic-security InRelease [83.2 kB] Get:2 http://archive.ubuntu.com/ubuntu bionic InRelease [242 kB] Get:3 http://security.ubuntu.com/ubuntu bionic-security/universe Sources [21.8 kB] Get:4 http://security.ubuntu.com/ubuntu bionic-security/multiverse amd64 Packages [1364 B] Get:5 http://security.ubuntu.com/ubuntu bionic-security/universe amd64 Packages [104 kB] Get:6 http://security.ubuntu.com/ubuntu bionic-security/main amd64 Packages [229 kB] Get:7 http://archive.ubuntu.com/ubuntu bionic-updates InRelease [88.7 kB] Get:8 http://archive.ubuntu.com/ubuntu bionic-backports InRelease [74.6 kB] Get:9 http://archive.ubuntu.com/ubuntu bionic/universe Sources [11.5 MB] Get:10 http://archive.ubuntu.com/ubuntu bionic/multiverse amd64 Packages [186 kB] Get:11 http://archive.ubuntu.com/ubuntu bionic/universe amd64 Packages [11.3 MB] Get:12 http://archive.ubuntu.com/ubuntu bionic/restricted amd64 Packages [13.5 kB] Get:13 http://archive.ubuntu.com/ubuntu bionic/main amd64 Packages [1344 kB] Get:14 http://archive.ubuntu.com/ubuntu bionic-updates/universe Sources [115 kB] Get:15 http://archive.ubuntu.com/ubuntu bionic-updates/restricted amd64 Packages [10.8 kB] Get:16 http://archive.ubuntu.com/ubuntu bionic-updates/universe amd64 Packages [711 kB] Get:17 http://archive.ubuntu.com/ubuntu bionic-updates/multiverse amd64 Packages [6161 B] Get:18 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 Packages [519 kB] Get:19 http://archive.ubuntu.com/ubuntu bionic-backports/universe amd64 Packages [2975 B] Fetched 26.6 MB in 28s (936 kB/s) Reading package lists... Done
So, in order to continue the tutorial, we'll have to get sofware—wget and other programs—for our container even though our host operating system already has that software:
[email protected]:/# apt-get install wget Reading package lists... Done Building dependency tree Reading state information... Done The following additional packages will be installed: ca-certificates libpsl5 libssl1.1 openssl publicsuffix The following NEW packages will be installed: ca-certificates libpsl5 libssl1.1 openssl publicsuffix wget 0 upgraded, 6 newly installed, 0 to remove and 4 not upgraded. Need to get 2266 kB of archives. After this operation, 6341 kB of additional disk space will be used. Do you want to continue? [Y/n] y Get:1 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libssl1.1 amd64 1.1.0g-2ubuntu4.1 [1128 kB] . . . Updating certificates in /etc/ssl/certs... 133 added, 0 removed; done. Processing triggers for libc-bin (2.27-3ubuntu1) ... Processing triggers for ca-certificates (20180409) ... Updating certificates in /etc/ssl/certs... 0 added, 0 removed; done. Running hooks in /etc/ca-certificates/update.d... done.
To boot, we need a few other things too (like Redis), so let's go get them. build-essential is a really huge pile of stuff, enough to build some serious Linux applications (make) including the ability to handle all sorts of stuff like locale and timezones. Here, we're going to download Redis source code and build it. (No, I didn't know Redis didn't have binary distributions either.) So, we'll build Redis, install it, then run it as a server, including reserving and opening a port, all activites that we might not want to undertake lightly were this sample software going to be unceremoniously dumped into our very reliable and productive operating system on which we daily depend!
[email protected]:/# apt-get install build-essential tcl8.5 -y [email protected]:/# wget http://download.redis.io/releases/redis-stable.tar.gz [email protected]:/# tar xzf redis-stable.tar.gz [email protected]:/# cd redis-stable [email protected]:/redis-stable# make [email protected]:/redis-stable# make install [email protected]:/redis-stable# ./utils/install_server.sh Welcome to the redis service installer This script will help you easily set up a running redis server Please select the redis port for this instance:  Selecting default: 6379 . . . Is this ok? Then press ENTER to go on or Ctrl-C to abort. Copied /tmp/6379.conf => /etc/init.d/redis_6379 Installing service... Success! Starting Redis server... Installation successful! [email protected]:/redis-stable# service redis_6379 start /var/run/redis_6379.pid exists, process is already running or crashed [email protected]:/redis-stable# ps -ef | grep [r]edis root 6858 1 0 14:57 ? 00:00:00 /usr/local/bin/redis-server 127.0.0.1:6379
Now Redis is running in our Docker container, but let's assume that we're so happy about this that we want to save it off in order to use it elsewhere or even distribute it to friends for their use. Remember that we're layered upon the work of a nice container that our Ubuntu friends set up for us.
First, we exit our container—back to our host.
[email protected]:/redis-stable# exit exit
Next, we make a list of all of our Docker containers, whether running or stopped. (Because I've been creating containers and experimenting, I had a big pile of them, but I've cut them down to just the one we care about here.)
[email protected]:~$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 178ff3c44fc2 ubuntu:latest "bash" About an hour ago Exited (0) 50 seconds ago my-redis
We'll commit out container as an image (compiling its changes into an image).
[email protected]:~$ docker commit -m "Added Redis" -a "Russell Bateman" my-redis windofkeltia/my-redis:latest
Because of how git works, we've committed to a local repository belonging to Docker. Our image won't go up to Docker Hub until we do this:
[email protected]:~$ docker push
...which we may not want to do because this is only a small, mostly useless demonstration. (Again, if you know anything about git, you understand what's happening here.
A Docker image isn't the whole elephant that's created. In this case, the elephant is created one layer at a time. We've just added only a tiny layer amounting to a) installing wget so that we can copy down Redis as source code to build, b) building Redis and, finally, c) launching Redis as a service. What goes into our container image is all the cool stuff the Ubuntu guys did (the lion's share of what's in there) plus our special modifications.
Simply put, this way of building containers makes most container images very small things indeed to pull down and use.
However, this is very messy since your container is little more than a black box. For someone else to know what you've done would require documentation, which is very annoying and possibly inaccurate.
There is a better way.
It's possible to specify the contents and procedures of a container in a formal, reproduceable way. You make statements in a prescribed fashion in a file named, Dockerfile (dockerfile is also recognized and accepted).
If what you create needs to run on, for example, a distinctly different Linux distribution, let's say CentOS instead of Ubuntu, then you absolutely do not want your image to be done based on Debian because the result would useless on Red Hat, SuSE and other Linuces. You should use a Dockerfile to specify images instead.
Here is the Dockerfile that describes the image we created earlier:
FROM ubuntu:latest ARG DEBIAN_FRONTEND=noninteractive RUN apt-get update -y RUN apt-get install -y --no-install-recommends apt-utils RUN apt-get install -y wget RUN apt-get install -y build-essential tcl8.5 RUN wget http://download.redis.io/releases/redis-stable.tar.gz RUN tar xzf redis-stable.tar.gz RUN cd redis-stable && make && make install RUN ./redis-stable/utils/install_server.sh EXPOSE 6379 ENTRYPOINT [ "redis-server" ]
Here's what's going on...
Upon creating Dockerfile in a subdirectory, make that subdirectory your current working directory and then run Docker to build it. Then you can create a running container:
[email protected]:~/dev/docker-dev/redis$ vim dockerfile (insert contents above) [email protected]:~/dev/docker-dev/redis# docker build -t redis . [email protected]:~/dev/docker-dev/redis$ docker run -d -p 6379:6379 redis ebeee42372a5029e4cbb8287bb9312540bad9db02e71c9e1b8fd407622f733e8 [email protected]:~/dev/docker-dev/redis$ ps -ef | grep [d]ocker root 2950 3800 0 17:05 ? 00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 6379 \ -container-ip 172.17.0.2 -container-port 6379 root 2958 3826 0 17:05 ? 00:00:00 docker-containerd-shim -namespace moby \ -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/ebeee42372a5029e4...07622f733e8 \ -address /var/run/docker/containerd/docker-containerd.sock -containerd-binary /usr/bin/docker-containerd \ -runtime-root /var/run/docker/runtime-runc root 3800 1 0 Oct10 ? 00:07:20 /usr/bin/dockerd -H fd:// root 3826 3800 0 Oct10 ? 00:05:13 docker-containerd --config /var/run/docker/containerd/containerd.toml
At this point, the container is running as you can recognize by familiar numbers in the processor status command issued after launching: