Docker for Beginners: Say Goodbye to Deployment Nightmares!
Published: 2024-07-10 10:24:41

Entry-level developers often face pain points such as inefficient code updates, releases, and deployments, along with challenges in maintaining environment consistency. Code that runs smoothly in the development environment might encounter errors on a different machine.  

In the era of cloud computing, deploying code using traditional methods feels outdated. This blog post will introduce you to Docker. 

Docker, based on the Go language and open-sourced under the Apache 2.0 license, is an application container engine. This containerization technology enables developers to package their applications and dependencies into lightweight, portable containers. These containers can be deployed on any popular Linux machine without worrying about environmental discrepancies. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mfkahvqh3ocf573ejxn5.PNG)

## Docker vs. Virtual Machines (VMs)
 
As mentioned, Docker utilizes virtualization. When discussing virtualization, Virtual Machines (VMs) inevitably come into play. Since both rely on virtualization, it's natural to wonder about their differences. Let's delve into that.

Firstly, let's examine a typical VM architecture diagram. Above the infrastructure (representing the underlying hardware), there's a hypervisor. Acting as an intermediary between the physical server and operating systems, it allows multiple operating systems and applications to share the underlying hardware. Virtual machines run multiple distinct operating systems on top of the host operating system and utilize the hypervisor to access the underlying hardware. 

If you needed to run three isolated applications, you would create three virtual machines. This means three operating systems would run on top of the hypervisor, resulting in significant resource overhead. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qf0d6zkav3ik1x13t1iv.PNG)

(Note: The diagram should ideally show a Host Operating System above the Hypervisor) 

Docker, in contrast, is remarkably lightweight. Its daemon (Docker Daemon) replaces the hypervisor, running directly on the operating system. The application's source code and dependencies are packaged into a Docker image. The Docker engine then uses this image to create a container. Different applications run in isolated containers. 

Moreover, the Docker daemon communicates directly with the operating system to allocate resources to Docker containers. Without the overhead of a full operating system, container startup time is reduced from minutes for virtual machines to mere milliseconds for Docker containers, significantly boosting efficiency and saving substantial disk space and other system resources. 

While Docker offers compelling advantages, it's not a complete replacement for virtual machines. Each technology has its strengths. Virtual machines excel at isolating the entire runtime environment, commonly used by cloud providers to separate users through VMs. Docker, however, typically isolates different applications. 

## How to Use Docker 

**Docker Installation (CentOS 7 Example) **

### 1. Install essential system tools:
 

```
yum install -y yum-utils device-mapper-persistent-data lvm2 
```

**2. Update the system kernel and yum cache: **

```
yum update
yum makecache fast
```

**3. Install Docker and start it: **

```
yum -y install docker-ce
systemctl start docker 
```

**4. Test if Docker is installed successfully:**

```
docker run hello-world 
```

   If you see "Hello from Docker" on your screen, congratulations! The installation is complete. 

## Concepts 
Before diving deeper into Docker, let's cover some key concepts for a smoother learning experience. 
1. Docker images: Docker image is a template used to create Docker containers
2. Docker container: Docker creates containers through images for running a set of independent applications
3. Docker registry: Code repository for saving images

Typically, we download the appropriate image from a Docker registry and use it to create containers for running our applications. 

## Examples 
Let's connect these concepts through two common examples to illustrate how to use Docker. 

### Installing Python with Docker 
As mentioned, the first step in using a Docker container is to download the corresponding image from a repository. This brings us to the first command in our tutorial: 

#### docker search 

- Purpose: Search for a specific image in the image repository. 
- Syntax: `docker search [OPTIONS] TERM `
- Example: `docker search python`
- Explanation: Search for Python-related images in the repository. 

```
[root@novita ~]# docker search pythonINDEX       NAME                                       DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
docker.io   docker.io/python                           Python is an interpreted, interactive, obj...   4356      [OK]
docker.io   docker.io/django                           Django is a free web application framework...   856       [OK]
docker.io   docker.io/pypy                             PyPy is a fast, compliant alternative impl...   196       [OK]
docker.io   docker.io/kaggle/python                    Docker image for Python scripts run on Kaggle   125                  [OK]
docker.io   docker.io/arm32v7/python                   Python is an interpreted, interactive, obj...   38
docker.io   docker.io/centos/python-35-centos7         Platform for building and running Python 3...   36
```

  We'll choose the official image with the tag (version) 3.7.

#### docker pull

- Purpose: Pull or update a specified image from the image repository. 
- Syntax: `docker pull [OPTIONS] NAME:TAG `
- Example: `docker pull python:3.7`
- Explanation: Download the Python image with tag 3.7 from the repository. 

```
[root@novita ~]# docker pull python:3.7
Trying to pull repository docker.io/library/python ...3.7: Pulling from docker.io/library/python
5ae19949497e: Downloading [=============>                                     ] 13.72 MB/50.38 MB
ed3d96a2798e: Download complete
f12136850781: Downloading [============================================>      ] 8.789 MB/9.978 MB
1a9ad5d5550b: Downloading [==>                                                ] 2.628 MB/51.77 MB
6f18049a0455: Waiting
ce39fa9d79d1: Waiting
Digest: sha256:d8718f4c8f28360c88d2d8b53681edb3c95e6a7bacedabd32eb5b1d120a75dc5
Status: Downloaded newer image for docker.io/python:3.7
```

  Docker has now downloaded the specified image. To view existing Docker images: 

#### docker images

- Purpose: List local images. 
- Syntax: `docker images [OPTIONS] [REPOSITORY[:TAG]] `
- Example: `docker images`
- Explanation: View all local Docker images. 

```
[root@novita ~]# docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
docker.io/python        3.7                 42d620af35be        6 days ago          918 MB
docker.io/rabbitmq      3-management        7aae48fa6ef6        7 days ago          179 MB
docker.io/golang        latest              f50db16df5da        9 days ago          774 MB
```

  The python3.7 image is present. Let's create a container using it.  

#### docker run
Before creating the container, let's briefly touch upon IDs. In Docker, IDs uniquely identify images or containers.  We interact with them using "command + ID." For instance, in the previous code, python3.7's image ID is 42d620af35be. 

- Purpose: Create a new container.
- Syntax: `docker run [OPTIONS] IMAGE [COMMAND] [ARG...] `

To test the Python environment, let's create a hello.py file locally: 

```
#!/usr/bin/python 
import sys 

print("Hello, Docker!") 
print(sys.version)  # Output the Python version number 
```

Assuming this code is saved in /root/code/python: 

```
[root@novita python]# docker run  -v $PWD:/usr/src/code  -w /usr/src/code python:3.7 python hello.py 
Hello, Docker! 
3.7.4 (default, Jul 13 2019, 14:04:11) [GCC 8.3.0] 
```

Let's break down this command: 
- `docker run`: Creates a container. 
- `-v $PWD:/usr/src/code`: Mounts the local directory to the Docker container. Here, it mounts the directory where the command is executed to /usr/src/code inside the container. The colon separates the local path from the container path. You can use absolute paths instead of $PWD. 
- `-w /usr/src/code`: Sets the working directory for the container. Since the Python code directory is mounted to /usr/src/code, we set it as the working directory.  
- `python:3.7`: Specifies the image. You can use the image ID instead. 
- `python hello.py`: Executes this command within the container. 

**Thought Experiment**: If you have a hello.go file in /root/data/ and want to run it using a Golang container with image ID f50db16df5da, what would the command be? (Hint: The command to run Go code is `go run FILENAME`) 

## Installing MariaDB with Docker 
Assuming you can now search for and download a MariaDB image, let's focus on starting it. 

Services like databases require corresponding ports to be open on the host. How do we achieve this with Docker?  We'll use port mapping, similar to mounting directories.  

```
[root@novita python]# docker run -d -v /data/mysql/:/var/lib/mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456  --restart unless-stopped 3a2ef06682ac 
```

- `-d`: Run the container in the background. 
- `-p 3306:3306`: Maps port 3306 in the container to port 3306 on the host. 
- `-e MYSQL_ROOT_PASSWORD=123456`: Sets the root password for MariaDB to 123456. 
- `--restart unless-stopped`: Restarts the container automatically if it stops unexpectedly. 
- `3a2ef06682ac`: The image ID for creating the container. 

Now, let's verify if the database service is running: 

  **docker ps**
- Purpose: List containers. 
- Syntax: `docker ps [OPTIONS] `

`[root@novita ~]# docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                                                        NAMES
107b52416c13        3a2ef06682ac        "docker-entrypoint..."   5 minutes ago       Up 5 minutes        0.0.0.0:3306->3306/tcp                                                                       quizzical_mcnulty `

  Next, we'll access the container's internal environment. 

  **docker exec**
- Purpose: Execute a command in a running container. 
- Syntax: `docker exec [OPTIONS] CONTAINER COMMAND [ARG...] `
- Example: `docker exec -it 107b52416c13 bash `
- Explanation: Access the container with ID 107b52416c13 and execute the bash command. 

  Inside the container, you can interact with it like a regular Linux environment. Using the previously set credentials, you can access MariaDB.  

```
[root@novita ~]# docker exec -it 107b52416c13 bash 
root@107b52416c13:/# mysql -u root -p123456 
Welcome to the MariaDB monitor.  Commands end with ; or \g. 
Your MariaDB connection id is 11 
Server version: 10.4.6-MariaDB-1:10.4.6+maria~bionic mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. 
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. 
MariaDB [(none)]> 
```

So far, you have learned the basics of Docker. Try exploring more.