Skip to content

Instantly share code, notes, and snippets.

@nikkomiu
Created April 11, 2019 13:42
Show Gist options
  • Save nikkomiu/ac13f81786455d9c8daa7d85dab0f6c8 to your computer and use it in GitHub Desktop.
Save nikkomiu/ac13f81786455d9c8daa7d85dab0f6c8 to your computer and use it in GitHub Desktop.

Getting Started with Docker

This document is meant to be a starting guide to Docker and the components of Docker.

Introduction

What is Docker

Docker allows for easy deployment of applications into a sandbox environment that runs on the host operating system (i.e. Linux). The key benefit to Docker is that it allows users to package an application with all of its dependencies into a standardized unit for software development. Unlike VMs, containers don't have the heavy overhead of a Guest OS and therefore have higher efficient usage of the system resources avaliable.

Containers vs Virtual Machines

Since virtual machines are very popular in modern infrastructure it will probably be best to explain how containers are different that virtual machines at a high level.

A container runs natively on Linux and shares the kernel of the host machine with other containers. It runs a discrete process, taking no more memory than any other executable, making it lightweight.

By contrast, a virtual machine (VM) runs a full-blown “guest” operating system with virtual access to host resources through a hypervisor. In general, VMs provide an environment with more resources than most applications need.

Basic Docker Terminology

Image

A package with all the dependencies and information needed to create a container. An image contains all of the dependencies (such as frameworks) plus deployment and execution configuration to be used by the container runtime. Usually an image derives from multiple base images that are layers stacked on top of each other to form the container's filesystem. Important: An image is immutable once it has been created.

Dockerfile

A text file containing instructions on how to build an image. Similarly to a bash script, the first line states the base image to begin with followed by instructions for installing required programs, copying files, etc.

Build

The action of building an image based on the contents of the Dockerfile.

Container

An instance of a Docker image. A container represents the execution of a single application instance. It consists of the Docker image, execution environment, and a set of instructions to execute. In Docker you scale a service by starting multiple containers from the same Docker image.

Volume

A writable filesystem that the container can use to write persistent data. Since images are read-only and most systems need to write to the filesystem, volumes add the writable filesystem to a container.

Tag

A label that can be applied to an image so that different images or versions of the same image can be easlily identified.

Repository

A collection of related Docker images that are tagged to indicate the version of the image. Some repos contain multiple variants of a specific image, such as an image including the SDK (heavier), a runtime only image (lighter), etc. These variants can be identified by different tagging schemes. There can also be platform variants for a single image based on a tag such as Linux and Windows images.

Registry

A service that provides access to a set of Docker images. The default registry is Docker Hub (hub.docker.com) (owned by the Docker organization). It contains many of the most popular and most used open source images. Companies usually have a registry to store the images for a large number of groups/teams.

Installing Docker

TODO: ADD REFERENCE TO MARCY'S INSTALL GUIDE

TODO: ADD VALIDATION STEP TO ENSURE DOCKER IS WORKING PROPERLY

Running a Basic Container

We will start with running a basic container that just outputs some text to the log and exits when it is done. This exercise is the "Hello World!" of Docker.

During this section we will be using a very popular Docker Linux distribution called Alpine. Alpine is a lightweight minimalistc OS that makes it perfect for running Docker.

Lets start by starting the container on our Docker instance:

docker run hello-world

Expected Output:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete
Digest: sha256:2557e3c07ed1e38f26e389462d03ed943586f744621577a99efb77324b0fe535
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

This will start the docker container based on the hello-world image and run it in the foreground. If we want to run the container as a background job we can use the -d flag and Docker will not hold the terminal session or write logs out to the terminal window. When the container has exited your terminal session will be released from the container.

You will notice that in the output it starts with "Unable to find image". This is because the image has not yet been pulled down from Docker Hub and needs to be downloaded before it can be started. Also, if you have a keen eye you will notice that when we ran the command we just specified hello-world but Docker said it couldn't find hello-world:latest the latest part is the tag for that image and if no tag is specified the default tag is latest.

Everything in the output from Hello from Docker! onward is the log from the container as it is running. We will get more into how Docker logging works later.

Getting Container Information

We can now get a list of all of the containers:

docker ps -a

Output:

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
9910ae39e084        hello-world         "/hello"            3 minutes ago       Exited (0) 3 minutes ago                       determined_hamilton

This will show us a list of all of the containers (because of the -a flag) on the Docker instance. If we only want to see the running containers we can run docker ps without the -a and get only those containers that are still running. However, since this "hello-world" example container was very simple in that it only outputted some info to the log and exited it isn't still running.

Getting Container Logs

We can also go back and view the logs for the container even though it isn't running anymore by running the following command (Note you will need to change the 9910ae39e084 part of the command to reflect the container id from the docker ps -a command):

docker logs 9910ae39e084

Logs will be stored on the disk until the container is removed. This is great for development environments where you may want to see why a container crashed. However, in a distributed Docker environment (like Kubernetes) you will not be able to see container logs for long after the container has exited. Which is why Kubernetes ships with a standard way to output logs to a centralized logging service such as Elasticsearch.

Fun Fact: When we got the logs above we got the logs for the container id 9910ae39e084. However, we only need to specify enough of the container id for the results to be unique so since this was the only container running we only needed to specify the first character 9 (since that's all it would take for that container id to be unique)

Removing a Container

Now that we are finished using this container we can remove it with the following (again replacing 9910ae39e084 with your container id):

docker rm 9910ae39e084

Output:

9910ae39e084

This will remove all of the runtime parts of the container and leave the immutable image we based the container off of alone. You will notice that this command only outputted the container id again, this is because Docker tries to make it easy to chain commands together where we could take the output of one command to feed the input of another command.

Removing an Image

If we want to also remove the image that the hello-world container was built from we can do so with:

docker rmi hello-world

Output:

Untagged: hello-world:latest
Untagged: hello-world@sha256:2557e3c07ed1e38f26e389462d03ed943586f744621577a99efb77324b0fe535
Deleted: sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e
Deleted: sha256:af0b15c8625bb1938f1d7b17081031f649fd14e6b233688eea3c5483994a66a3

This will remove the tags from the image and then delete the image from the system.

Pulling an Image

Now that we have went over some of the basics of running containers lets get into a more common use case for cotainerization, a web service. For this we will keep with a basic web service by creating an NGINX container that serves some static HTML content to a user.

This time we can pre-pull the Docker image for NGINX by running:

docker pull nginx

Output:

Using default tag: latest
latest: Pulling from library/nginx
27833a3ba0a5: Pull complete
e83729dd399a: Pull complete
ebc6a67df66d: Pull complete
Digest: sha256:c8a861b8a1eeef6d48955a6c6d5dff8e2580f13ff4d0f549e082e7c82a8617a2
Status: Downloaded newer image for nginx:latest

This command will download the image from Docker Hub and put it on our local machine so we can start a new container based on that image without having to pull it down when it tries to start the contianer.

Running a Web Container

This section will cover:

  • Running a Container Exposing a Port
  • Interacting With a Running Container in Terminal
  • Building an Image from a Dockerfile
  • Running a Custom Docker Image

To get started we can go to the NGINX Info on Docker Hub and view details about the NGINX image. The Docker Hub page will usually contain a description of the image, a list of the tags available, some usage details, and the License. We can also see on the Docker Hub page in the top right corner the command we used in the previous section to pull the image down locally.

From the content on the Docker Hub page for NGINX we can start up a basic server by running:

docker run -d -p 8080:80 nginx

Output:

167eb5d3711efa2e7049397a4d0dcaedc64f56d93292e18bb7b22a38a275df23

This command did the following:

  • Start a new container based on the nginx image
  • Run the container in the background because of the -d flag
  • Expose the container port 80 to the host port 8080 because of the -p 8080:80 argument

The resulting output of starting this container was just the longform of the container id for the newly started container. This would allow you to pipe that output into subsequent commands for Docker (which is similar to how Git commits work in that you usually work with the shorthand SHA but the full SHA still exists).

Now that we have started the container we should be able to see it in the process list (ps):

docker ps

Output:

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
167eb5d3711e        nginx               "nginx -g 'daemon of…"   2 minutes ago       Up 2 minutes        0.0.0.0:8080->80/tcp   quizzical_meitner

This shows us that the container with our container id is running, what the command it's running is, when it was created, its status, ports exposed, and the name of the running container (Docker will automatically generate this if it is not specified with the --name my-cool-nginx argument)

So now that the container is running and our HTTP port is exposed on 8080 we can go to http://localhost:8080 to view the page. We should see a heading with Welcome to nginx! and some more info below it.

Interacting with a Running Container

While that container is running we can log into it

Removing a Container While it is Running

We can try to remove the container while it is still running. However, Docker is not going to allow us to remove the container while it is running:

docker rm 167eb5d3711e

Output:

Error response from daemon: You cannot remove a running container 167eb5d3711efa2e7049397a4d0dcaedc64f56d93292e18bb7b22a38a275df23. Stop the container before attempting removal or force remove

To remove the container you can either:

  • Stop the container (docker stop <container_id>) then remove it (docker rm 167eb5d3711e)
  • Force the removal of the container with docker rm -f 167eb5d3711e

Removing an Image While a Container is Running

We can try to remove the image while the NGINX container is running. However, since there is currently a container using the image it cannot be deleted:

docker rmi nginx

Output:

Error response from daemon: conflict: unable to remove repository reference "nginx" (must force) - container 167eb5d3711e is using its referenced image 2bcb04bdb83f

If you would really like to delete this image regardless of containers using it you can use the -f flag to force the removal of the image which will not actually delete the image from the disk but just remove the tag from the image so it can be easily cleaned up later.

Creating a Docker Image from a Dockerfile

Now that we have gone over running pre-built containers we should talk about creating a container that we built ourselves. For this we will be creating a Dockerfile in a project directory on our system. Dockerfile is the default name of the file for a Docker image creation script.

We can write a simple Dockerfile in our project directory as:

Dockerfile

FROM nginx

COPY www /usr/share/nginx/html

This Dockerfile has two components in it. The first is the FROM command which is required to be the first command in a Dockerfile. The FROM specifies a Docker image that we want to base our image off of. The second command is COPY which copies a file or directory from the local system to the Dockerfile. So in this case COPY is copying the local www folder to the container's folder /usr/share/nginx/html.

With this contianer we will want to create that www folder with a simple index.html file in it for NGINX to serve:

www/index.html

<html>
<body>
  <p>Hello from Docker!</p>
</html>

Now that we have the project set up your directory structure should look like:

my-project/
  |- www/
  |  |- index.html
  |- Dockerfile

Note: If you look at the Docker Hub page for NGINX you will see the Hosting some simple static content heading that talks about the Dockerfile we just created (for reference).

Managing Our Container

We can manage the run state of our container with some basic commands:

docker stop 167eb5d3711e
docker start 167eb5d3711e
docker restart 167eb5d3711e

All of these commands will return the container ids that were stopped. They all just take the container id(s) or container name(s) as their argument.

Building a Docker Image

Now that we have created our simple project we are ready to build our Docker Image:

docker build -t my-nginx .

Output:

Sending build context to Docker daemon  3.584kB
Step 1/2 : FROM nginx
 ---> 2bcb04bdb83f
Step 2/2 : COPY www /usr/share/nginx/html
 ---> 775008b2c29c
Successfully built 775008b2c29c
Successfully tagged my-nginx:latest

This command will run the actions we specified in the Dockerfile and build our image. The -t my-nginx is how we tag an image that we have created. In this case, we are tagging the image to be called my-nginx. The final . specifies the directory we want the Dockerfile to build in (i.e. the working directory for the build). The . in UNIX file structures represents the current directory.

Fun Fact: The Dockerfile does not have to be called Dockerfile. However, most IDEs understand Dockerfile as a file extension so it is recommended if you have additional Dockerfiles you prefix Dockerfile with the name of the image (i.e. dev.Dockerfile) so IDEs will add thier features to that file. When using a custom named Dockerfile specify with the build parameter of -f dev.Dockerfile where dev.Dockerfile is the name of a Dockerfile.

Viewing Local Images

We can see that the image has been created by running:

docker images

Output:

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
my-nginx            latest              775008b2c29c        3 seconds ago       109MB
nginx               latest              2bcb04bdb83f        2 weeks ago         109MB

This command lists out all of the container images that are currently on your local machine, when they were created, and how big they are.

If you notice this outputted our custom image that we created an earlier section so we should be able to start that container now.

Running a Custom Image

Now that we have our custom container created we can run it just as simply as we did before with the plain NGINX image:

docker run -d -p 8089:80 my-nginx

Output:

fb1191a9c10e470709faf3547161455bc9c7bb17c762be07605fe2df5433e141

Note: If you have trouble getting the container to start try changing the port. It may be being used by your system from some other process.

If you notice there are only two differences between this command and the last time when we ran the NGINX container. The first is that we used a different port 8089 since multiple Docker containers cannot expose their container port on the same host port. The second is that we used our new container's image name to create our new container off of.

Now that the container is created and running we can view the page that we created by going to http://localhost:8089.

If everything worked, congratulations! You've just created and ran your first custom Docker image.

Recap of Useful CLI Commands

  • docker ps is the command for listing all running containers (append a -a to the command and you will see all containers running or not).
  • docker run <flags> <image> <optional_entrypoint> is the command for starting a new container based on an image.
  • docker build <flags> <directory> is the command for building a custom image. Use -f mycustom.Dockerfile to specify a different dockerfile. Usually you will use . for the directory (meaning this directory).
  • docker exec <flags> <container_id> <command> is the command that allows you to execute commands inside of a container. Remember, you can run in "interactive" mode with the -it flags.
  • docker start <container_id> is the command to start a stopped container.
  • docker stop <container_id> is the command to stop a running container.
  • docker restart <container_id> is the command to restart a running container (or it will start a stopped one).
  • docker rm <flags> <container_id> is the command to remove a container from the system. Remember, you can force a running container to be removed with the -f flag.
  • docker pull <image_name>
  • docker system prune

Kitematic

Now that we have covered the CLI for Docker it is worth mentioning Kitematic. Kitematic is an open source GUI for Docker. It is primarily useful when you want to run containers on your local machine as it gives some great information in a simple interface.

To download Kitematic click here and download the zip for your system.

Building a Custom Container

Dockerfile Reference

Services using Compose

Docker Compose Reference

Final Thoughts

There is a lot more to Docker that this does not cover. So to learn more about Docker, how it works, and what options are avaliable you should look at:

Reference

This document was created using the following resources:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment