Dockerでプログラマが最低限知るべきことが、最速でわかるチュートリアル

#はじめに
Dockerを理解するには試してみるのが一番です。
でも、あなたはある日突然「あと一時間でDockerを理解する必要がある」
状況に追い込まれた不運なプログラマになってしまうかもしれません。
そんな状況で公式サイトのチュートリアルは長すぎることでしょう。英語ですし。
なので、Dockerでプログラムが最低知るべきことが最速でわかるチュートリアルを書いてみました。

#簡単に「Dockerそれっておいしいの？」に答える
Dockerは単に仮想環境を提供するアプリケーションです。LinuxのVMを提供します。ただ、コンテナ型ってやつなので軽量です。VMが短時間で構築できて便利です。コンテナ型ではないフルの仮想環境はゲストOSに中間マージンを払っているようなものなので、ゲストOSとホストOSが同じことやってるならリソースのムダです。ならば、ホストOSと直取引し、ゲストOSに支払っていた中間マージンを削ってコスト削減を図ろう、というのがコンテナ型仮想化の発想です。

この辺の事情は他のサイトでさんざん語られています。以下とか詳しいかと
[さくらのナレッジ　Docker入門（第一回）～Dockerとは何か、何が良いのか～](https://knowledge.sakura.ad.jp/13265/)

あれ？それじゃあ、WindowsでDocker使えるの？と疑問に思ったかもしれません。WindowsでもDockerは使えますが、その辺のからくりは以下が詳しいです。
[図解で理解できる（はず）Microsoftの仮想化技術――Windows上で稼働するLinux、動かしているのはどのテクノロジー？（その2） (2/2)](http://www.atmarkit.co.jp/ait/articles/1710/24/news010_2.html)

#インストール
Macなら以下のサイトをご覧ください。インストーラがダウンロードできます。
[Install Docker for Mac](https://docs.docker.com/docker-for-mac/install/)

Linuxの方はapt-get等を使ってレポジトリからインストールするでしょう。ubuntuですと、以下にインストール手順がのっています。
[Install using the repository](https://docs.docker.com/install/linux/docker-ce/ubuntu/#install-using-the-repository)

Windowsの場合「何を」インストールするか少々複雑です。できれば最新版をインストールしたいところなのですが、最新版の「Docker CE for Windows」はシステム要件が厳しいです。動くのはWindows 10 Proぐらいで、Windows 7はおろか、Windows 10でもhomeでは動きません。これは「Docker for Windows」がHyper-vを前提にしているためです。システム要件を満たさない場合は、「Docker Toolbox on Windows」をインストールしてください。
では、Windows 10 Proなら「Docker CE for Windows」をインストールすれば良いかというと一つ問題があります。Hyper-vが有効化されていると、VirtualBoxなど他の仮想化アプリケーションが使えなくなるので、「Docker CE for Windows」とVirtualBoxは併用できません。以上を加味して、「Docker CE for Windows」か「Docker Toolbox on Windows」のどちらをインストールするかを選択してください。

[Install Docker for Windows](https://docs.docker.com/docker-for-windows/install/)

[Install Docker Toolbox on Windows](https://docs.docker.com/toolbox/toolbox_install_windows/)

#sudoについて
一般ユーザにはDockerを動かす権限がないので、linuxやmacでDockerを使う場合はコマンドの前に```sudo```をつけます。それが面倒な場合は、Dockerグループに自身のユーザを加えればよいです。方法は公式サイトの以下にのっています。
[Manage Docker as a non-root user](https://docs.docker.com/install/linux/linux-postinstall/#manage-docker-as-a-non-root-user)

ただし、どんな場合でもそうですが、利便性を得ることでセキュリティーが犠牲になっていることを理解しましょう。
[Dockerでユーザーをdockerグループに追加することの危険性を理解しよう](https://qiita.com/matyapiro31/items/3e6398ce737e2cdb5a22)
に、Dockerを踏み台にしてroot権限を実行されてしまう問題点が指摘されています。

本稿ではDockerグループとなっている前提で```sudo```を省略しています。Dockerグループになっていない場合、以降のコマンドは```sudo```をつけて実行してください。

#hello-world
まず、一番簡単なHello Worldをやってみます。hello-worldというDockerイメージが用意されていますので、```docker run　hello-world```コマンドで起動します。

```shell_session
ozw@ozw-MacBookAir:~$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
d1725b59e92d: Pull complete 
Digest: sha256:0add3ace90ecb4adbf7777e9aacf18357296e799f81cabc9fde470971e499788
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
```

と、こんな画面になればOKです。

##解説
ちょっとだけ解説。まず最初の行

```
Unable to find image 'hello-world:latest' locally
```
「あなたのローカルPCに'hello-world'の最新イメージは見つかりません」と言っています。
'hello-world'イメージが見つからないので、```docker run```コマンドはDocker Hubレポジトリからhello-worldのDockerイメージをダウンロードします（以後、レポジトリからDockerイメージをダウンロードする事を"pullする"と呼びます）。それをやっていたのが、以降の行です。

```
latest: Pulling from library/hello-world
d1725b59e92d: Pull complete
Digest: sha256:0add3ace90ecb4adbf7777e9aacf18357296e799f81cabc9fde470971e499788
Status: Downloaded newer image for hello-world:latest
```

これをやっていた時に一瞬、プログレスバーのようなものが見えた方もいるかもしれません（一瞬なので気づかなかったかもしれませんが）。
この後、Docker runコマンドはpullしてきたDockerイメージを元にコンテナを作成し、Hello Worldのメッセージを表示します。

pullしてきたDockerイメージの一覧は```docker image ls```コマンドで確認できます。

```shell_session
ozw@ozw-MacBookAir:~$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              4ab4c602aa5e        3 weeks ago         1.84kB
```

作成済Dockerコンテナの一覧は```docker container ls -a```コマンドで確認できます。

```shell_session
ozw@ozw-MacBookAir:~$ docker container ls -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
17a70d575dc1        hello-world         "/hello"            3 minutes ago       Exited (0) 3 minutes ago                       gifted_goldstine
```

"-a"は「コンテナ一覧をすべて表示」するためのオプションです。これをつけないと起動中のコンテナしか表示されません。

ところで、上記のコマンドではなく、```docker images```や```docker ps```というコマンドで教わった、という方もいらっしゃるかもしれません。これらは古いコマンドで、新しいコマンドは「何を」「どうする」かを明確にし、わかりやすくなっています。なお、古いコマンドは新しいコマンドのエイリアスとして定義されていますので、古いコマンドも今まで通り使えます。

詳しくはこちら
[docker container / image コマンド新旧比較](https://qiita.com/zembutsu/items/6e1ad18f0d548ce6c266)


##実験
今はhello-worldイメージがローカルにありますが、この状態でもう一回同じことをやったらどうなるでしょう？既にローカルにイメージがあるのでpullされないはずです。やってみましょう。

```shell_session
ozw@ozw-MacBookAir:~$ docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
```
前回とは違いpullをしている箇所は見当たりません。

前回と同様、```docker image ls```と```docker container ls -a```にて現在の状態を確かめます。

```shell_session
ozw@ozw-MacBookAir:~$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              4ab4c602aa5e        3 weeks ago         1.84kB
ozw@ozw-MacBookAir:~$ docker container ls -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
484810ae0812        hello-world         "/hello"            3 minutes ago       Exited (0) 3 minutes ago                       heuristic_banach
17a70d575dc1        hello-world         "/hello"            7 minutes ago       Exited (0) 7 minutes ago                       gifted_goldstine
```

Dockerイメージは既にローカルにあるものが再利用されたので１つしかpullされず、コンテナはrunした回数だけ作成されました。
OSのインストールDVDを仮想化したものがDockerイメージ、OSが起動しているサーバを仮想化したものがコンテナに相当します。サーバは必要に応じて何台も構築するけど、OSのインストールDVDは一つあればいいのと同じ事です。

##Dockerイメージとコンテナの削除
では、Hello Worldは卒業と言う事で、今回できたDockerイメージとコンテナをローカルPCから削除します。

dockerイメージの削除コマンドは```docker image rm```
コンテナの削除コマンドは```docker container rm```
です。コンテナ→Dockerイメージの順に削除します。逆順だと以下のように
「まだコンテナあるから消せないよ」と怒られます。

```shell_session
ozw@ozw-MacBookAir:~$ docker image rm hello-world
Error response from daemon: conflict: unable to remove repository reference "hello-world" (must force) - container 17a70d575dc1 is using its referenced image 4ab4c602aa5e
```

では、コンテナを削除します。引数に削除したいコンテナのコンテナIDを指定します。こんなふうに

```shell_session
ozw@ozw-MacBookAir:~$ docker container rm 484810ae0812
```

最後の引数の暗号のような文字列はコンテナIDです。コンテナIDは```docker container ls -a```した時、"CONTAINER ID"列に表示された文字列です。上の例では12文字すべて入力しましたが、実は1文字だけでもいいです。こんなふうに

```shell_session
ozw@ozw-MacBookAir:~$ docker container rm 4
```

コンテナIDは候補を１つに絞るのに必要最小限の文字さえ入力すればいい、というのがDockerの仕様です。ならば、うっかり１つに絞りきれないような文字列を入力してしまったらどうなるでしょう？例えば、1で始まるコンテナIDが２つあるのにコンテナIDに"1"しか入力しなかったら、２つのコンテナが消されてしまうのでしょうか？
心配ご無用、その場合は次のようなエラーとなります。

```shell_session
ozw@ozw-MacBookAir:~$ docker container rm 1
Error response from daemon: Multiple IDs found with provided prefix: 1fc98833ff49c61dd2436cb3a820670e706976a999cdf1f778de3cd4d01c477d
```

上のようなエラーがでたら、コンテナIDの文字列を増やしてもう一回やってみましょう。

次にDockerイメージを削除します。引数はDockerイメージ名です。

```shell_session
ozw@ozw-MacBookAir:~$ docker image rm hello-world
```

#もっとLinuxっぽく
では、次の一歩を踏み出しましょう。hello-worldの表示をよく見ると、親切なことに次に何をしたら良いかが書いてあります。

こんなふうに

```shell_session
To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash
```

さらに野心のある人はこのコマンドを試せ、とあるので試してみましょう。どうやらubuntuが動くらしいですよ。

```shell_session
ozw@ozw-MacBookAir:~$ docker run -it ubuntu bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
124c757242f8: Pull complete 
9d866f8bde2a: Pull complete 
fa3f2f277e67: Pull complete 
398d32b153e8: Pull complete 
afde35469481: Pull complete 
Digest: sha256:de774a3145f7ca4f0bd144c7d4ffb2931e06634f11529653b23eba85aef8e378
Status: Downloaded newer image for ubuntu:latest
root@5405e34673d8:/# 
```
hello-worldのときよりpullに時間がかかって本物っぽいです。

最後のコマンドプロンプトが "root" になっていますね。これはubuntuにログインできたということでしょうか？ubuntuのバージョンを表示させて確認してみましょう。

```shell_session
root@5405e34673d8:/# cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.1 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.1 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic
```
どうやら、ubuntuの18.04が動いているようです。

>バージョンの確認方法は以下を参考にしました。
>[Ubuntuのバージョン確認
](https://qiita.com/glires/items/a2a07565207509af0e83)

うまく行った記念にtouchで空ファイルを残しておきます。

```shell_session
root@5405e34673d8:/# touch hogezawa
root@5405e34673d8:/# ls -l
total 64
drwxr-xr-x   2 root root 4096 Aug 21 21:14 bin
drwxr-xr-x   2 root root 4096 Apr 24 08:34 boot
drwxr-xr-x   5 root root  360 Oct  7 12:16 dev
drwxr-xr-x   1 root root 4096 Oct  7 12:16 etc
-rw-r--r--   1 root root    0 Oct  7 12:18 hogezawa
 ... 以下略
```

ところが、hello-worldにコンテナの起動方法は書いてあるのに、ここから抜ける方法は書いてありません。なんともハシゴを外された感があり、不親切です。この状況から脱するためには、ctrlキーを押しながら'pq'と押します。（余談ですが、'pq'という文字の形から、appleのbuletoothイヤホン"AirPods"を連想するのは私だけでしょうか？）

では、例によって```docker image ls```と```docker container ls -a```にて現在の状態を確かめます。

```shell_session
ozw@ozw-MacBookAir:~$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              cd6d8154f1e1        4 weeks ago         84.1MB
ozw@ozw-MacBookAir:~$ docker container ls -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
5405e34673d8        ubuntu              "bash"              4 minutes ago       Up 4 minutes                            jovial_torvalds
```

#オプションの意味は？
上記のrunコマンドでは```-it```というオプションを指定しています。これは```-i```オプションと```-t```オプションを合わせて書いたものです。```-i```オプションにはゲストとホストの標準入力をつなげる役割が、```-t```オプションには逆に標準出力をつなげる役割があります。ですので、```-it```オプションをつけないと、なんのコマンドも打てないし、なんの出力も受け取れません。他、よく使われるオプションに以下のようなものがあります。

|オプション|意味|
|---|---|
|-d|バックグラウンドで実行|
|-i|ホストの標準入力をゲストの標準入力に入力|
|-p|ポートを読み替える|
|-t|ゲストの標準出力をホストの標準出力に出力|
|-v|共有フォルダの設定|

詳しくは```docker run --help```で見てください。オプションで基本何をするかというと、ゲストとホストのインタフェースを指定します。上の例でも-dオプション以外、インタフェースに関するものです。標準入出力も、ポートも、共有フォルダも、ホストとゲストのやり取りするもの、という点で共通しています。

#attach と exec の違い
コンテナのSTATUSを見ると、まだこのコンテナは起動しているようです。ならば、またログインできるはずです。起動しているコンテナにログインする方法は２つあります。

```docker container attach```コマンドを使う方法と
```docker container exec```コマンドを使う方法
です。違いはなんでしょうか？
attachは「ゲストOSの標準入力／標準出力を、ホストOSの標準入力／標準出力とつなげる」という意味です。今、ゲストOSではbashが動いているはずです。なぜなら、最初のrunコマンドで```docker run -it ubuntu bash```と書いているからです。このコマンド最後の引数は、ゲストOSで最初に起動するプロセスを指定します。attachは、このゲストOSで起動しているbashとホストOSの入出力をつなげるためのコマンドです。
一方、execは「引数のコマンドを実行する」という意味です。execでは現在起動しているbashとは別に、新たにbashを起動します。ですので、両者でプロセス一覧を表示してみると、起動しているプロセスの数が違うはずです。

まずはattachの方からためします。引数はコンテナID（の最初の１文字）です。

```shell_session
ozw@ozw-MacBookAir:~$ docker container attach 5
root@5405e34673d8:/# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  18508  3472 pts/0    Ss   12:16   0:00 bash
root        21  0.0  0.0  34400  2924 pts/0    R+   12:24   0:00 ps aux
```

ctrlキー+'pq'でぬけます。
次にexecを試してみます。引数にはコンテナIDの他に、"-it"オプションと、起動するプロセスが必要です。

```shell_session
ozw@ozw-MacBookAir:~$ docker container exec -it 5 bash
root@5405e34673d8:/# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  18508  3472 pts/0    Ss+  12:16   0:00 bash
root        22  0.3  0.0  18508  3484 pts/1    Ss   12:25   0:00 bash
root        33  0.0  0.0  34400  2976 pts/1    R+   12:25   0:00 ps aux
root@5405e34673d8:/# exit
exit
```
このように、execでプロセスが１つ増えました。
今度は、```exit```と入力して抜けます。

attachとexecとで抜け方を変えたのは、attachで入っているとき```exit```で抜けると、コンテナで起動している唯一のプロセスを止めてしまい、コンテナが停止してしまうからです。厄介なことに、PID=1のbashを止めてしまうと、attachする先もなく、execでプロセスを起動するための親プロセスもなくなってしまうので、このコンテナにつなぐ術がなくなってしまいます。このように、attachでコンテナにつなぐと一番大事なプロセスを誤って殺してしまう可能性があるため、execでつなぐ方法の方が安全です。

ところで、この結果を見て奇妙に感じる方がいらっしゃるかもしれません。奇妙な点とは
1.auxオプションをつけたのに、表示結果が少なすぎる
2.PID=1のプロセスがinitではない
の２点です。普通Linuxで全プロセスを表示させると以下のように膨大な数のプロセス一覧が表示されます。

```shell_session
ozw@ozw-MacBookAir:~$ ps aux | more
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.2 225636  9380 ?        Ss   13:26   0:03 /sbin/init splash
root         2  0.0  0.0      0     0 ?        S    13:26   0:00 [kthreadd]
root         4  0.0  0.0      0     0 ?        I<   13:26   0:00 [kworker/0:0H]
root         6  0.0  0.0      0     0 ?        I<   13:26   0:00 [mm_percpu_wq]
root         7  0.0  0.0      0     0 ?        S    13:26   0:00 [ksoftirqd/0]
root         8  0.0  0.0      0     0 ?        I    13:26   0:04 [rcu_sched]
 ... 以下略
```
ですが、コンテナ型仮想化では、initなどのベースのプロセスはゲストOSのものを使います。ですので、ホストOSのプロセスにinitなどのプロセスは含まれず、シンプルなプロセス一覧となります。「ゲストOSに中間マージンを払わない」の意味がわかって頂けたのではないでしょうか？

##実験
attachは「既に起動している」プロセスにつなぎに行きます。そのことが実感できる実験をやってみましょう。まず、attachでコンテナにつないだ後、別のターミナルからattachしてみます。こんなふうに。

![Screenshot from 2018-10-07 21-30-26.png](https://qiita-image-store.s3.amazonaws.com/0/292064/87be0235-9cef-97db-1bcc-ebe0bc8e6881.png)

一方の入力をするとアラ不思議！もう一方も連動して動きます。同じプロセスに2つのターミナルがattachしているため、このようなことが起こります。当たり前の挙動なのですが、面白いのでぜひ試して見てください。

#イメージを保存する
コンテナを変更した結果を保存するには```docker container commit```コマンドを使います。と、その前に起動中のコンテナはcommitできないので、まずは```docker container stop```コマンドでコンテナを停止します。 引数はコンテナIDです。

```shell_session
ozw@ozw-MacBookAir:~$ docker container stop 5
5
```
その後、commitして現在のコンテナから新しいDockerイメージを作成します。引数はコンテナIDと新しいDockerイメージ名です。

```shell_session
ozw@ozw-MacBookAir:~$ docker container commit 5 ubuntu-hoge
sha256:2005790094a102776bf8d3e2f858ffd1bef0d0a2c3a766d6aba8d6492742a205
ozw@ozw-MacBookAir:~$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu-hoge         latest              2005790094a1        11 seconds ago      84.1MB
ubuntu              latest              cd6d8154f1e1        4 weeks ago         84.1MB
```
ちゃんと、Dockerイメージができたか、runし直してみて確認します。

```shell_session
ozw@ozw-MacBookAir:~$ docker run -it ubuntu-hoge bash
root@cbfb98275513:/# ls -l
total 68
drwxr-xr-x   2 root root 4096 Aug 21 21:14 bin
drwxr-xr-x   2 root root 4096 Apr 24 08:34 boot
drwxr-xr-x   5 root root  360 Oct  7 12:38 dev
drwxr-xr-x   1 root root 4096 Oct  7 12:38 etc
-rw-r--r--   1 root root    0 Oct  7 12:18 hogezawa
 ... 以下略
```
hogezawaファイルもちゃんとありました。先程作ったコンテナで間違いないです。

#コンテナのライフサイクル
Dockerはコンテナのライフサイクルがわかるようになると理解が深まります、以下にDockerコンテナのライフサイクルが図示されていますので、参考になるかと
[Dockerにおけるコンテナのライフサイクル](http://enakai00.hatenablog.com/entry/20140628/1403933390)

#まとめ
本稿で登場したコマンドをまとめます。
```docker run```
```docker image ls```
```docker image rm```
```docker container ls```
```docker container attach```
```docker container exec```
```docker container stop```
```docker container rm```

さしあたって以上でDockerを使うに困らないかと思います。

本稿は「最速」での理解を重視して、DockerfileやDocker Composeには触れませんでしたが、これらも重要な概念ですので、余裕ができたら取り組んでみてください。