Docker : From Zero to Hero 🛸 ( part 1)
Tags: webdev, devops, docker, kubernetes
Published: 2023-05-03 06:04:50

Docker is a tool that allows developers to package, distribute and run applications as containers.It provides an efficient and consistent way to deploy applications across different environments, from development to production, without worrying about dependencies or underlying infrastructure.

A real-life example of the benefits of Docker can be seen in web application development. In the past, deploying a web application could be a cumbersome and error-prone process. Developers would have to manually install and configure the necessary software components on each server, and any differences in configuration could lead to issues in production.

With Docker, the application and its dependencies are packaged together in a container, which can be easily deployed to any environment that supports Docker. This means that developers can be confident that their application will run the same way in development, testing, and production, and that the deployment process will be faster and more reliable.

In addition, Docker allows for better resource utilization, as multiple containers can run on a single machine without interfering with each other. This can lead to cost savings and improved scalability.

Overall, Docker simplifies the process of deploying and running applications, making it a valuable tool for developers and IT professionals alike.

# Why do we need `Docker` if there is `virtual machine` ?

While both Docker and virtual machines have some similarities, they serve different purposes and have different benefits.

Virtual machines emulate an entire operating system, including the kernel, and allow you to run multiple operating systems on the same physical machine. Each virtual machine requires its own set of resources, including memory, disk space, and CPU time, which can make it difficult to scale and manage large applications.

Docker, on the other hand, uses containerization technology to allow you to package and run applications in lightweight, portable containers. Unlike virtual machines, Docker containers share the same host operating system kernel, which makes them more lightweight and efficient.

Here are some reasons why you might choose to use Docker over virtual machines:

1. **Resource efficiency:** Docker containers use fewer resources than virtual machines because they share the same host operating system kernel. This means you can run more containers on the same physical machine than you could with virtual machines.
2. **Portability:** Docker containers are portable across different environments and can run on any machine that has Docker installed, regardless of the underlying infrastructure.
3. **Fast deployment:** Docker containers can be deployed and started much faster than virtual machines, which can take several minutes to start up.
4. **Isolation**: Docker containers provide a high degree of isolation between applications, which can help to prevent conflicts and security vulnerabilities.
5. **Version control:** Docker containers allow you to version control your application and its dependencies, making it easier to roll back to previous versions or test new versions without affecting the production environment.

In summary, Docker is a lightweight and efficient alternative to virtual machines that offers many benefits for modern application development and deployment.

# Is `Docker` and `Container` same thing ?

Docker is a platform that allows you to build, package, and deploy applications in containers. A container is a lightweight, portable, and self-contained environment that contains everything an application needs to run, including code, libraries, and system tools.

Here are some differences between Docker and containers:

1. **Docker is a platform**: Docker is a platform that provides a set of tools and services to build, deploy, and manage containers. It includes the Docker Engine, which is responsible for running and managing containers, as well as other tools like Docker Compose, Docker Swarm, and Docker Registry.
2. **Containers are the building blocks**: Containers are the fundamental building blocks of Docker. They provide a lightweight and portable way to package and run applications in a self-contained environment.
3. **Docker provides a standardized format**: Docker provides a standardized format for containers, which makes it easy to build, share, and deploy them across different environments. This format includes everything the container needs to run, including the code, dependencies, and system tools.
4. **Docker provides a container runtime**: Docker provides a container runtime, which is responsible for creating, starting, stopping, and managing containers. It provides a layer of abstraction between the container and the host operating system, which allows containers to be run on any system that supports Docker.

In summary, Docker is a platform that provides a standardized way to build, package, and deploy applications in containers, while containers are the lightweight and portable environments that contain everything an application needs to run.

# Docker Engine

The Docker engine is the core component of Docker that allows you to create, run, and manage Docker containers. It is a lightweight and efficient runtime that can be installed on various operating systems, including Windows, macOS, and Linux.

The Docker engine consists of several key components, including:

1. **The Docker daemon:** This is the background service that runs on the host system and manages the containers and images.
2. **The Docker CLI:** This is the command-line interface that allows you to interact with the Docker daemon and perform various operations such as building and running containers.
3. **The Docker API:** This is the interface that allows external tools and applications to interact with the Docker daemon.

The Docker engine provides a powerful and flexible platform for building, running, and managing containerized applications. It allows you to package your application and its dependencies into a lightweight, portable container that can run on any system with Docker installed. This makes it easy to deploy and scale applications across different environments and platforms.

# **Docker daemon**

The Docker daemon is the core component of the Docker engine that runs as a background service on the host system. It is responsible for managing Docker containers, images, networks, and volumes.

The Docker daemon listens to requests from the Docker CLI, Docker API, and other tools, and takes the necessary actions to create, start, stop, and remove containers. It also manages the networking and storage resources required by containers, as well as the caching and sharing of images.

The Docker daemon runs as a system service and can be configured to start automatically when the host system boots up. It is designed to be lightweight and efficient, using minimal system resources to provide fast and responsive container management.

Overall, the Docker daemon plays a critical role in the Docker architecture, enabling the creation, deployment, and management of containerized applications in a consistent and reliable manner.

# Docker API

The Docker API is the interface that allows external tools and applications to interact with the Docker daemon. It provides a RESTful API that allows you to manage Docker containers, images, networks, and volumes programmatically, using standard HTTP requests.

The Docker API supports a wide range of operations, including creating and starting containers, managing images and networks, and accessing logs and stats for running containers. It is designed to be platform-agnostic, making it easy to integrate Docker into a wide range of tools and workflows.

The Docker API can be accessed using various programming languages and tools, including Python, Java, Node.js, and cURL. It is also supported by many popular DevOps tools such as Jenkins, Ansible, and Terraform.

Overall, the Docker API provides a powerful and flexible interface for automating Docker operations and integrating Docker into your DevOps workflows. It allows you to manage Docker resources programmatically, making it easy to create, deploy, and manage containerized applications in a consistent and scalable manner.

# Docker CLI

The Docker CLI (Command Line Interface) is a command-line tool that allows you to interact with the Docker daemon and manage Docker containers, images, networks, and volumes. It provides a simple and powerful way to build, run, and manage Docker applications from the command line.

There are many Docker commands available, but here are some of the most commonly used ones:

1. **`docker run`**: This command is used to create and start a new container from an image.
2. **`docker build`**: This command is used to build a new Docker image from a Dockerfile.
3. **`docker push`**: This command is used to upload a Docker image to a registry.
4. **`docker pull`**: This command is used to download a Docker image from a registry.
5. **`docker ps`**: This command is used to list running containers.
6. **`docker stop`**: This command is used to stop a running container.
7. **`docker rm`**: This command is used to remove a container.
8. **`docker images`**: This command is used to list available Docker images.
9. **`docker rmi`**: This command is used to remove a Docker image.
10. **`docker network`**: This command is used to manage Docker networks.
11. **`docker volume`**: This command is used to manage Docker volumes.
12. **`docker exec`**: This command is used to execute a command inside a running container.
13. **`docker-compose`**: This command is used to manage multi-container Docker applications.

These are just a few examples of the many Docker commands available. To see a full list of available commands, you can use the **`docker --help`** command or refer to the Docker documentation. We will discuss each and every command in detail now. But for this article we will discuss only 4 commands and in the upcoming articles we explore remaining commands.

## `docker run`

The **`docker run`** command is used to create and start a new Docker container from a specified image. When you run the **`docker run`** command, Docker creates a new container based on the specified image and starts it up.

Here's the basic syntax for the **`docker run`** command:

```jsx
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
```

- **`OPTIONS`**: Specifies any additional options or flags to pass to the **`docker run`** command.
- **`IMAGE`**: Specifies the Docker image to use as the basis for the new container.
- **`COMMAND`**: Specifies the command to run inside the container.
- **`ARG`**: Specifies any additional arguments to pass to the command.

Here is a list of some of the most commonly used options for the **`docker run`** command and their explanations:

- **`d, --detach`**: Runs the container in the background (detached mode) and returns control to the terminal. This option is useful when you want to run a container as a background process.
- **`p, --publish`**: Publishes a container's port(s) to the host machine. This option takes two arguments: the first specifies the port to expose inside the container, and the second specifies the port to map on the host machine. For example, **`docker run -p 8080:80`** maps port 80 inside the container to port 8080 on the host machine.
- **`v, --volume`**: Mounts a host directory or a named volume as a data volume inside the container. This option takes two arguments: the first specifies the path to the directory or volume on the host machine, and the second specifies the path inside the container to mount the volume. For example, **`docker run -v /path/on/host:/path/in/container`** mounts the host directory **`/path/on/host`** inside the container at **`/path/in/container`**.
- **`-name`**: Assigns a custom name to the container instead of using a randomly generated one. This option is useful when you need to refer to a container by a specific name.
- **`e, --env`**: Sets an environment variable inside the container. This option takes an argument in the form of **`VAR=value`**. For example, **`docker run -e VAR=value`** sets the environment variable **`VAR`** to **`value`** inside the container.
- **`it, --interactive --tty`**: Runs the container in interactive mode and allocates a pseudo-TTY. This option is useful when you need to interact with the container's command line.
- **`-rm`**: Removes the container automatically when it exits. This option is useful when you want to create a container for a specific task that you don't need to keep around after it completes.
- **`-network`**: Specifies the network to use for the container. This option is useful when you need to connect a container to a specific network.
- **`-restart`**: Specifies the restart policy for the container. This option takes an argument in the form of **`on-failure`**, **`always`**, **`unless-stopped`**, or a specific number of seconds to wait before restarting. This option is useful when you need to automatically restart a container if it exits unexpectedly.
- **`w, --workdir`**: Sets the working directory inside the container. This option takes an argument in the form of a path. For example, **`docker run -w /app`** sets the working directory inside the container to **`/app`**.

### How to set the multiple env variables ?

To set multiple environment variables when running a container using the **`docker run`** command, you can use multiple **`-e`** flags or pass a file containing the variables using the **`--env-file`** flag. Here are some examples:

1. Using multiple **`-e`** flags:
    
    ```jsx
    docker run -e VAR1=value1 -e VAR2=value2 myimage
    ```
    
2. Using an env file:
    
    Create a file named **`env.list`**and add the environment variables with the **`key=value`**
     format, one per line:
    
    ```jsx
    VAR1=value1
    VAR2=value2
    ```
    
    Then pass this file to the container using the **`--env-file`**flag:
    
    ```jsx
    docker run --env-file env.list myimage
    ```
    

In both cases, the container will have access to the environment variables **`VAR1`**and **`VAR2`**.

### Volume overview

The **`--volume`** or **`-v`** option in Docker is used to mount a directory or file from the host machine into the container. This allows the container to access and modify files on the host system.

To set up a volume when running a container using the **`docker run`** command, you can use the following syntax:

```jsx
docker run -v /path/on/host:/path/in/container myimage
```

This will mount the directory located at **`/path/on/host`**on the host machine into the **`/path/in/container`** directory in the container.

You can also use named volumes or anonymous volumes to manage your data. Here are some examples:

- Using named volumes:
    
    ```jsx
    docker run -v myvolume:/path/in/container myimage
    ```
    
    This creates a named volume named **`myvolume`**and mounts it into the **`/path/in/container`**
    directory in the container. You can use the same volume across multiple containers to share data.
    
- Using anonymous volumes:
    
    ```jsx
    docker run -v /path/in/container myimage
    ```
    
    This creates an anonymous volume and mounts it into the **`/path/in/container`** directory in the container. Anonymous volumes are created and managed by Docker and are not intended to be shared across containers.
    

You can also set options for your volumes, such as read-only mode, using the following syntax:

```jsx
docker run -v /path/on/host:/path/in/container:ro myimage
```

## `docker build`

**`docker build`** is a command in Docker that is used to build Docker images from a Dockerfile. A Dockerfile is a text file that contains a set of instructions that are used to build a Docker image. When you run the **`docker build`** command, Docker reads the instructions from the Dockerfile and builds a Docker image that you can use to run a Docker container.

Here is an example command to build a Docker image from a Dockerfile:

```jsx
docker build -t myimage:latest .
```

In this example, we use the **`docker build`**command to build a Docker image with the tag **`myimage:latest`**. The **`.`**at the end of the command specifies the build context, which is the directory containing the Dockerfile and any other files needed to build the Docker image.

The **`docker build`** command has several options that you can use to customize the build process. Here are some of the most commonly used options:

- **`t`**: specifies the name and optionally a tag to assign to the built image.
- **`f`**: specifies the name of the Dockerfile to use for building the image.
- **`-build-arg`**: passes build-time variables to the Dockerfile.
- **`-no-cache`**: forces Docker to build the image from scratch, without using any cached layers.
- **`-pull`**: forces Docker to pull the latest version of the base image before building the new image.

Here is an example command that uses some of these options:

```jsx
docker build -t myimage:latest -f Dockerfile.prod --build-arg ENVIRONMENT=production --no-cache --pull .
```

In this example, we specify a different Dockerfile (**`Dockerfile.prod`**) to use for building the image, and pass a build-time variable (**`ENVIRONMENT`**) to the Dockerfile using the **`--build-arg`** option. We also specify the **`--no-cache`**option to force Docker to build the image from scratch, and the **`--pull`**option to ensure that we are using the latest version of the base image.

### Docker Image , Dockerfile and Docker Container

Think of a Docker image as a recipe for a dish. Just as a recipe lists all the ingredients and instructions for making a dish, a Docker image lists all the dependencies and configurations required for running an application. When you run a Docker image, it creates a Docker container, just as when you cook a dish using a recipe, it creates a serving of the dish.

Now, let's compare this to a Dockerfile. A Dockerfile is like a chef's notebook that lists all the steps and ingredients required for creating a recipe. In the same way, a Dockerfile lists all the steps and configurations required for creating a Docker image. Just as a chef can use their notebook to create different versions of a dish, a developer can use a Dockerfile to create different versions of a Docker image.

To summarize, a Docker image is like a recipe, a Docker container is like a serving of the dish made from the recipe, and a Dockerfile is like a chef's notebook that lists all the steps and ingredients required for creating the recipe.

### Dockerfile

A Dockerfile is a text file that contains instructions for building a Docker image. The Dockerfile defines how the Docker image should be built and what should be included in it. Here are the components of a Dockerfile:

1. **Base Image**: A Dockerfile starts with a base image, which serves as the foundation for your application. The base image is the starting point for building your Docker image. You can choose from a variety of base images available on Docker Hub or create your own custom base image.
2. **Maintainer**: The MAINTAINER instruction allows you to specify the author and email of the person who created the Dockerfile. This is an optional field but it's a good practice to include it.
3. **RUN**: The RUN instruction is used to execute commands during the Docker build process. For example, you can use the RUN instruction to install dependencies, configure the environment, and run tests.
4. **COPY**: The COPY instruction is used to copy files from the host machine to the Docker image. This is useful for copying your application code, configuration files, and other assets.
5. **WORKDIR**: The WORKDIR instruction sets the working directory for subsequent instructions. This is useful for ensuring that your application is running in the correct directory.
6. **EXPOSE**: The EXPOSE instruction is used to specify the port on which your application listens. This does not actually publish the port, it only documents it. To publish the port, you need to use the -p flag when running the container.
7. **CMD**: The CMD instruction specifies the default command that should be run when the container starts. This is usually the command that starts your application.
8. **ENV**: The ENV instruction is used to set environment variables in the Docker image. This is useful for configuring your application.

```jsx
# Base image
FROM node:14-alpine

# Set working directory
WORKDIR /app

# Copy package.json and package-lock.json to the working directory
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy source code to the working directory
COPY . .

# Expose port 3000
EXPOSE 3000

# Define a named volume to persist data
VOLUME /app/data

# Set environment variables
ENV NODE_ENV=production
ENV PORT=3000

# Start the application
CMD ["npm", "start"]
```

This **`Dockerfile`** uses the official Node.js 14 Alpine base image and sets the working directory to **`/app`**. It then copies the **`package.json`** and **`package-lock.json`** files to the working directory and installs the dependencies using **`npm install`**.

Next, it copies the source code to the working directory and exposes port 3000 using the **`EXPOSE`** command. It defines a named volume called **`data`** that will be used to persist data even if the container is stopped and removed.

The **`ENV`** command sets two environment variables, **`NODE_ENV`** and **`PORT`**, and the **`CMD`** command specifies that the application should be started using **`npm start`**.

With this **`Dockerfile`**, you can build a Docker image that includes your Node.js application and all its dependencies. You can then run the container using the **`docker run`** command and mount the named volume to persist data, like this:

```jsx
docker run -d -p 3000:3000 --name my-app -v data:/app/data my-image
```

This will start the container, map port 3000 on the container to port 3000 on the host machine, mount the named volume **`data`**to the **`/app/data`**directory inside the container, and name the container **`my-app`**.

### Multiple env ? i am confused 😕

The **`--env`**option in **`docker run`** and the **`ENV`** instruction in the Dockerfile serve the same purpose, which is to set environment variables for the container.

However, there are a few differences between them:

1. **`-env`** is used at runtime, while **`ENV`** is used at build time.
2. When using **`-env`**, you can specify the environment variables as key-value pairs in the command-line, while in **`ENV`**, you specify them as environment variable assignments in the Dockerfile.
3. The values set with **`-env`** can override the values set in the Dockerfile with **`ENV`**. This means that if the same environment variable is set with both methods, the value set with **`-env`** takes precedence.

For example, let's say we have the following Dockerfile:

```jsx
FROM alpine:latest

ENV MY_VAR=value

CMD ["sh"]
```

If we build the image with **`docker build -t my-image .`**, the **`MY_VAR`** environment variable will be set to **`value`** in the image.

However, if we run a container from the image with **`docker run --env MY_VAR=new-value my-image`**, the **`MY_VAR`** environment variable will be set to **`new-value`** instead of **`value`**.

In summary, both **`--env`** and **`ENV`** are used to set environment variables in Docker containers, but **`--env`** is used at runtime and can override values set in the Dockerfile with **`ENV`**.

### `--build-arg`

**`-build-arg`** is a Docker CLI option that allows you to pass build-time variables to a Dockerfile. These variables can be used in the Dockerfile to customize the build process.

When you use **`--build-arg`** option, you can specify the value of the argument by passing it on the command line, like this:

```jsx
docker build --build-arg VARIABLE_NAME=VALUE .
```

In the Dockerfile, you can reference the build-time variable using the syntax **`${VARIABLE_NAME}`**
. For example:

```jsx
FROM alpine
ARG VARIABLE_NAME
ENV ENV_VARIABLE ${VARIABLE_NAME}
```

In this example, we are using the **`ARG`** instruction to define the **`VARIABLE_NAME`** build-time variable. We are then using the **`ENV`** instruction to define an environment variable called **`ENV_VARIABLE`** and setting its value to the value of the **`VARIABLE_NAME`** build-time variable.

Using **`--build-arg`** is useful when you want to pass dynamic information to the Docker build process, such as version numbers, build paths, or other configuration options. By using build-time variables, you can make your Docker builds more flexible and reusable.

## `docker push`

**`docker push`** is a command used to push a Docker image from a local machine to a Docker registry. A Docker registry is a service that stores and distributes Docker images. Docker Hub is a public Docker registry that allows users to store and share Docker images.

To push an image to a registry, you must first tag it with the registry name and repository name. For example, if you want to push an image with the tag **`myimage`** to the Docker Hub repository named **`myrepo`**, you would run the following command:

```jsx
docker tag myimage myusername/myrepo:myimage
```

This command tags the image with the registry name **`myusername`**, the repository name **`myrepo`**, and the tag **`myimage`**.

Once you have tagged the image, you can push it to the Docker Hub repository using the **`docker push`** command:

```jsx
docker push myusername/myrepo:myimage
```

This command uploads the image with the tag **`myimage`** to the Docker Hub repository **`myrepo`** under the username **`myusername`**.

Note that you must be logged in to the Docker registry before you can push an image. You can use the **`docker login`** command to log in to a registry using your Docker Hub credentials.

### `docker tag`

The **`docker tag`**command is used to create a new tag for an existing Docker image. 

The basic syntax of the **`docker tag`** command is as follows:

```jsx
docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
```

Here, **`SOURCE_IMAGE`**is the name of the source Docker image, and **`TARGET_IMAGE`**is the name of the new image you want to create. You can optionally specify a tag for both the source and target images.

For example, let's say you have an image named **`myimage`**with the tag **`latest`**. To create a new tag for this image, you can run the following command:

```jsx
docker tag myimage:latest myrepo/myimage:v1.0
```

This command will create a new tag named **`v1.0`**for the **`myimage`**image, and will associate it with the **`myrepo`** repository on your Docker registry.

You can also use the **`docker tag`**command to rename an image. For example, if you want to rename the **`myimage`** image to **`mynewimage`**, you can run the following command:

```jsx
docker tag myimage mynewimage
```

This will create a new image named **`mynewimage`** that is identical to the **`myimage`** image.

In summary, the **`docker tag`** command is a useful tool for managing your Docker images and providing additional names or versions for your images.

## `docker pull`

The **`docker pull`** command is used to download Docker images from a registry. It allows you to fetch a specific version of an image or the latest version of the image. The basic syntax of the **`docker pull`**command is as follows:

```jsx
docker pull [OPTIONS] NAME[:TAG|@DIGEST]
```

Here, **`OPTIONS`**are any additional options that you want to pass to the **`docker pull`**command. **`NAME`**is the name of the Docker image that you want to download. You can also specify the image tag using the **`:TAG`**syntax or the image digest using the **`@DIGEST`**syntax.

For example, to download the latest version of the **`nginx`**image from Docker Hub, you can use the following command:

```jsx
docker pull nginx
```

To download a specific version of the **`nginx`**image, you can specify the image tag as follows:

```jsx
docker pull nginx:1.20.1
```

This will download version 1.20.1 of the **`nginx`**image.

### **`OPTIONS`**

Here are some common options for `docker pull` command:

- **`-all-tags`** or **`a`**: By default, **`docker pull`** pulls only the latest image. This option pulls all the available tags for the specified image repository.
- **`-disable-content-trust`**: By default, Docker verifies the authenticity of the image with Notary. This option disables this check.
- **`-platform`**: This option specifies the platform for the pulled image. This is useful for multi-architecture images.
- **`-quiet`** or **`q`**: This option only displays the image ID of the pulled image.
- **`-registry-mirror`**: This option specifies a registry mirror to use for pulling the image.
- **`-no-trunc`**: By default, **`docker pull`** truncates the image ID to the first 12 characters. This option displays the full length of the image ID.

Example usage:

```jsx
# Pull the latest version of an image
docker pull myimage:latest

# Pull all available tags for an image repository
docker pull --all-tags myimage

# Disable content trust verification
docker pull --disable-content-trust myimage

# Pull a specific platform for a multi-architecture image
docker pull --platform linux/amd64 myimage

# Only display the image ID
docker pull --quiet myimage

# Use a registry mirror to pull the image
docker pull --registry-mirror=https://myregistrymirror.com myimage

# Display the full length of the image ID
docker pull --no-trunc myimage
```

> A **Docker registry mirror** is a server that caches and serves Docker images from a remote registry such as **Docker Hub**. When you pull an image, Docker first checks the local registry mirror for the image, and if it's not found, it pulls the image from the remote registry. This can improve the speed of pulling images, especially in situations where multiple users or systems need to pull the same images repeatedly. This only applies if you are using `--registry-mirror` option to pull the image.
> 

> **Notary** is an open-source project that provides a trust framework for signing and verifying container images. It helps ensure the integrity and authenticity of container images by allowing you to digitally sign them and store the signature in a trusted registry.
>