Docker, Without Volumes


In the previous video we saw how Docker Containers can be created from a Docker Image, and that a core difference between a Docker Image and a Docker Container is that a Container has a read/write layer (the Container Layer). This container layer allows you to work with the Container just as if it were a Virtual Machine or similar.

If you create a new Docker Container (docker run {image}) and make some changes to that running container, those changes exist whilst the container exists.

This means if you stop the running container, then restart that container, the changes you made are still there.

However, if you start another Docker Container from the same Docker Image, then the changes you made to the first container are not there.

We can see this for ourselves by running two containers from the same image, and making some changes:

$ docker run -d -p 81:80 nginx
994aefb6258f6c464e3798f82d10c21f16cb26e52fe145724285bbd50b076c63

$ docker run -d -p 82:80 nginx
b3e782a8943b5b1712547c3b9f79c63f1eae5debd440245bac051f82320cf804

Here I am running two instances of the nginx container.

We've seen most of this command already. The only new part is the use of the -d flag. This means the container is started in a -detached state.

In the previous video we ran our containers without the -d flag, and found that the running container would take over our terminal output. When we press ctrl+c, we found the container would exit / stop. This is useful sometimes, but generally I run my containers in the background / detached.

$ docker ps -a

CONTAINER ID  IMAGE   COMMAND                  CREATED         STATUS        PORTS                NAMES
b3e782a8943b  nginx   "nginx -g 'daemon ..."   4 minutes ago   Up 4 minutes  0.0.0.0:82->80/tcp   loving_knuth
994aefb6258f  nginx   "nginx -g 'daemon ..."   4 minutes ago   Up 4 minutes  0.0.0.0:81->80/tcp   nostalgic_shannon

We have two running container instances of the base / default nginx image. We can visit either in the browser and see the same thing:

  • 127.0.0.1:81
  • 127.0.0.1:82

Both should be showing the "Welcome to nginx!" page.

Let's break the container named nostalgic_shannon:

$ docker exec -i -t nostalgic_shannon /bin/bash

# also you could do
# docker exec -i -t 994aefb6258f /bin/bash

# also you can combine the flags, e.g.
# docker exec -it nostalgic_shannon /bin/bash

root@994aefb6258f:/#

As the command name implies, docker exec executes or runs a command against a running container.

The -i flag runs the command in -interactive mode. This gives us access to the containers STDIN stream. Without this we could type a command but then our terminal would appear to hang.

The -t flag allocates a psuedo-TTY. From the Docker help this: "Allocate a pseudo-TTY". To you and me this means we get access to the containers command line interface. This is the best explanation I found.

Further output options are:

$ docker exec --help

Usage:  docker exec [OPTIONS] CONTAINER COMMAND [ARG...]

Run a command in a running container

Options:
  -d, --detach               Detached mode: run command in the background
      --detach-keys string   Override the key sequence for detaching a container
  -e, --env list             Set environment variables
      --help                 Print usage
  -i, --interactive          Keep STDIN open even if not attached
      --privileged           Give extended privileges to the command
  -t, --tty                  Allocate a pseudo-TTY
  -u, --user string          Username or UID (format: <name|uid>[:<group|gid>])

Once we have terminal access to our container we can do whatever we need to do. In this case we are going to deliberately break nginx on nostalgic_shannon:

$ docker exec -i -t nostalgic_shannon /bin/bash

root@994aefb6258f:/# mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak
root@994aefb6258f:/# service nginx restart

This will kick you out of the container, back to your terminal, and will have stopped nostalgic_shannon

$ docker ps -a

CONTAINER ID  IMAGE   COMMAND                  CREATED         STATUS                           PORTS                NAMES
b3e782a8943b  nginx   "nginx -g 'daemon ..."   4 minutes ago   Up 4 minutes                     0.0.0.0:82->80/tcp   loving_knuth
994aefb6258f  nginx   "nginx -g 'daemon ..."   4 minutes ago   Exited (0) About a minute ago                         nostalgic_shannon

You can see that whilst the website at 127.0.0.1:81 is offline, the site at 127.0.0.1:82 is still up and running.

Likewise, if we jump onto the container named loving_knuth we see the /etc/nginx/conf.d/default.conf file has not been moved.

This is helpful to know, and a fundamental concept to grasp.

Let's clean up our leftovers:

$ docker stop b3e782a8943b 994aefb6258f
$ docker rm b3e782a8943b 994aefb6258f

There are shortcuts to these commands. We will cover these a little later in this series, as "with great power comes great responsibility", and all that.

What Happens To My Data When A Docker Container Exits?

To clear up any possible confusion:

Data inside a container remains with that container for the lifetime of the container.

To better understand this let's take a quick example:

$ docker run -d -p 81:80 nginx

dc64c219e9572c2c5365ea0dfc9a9510141d5e13a9aed33cea762dd277927812

$ docker exec -it dc64c21 /bin/bash

root@dc64c219e957:/# touch hello

root@dc64c219e957:/# ls -la hello
-rw-r--r-- 1 root root 0 Sep  2 09:50 hello

Now, unfortunately we don't have a text editor (think vi, vim, pico, etc) installed in the nginx container.

We could create our own image which does install a text editor, but really we don't need one to illustrate this point.

Instead, I have run a new container instance from the nginx base image.

I have then jumped onto the command line for this new container and used touch to create a new, empty file inside the container.

We know from the earlier experiment that this file would not exist in any other container created from the nginx base image. We also know that deleting this file would have no impact on any other containers based off this image.

But what happens if we stop this container? Does the file hang around, or does the container revert back to its original state when we next run it? Let's find out:

root@dc64c219e957:/# exit
exit

$ docker ps -a

CONTAINER ID    IMAGE   COMMAND                  CREATED          STATUS         PORTS                NAMES
dc64c219e957    nginx   "nginx -g 'daemon ..."   5 minutes ago    Up 5 minutes   0.0.0.0:81->80/tcp   inspiring_goldberg

We ran our container in -d / detached mode, so exiting the running container does not stop the container.

Stopping a running container is easy enough:

$ docker stop dc64c219e957
dc64c219e957

$ docker ps -a

CONTAINER ID    IMAGE   COMMAND                  CREATED             STATUS                     PORTS    NAMES
dc64c219e957    nginx   "nginx -g 'daemon ..."   6 minutes ago       Exited (0) 5 seconds ago            inspiring_goldberg

Now, if we restart the container, will the hello file still be there?

$ docker exec dc64 ls -la /

total 56
drwxr-xr-x  31 root root 4096 Sep  2 09:54 .
drwxr-xr-x  31 root root 4096 Sep  2 09:54 ..
-rwxr-xr-x   1 root root    0 Sep  2 09:49 .dockerenv
lrwxrwxrwx   1 root root    7 Jul 23 00:00 bin -> usr/bin
drwxr-xr-x   2 root root 4096 Jul 13 13:04 boot
drwxr-xr-x   5 root root  340 Sep  2 09:57 dev
drwxr-xr-x  41 root root 4096 Sep  2 09:49 etc

# yes, here it is
-rw-r--r--   1 root root    0 Sep  2 09:50 hello

drwxr-xr-x   2 root root 4096 Jul 13 13:04 home
lrwxrwxrwx   1 root root    7 Jul 23 00:00 lib -> usr/lib
lrwxrwxrwx   1 root root    9 Jul 23 00:00 lib32 -> usr/lib32
lrwxrwxrwx   1 root root    9 Jul 23 00:00 lib64 -> usr/lib64
lrwxrwxrwx   1 root root   10 Jul 23 00:00 libx32 -> usr/libx32
drwxr-xr-x   2 root root 4096 Jul 23 00:00 media
drwxr-xr-x   2 root root 4096 Jul 23 00:00 mnt
drwxr-xr-x   2 root root 4096 Jul 23 00:00 opt
dr-xr-xr-x 342 root root    0 Sep  2 09:57 proc
drwx------   2 root root 4096 Sep  2 09:54 root
drwxr-xr-x   4 root root 4096 Sep  2 09:57 run
lrwxrwxrwx   1 root root    8 Jul 23 00:00 sbin -> usr/sbin
drwxr-xr-x   2 root root 4096 Jul 23 00:00 srv
dr-xr-xr-x  13 root root    0 Sep  2 09:16 sys
drwxrwxrwt   2 root root 4096 Jul 26 07:33 tmp
drwxr-xr-x  18 root root 4096 Jul 26 07:33 usr
drwxr-xr-x  16 root root 4096 Sep  2 09:49 var

To quickly explain:

docker exec dc64 ls -la /

We've already seen docker exec used.

This time I am simply passing in enough of the container's unique ID (dc64c219e9572c2c5365ea0dfc9a9510141d5e13a9aed33cea762dd277927812) to be sufficiently unique enough to identify this container. It's like the shortest amount of letters I can type to get away with typing no more :) As crazy as it might sound, as we only have one running container, the shortest I could have gone for here is docker exec d ls -la /, but that would have been even more confusing.

Next, ls -la / is a standard unix command for listing a directories contents in long format (-l) and showing all (-a) files and folders, including hidden files / folders. Then / is the directory I want to list, where / is the system's root directory.

Be Careful When Removing Containers

If you recall, in the previous video we saved ourselves the burden of tidying up after ourselves by passing in the --rm flag:

docker run --rm dfe0dd23e4af

We saw that when this container was stopped, the container instance itself was removed (--rm) from disk.

This is helpful as it means we don't end up with stragglers / unwanted leftovers.

However, as we now know, it also means any data inside that container will be removed also.

To avoid this problem we need to use Docker Volumes.

Docker Without Volumes - In Summary

In this video we have learned that running containers are separate from each other. They do not share data beyond what was originally provided by the Docker Image.

From one perspective this is very useful. If you make a mistake such as accidentally deleting a file on any particular container instance, you can safely remove that container instance without affecting any other containers ran from the same image.

However, from a different perspective this is rather frustrating. Sometimes you want those changes to be available to every container ran from the same image, or even to be able to share the same data between containers from different images.

An example of this would be in running nginx, and php. Here nginx needs the files in order to serve them to site visitors, and php needs the files in order to process the .php code and run our site as expected.

This is where Docker Volumes come into play.

Code For This Course

Get the code for this course.

Episodes