Posts Docker Basics and Practice
Post
Cancel

Docker Basics and Practice

This article is referenced and noted from the practical Docker series on Viblo.

Series link here: Practical Docker from Basics

I. Installing Docker and Some Basic Concepts

Installation can be referenced in the documentation

WHAT IS DOCKER

Benefits of Containerized Applications

  • Build once, run anywhere (works on my machine problem solved)
  • Isolated environment
  • Easy development
  • Scalability (container orchestration)

Previously we had virtual machines with similar characteristics, but why do we have Docker too? Docker virtualizes at the OS level while virtual machines virtualize at the hardware level

IMAGES AND CONTAINERS

Since we already know what containers are it’s easier to explain images through them: Containers are instances of images. A basic mistake is to confuse images and containers.

Docker images are read-only files that cannot be changed (actually you can add layers by starting from a base image). Images are created by building an instruction file called a Dockerfile.

Containers are created from images (read-write). They contain everything necessary to run the program and are an isolated environment on the host that can interact with other machines and itself through TCP/UDP protocols.

Docker run vs Docker start: The command docker run is used to only START a container for the very first time. To run an existing container what you need is docker start $container-name

docker run = create docker container + start docker container

II. Sharing Data Between Host-Container and Container-Container

Data sharing means containers want to operate on or use data on the host machine or on other containers.

Host-Container Data Sharing

Suppose we just created data on the host machine with path X

1
docker run -ti -v /home/viethoang/petproject:/home/petproject_container ubuntu

Parameter explanation

-ti: provides terminal operations and connection

-v: shares data between host and container. The first path is of the host machine, the one after : is of the container ubuntu is the Ubuntu OS image

After running the command, we enter the container’s terminal and see that the “petproject” folder on the host maps to the “petproject_container” folder. Any changes in the host folder will affect the container and vice versa, for example adding or deleting. The same applies when in the container. However, if we delete the container, the data on the host will still remain.

Data Sharing Between 2 Containers

Now we have data on the host machine that we want to share with both containers. We already have 1 container shared from the host with the id as shown below, so to share data from host to the remaining container we can use the command:

1
docker run -ti --name C2 --volume-from a8232b31eb20 ubuntu 

Managing Volumes to Share Data for Containers

When a container is created, a volume is also created and attached to that container. When the container is deleted, the volumes still remain there and are only lost when we delete them directly.

1. Creating Volumes

Command docker volume ls to view volumes.

Create a new volume named D1: docker volume create D1.

2. Mounting Volumes to Containers

We execute the command:

1
 docker run -it --name C3 --mount source=D1,target=/home/disk ubuntu

Parameter --mount is to mount additional volumes when creating containers

source=D1 is the volume parameter to mount into the container

target=/home/disk where /home/disk is the directory that maps volume D1 into the container

3. Creating Volumes Mapped to Host Machine

Now we want a volume mapped to a fixed existing folder on the host:

1
docker volume create --opt device=/home/viethoang/petproject --opt type=none --opt o=bind D2

Check again:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
~ docker volume inspect D2
[
    {
        "CreatedAt": "2021-07-28T16:19:19+07:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/D2/_data",
        "Name": "D2",
        "Options": {
            "device": "/home/viethoang/petproject",
            "o": "bind",
            "type": "none"
        },
        "Scope": "local"
    }
]

  1. Creating Container with Local Mapped Volume
1
docker run -it --name C4 -v D2:/home/disk ubuntu

Container C4 is created from ubuntu image with mounted volume D2 and the volume is mapped to a folder on the host from the previous part

1
2
3
4
~ docker exec -it  C4 bash
root@976236084a9c:/# ls /home/disk/
20202  20203  Algorithm-application  DE  Non-Trivial-DE  aia  ktlt
root@976236084a9c:/# 

III. Creating and Managing Networks in Docker

Some Concepts and Default Networks

Concepts

Docker network ensures containers connect to the network:

  • Containers on the same network can communicate with each other through name and port they listen on that network
  • Connect to one or many hosts
  • Connect containers with other networks outside docker
  • Connect between container clusters

Default Network

There are 3 types of networks automatically created in docker: bridge, none, host which can be viewed with the command docker network ls.

bridge this is Docker’s default network driver. Bridge is the most suitable driver for communication between independent containers. Containers in the same network can communicate with each other via IP address. If no driver is specified, bridge will be the default network driver when initializing.

none driver provides a container with its own networking stack and network namespace, usually used with custom networks. This driver cannot be used in swarm clusters.

host used when containers need to communicate with the host and use the host machine’s network directly.

Creating Container-to-Container Connections

Try creating 2 containers on the same bridge network so the 2 containers can connect to each other:

1
 docker run -it --rm --name B1 busybox`

After creation, check if container B1 has appeared in the bridge network:

1
 docker network inspect bridge

Or check by container:

1
docker inspect B1

Then create a similar B2 container, check and try to ping from B1 to B2:

1
docker attach B1

The above command will enter container B1 and use its terminal to ping.

Creating Connection Between Host and Container

1
docker run -it --name B3 -p 8888:80 busybox

-p 8888:80 means mapping port 8888 on host to port 80 on container, meaning port 8888 on host will display content from port 80 of the container.

Creating Docker Network

Purpose is to create separate networks for containers to use independently from each other

1: Create network

docker network create --driver bridge network1

The above command creates network1, we can check with docker network ls.

2: Delete network

docker network rm name_network

3: Create container connecting to a specified network

docker run -it --name B4 --network network1 busybox

4: Connect a running container to another network

Example: we have 2 networks network1 and network2, there’s a container B5 currently connected to network1 and we want this container to connect to network2 as well, then we run the command.

docker network connect network2 B5

The above command connects container B5 to network2

IV. Dockerfile, Building Images from Dockerfile

Introduction to Dockerfile

Dockerfile is a text file that contains instructions for docker to build images

Dockerfile = comments + command + arguments

Docker images are made up of multiple layers and by default start from a base layer. These layers are created from commands in the dockerfile

Writing Dockerfile

Dockerfile Syntax

The general syntax of a Dockerfile is:

1
INSTRUCTION arguments
  • INSTRUCTION is the name of directives in Dockerfile, each directive performs a specific task, defined by Docker. When declaring these directives must be written in UPPERCASE.
  • A Dockerfile must start with the FROM directive to declare what image will be used as the base to build your image.
  • arguments is the content part of the directives, determining what the directive will do.

Example:

1
2
3
4
5
6
FROM alpine:3.4

RUN apk update && \
    apk add curl && \
    apk add git && \
    apk add vim

Main Directives in Dockerfile

FROM

Specifies which image will be used as the base image for the build process to execute subsequent commands. These base images will be downloaded from Public Repository or Private Repository depending on individual setup.

Syntax:

1
2
3
FROM <image> [AS <name>]
FROM <image>[:<tag>] [AS <name>]
FROM <image>[@<digest>] [AS <name>]

The FROM directive is mandatory and must be placed at the top of the Dockerfile.

Example:

1
2
3
FROM ubuntu
or
FROM ubuntu:latest

LABEL

The LABEL directive is used to add metadata to Docker Image when they are built. They exist as key - value pairs, stored as strings. You can specify multiple labels for a Docker Image, and of course each key - value pair must be unique. If the same key is declared with multiple values, the most recently declared value will override the previous value.

Syntax:

1
LABEL <key>=<value> <key>=<value> <key>=<value> ... <key>=<value> 

You can declare metadata for Image line by line or separate into individual lines.

Example:

1
2
LABEL com.example.some-label="lorem"
LABEL version="2.0" description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."

To view metadata of a Docker Image, use the command:

1
docker inspect <image id>

Example:

MAINTAINER

The MAINTAINER directive is used to declare information about the author who wrote the Dockerfile.

Syntax:

1
MAINTAINER <name> [<email>]

Example:

1
MAINTAINER NamDH <namduong3699@gmail.com>

Currently, according to official documentation from Docker, declaring MAINTAINER is gradually being replaced by LABEL maintainer due to its flexibility when besides author name and email information, we can add many other optional information through metadata tags and can easily get information with the docker inspect ... command.

Example:

1
LABEL maintainer="namduong3699@gmail.com"

RUN

The RUN directive is used to run a command during the image build process, usually Linux commands. Depending on the base image declared in the FROM section, there will be corresponding commands. For example, to run the update command for Ubuntu it would be RUN apt-get update -y while for CentOS it would be Run yum update -y. The command result will be committed, and that commit result will be used in the next step of the Dockerfile.

Syntax:

1
2
RUN <command>
RUN ["executable", "param1", "param2"]

Example:

1
2
3
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
-------- or --------
RUN ["/bin/bash", "-c", "echo hello"]

In shell form you can execute multiple commands at once with \:

1
2
3
FROM ubuntu
RUN apt-get update
RUN apt-get install curl -y

or

1
2
3
FROM ubuntu
RUN apt-get update; \
    apt-get install curl -y

ADD

The ADD directive will copy files, directories from the build machine or remote file URLs from src and add them to the image filesystem dest.

Syntax:

1
2
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

Where:

  • src can declare multiple files, directories, …
  • dest must be an absolute path or relative to WORKDIR directive.

Example:

1
2
3
ADD hom* /mydir/
ADD hom?.txt /mydir/
ADD test.txt relativeDir/

You can also set permissions on newly copied files/directories:

1
2
3
4
ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/

COPY

The COPY directive is similar to ADD in copying files, directories from <src> and adding them to <dest> of the container. Unlike ADD, it doesn’t support adding remote file URLs from network sources.

Syntax:

1
2
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

ENV

The ENV directive is used to declare environment variables. These variables are declared as key - value pairs using strings. The values of these variables will be available for subsequent Dockerfile directives.

Syntax:

1
ENV <key>=<value> ...

Example:

1
2
3
ENV DOMAIN="viblo.asia"
ENV PORT=80
ENV USERNAME="namdh" PASSWORD="secret"

You can also change environment variable values with container startup command:

1
docker run --env <key>=<value>

ENV can only be used in the following commands:

  • ADD
  • COPY
  • ENV
  • EXPOSE
  • FROM
  • LABEL
  • STOPSIGNAL
  • USER
  • VOLUME
  • WORKDIR

CMD

The CMD directive defines commands that will be run after the container is started from the built image. You can declare multiple but only the last CMD will be executed.

Syntax:

1
2
3
CMD ["executable","param1","param2"]
CMD ["param1","param2"] 
CMD command param1 param2

Example:

1
2
FROM ubuntu
CMD echo Viblo

USER

Sets username or UID to use when running the image and when running commands in RUN, CMD, ENTRYPOINT after it.

Syntax:

1
2
3
USER <user>[:<group>]
or
USER <UID>[:<GID>]

Example:

1
2
3
FROM alpine:3.4
RUN useradd -ms /bin/bash namdh
USER namdh

Creating Images with Dockerfile

Above I’ve covered the structure and main directives of a Dockerfile. Now we’ll practice building an image by writing a simple Dockerfile. Note that before starting, make sure your machine has Docker installed. If not, you can refer to the installation guide here.

Creating Dockerfile

Example 1

In this step, we’ll create a new path for the Dockerfile.

1
mkdir myproject && cd myproject

Next is creating the Dockerfile, note that the name of Dockerfile must be exactly “Dockerfile” with no file extension. If not named correctly, the system will report an error that the file cannot be found when building.

1
touch Dockerfile

After creating the Dockerfile, we proceed to enter the directives:

1
2
FROM alpine
CMD ["echo", "Hello world!"]

As you can see, the Dockerfile content above only contains 2 directives FROM and CMD. CMD contains the echo command and will print “Hello world!” to screen when the container is started from the image built from this Dockerfile. We proceed to create the image with the command:

1
docker build -t <image name> .

Finally, we run docker run <imageID> to create and run the container. The result will be “Hello world!” printed on screen.

V. Docker-compose

Concepts

Is a tool that helps us set up and manage multiple containers, networks, volumes (collectively called services) and configure services quickly and simply by running according to specifications in the docker-compose.yml file

Main Functions

  • Create and manage multiple independent environments in one host machine ensuring independent storage partitions to avoid conflicts
  • Only recreate changed containers, recognize unchanged containers and reuse them
  • Define and use environment variables in YAML file

Docker-compose.yml

Is a file saved in yaml format, this file stores instructions for docker compose to read and execute those instructions, such as creating containers from images, creating networks, configuring services.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: "3.9"  # optional since v1.27.0
services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/code
      - logvolume01:/var/log
    links:
      - redis
  redis:
    image: redis
volumes:
  logvolume01: {}

Docker-compose Practice

Our practice task is to define a docker file to create the following components

  • MySQL Container
  • Apache HTTP Container
  • PHP-FPM Container
  • Network (bridge) for the above services to connect to this network
  • Map port 9999 of host machine to port 80 of HTTP server

Creating docker-compose.yml

Create mycode directory, inside we create docker-compose.yml file with the following content (I have commented explanations in the file content).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
version: "3" #docker composer version

#Create network named my-network
networks:
    my-network:
        driver: bridge

# Create services (containers)
services:

    #Create my-php container from php:latest image connected to my-network
    my-php:
        container_name: php-product
        image: 'php:latest'
        hostname: php
        restart: always
        networks:
            - my-network

    #Create my-httpd container from httpd:latest image connected to my-network, map port 9999 of host to port 80
    my-httpd:
        container_name: c-httpd01
        image: 'httpd:latest'
        hostname: httpd
        restart: always
        networks:
            - my-network
        ports:
            - "9999:80"
            - "443:443"
            
     #Create my-mysql container from mysql:latest image connected to my-network, configure environment variables
    my-mysql:
        container_name: myql-product
        image: "mysql:latest"
        hostname: mysql
        restart: always
        networks:
            - my-network
        environment:
            - MYSQL_ROOT_PASSWORD=123abc
            - MYSQL_DATABASE=db_site
            - MYSQL_USER=sites
            - MYSQL_PASSWORD=123abc

The file above is divided into 3 parts

  • The first part declares the docker composer version, here I use version 3

  • The next part declares network creation, here I create a network named my-network of bridge type
  • The next part creates services, here creating 3 services which are 3 containers (php, httpd, mysql), all these services connect to the my-network created in part 2

    • Container my-php created from php:latest image connected to my-network
    • Container my-httpd created from httpd:latest image connected to my-network, mapping port 9999 of host to port 80 and port 443 to 443 of host
    • Container my-mysql created from mysql:latest image connected to my-network, configuring environment variables like MYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD

Run docker-compose.yml file to create services

After creating the docker compose file, now we’ll run this file to create the defined services.

Go to the directory containing docker-compose.yml file and run the command

docker-compose up

To stop running services use the command

docker-compose stop

To terminate running services and completely delete containers use the command

docker-compose down

Monitor service logs

docker-compose logs [SERVICES]

VI. Docker swarm

VII. References

Other reference sources:

Understanding Dockerfile and Creating Docker Images

Dockerfile tutorial

Devops with Docker

This post is licensed under CC BY 4.0 by the author.