A Week of Learning: Docker

by Cameron Gould on Thu Jan 06 2022

docker2022learning in public

docker logo

This week, I took a few hours to really sit down and understand the core concepts of Docker. I’ve used the tool many times both in personal projects and in industry, but I never really strapped in and learned the core concepts. That changed, and I certainly feel enlightened. This week, I explored the core concepts of Docker. Let’s dive in!

The Basics

Docker, at its core, is very similar to a virtual machine. The purpose of Docker is to create an environment that can be run on any machine, regardless of the hardware or operating system installed on the machine being used. This is achieved by creating a very lightweight environment which is akin to a virtual machine, but smaller.

The Docker Flow

The Docker flow involves taking a configuration called a docker image and using it to create a docker container. An image is the configuration, and the container is the actual environement that is built using the image. Images can be configured via a dockerfile. you can then use the docker run command to create a container from an image. While you can build your own images, there are many pre-built images that exist on Docker Hub. These are useful because you can find images that are pre-configured for certain development environments, which saves you time.

Containers have three states: running, stopped, and… gone. Docker containers can be configured to run in a “detached” instance, where it runs continuously in the background, by using the -d flag when using docker run. You can also use docker ps in a new terminal window to see what containers are running on your system. You’ll be able to see a lot of information about the containers, including their name, ID, status, and a few other details. The status can tell you if a container is running or stopped. A stopped container usually stops due to some error that persists in the container (maybe some code failed and caused the container to crash). You can use docker logs to see the console output of your container, which is helpful with bug fixes.

You can use docker exec to enter a detached container’s file subsystem in your terminal, and use cmd+p and then cmd+q to exit the container without stopping it. If you wish to stop your container upon exit, use cmd+d instead.

Docker images do not store changes when you run a container from them. If you add a file to a container, and then create another container using the same image, that new file will not appear in the new container. For that, you’ll want to perform a docker commit. This command creates a new image based on the container that you committed. This means that any files which were added to the container now exist in the new image. This is sort of a version control similar to Git.

That’s the Docker flow: an image uses docker run to become a container → files within a container may evolve over time, and you’ll want to update your images to reflect those changes → docker commit will create a new image, which can be used to start the cycle over again.

Docker Run

Docker run has a lot going on. There are many flags you can set which can help create very specific containers. -it is the “interactive terminal” flag which displays the container in a visually helpful way in the terminal. -d creates a detached container which runs in the background. --rm will delete the container upon exit automatically. --memory <max> lets you set an upper limit on how much space the docker container can take up. --cpu-shares dynamically determines how much CPU usage the docker container is allowed, relative to other containers. --cpu-quota is a hard limit on CPU usage, rather than it being dynamic. -p is the “publish” flag which can be used to specify which port(s) are exposed on the docker container. And there are plenty more!

Networking and Exposing Ports

As stated above, the -p flag is used to expose ports. The syntax is -p 3000:3000 where the first port is the port inside the docker container which is being exposed, while the scond port is the port on the machine running the container. To translate to English, I am telling Docker to expose the container’s port 3000, and make it available via my machine’s port 3000.

Here’s a cool thing Docker can do: dynamically decide which port on your machine to use. Just provide one port instead of two (-p 3000) and Docker will find an open port on your machine to use. If you want to find what port Docker ended up using, type docker port echo-server in a new terminal window and it will show you the source container name, and which port on your machine that you can use to access the exposed container port.

You can also create virtual ports to allow for multiple containers to communicate with one another within their own network. You can list your created docker networks with docker network ls, and create a network with docker network create <name of network>. You can then tell your container to use your network by including the network in your run commmand: docker run --rm -it --net <name of network>. If you add another container to the network, they can communicate inside a common network!

Volumes

Volumes are one of the final concepts I learned about this week. Volumes are a way for Docker containers to store data. The point of the container is to stay fairly small and portable, so saving a bunch of data in the container is not a good idea. The proper way to store data that the container uses to perform functions, or creates as a result of function performance, is to use a volume.

There are two types of volumes. The first type of volume is a persistent volume. This volume is like a permanent data store. This is almost like a data base persistency layer, wherein all the data you create and save in this volume will stick around, even when the container stops. Remember, when you build and rebuild a container from the same image (without committing the changes) any changes you made to the container will vanish. The persistency layer allows you to save things that can stick around permanently… until you delete them from the volume of course.

The second type of volume is an ephemeral volume. Unlike persistent volumes, the ephemeral volume goes away once the docker container stops using them. It’s almost like summoning more storage on the fly. This is a very efficient volume because you don’t hold on to/reserve storage that you aren’t using, and you also delete stuff once you’re done. This is useful when you need a little extra space to cache data for processing, but don’t need to keep it around.

That’s All!

That’s what I picked up this week! I’m starting this weekly learning to help boost my skills in the software industry. I think it will be a fun experience! Feel free to join me in this journey. Hopefully after reading this, you have a better understanding of Docker as well.