Docker Quick Start Guide

Docker Workflow

The rough flow up to starting a container is as follows.

  1. Write code.
  2. Build a Docker image.
  3. Push the image to a repository.
  4. Download the image in the production environment.
  5. Start the container.

docker pull: Download a Docker Image

First, as a way to use something already created up to step 2, let’s look at the docker pull command.

You can download images uploaded to Docker Hub. You can also download images from a separately built Docker registry other than Docker Hub.

The command for downloading an image has the following format.

docker pull [image name](:version)

For example, pulling nginx looks like this.

$ docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
5b1423465504: Pull complete
1cdde8b981f2: Pull complete
6c0b05f215c0: Pull complete
004f1937a10a: Pull complete
fd61d71c75fe: Pull complete
717bf61a04cf: Pull complete
Digest: sha256:b95a99feebf7797479e0c5eb5ec0bdfa5d9f504bc94da550c2f58e839ea6914f
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest

You can also specify a version by adding a tag after the image name. If nothing is specified, the latest version (latest) is used.

To specify a version for nginx, use the following.

$ docker pull nginx:1.21.1

docker images: List Docker Images

You can check the list of downloaded or created images with the following command.

docker images

For example, the command output is displayed as follows.

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx        latest    fc5ec3f147e4   13 days ago   135MB

docker rmi: Delete a Docker Image

Images that are no longer needed can be deleted with the following command.

docker rmi [image ID|image name]

For example, deleting nginx with rmi looks like this.

$ docker rmi nginx
Untagged: nginx:latest
Untagged: nginx@sha256:b95a99feebf7797479e0c5eb5ec0bdfa5d9f504bc94da550c2f58e839ea6914f
Deleted: sha256:fc5ec3f147e43825672b6b308944040fa0ba4a547f4b50e911770f0d154eeed0
Deleted: sha256:3e85098616c4513b241d95d1a0c49740da10d191738bdd6b9220eab71ad15ddb
Deleted: sha256:714a2b51cf12dfc0219555fc2c71ff56a99da0f257fc1e81fbdc722d55821519
Deleted: sha256:ca9b331c8e9078528f222eacfdff111fc19a511e3f6282d307780b2d06da0e2a
Deleted: sha256:082aa9054c2265e06a3a62ab2afdbeb22768565808c7e5143f4652a997825b06
Deleted: sha256:f5355920965616631532773783ab892ba7d5b686833964b34968bd64d22b5521
Deleted: sha256:21ec097e7be7f53b7882fa719131a83be79e5b65e73993fa54929e5065e5b3e5

After deleting it, check whether it was actually removed.

$ docker images
REPOSITORY   TAG       IMAGE ID   CREATED   SIZE

You can confirm that nginx was deleted by the rmi command above.

docker run: Run Docker

To start a container from a Docker image, run the following command.

docker run [image name] (options)

You can start it without adding any options, but the following options are available for more convenient use.
Only commonly used options are listed here, so refer to the official Docker documentation for other options.

Option Meaning Example
--name Specify container name docker run --name "test" nginx
-d Run in the background docker run -d nginx
-it (shell specified) Print results to the console docker run -it nginx bash
-p host:container Port mapping docker run -p 8080:80 nginx
-v host:container Directory sharing docker run -v /test1:/test2 nginx
-e Set environment variables docker run -e foo=bar nginx
-w Specify working directory inside the container docker run -it -w=/tmp/work nginx bash

As an example, the startup settings and execution command are as follows.

docker run --name my-nginx -p 8080:80 -v ~/develop/docker:/usr/share/nginx/html -e foo=bar -w /tmp/work -it nginx bash
  • Image used: nginx
  • Container name: my-bash
  • Port: maps container port 80 to host port 8080
  • Directory: mounts the host’s ~/develop/docker to the container’s /usr/share/nginx/html
  • Environment variable: sets foo to bar
  • Working directory: /tmp/work
  • Prints results to the console
  • Specifies bash as the shell

docker ps: List Containers

The command for listing containers is as follows. In this case, only running containers are displayed.

docker ps

Let’s list the running containers.

$ docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS                  NAMES
caf5180d8506   nginx     "/docker-entrypoint...."   4 minutes ago   Up 7 seconds   0.0.0.0:8080->80/tcp   my-nginx

If there are no running containers, it will be displayed as follows.

$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

If you want to see all containers, including stopped containers, add the -a option.

docker ps -a

When you add the -a option, stopped containers can also be checked as follows.

$ docker ps -a
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS                      PORTS     NAMES
caf5180d8506   nginx     "/docker-entrypoint...."   6 minutes ago   Exited (0) 32 seconds ago             my-nginx

Stop Docker Execution: docker stop

To stop a running container, execute the following command.

docker stop [container ID|container name]

The container ID and container name here are the leftmost and rightmost columns, CONTAINER ID and NAME, printed when running docker ps above.

For practice, start nginx first.

$ docker run --name my-nginx -p 8080:80 -v ~/develop/docker:/usr/share/nginx/html -d nginx
caf5180d85067eae71a9ee6f4bb52e8222cc7f6a54b4b318384d0cd2c8794a63

After it starts, stop the container.

$ docker stop my-nginx
my-nginx

docker logs: Check Container Execution Logs

The method for viewing container execution logs is as follows.

docker logs [container ID|container name]

Based on the container shown by running docker ps above, enter the command as follows.

$ docker logs caf5180d8506
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh

... omitted ...

2022/09/05 18:20:28 [notice] 1#1: start worker process 27
2022/09/05 18:20:28 [notice] 1#1: start worker process 28

docker inspect: View Container Metadata

To view container metadata, use the following command.

docker inspect [container ID|container name]

When the command is actually executed, it is printed as follows.

% docker inspect caf5180d8506
[
    {
        "Id": "caf5180d85067eae71a9ee6f4bb52e8222cc7f6a54b4b318384d0cd2c8794a63",
        "Created": "2022-09-05T18:10:41.606382836Z",
        "Path": "/docker-entrypoint.sh",
        "Args": [
            "nginx",
            "-g",
            "daemon off;"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 5950,
            "ExitCode": 0,

... omitted ...
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:02",
                    "DriverOpts": null
                }
            }
        }
    }
]

docker exec: Execute in Docker

To enter a currently running container that was started in the background, use the following command. At this time, you must specify a shell.

docker exec -it [container ID|container name] [shell name] 

Enter the my-nginx container by specifying bash.

$ docker exec -it my-nginx bash
root@61dc29dec3e4:/tmp/work#

docker rm: Delete a Container

Stopped containers can be deleted with the following command.

docker rm [container ID|container name]

Let’s delete the my-nginx container used above.

$ docker rm my-nginx
my-nginx

Batch Delete Containers and Images

As explained above, images can be deleted with docker rmi, and containers can be deleted with docker rm. These commands require specifying each deletion target, so they are inconvenient when you want to delete items in bulk. Bulk deletion methods are as follows.

Delete stopped containers and anonymous images:

docker system prune

Delete all containers, including running containers:

docker rm -vf $(docker ps -a -q)

Delete all images:

docker rmi -f $(docker images -a -q)

Creating a Docker Image

Above, we pulled a repository from Docker Hub as a way to download a Docker image.

There are several other ways to obtain a Docker image, and here we will look at the following two points.

  • Create an image from a container.
  • Describe it in a Dockerfile.

Create an Image from a Container

When a new package is downloaded inside a container, the container becomes different from the container created from the image used at startup. The following command creates an image that can create a container with those new additions.

docker commit [container ID] [new container name]

docker container ls

Let’s look at the items displayed when running the docker container ls command.

$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
2axaf8tx4gjd        registry            "/entrypoint.sh /etc..."   7 weeks ago         Up 7 weeks          0.0.0.0:5000->5000/tcp   devkuma-private-registry

This command displays running containers and exited containers.

Item Description
CONTAINER ID Unique identifier for identifying a container
IMAGE Docker image used to create the container
COMMAND Application process executed in the container
CREATED Time elapsed since container creation
STATUS Up Container execution status such as Up (running) or Exited (stopped)
PORTS Connection relationship between host ports and container ports (port forwarding)
NAMES Container name

Write in a Dockerfile

You can create an image by writing image settings in a Dockerfile. It is a required configuration file when creating a Docker image. An image is created from the instructions written in this file.

The Dockerfile is as follows.

Dockerfile

FROM centos:7
  
RUN echo "now building..."
RUN yum -y install httpd
RUN sed -i '/#ServerName/a ServerName www.example.com:80' /etc/httpd/conf/httpd.conf
EXPOSE 80
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]

Looking at this file, you can see that the Dockerfile is written with commands such as FROM, RUN, and ADD, along with their arguments. The meanings and usage of commands such as FROM, RUN, and ADD are shown in the table below. Only commonly used ones are listed here as well, so refer to the official documentation for the rest.

Command Meaning Usage
FROM Specify base image FROM [image name] or FROM [image name]:[tag]
RUN Execute command RUN [command]
CMD Command executed when the container starts CMD [command] or CMD ["executable binary", "parameter 1", "parameter 2"]
EXPOSE Expose port EXPOSE [port]
VOLUME Mount VOLUME ["/data"]
ADD Add file/directory ADD [source] [destination]
COPY Copy file/directory COPY [source] [destination]
WORKDIR Specify working directory WORKDIR [directory path]
ENV Set environment variable ENV [key]=[value]

Instruction

FROM
  • Specifies the base image for the Docker image.

When specifying mysql, write the following.

FROM mysql
COPY
  • Copies a file or directory from the host computer to the Docker container.
  • It runs only once when the image is built.
COPY ./cnf/my.cnf /etc/mysql/conf.d/my.cnf
RUN
  • Defines a command executed in Docker for the Docker image.
  • Like COPY, it runs only once when the image is built.
  • It is not used unless there is a specific instruction.

The following compiles Test.java with javac.

RUN javac Test.java
ADD

It is basically the same as COPY, with the following additional features.

  • If the source is a remote URL, it downloads it and copies it to the destination.
  • If the source is a commonly used compressed file format such as zip, it extracts it.
  • If the source is a remote URL plus a compressed file format, it does not extract it.
CMD
  • Specifies the process or command executed in the container. This is the command executed every time the image runs.
  • Only one CMD can be created in a Dockerfile. If multiple are created, only the last command is executed.

The following runs mysqld.

CMD ["mysqld"]

Docker Build: docker build

Looking at the Dockerfile commands above, you can do things similar to options in docker run.

To build an image using the Dockerfile written this way, use the following command.

docker build [Dockerfile path]

By default, the file name Dockerfile is used. To change the file name, you need to add the -f option during build.

When this build is executed, the image is created as if each line (layer) of the Dockerfile is executed one by one.

You can see this from the output when the build is executed. Running the Dockerfile above produces the following.

$ docker build .
[+] Building 89.3s (8/8) FINISHED
 => [internal] load build definition from Dockerfile                                                                           0.0s
 => => transferring dockerfile: 252B                                                                                           0.0s
 => [internal] load .dockerignore                                                                                              0.0s
 => => transferring context: 2B                                                                                                0.0s
 => [internal] load metadata for docker.io/library/centos:7                                                                    5.0s
 => [1/4] FROM docker.io/library/centos:7@sha256:c73f515d06b0fa07bb18d8202035e739a494ce760aa73129f60f4bf2bd22b407             49.1s
 => => resolve docker.io/library/centos:7@sha256:c73f515d06b0fa07bb18d8202035e739a494ce760aa73129f60f4bf2bd22b407              0.0s
 => => sha256:c73f515d06b0fa07bb18d8202035e739a494ce760aa73129f60f4bf2bd22b407 1.20kB / 1.20kB                                 0.0s
 => => sha256:73f11afcbb50d8bc70eab9f0850b3fa30e61a419bc48cf426e63527d14a8373b 530B / 530B                                     0.0s
 => => sha256:c9a1fdca3387618f8634949de4533419327736e2f5c618e3bfebe877aa331352 2.77kB / 2.77kB                                 0.0s
 => => sha256:6717b8ec66cd6add0272c6391165585613c31314a43ff77d9751b53010e531ec 108.37MB / 108.37MB                            46.2s
 => => extracting sha256:6717b8ec66cd6add0272c6391165585613c31314a43ff77d9751b53010e531ec                                      2.6s
 => [2/4] RUN echo "now building..."                                                                                           0.3s
 => [3/4] RUN yum -y install httpd                                                                                            34.3s
 => [4/4] RUN sed -i '/#ServerName/a ServerName www.example.com:80' /etc/httpd/conf/httpd.conf                                 0.2s
 => exporting to image                                                                                                         0.2s
 => => exporting layers                                                                                                        0.2s
 => => writing image sha256:b32d2ca733218ab8a1d1840b775d2baed16cab3e1775477ae3839e990ffbc479                                   0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

From this output, you can see that each line (layer) is executed one by one on top of the base image written with FROM, just as written in the Dockerfile.

You can check whether a new image was created.

% docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
<none>       <none>    b32d2ca73321   13 minutes ago   417MB

Here, you can see that REPOSITORY and TAG are not specified and are shown as <none>.

You can also check the above contents again with the following command.

docker history [image ID|image name]
$ docker history b32d2ca73321
IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
b32d2ca73321   9 minutes ago   CMD ["/usr/sbin/httpd" "-D" "FOREGROUND"]       0B        buildkit.dockerfile.v0
<missing>      9 minutes ago   EXPOSE map[80/tcp:{}]                           0B        buildkit.dockerfile.v0
<missing>      9 minutes ago   RUN /bin/sh -c sed -i '/#ServerName/a Server...   11.8kB    buildkit.dockerfile.v0
<missing>      9 minutes ago   RUN /bin/sh -c yum -y install httpd # buildk...   117MB     buildkit.dockerfile.v0
<missing>      9 minutes ago   RUN /bin/sh -c echo "now building..." # buil...   0B        buildkit.dockerfile.v0
<missing>      6 months ago    /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>      6 months ago    /bin/sh -c #(nop)  LABEL org.label-schema.sc...   0B
<missing>      6 months ago    /bin/sh -c #(nop) ADD file:5b1e63a3cb041177b...   301MB

Push the Created Image

Let’s look at how to publish an image created this way to Docker Hub.

First, you need a Docker Hub account, so create one at the following site.

To publish the image, you first need to specify a tag. Add a tag using the username registered on Docker Hub as shown below.

docker tag [Image ID] [DockerHub username]/[repository name]

To tag the image newly created above, run the following command.

docker tag b32d2ca73321 devkuma/centos7

When executed, no output is displayed.

% docker tag b32d2ca73321 devkuma/centos7

If you list the images, you can see that REPOSITORY and TAG are set as specified.

% docker images
REPOSITORY        TAG       IMAGE ID       CREATED          SIZE
devkuma/centos7   latest    b32d2ca73321   17 minutes ago   417MB

Before pushing, you need to log in to Docker Hub, so run the following command to log in.

docker login
% docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: devkuma
Password:

Login Succeeded

Logging in with your password grants your terminal complete access to your account.
For better security, log in with a limited-privilege personal access token. Learn more at https://docs.docker.com/go/access-tokens/
user@AL02263852 docker

If you have completed up to this point, you can upload the repository to Docker Hub.

docker push [DockerHub username]/[repository name]

Now run the following command.

$ docker push devkuma/centos7
Using default tag: latest
The push refers to repository [docker.io/devkuma/centos7]
5818d7ffd9ac: Pushed
085e94a3a50e: Pushed
6a9a145374b8: Pushed
65f23ff12f4d: Mounted from library/centos
latest: digest: sha256:2845b5f6439dff6a80ed8ed45a10cfd39e19d8178182fe043db939ccc3b7f982 size: 1156

Managing Multiple Containers (docker-compose)

When you want to start and stop multiple containers, running docker run and docker stop for each one takes a lot of time. docker-compose solves this by managing them in bulk. This method writes information for multiple containers in a YAML file (docker-compose.yaml) and manages them.

An example file is as follows.

docker-compose.yaml

version: '3'
services:
  db:
    image: postgres
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

Details are not explained here, but roughly, the version property contains the docker-compose version, and the services property contains information about the servers to start. In this file, it is written to start a DB server and a web server. The contents under it are also similar to what is written in a Dockerfile. For details, refer to the official Docker documentation.

After writing this YAML file, you can start the containers by running the following command.

docker-compose –f [yaml file path] up

For started containers, run the following command to stop the containers listed in the YAML file.

docker-compose -f [yaml file path] dowm

To collect and view the logs of multiple containers started this way, run the following command.

dokcer-compose -f [yaml file path] logs

There are cases where you want to start multiple web servers, such as when configuring server clustering. Starting multiple containers that perform the same role is called creating replicas. Let’s look at how to start multiple container replicas with docker-compose.

To start multiple containers, you need to change the port settings in the YAML file above. In particular, specify a range so that ports do not overlap between containers.

docker-compose2.yaml

version: '3'
services:
  db:
    image: postgres
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/myapp
    ports:
      - "3000-3004:3000"
    depends_on:
      - db

If you change the contents as shown, host-side ports 3000, 3001, 3002, 3003, and 3004 are assigned, so a set of five replicas can be created. Use this docker-compose file to start them, but add the --scale option when running the up command and specify the number of replicas.

docker-compose –f [yaml file path] up –scale [container name]=[number of replicas]

Network

Containers defined with docker-compose can communicate with each other by default. However, they cannot be accessed from other locations, meaning containers other than those written in the YAML file. This is because a network namespace is created and assigned to the container when the container starts.

When containers are started individually and no special settings are configured, a network namespace is created so they are connected to the default Bridge network. Meanwhile, container groups created by docker-compose have a separate Bridge network configured and a network namespace is created so they connect to that Bridge network.
Therefore, containers created by docker-compose and containers created individually cannot communicate.

To solve this problem and connect them, the network must be configured. This is not explained here; it will be covered later.

To view the network list, run the following command.

docker network ls
$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
7f8103f64ff5   bridge    bridge    local
540c2f32637b   host      host      local
a0053c593c0e   none      null      local

From this output, you can see that there are several types in the DRIVER column. These correspond to network modes. Docker networks have the following four modes.

  • bridge
    • Because IPs are assigned from the DockerBridge network inside the host, the IP range is separate from the host.
  • host
    • Because IPs are assigned from the host’s IP range and host ports are used, port mapping with options such as -p is not possible.
  • null
    • It is not linked to either the Bridge or host, and no IP is assigned. Therefore, it becomes unreachable.
  • overlay
    • Used when there are multiple hosts.

To create a new network, run the following command.

docker network create –driver bridge [network name]

To delete a network, run the following command.

docker network rm [network name]

Storage

Containers can have short lifecycles, and data is stored on the host rather than in the container.
For this purpose, as specified with -v in the docker run command, specify the host folder and container folder and perform the mount.