Working with Docker Images

DockerDockerBeginner
Practice Now

Introduction

In this lab, we will explore Docker images, which are the foundation for creating and running containers. We will learn how to pull images from Docker Hub, run containers using different image versions, list and remove images, understand image layers, search for images, and perform basic image tagging. This hands-on experience will provide you with essential skills for working with Docker images effectively. Don't worry if you're new to Docker - we'll guide you through each step with detailed explanations.

Pulling Images from Docker Hub

Docker Hub is a public repository of Docker images, similar to GitHub for code. It's where you can find pre-built images for many popular software applications and operating systems. Let's start by pulling (downloading) the official Nginx image.

Open a terminal on your system. You should see a prompt that looks something like this:

labex:project/ $

Now, let's pull the Nginx image. Type the following command and press Enter:

docker pull nginx

This command tells Docker to download the latest version of the Nginx image from Docker Hub. You should see output similar to this:

Using default tag: latest
latest: Pulling from library/nginx
5040bd298390: Pull complete
d7a91cdb22f0: Pull complete
9cac4850e5df: Pull complete
Digest: sha256:33ff28a2763feccc1e1071a97960b7fef714d6e17e2d0ff573b74825d0049303
Status: Downloaded newer image for nginx:latest

Let's break down what's happening here:

  1. "Using default tag: latest" - When you don't specify a version, Docker assumes you want the latest version.
  2. The next few lines show Docker downloading different "layers" of the image. Each layer represents a set of filesystem changes.
  3. The "Digest" is a unique identifier for this exact version of the image.
  4. The last line confirms that the image has been successfully downloaded.

Now that we've downloaded the image, let's verify that it's on our system. We can do this by listing all the images Docker has locally:

docker images

You should see output similar to this:

REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
nginx         latest    605c77e624dd   2 weeks ago    141MB

This tells us:

  • REPOSITORY: The name of the image (nginx)
  • TAG: The version of the image (latest)
  • IMAGE ID: A unique identifier for this image
  • CREATED: When this version of the image was created
  • SIZE: How much disk space the image takes up

Don't worry if the exact numbers are different - the important thing is that you see an entry for nginx.

If you're curious about what other images are on your system, you might see entries for "jenkins/jenkins" and "gcr.io/k8s-minikube/kicbase". These are pre-installed images that we won't be using in this lab.

Running Different Versions of an Image

Docker allows you to run specific versions of an image by using tags. Tags are like aliases for specific versions of an image. Let's explore this concept with the Python image.

First, let's pull the latest Python image:

docker pull python

You'll see similar output to when we pulled the Nginx image. This is downloading the latest version of Python.

Now, let's pull a specific version of Python, say version 3.7:

docker pull python:3.7

Notice how we added :3.7 after python. This tells Docker to pull version 3.7 specifically, rather than the latest version.

Let's list our Python images to see the different versions:

docker images python

You should see output similar to this:

REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
python       3.7       1f1a7b570fbd   2 weeks ago    907MB
python       latest    98ccd1643c71   2 weeks ago    920MB

Now we have two Python images: one tagged latest (which is actually Python 3.9 or 3.10, depending on when you're doing this lab), and one tagged 3.7.

Let's run containers using these different versions to see the difference:

docker run --rm python:latest python --version

This command does a few things:

  1. docker run creates and starts a new container
  2. --rm tells Docker to remove the container after it exits
  3. python:latest specifies which image to use
  4. python --version is the command to run inside the container

You should see the latest Python version in the output.

Now let's do the same with Python 3.7:

docker run python:3.7 python --version

This time, you should see Python 3.7.x in the output, where x is the latest patch version of Python 3.7.

These commands demonstrate how you can use different versions of the same software by using different image tags. This is incredibly useful when you need to run applications that require specific versions of Python (or any other software).

Listing and Removing Images

As you work with Docker, you'll accumulate images over time. It's important to know how to manage these images, including how to list them and remove ones you no longer need.

Let's start by listing all the images on your system:

docker images

You should see a list of all the images you've pulled so far, including Nginx and the Python images.

Now, let's say we want to remove the Python 3.7 image to free up some space. We can do this using the docker rmi command (rmi stands for "remove image"):

docker rmi python:3.7

If this command succeeds, you'll see output like this:

Untagged: python:3.7
Untagged: python@sha256:1f93c63...
Deleted: sha256:1f1a7b57...
Deleted: sha256:8c75ecde...
...

However, you might instead see an error message like this:

Error response from daemon: conflict: unable to remove repository reference "python:3.7" (must force) - container <container_id> is using its referenced image <image_id>

This error occurs if there's a container (running or stopped) that was created from this image. Docker prevents you from removing images that are in use to maintain system integrity.

To resolve this, we need to remove any containers using this image first. Let's list all containers (including stopped ones):

docker ps -a

Look for any containers that were created from the python:3.7 image. If you find any, remove them using the docker rm command:

docker rm <container_id>

Replace <container_id> with the actual ID of the container you want to remove.

Now try removing the image again:

docker rmi python:3.7

This time it should succeed.

Let's verify that the image has been removed by listing Python images again:

docker images python

You should no longer see the Python 3.7 image in the list.

Understanding Image Layers

Docker images are built using a layered filesystem. Each layer represents a set of filesystem changes. This layered approach allows Docker to be efficient with storage and network usage. Let's explore this concept.

First, let's inspect the layers of the Nginx image we pulled earlier:

docker inspect --format='{{.RootFS.Layers}}' nginx

You'll see output similar to this:

[sha256:2edcec3590a4ec7f40cf0743c15d78fb39d8326bc029073b41ef9727da6c851f sha256:e379e8aedd4d72bb4c529a4ca07a4e4d230b5a1d3f7a61bc80179e8f02421ad8 sha256:b5357ce95c68acd9c9672ec76e3b2a2ff3f8f62a2bcc1866b8811572f4d409af]

Each of these long strings (called SHA256 hashes) represents a layer in the image. Each layer corresponds to a command in the Dockerfile used to build the image.

To better understand layers, let's create a simple custom image. First, create a new file named Dockerfile in your current directory:

nano Dockerfile

In this file, add the following content:

FROM nginx
RUN echo "Hello from custom layer" > /usr/share/nginx/html/hello.html

This Dockerfile does two things:

  1. It starts with the Nginx image we pulled earlier (FROM nginx)
  2. It adds a new file to the image (RUN echo...)

Save and exit the file (in nano, you can do this by pressing Ctrl+X, then Y, then Enter).

Now let's build this image:

docker build -t custom-nginx .

Sample output:

Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM nginx
 ---> 5ef79149e0ec
Step 2/2 : RUN echo "Hello from custom layer" > /usr/share/nginx/html/hello.html
 ---> Running in 2fa43e649234
Removing intermediate container 2fa43e649234
 ---> 73b62663b5c3
Successfully built 73b62663b5c3
Successfully tagged custom-nginx:latest

This command builds a new image based on our Dockerfile and tags it as custom-nginx. The . at the end tells Docker to look for the Dockerfile in the current directory.

Now, let's inspect the layers of our custom image:

docker inspect --format='{{.RootFS.Layers}}' custom-nginx

You'll notice that this image has one more layer than the original Nginx image. This additional layer represents the changes made by our RUN command.

Understanding layers is crucial because:

  1. Layers are cached, speeding up builds of similar images
  2. Layers are shared between images, saving disk space
  3. When pushing or pulling images, only changed layers need to be transferred

Searching for Images on Docker Hub

Docker Hub hosts a vast collection of images. While you can search for images on the Docker Hub website, Docker also provides a command-line tool to search for images directly from your terminal.

Let's start by searching for Nginx images:

docker search nginx

This will return a list of images related to Nginx. The output includes several columns:

  • NAME: The name of the image
  • DESCRIPTION: A brief description of the image
  • STARS: The number of stars the image has on Docker Hub (indicating popularity)
  • OFFICIAL: Whether this is an official image maintained by Docker
  • AUTOMATED: Whether this image is automatically built from a GitHub repository

For example, you might see something like this:

NAME                              DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
nginx                             Official build of Nginx.                        15763     [OK]
jwilder/nginx-proxy               Automated Nginx reverse proxy for docker c...   2088                 [OK]
...

The official Nginx image is typically at the top of this list.

Now, let's try searching for a specific version of Python:

docker search python:3.8

You'll notice that this search doesn't work quite as you might expect. Docker search doesn't support searching for specific tags (like 3.8). Instead, it will search for images with "python:3.8" in their name or description.

To find specific versions of an image, it's often better to:

  1. Search for the general image name (e.g., docker search python)
  2. Visit the Docker Hub website for more detailed information
  3. Use docker pull to download the image and then inspect it locally

Remember, docker search is a quick way to find images, but for more detailed information, the Docker Hub website is often more useful.

Saving and Loading Images

Docker allows you to save images as tar files and load them later. This is useful for transferring images between systems without using a registry, or for backing up images.

Let's start by saving the Nginx image to a file:

docker save nginx > nginx.tar

This command saves the Nginx image to a file named nginx.tar in your current directory. The > symbol is used to redirect the output of the docker save command to a file.

You can verify that the file was created by listing the contents of your current directory:

ls -lh nginx.tar

You should see the nginx.tar file listed, along with its size (which should be over 100MB).

Now, let's remove the Nginx image from our system to simulate transferring the image to a system that doesn't have it:

docker rmi nginx

Verify that the image is gone:

docker images nginx

You should see no results, indicating that the Nginx image has been removed from your system.

Now, let's load the image back from the tar file:

docker load < nginx.tar

The < symbol is used to redirect the contents of the nginx.tar file as input to the docker load command.

After the load completes, verify that the Nginx image is back:

docker images nginx

You should see the Nginx image in the list again, just as it was before we removed it.

This process of saving and loading images can be very useful when you need to:

  • Transfer images to a system without internet access
  • Back up specific versions of images
  • Share custom images with others without using a registry

Image Tagging Basics

Tagging is a way to create aliases for your Docker images. It's commonly used for versioning and organizing images. Let's explore how to tag images.

First, let's create a new tag for our Nginx image:

docker tag nginx:latest my-nginx:v1

This command creates a new tag my-nginx:v1 that points to the same image as nginx:latest. Here's what each part means:

  • nginx:latest is the source image and tag
  • my-nginx is the new image name we're creating
  • v1 is the new tag we're assigning

Now, list your images to see the new tag:

docker images

You should see both nginx:latest and my-nginx:v1 in the list. Notice that they have the same Image ID - this is because they're actually the same image, just with different names.

You can use this new tag to run a container:

docker run -d --name my-nginx-container my-nginx:v1

This command does the following:

  • -d runs the container in detached mode (in the background)
  • --name my-nginx-container gives a name to our new container
  • my-nginx:v1 is the image and tag we're using to create the container

Verify that the container is running:

docker ps

You should see your container in the list of running containers.

Tagging is useful for several reasons:

  1. Version control: You can tag images with version numbers (v1, v2, etc.)
  2. Environment separation: You might tag images for different environments (dev, staging, prod)
  3. Readability: Custom tags can make it clearer what an image is for

Remember, tags are just aliases - they don't create new images, they just create new names that point to existing images.

Summary

In this lab, we explored various aspects of working with Docker images. We learned how to:

  1. Pull images from Docker Hub
  2. Run containers using different image versions
  3. List and remove images
  4. Understand image layers
  5. Search for images on Docker Hub
  6. Save and load images
  7. Perform basic image tagging

These skills form the foundation for effectively managing Docker images in your projects. As you continue your Docker journey, you'll find these operations essential for building and deploying containerized applications.

Remember, Docker images are at the core of how Docker operates. They provide a consistent, portable, and efficient way to package and distribute applications. By mastering these image operations, you're well on your way to becoming proficient with Docker.

Keep practicing these commands and exploring different images. The more you work with Docker, the more comfortable and proficient you'll become. Happy Dockering!

Other Docker Tutorials you may like