4: The big picture
The aim of this chapter is to paint a quick big-picture of what Docker is all about before we dive in deeper in later chapters.
We’ll break this chapter into two:
- The Ops perspective
- The Dev perspective
In the Ops Perspective section, we’ll download an image, start a new container, log in to the new container, run a command inside of it, and then destroy it.
In the Dev Perspective section, we’ll focus more on the app. We’ll clone some app-code from GitHub, inspect a Dockerfile, containerize the app, run it as a container.
These two sections will give you a good idea of what Docker is all about and how the major components fit together. It’s recommended that you read both sections to get the dev and the ops perspectives. DevOps anyone?
Don’t worry if some of the stuff we do here is totally new to you. We’re not trying to make you an expert in this chapter. This is about giving you a feel of things — setting you up so that when we get into the details in later chapters, you have an idea of how the pieces fit together.
If you want to follow along, all you need is a single Docker host with an internet connection. I recommend Docker Desktop for your Mac or Windows PC. However, the examples will work anywhere that you have Docker installed. We’ll be showing examples using Linux containers and Windows containers.
If you can’t install software and don’t have access to a public cloud, another great way to get Docker is Play With Docker (PWD). This is a web-based Docker playground that you can use for free. Just point your web browser to https://labs.play-with-docker.com/ and you’re ready to go (you’ll need a Docker Hub or GitHub account to be able to login).
As we progress through the chapter, we may use the terms “Docker host” and “Docker node” interchangeably. Both refer to the system that you are running Docker on.
The Ops Perspective
When you install Docker, you get two major components:
- the Docker client
- the Docker daemon (sometimes called the “Docker engine”)
The daemon implements the runtime, API and everything else required to run Docker.
In a default Linux installation, the client talks to the daemon via a local IPC/Unix socket at /var/run/docker.sock
. On Windows this happens via a named pipe at npipe:////./pipe/docker_engine
. Once installed, you can use the docker version
command to test that the client and daemon (server) are running and talking to each other.
If you get a response back from the Client
and Server
, you’re good to go.
If you are using Linux and get an error response from the Server component, make sure that Docker is up and running. Also, try the command again with sudo
in front of it: sudo docker version
. If it works with sudo
you will need to add your user account to the local docker
group, or prefix the remainder of the commands in the book with sudo
.
Images
It’s useful to think of a Docker image as an object that contains an OS filesystem, an application, and all application dependencies. If you work in operations, it’s like a virtual machine template. A virtual machine template is essentially a stopped virtual machine. In the Docker world, an image is effectively a stopped container. If you’re a developer, you can think of an image as a class.
Run the docker image ls
command on your Docker host.
If you are working from a freshly installed Docker host, or Play With Docker, you will have no images and it will look like the previous output.
Getting images onto your Docker host is called “pulling”. If you are following along with Linux, pull the ubuntu:latest
image. If you are following along on Windows, pull the mcr.microsoft.com/powershell:lts-nanoserver-1903
image.
Windows images can be large and take a long time to pull.
Run the docker image ls
command again to see the image you just pulled.
We’ll get into the details of where the image is stored and what’s inside of it in later chapters. For now, it’s enough to know that an image contains enough of an operating system (OS), as well as all the code and dependencies to run whatever application it’s designed for. The ubuntu
image that we’ve pulled has a stripped-down version of the Ubuntu Linux filesystem, including a few of the common Ubuntu utilities. The mcr.microsoft.com/powershell:lts-nanoserver-1903
image contains a Windows Server Core OS plus PowerShell.
If you pull an application container such as nginx
or mcr.microsoft.com/windows/servercore/iis
, you will get an image that contains some OS, as well as the code to run either NGINX
or IIS
.
It’s also worth noting that each image gets its own unique ID. When referencing images, you can refer to them using either IDs
or names. If you’re working with image ID’s, it’s usually enough to type the first few characters of the ID — as long as it’s unique, Docker will know which image you mean.
Containers
Now that we have an image pulled locally, we can use the docker container run
command to launch a container from it.
For Linux:
For Windows:
Look closely at the output from the previous commands. You should notice that the shell prompt has changed in each instance. This is because the -it
flags switch your shell into the terminal of the container — you are literally inside of the new container!
Let’s examine that docker container run
command.
docker container run
tells the Docker daemon to start a new container. The -it
flags tell Docker to make the container interactive and to attach the current shell to the container’s terminal (we’ll get more specific about this in the chapter on containers). Next, the command tells Docker that we want the container to be based on the ubuntu:latest
image (or the mcr.microsoft.com/powershell:lts-nanoserver-1903
image if you’re following along with Windows). Finally, we tell Docker which process we want to run inside of the container. For the Linux example we’re running a Bash shell, for the Windows container we’re running PowerShell.
Run a ps
command from inside of the container to list all running processes.
Linux example:
Windows example:
The Linux container only has two processes:
- PID 1. This is the
/bin/bash
process that we told the container to run with thedocker container run
command. - PID 9. This is the
ps -elf
command/process that we ran to list the running processes.
The presence of the ps -elf
process in the Linux output can be a bit misleading as it is a short-lived process that dies as soon as the ps
command completes. This means the only long-running process inside of the container is the /bin/bash
process.
The Windows container has a lot more going on. This is an artefact of the way the Windows Operating System works. However, even though the Windows container has a lot more processes than the Linux container, it is still a lot less than a regular Windows Server.
Press Ctrl-PQ
to exit the container without terminating it. This will land your shell back at the terminal of your Docker host. You can verify this by looking at your shell prompt.
Now that you are back at the shell prompt of your Docker host, run the ps
command again.
Notice how many more processes are running on your Docker host compared to the container you just ran. Windows containers run far fewer processes than Windows hosts, and Linux containers run far less than Linux hosts.
In a previous step, you pressed Ctrl-PQ
to exit from the container. Doing this from inside of a container will exit you from the container without killing it. You can see all running containers on your system using the docker container ls
command.
The output above shows a single running container. This is the container that you created earlier. The presence of the container in this output proves that it’s still running. You can also see that it was created 7 minutes ago and has been running for 7 minutes.
Attaching to running containers
You can attach your shell to the terminal of a running container with the docker container exec
command. As the container from the previous steps is still running, let’s make a new connection to it.
Linux example:
This example references a container called “vigilant_borg”. The name of your container will be different, so remember to substitute “vigilant_borg” with the name or ID of the container running on your Docker host.
Windows example:
This example references a container called “pensive_hamilton”. The name of your container will be different, so remember to substitute “pensive_hamilton” with the name or ID of the container running on your Docker host.
Notice that your shell prompt has changed again. You are logged into the container again.
The format of the docker container exec
command is: docker container exec <options> <container-name or container-id> <command/app>
. In our examples, we used the -it
options to attach our shell to the container’s shell. We referenced the container by name, and told it to run the bash shell (PowerShell in the Windows example). We could easily have referenced the container by its hex ID.
Exit the container again by pressing Ctrl-PQ
.
Your shell prompt should be back to your Docker host.
Run the docker container ls
command again to verify that your container is still running.
Stop the container and kill it using the docker container stop
and docker container rm
commands. Remember to substitute the names/IDs of your own containers.
Verify that the container was successfully deleted by running the docker container ls
command with the -a
flag. Adding -a
tells Docker to list all containers, even those in the stopped state.
You’ve just pulled a Docker image, started a container from it, attached to it, executed a command inside it, stopped it, and deleted it.
The Dev Perspective
Containers are all about the apps.
In this section, we’ll clone an app from a Git repo, inspect its Dockerfile, containerize it, and run it as a container.
The Linux app can be cloned from: https://github.com/nigelpoulton/psweb.git
The Windows app can be cloned from: https://github.com/nigelpoulton/win-web.git
The rest of this section will focus on the Linux NGINX example. However, both examples are containerizing simple web apps, so the process is the same. Where there are differences in the Windows example we will highlight them to help you follow along.
Run all of the following commands from a terminal on your Docker host.
Clone the repo locally. This will pull the application code to your local Docker host ready for you to containerize it.
Be sure to substitute the following repo with the Windows repo if you are following along with the Windows example.
Change directory into the cloned repo’s directory and list its contents.
The Linux example is a simple nodejs web app. The Windows example is an IIS server running some static HTML.
Both Git repos contain a file called Dockerfile
. This is a plain-text document that tells Docker how to build an app and dependencies into a Docker image.
List the contents of the Dockerfile.
The contents of the Dockerfile in the Windows example are different. However, this isn’t important at this stage. For now, it’s enough to understand that each line represents an instruction that Docker uses to build an image.
At this point we have pulled some application code from a remote Git repo. We also have a Dockerfile containing instructions on how to build the app into a Docker image.
Use the docker image build
command to create a new image using the instructions in the Dockerfile. This example creates a new Docker image called test:latest
.
The command is the same for the Linux and Windows examples, and be sure to run it from within the directory containing the app code and Dockerfile.
Note: It may take a long time for the build to finish in the Windows example. This is because of the image being pulled is several gigabytes in size.
Once the build is complete, check to make sure that the new test:latest
image exists on your host.
You have a newly-built Docker image with the app and dependencies inside.
Run a container from the image and test the app.
Linux example:
Open a web browser and navigate to the DNS name or IP address of the Docker host that you are running the container from, and point it to port 8080. You will see the following web page.
If you are following along with Docker for Windows or Docker for Mac, you will be able to use localhost:8080
or 127.0.0.1:8080
. If you’re following along on Play With Docker, you will be able to click the 8080
hyperlink above the terminal screen.
Windows example:
Open a web browser and navigate to the DNS name or IP address of the Docker host that you are running the container from, and point it to port 8080. You will see the following web page.
The same rules apply if you’re following along with Docker Desktop or Play With Docker.
Well done. You’ve taken some application code from a remote Git repo and built it into a Docker image. You then ran a container from it. We call this “containerizing an app”.
Chapter Summary
In the Ops section of the chapter you downloaded a Docker image, launched a container from it, logged into the container, executed a command inside of it, and then stopped and deleted the container.
In the Dev section, you containerized a simple application by pulling some source code from GitHub and building it into an image using instructions in a Dockerfile. You then ran the containerized app.
This big picture view should help you with the up-coming chapters where we will dig deeper into images and containers.