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 engine (sometimes called the “Docker daemon”)
The engine implements the runtime, API and everything else required to run containers.
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 all docker
commands 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 images
command on your Docker host.
If you are working from a freshly installed Docker host, or Play With Docker, you’ll have no images and it will look like the previous output.
Getting images onto your Docker host is called pulling. Pull the ubuntu:latest
image.
Run the docker images
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 and a few of the common Ubuntu utilities.
If you pull an application container, such as nginx:latest
, you’ll get an image with a minimal OS as well as the code to run the app (NGINX).
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 run
command to launch a container from it.
Look closely at the output from the previous command. You’ll see that the shell prompt has changed. This is because the -it
flags switch your shell into the terminal of the container — your shell is now inside of the new container!
Let’s examine that docker run
command.
docker run
tells Docker 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. Finally, it tells Docker which process we want to run inside of the container – a Bash shell.
Run a ps
command from inside of the container to list all running processes.
There are only two processes:
- PID 1. This is the
/bin/bash
process that we told the container to run with thedocker 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.
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.
Pressing Ctrl-PQ
from inside a container will exit you from the container without killing it. You can see all running containers on your system using the docker ps
command.
The output shows a single running container. This is the one you created earlier and proves it’s still running. You can also see 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 exec
command. As the container from the previous steps is still running, let’s make a new connection to it.
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.
Notice that your shell prompt has changed again. You are logged into the container again.
The format of the docker exec
command is: docker exec <options> <container-name or container-id> <command/app>
. We used the -it
flags to attach our shell to the container’s shell. We referenced the container by name and told it to run the bash shell. 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 ps
command again to verify that your container is still running.
Stop the container and kill it using the docker stop
and docker rm
commands. Remember to substitute the names/IDs of your own containers.
It may take a few seconds for the container to gracefully stop.
Verify that the container was successfully deleted by running the docker ps
command with the -a
flag. Adding -a
tells Docker to list all containers, even those in the stopped state.
Congratulations, 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
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.
Change directory into the cloned repo’s directory and list its contents.
The app is a simple nodejs web app running some static HTML.
The Dockerfile is a plain-text document that tells Docker how to build the app and dependencies into a Docker image.
List the contents of the Dockerfile.
For now, it’s enough to know that each line represents an instruction that Docker uses to build the app into an image.
At this point we’ve pulled some application code from a remote Git repo and we’ve looked at the application’s Dockerfile that contains the instructions Docker uses to build it as an image.
Use the docker build
command to create a new image using the instructions in the Dockerfile. This example creates a new Docker image called test:latest
.
Be sure to run the command from within the directory containing the app code and Dockerfile.
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.
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’re following along on Docker Desktop, you’ll be able to connect to 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.
Well done. You’ve copied some application code from a remote Git repo, built it into a Docker image, and ran it as a container. 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’ll dig deeper into images and containers.