What is Docker?

At its core, docker is tooling to manage containers. It is a simplified existing technology to enable to use by the masses.

It enables developers to packaging their applications using containers and integrate directly to CI/CD pipelines. “Build once, run anywhere”

What are Containers?

Container is a group of processes that run in isolation. All processes must be able to run on the shared kernel. Isolation is provided by a kernel feature called linux namespaces. Basically, namespaces gives the group of processes an isolated view of that kernel.

Each container has its own set of namespaces (isolated view):

  • PID - process IDs
  • USER - user and group IDs
  • UTS - hostname and domain name
  • NS - mount points
  • NET - Network devices, stacks and ports
  • IPC - interprocess communications, message queues

cgroups - Another feature of kernel control groups limit and monitor resources to these containers.

Containers are nothing more than these processes that are running inside of these namespaces and monitored by the control groups.

VMs versus containers

VMs are instances of full-blown operating systems while containers are just the basic group of processes that are required to run the instance of operating system.

Why containers are appealing to users?

  • No more “works on my machine”
  • Lightweight and fast
  • Better resource utilization
  • Can fit far more containers than VMs into a host
  • Standard developer to operations interface
  • Ecosystem and tooling

Running your first container

Note: The below instructions are for Mac OS X.

  1. Make sure you have Docker for Mac installed. You need to first run docker app.

  2. Open a terminal on your local computer

    • Run docker container run -t ubuntu top

    • Use the docker container run command to run a container with the ubuntu image, using the top command. The -t flag allocates a pseudo-TTY, which you need for the top to work correctly.

  3. Open another tab in terminal

    • Use the docker container ls command to get the ID of the running container you just created.

    • Then use that <CONTAINER ID> to run bash inside that container using the docker container exec command docker container exec -it <CONTAINER ID> bash.

      Since we are using bash and want to interact with this container from our terminal, use the -it flag to run using interactive mode while allocating a psuedo-terminal.

    • Inside bash run ps -ef to inspect the running processes. You should see only the top process, bash process, and our ps process.

    • For comparison, exit the container, and run ps -ef or top on the host. These commands will work on Linux or Mac. For Windows, you can inspect the running processes using tasklist.

Running multiple containers

  1. Explore the Docker Store.

    • The Docker Store is the public central registry for Docker images. Anyone can share images here publicly. The Docker Store contains community and official images that can also be found directly on the Docker Hub.

    • When searching for images you will find filters for Store vs Community images. Store images include content that has been verified and scanned for security vulnerabilities by Docker. Go one step further and search for “Certified” images, that are deemed enterprise-ready and are tested with Docker Enterprise Edition product. It is important to avoid using unverified content from the Docker Store when developing your own images that are intended to be deployed into the production environment. These unverified images may contain security vulnerabilities or possibly even malicious software.

    • In the next step, you will start a couple of containers using some verified images from the Docker Store: nginx web server, and mongo database.

  2. Run an Nginx server.

    • Let’s run a container using the official Nginx image from the Docker Store. Use docker container run --detach --publish 8080:80 --name nginx nginx

    • We are using a couple of new flags here. The --detach flag will run this container in the background. The --publish flag publishes port 80 in the container (the default port for nginx), using port 8080 on our host. Remember that the NET namespace gives processes of the container their own network stack. The --publish flag is a feature that allows us to expose networking through the container onto the host.

      How do you know port 80 is the default port for nginx? Because it is listed in the documentation on the Docker Store. In general, the documentation for the verified images is very good, and you will want to refer to them when running containers using those images.

      We are also specifying the --name flag, which names the container. Every container has a name. If you don’t specify one, Docker will randomly assign one for you. Specifying your own name makes it easier to run subsequent commands on your container since you can reference the name instead of the id of the container. For example: docker container inspect nginx, instead of docker container inspect 5e1.

      Since this is the first time you are running the nginx container, it will pull down the nginx image from the Docker Store. Subsequent containers created from the Nginx image will use the existing image located on your host.

      Nginx is a lightweight web server. You can access it on port 8080 on your localhost by localhost:8080.

  3. Now, run a mongoDB server.

    • We will use the official mongoDB image from the Docker Store. Instead of using the latest tag (which is the default if no tag is specified), we will use a specific version of the mongo image:3.4. – docker container run --detach --publish 8081:27017 --name mongo mongo:3.4
    • Again, since this is the first time we are running a mongo container, we will pull down the mongo image from the Docker Store. We are using the --publish flag to expose the 27017 mongo port on our host. We have to use a port other than 8080 for the host mapping, since that port is already exposed on our host. Again refer to the official docs on the Docker Store to get more details about using the mongo image.
  4. Access localhost:8081 to see some output from mongo.

  5. Check your running containers with docker container ls

  6. Run docker container stop [container id] for each container in the list. You can also use the names of the containers that you specified before.

    docker container stop d67 ead af5

    You only have to reference enough digits of the ID to be unique. Three digits is almost always enough.

  7. Remove the stopped containers.

    docker system prune is a really handy command to clean up your system. It will remove any stopped containers, unused volumes and networks, and dangling images.

What is a Docker image?

It is a tar file or archive of the file system of the container. They are used for sharing and redistribution.

Docker Registry

  • Push and pull images from the registry
  • Default registry: Docker hub
  • Public and free for public images
  • Many pre-packaged images available
  • Private Registry
  • Self host or cloud provider options

When we get to use docker within an enterprise, you are going to look towards using a private registry or setting up a private registry. There is hosted options available or you can do it yourself or host the registry yourself. When you are hosting the docker registry yourself, then the important thing to see is that the docker registry is actually available as a docker image.So, you can run docker run registry in order to run your private registry.

Creating a docker image

  1. Create a docker file
    • Docker file contains list of instructions for how to construct a container.
  2. Once we have our docker file we pass it to docker build docker build -f Dockerfile and then docker engine will build our docker image.

Creating and building docker images

  1. Create app.py using vi app.py

     from flask import Flask
    
     app = Flask(__name__)
    
     @app.route("/")
     def hello():
     return "Hello, World!"
    
     if __name__ == "__main__":
     app.run(host = '0.0.0.0')
    
    • Press <Esc>

    • Type :x

  2. Create a docker file using vi Dockerfile

     FROM python:3.6.1-alpine
     RUN pip install flask
     CMD ["python","app.py"]
     COPY app.py /app.py
    
    • Press <Esc>

    • Type :x

  3. Run docker image ls to verify that your image shows up in your image list.

Run the Docker image

  1. docker run -p 5001:5000 -d python-hello-world

    -p flag maps a port running inside the container to your host. In this case, you’re mapping the Python app running on port 5000 inside the container to port 5001 on your host. Note that if port 5001 is already in use by another application on your host, you may have to replace 5001 with another value, such as 5002.

  2. Navigate to localhost:5001 in a browser to see the results. You should see “hello world!” on your browser.

  3. Check the log output of the container.

    docker container logs [container id]

The Dockerfile is how you create reproducible builds for your application. A common workflow is to have your CI/CD automation run docker image build as part of its build process. Once images are built, they will be sent to a central registry, where they can be accessed by all environments (such as a test environment) that need to run instances of that application. In the next step, you will push your custom image to the public docker registry: the docker hub, where it can be consumed by other developers and operators.

Push to a central registry

  1. Navigate to Docker Hub and create a free account, if you haven’t already. You can use the Docker Hub as your central registry. Docker hub is a free service to publicly store available images, or you can pay to store private images. Most organizations that heavily use Docker will set up their own registry internally. To simplify things, we will use the Docker Hub, but the following concepts apply to any registry.

  2. Log in to the Docker registry account by typing docker login on your terminal. Type in your docker ID/username and docker password.

  3. Tag your image with your username. The Docker Hub naming convention is to tag your image with [dockerhub username]/[image name]. To do this, tag your previously created image python-hello-world to fit that format.

    docker tag python-hello-world [dockerhub username]/python-hello-world

  4. Once you have a properly tagged image, use the docker push command to push your image to the Docker Hub registry.

    docker push [dockerhub username]/python-hello-world

  5. Check out your image on docker hub in your browser. Navigate to Docker Hub, and go to your profile to see your newly uploaded image. Now that your image is on Docker Hub, other developers and operations can use the docker pull command to deploy your image to other environments.

Deploy a change

  1. Update app.py by replacing the string “Hello World” with “Hello Beautiful World!” in app.py. Your file should have the following contents:

     from flask import Flask
    
     app = Flask(__name__)
    
     @app.route("/")
     def hello():
     return "Hello Beautiful World!"
    
    
     if __name__ == "__main__":
     app.run(host='0.0.0.0')
    
  2. Now that your app is updated, you need to rebuild your app and push it to the Docker Hub registry. First rebuild, this time use your Docker Hub username in the build command.

    docker image build -t [dockerhub username]/python-hello-world .

    docker push [dockerhub username]/python-hello-world

There is a caching mechanism in place for pushing layers too. Docker Hub already has all but one of the layers from an earlier push, so it only pushes the one layer that has changed.

When you change a layer, every layer built on top of that will have to be rebuilt. Each line in a Dockerfile builds a new layer that is built on the layer created from the lines before it. This is why the order of the lines in your Dockerfile is important. We optimized our Dockerfile so that the layer that is most likely to change (COPY app.py /app.py) is the last line of the Dockerfile. Generally for an application, your code changes at the most frequent rate. This optimization is particularly important for CI/CD processes, where you want your automation to run as fast as possible.

Clean Up

  1. Get a list of the containers running using docker container ls

  2. Run docker container stop [container id] for each container in the list that is running.

3, Remove the stopped containers. docker system prune is a really handy command to clean up your system. It will remove any stopped containers, unused volumes and networks, and dangling images.

Note:

  1. A Dockerfile is a recipe for creating Docker images.

  2. A Docker image gets built by running a Docker command (which uses that Dockerfile).

  3. A Docker container is a running instance of a Docker image.

Useful resources:

differences-between-a-dockerfile-docker-image-and-docker-container

Kubernetes vs. Docker: What Does It Really Mean?