Understanding Docker: A Beginner’s Guide for Junior DevOps Engineer

Andrey Byhalenko
DevOps Manuals and Technical Notes
9 min readOct 6, 2023

Understanding what Docker is, what problems it solves, and how it works is one of the basic requirements for a DevOps engineer.

By the end of this article, you will have a basic understanding of the Docker Engine and its components.

Imagine you’re a developer tasked with creating a web application for notifications.

Your design consists of three separate components: a User Interface (UI), a Backend, and a Notification Service. Each component lives in its own GitHub repository.

First, you will need to create three GitHub repos: one for the UI, one for the backend, and another for the notification service.

For your backend, you’ve chosen Redis as a caching service and MySQL as your primary database, so you need to install them on your operating system.

Once your code is ready, you have to ensure all services and dependencies are properly installed on the OS.

But what if you need the QA team to test it? If they use a different operating system, like MacOS when you’re on Windows, they’ll face a new set of challenges. Their setup might need different dependencies or configurations.

And what happens when there’s an OS upgrade and suddenly a dependency is no longer compatible?

It’s a complex, sometimes frustrating process. This was the norm before the advent of containerization.

Docker simplifies the deployment process, reduces issues related to “on my machine everything works” problems, and has become an integral tool for both developers and operations teams (DevOps) to streamline and optimize the software development life cycle.

What is Docker?

Docker is a popular platform designed to make it easier to create, deploy, and run applications using containers.

What are containers?

A container is a lightweight, stand-alone, and executable software package that contains everything needed (code, runtime, system tools, libraries, and settings) to run a piece of software.
Containers are isolated from each other and from the host system, ensuring consistent operation across various computing environments.

Containers vs Virtual Machines

A virtual machine (VM) emulates an entire computer, complete with an OS and virtualized hardware.

Think of a VM as a self-contained house. If you have multiple houses (VMs) on a plot of land (a physical computer), each is separate and independent.

Containers are like tents set up on the same plot of land. All tents share the same ground: the host operating system. However, each tent (container) has its own unique space.

Unlike VMs, containers share the same OS kernel as the system they run on, but they operate in isolated user spaces. This means they are lightweight and fast since there’s no need to emulate hardware or run a separate OS for each container.

Key Benefits and Features of Containers:

Consistency: Since a container is derived from an image, it ensures that the application runs consistently across different environments, from a developer’s local machine to staging and production servers.

Isolation: Even though containers share the same OS kernel, they operate independently.
If one container crashes, it doesn’t affect others.
Processes, file systems, memory, and the network stack are all segregated. This prevents potential conflicts and ensures security.

Lightweight: Unlike virtual machines, which require an entire operating system to run, containers share the host system’s kernel but maintain their own user space. This makes them more efficient in terms of resource utilization.

Ephemeral: Containers can be thought of as ephemeral or temporary. They can be easily created, started, stopped, and destroyed without affecting the underlying image or other containers.

Basic Docker Concepts:

  • Docker Image: This is like the blueprint for a container, a snapshot containing the application and all its requirements.
  • Docker Container: A live, operational version of the Docker image.
  • Dockerfile: A script with commands to build a Docker image. It defines how the image should be constructed.
  • Docker Hub: A cloud-based registry where Docker users and partners can create, test, store, and distribute container images.

Dive Deeper:

Dockerfile

A Dockerfile is a text document containing instructions used by Docker to build an image. Think of it as a recipe, specifying how your app and its environment should be pieced together. This makes it simple to share and replicate.

Here is what basic Hello Medium Dockerfile looks like:

FROM alpine
CMD [“echo”, “Hello Medium!”]

This Dockerfile instructs Docker Engine to build an alpine based image(FROM) and run specific command(CMD).

Let’s cover some key components and commands using the following Dockerfile:

FROM node:alpine

WORKDIR /app

COPY package.json .

RUN npm install -g typescript

ENV MY_VARIABLE=value

EXPOSE 3000

CMD ["npm", "run", "start:dev"]
  • FROM: Specifies the base image you’re starting from. Most applications start with a lightweight base image, like an Alpine or Debian image, or an image containing a specific runtime, like Node.js or Python.
FROM node:alpine
  • WORKDIR: Sets the working directory inside the container. All subsequent commands (like RUN, CMD, COPY, and ENTRYPOINT) will be executed in this directory.
WORKDIR /app
  • COPY: Copy files or directories from your host system to the container image.
COPY package.json .
  • RUN: Executes a command, typically used to install software packages, set up permissions, or perform other setup tasks.
RUN npm install -g typescript
  • CMD: Provides the default command for running a container from the image.
    Note: There can be only one CMD key in a Dockerfile.
CMD ["npm", "run", "start:dev"]
  • ENV: Sets environment variables.
ENV MY_VARIABLE=value
  • EXPOSE: Informs Docker that the container listens on a specified network port at runtime.
EXPOSE 3000

To build an image from a Dockerfile and tag it (-t), use the docker build command, followed by the location of the Dockerfile.

docker build -t my_image_name:latest .

Docker Image:

Docker Image is a snapshot or template of a container. It bundles the application, its dependencies, configurations, scripts, variables, and more. You can think of it as the blueprint for a Docker container.

Key Aspects of Docker Images:

Immutable: Once created, Docker images are immutable, meaning they unchangeable.
This ensures that the same image can be consistently run across different environments, leading to the “it works on my machine” phenomenon being a thing of the past.

Version Control: Docker images can be versioned, allowing you to use specific versions of an application or environment setup.
This is particularly useful to maintain multiple versions of an application or to roll back to a previous version.

Distribution: Docker images can be pushed to image registries, like Docker Hub or private registries.
This makes it easy to share and distribute software and its entire environment.

The order in which Dockerfile commands are placed is crucial. A Docker construction is made up of sequential build steps. Each instruction in a Dockerfile creates a new layer in the Docker Image.
Docker Image Layers are immutable, meaning they can’t be changed.

This layered approach provides several benefits:

Reuse: Common layers across different images can be reused, saving space.

Caching: While building an image, Docker caches these layers. If no changes are detected in a particular layer (like installing dependencies), Docker simply reuses the cached layer, speeding up the build process.

Size Efficiency: Changes only affect their specific layer, keeping image sizes manageable.

So in our Dockerfile, each instruction creates a layer.

FROM node:alpine

WORKDIR /app

COPY package.json .

RUN npm install -g typescript

ENV MY_VARIABLE=value

EXPOSE 3000

CMD ["npm", "run", "start:dev"]

Let’s see the layers and cache in example.
Create Docker Image from the following Dockerfile.

FROM alpine
COPY . .
CMD ["echo", "This is the first version of Docker Image"]

To build the image, run the command.

docker build -t my-docker-image:v1.0.0 .

Here is the build command output.

Let’s verify our image exists.

Now let’s inspect the layers of my-docker-image:v1.0.0 (image ID is 59a*** as you can see in the previous picture).

Let’s change our Dockerfile to the following.

FROM alpine
COPY . .
CMD ["echo", "This is the second version of Docker Image"]

Create a new image with version v1.0.1:

docker build -t my-docker-image:v1.0.1 .

Now let’s compare the outputs of the docker build command between the versions.

As you see, the build of the second step is shorter because it took some layers from the cache.

If you run docker image inspect, you will discover that the layer with digest sha256:cc2447***9438 is the same for both images.

Check out another example.

Download (docker pull) two different mysql images from Docker Hub (the world’s largest library for container images).

As you can see, mysql:8.0 pulled layer by layer.

Now let’s pull mysql:8.1 and see what happens.

As you see, the first layer already exist, so Docker don’t need to pull it.

Docker Container:

Let’s run my-docker-image:v1.0.0 using docker run command.

docker run is creating and running a new container from an image.

You see This is the first version of Docker Image output when you run the image because this is what you wrote in the Dockerfile, remember?

FROM alpine
COPY . .
CMD ["echo", "This is the first version of Docker Image"]

If you run docker container ls -a you will see your docker container. The status of container will be exited as it’s just printing the text and exits.
ls = list, -a = all (default shows just running containers).

Now let’s pull and run redis:latest image.
Type docker run redis:latest -d , image will be pulled if it’s not exist on the server.
-d option runs the container on the background.

What is that means?

Let’s type docker run redis:latest and press enter, the container runs and you see the container log on the command line.

The problem is that if you will press Control + C, the container will stop. To avoid that, you should run it on the background using -d option.

This command also prints the container id.

Executing docker container ls command, will show all running containers (in our case redis:latest).

Verify that container is running properly by checking logs.

What is Docker Hub?

Earlier I mentioned that I pulled the Redis image from the Docker Hub.
Docker Hub is a cloud-based registry where Docker users and partners can create, test, store, and distribute container images.

For example, I created a Docker image for my own application, and I want to share this image with the teams inside the organization.
So I can create an account in Docker Hub, create my private repository, push my image to this repository, and share it with my team.

Here is the link to Docker Hub — https://hub.docker.com/

There is a lot of alternatives to Docker Hub.

The most popular is Amazon ECR, Google Artifact Registry, Azure Container Registry.

In the ever-evolving landscape of software development and deployment, Docker has emerged as a revolutionary tool that addresses age-old challenges.

By introducing the concept of containers, Docker has simplified the complexities associated with ensuring software runs consistently across varied environments.

Understanding containerization isn’t just a skill for the modern DevOps professional, it’s a must have.

If you liked my articles, join my newsletter, and you will receive weekly DevOps tutorials, articles, and tips every Saturday.

As a bonus, you will receive a free step-by-step DevOps CI/CD project, which you can use in your portfolio.

Subscribe here: https://junior-devops-hub.ck.page

--

--

Andrey Byhalenko
DevOps Manuals and Technical Notes

I'm a DevOps Engineer, Photography Enthusiast, and Traveler. I write articles aimed at junior DevOps engineers and those aspiring to become DevOps engineers.