初心者による初心者のためのDocker入門 その１ dockerコマンド編

# はじめに

ずっと Docker 使ってみたいなと思いつつ使う機会がないまま・・・ついに今年１月に使う機会がやってきました！
その時に書いたメモなどを元に、「こんな記事があったら嬉しかった」と思える記事にまとめてみました。

この記事の対象読者は

 - コマンドライン操作が行える
 - Linuxを扱ったことがある
 - ファイルシステムやポート番号が何のことか分かる
 - 仮想マシンが何だか分かる

を想定しています。

記事は以下に分けて投稿する予定です。

 1. dockerコマンド編（この記事）
 2. Dockerfile編
 3. Dockerの仕組み編
 4. docker-compose編

Docker の利点を活かした使い方は３つ目以降で説明します。

筆者は Docker for Mac を使って試しています。
Windows の場合も基本的な考え方や docker コマンドの使い方は同じですが、Docker for Windows を利用していてコマンドプロンプトで操作する場合は、記事中の Unix コマンドは適宜 DOS コマンドに置き換えてください（それかコマンドを使わずに GUI で作業してください）。

# この記事の目標

シリーズ最初のこの記事では Docker を単なる「軽量な Linux 仮想マシン」を動かす環境と捉えて説明します。

 - サーバーと同じような開発環境を用意したい
 - ちょっと試したいサーバーアプリがあるけど、システムに直接インストールしたくない
 - Mac や Windows じゃ動作しない Linux プログラムを動かしたい

などなど、Linux 仮想マシンを使いたい場合に Docker を利用できるようになることを目指します。

そのために基本的な `docker` コマンドの使い方を覚えます。

# Dockerのインストール

[Download Docker Community Edition](https://www.docker.com/community-edition)からダウンロードしましょう。
Mac の場合は [Docker for Mac](https://www.docker.com/docker-mac) をインストールするだけです。

Windows の場合も Windows 10 以上かつ Home でなければ [Docker for Windows](https://www.docker.com/docker-windows) をインストールするだけです。
もし Windows 10 Home などで Docker for Windows を利用できない場合は、

 - 64bit
 - Windows 7以降
 - 仮想化支援技術(IntelならVT-x, AMDならAMD-v)がCPUでサポートされている
 - BIOSで上記仮想化支援機能(Virtualization)を有効にしている

の条件が満たされていれば [Docker Toolbox](https://docs.docker.com/toolbox/toolbox_install_windows/) が利用できます。"docker toolbox インストール" でググれば情報が色々出てくるかと思います。

Linux の場合もディストリビューションごとに用意されています。公式でなくともコミュニティによってパッケージが提供されているはずです。

Docker for Mac は起動するとメニューバー右上のステータスメニューに鯨のアイコンが表示されます。
これの Preferences で設定を行えます。Windowsも同様の設定があると思います。Linuxでは必要ありません（理由は後の記事で分かります）。

CPU数やメモリ使用量を最低限にして、使う時だけ起動する場合は以下のようにします。

 - General - Start Docker when you log in を OFF
 - Advanced - CPUs を 1 に
 - Advanced - Memory を 1.0 GB に

Docker をしっかり活用するようになったら適宜変更してください。

以下のコマンドで docker のバージョンを確認できます。`--version` だと簡易確認、`version` だと詳細確認。
__注: `docker` コマンドは Docker が起動していないと動作しません。__

```console
$ docker --version
Docker version 18.03.1-ce, build 9ee9f40
$ docker version
Client:
 Version:      18.03.1-ce
 API version:  1.37
 Go version:   go1.9.5
 Git commit:   9ee9f40
 Built:        Thu Apr 26 07:13:02 2018
 OS/Arch:      darwin/amd64
 Experimental: false
 Orchestrator: swarm

Server:
 Engine:
  Version:      18.03.1-ce
  API version:  1.37 (minimum version 1.12)
  Go version:   go1.9.5
  Git commit:   9ee9f40
  Built:        Thu Apr 26 07:22:38 2018
  OS/Arch:      linux/amd64
  Experimental: true
```

`docker info` で動作状況を確認できます。試してみてください。

# 用語の理解

とりあえず現時点での理解はこんな感じで。

![Docker初期概念図](https://qiita-image-store.s3.amazonaws.com/0/53805/34d92d74-8c51-2e69-b5b2-cd6185dff9f6.png)

## ホスト

Docker を動かしている環境のことです。あなたがこの記事の内容を試すために使っている自分の PC であり、その上で動いている macOS や Windows などのことですね。

## Docker

とりあえずこの記事を読んで作業するには、どうやって実現されているかは置いておいて、「CPUやメモリのオーバーヘッドが少ない、超軽量な仮想マシンを作って動かせるもの」と理解しておけばオッケー。
ただし __仮想マシンの OS には Linux しか利用できません__ 。

## コンテナ

１つの Linux 仮想マシンと思っておけばいいです。

## イメージ

コンテナを生成する元になるもの。
同じイメージを使えば、同じ Linux ディストリビューションで同じアプリケーションがインストールされた全く同じ環境を複製することができます。

「OS丸ごとディスク全体をバックアップしたもの」と思ってください。

## Docker Hub

あらかじめよく使われる便利なイメージが登録されています。
ここからイメージを取得して、それをベースに改造する形を取るのが一般的な利用方法になります。

取得可能なイメージは [Explore Official Repositories](https://hub.docker.com/explore/) で検索できます。

# 利用の流れ

必ずしも毎回この通りじゃないけど、大雑把に利用の流れを掴むために以下の順に解説します。

 0. Docker Hub のイメージを検索 (search)
 1. Docker Hub から使いたいイメージを取得する (pull)
 2. イメージからコンテナを生成して起動 (run)
 3. コンテナ内でコマンド実行 (exec)
 4. コンテナを停止 (stop)
 5. コンテナをイメージ化 (commit)
 6. いらなくなったらコンテナを削除 (rm)
 7. いらなくなったらイメージを削除 (rmi)

その他に以下のコマンドもよく利用します。

 - 取得済みイメージ一覧 (images)
 - 実行中コンテナ一覧 (ps)
 - 停止中も含めた全てのコンテナ一覧 (ps -a)
 - 停止中のコンテナを起動 (start)
 - ホストとコンテナ間でのファイルコピー (cp)

__run は 生成して起動__ だということに注意。色々作業して改変した後の状態で起動したいなら、

 - start を使う
 - commit してイメージ化したものから run

のどちらかを使います。
今はよく分からなくても、これから手を動かして作業しながら理解していきましょう。

また Docker Hub のアカウントを持っていると、自分の作成したイメージを Docker Hub に `push` することもできます。
今回の記事では `push` は対象外です。

# チュートリアル

nginx のイメージを利用して Web サーバーを立ててみます。

## イメージを取得する

公式が提供している nginx イメージ（何らかの Linux ディストリビューションをベースにして nginx が動作する設定にしてあるもの）があるのでそれを利用しましょう。
search コマンドで Docker Hub にあるイメージを検索してみると・・・

```console
$ docker search nginx
NAME                                                   DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
nginx                                                  Official build of Nginx.                        7722                [OK]
jwilder/nginx-proxy                                    Automated Nginx reverse proxy for docker con…   1233                                    [OK]
richarvey/nginx-php-fpm                                Container running Nginx + PHP-FPM capable of…   500                                     [OK]
jrcs/letsencrypt-nginx-proxy-companion                 LetsEncrypt container to use with nginx as p…   288                                     [OK]
kong                                                   Open-source Microservice & API Management la…   150                 [OK]
webdevops/php-nginx                                    Nginx with PHP-FPM                              94                                      [OK]
（略）
```

一番上にある Official build of nginx というのを使います。名前は nginx です。

なお search の使い方は [docker searchコマンドの使い方（実例付）](http://www.memotansu.jp/2017/01/docker/771/) が参考になります。

イメージを Docker Hub から取得してみます。

```console
$ docker pull nginx
```

取得済みイメージの一覧は以下で確認できます。

```console
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              3f8a4339aadd        2 weeks ago         108MB
```

## コンテナを生成して起動する

nginx のログを出力する `nginx/log` ディレクトリをホスト側に作成しておきましょう。

```console
$ mkdir -p nginx/log
```

生成と起動を一度に行う run コマンドを利用します。

```console
$ docker run -d -p 80:80 -v $(pwd)/nginx/log:/var/log/nginx --name webserver nginx
```

`-d` はバックグラウンド実行することを指定しています。コマンド実行後にコンテナIDが表示されます。
`-d` を付け忘れて実行するとコマンド入力が行えなくなります。その場合は慌てずに別ターミナルを立ち上げて作業しましょう。後述するコンテナの停止を行えば元のターミナルもまた入力できるようになります。

`-p 80:80` はホスト側の80番ポートをコンテナ側の80番ポートに転送するように指定しています。`-p 8080:80` と指定すれば http://localhost:8080 でアクセスできるようになります。

`-v $(pwd)/nginx/log:/var/log/nginx` はホストのディレクトリ(`$(pwd)/nginx/log`)をコンテナのディレクトリ(`/var/log/nginx`)としてマウントします。
__ホスト側のディレクトリも絶対パスで指定する__ 必要があります。
`$(pwd)`の部分はカレントディレクトリに置き換えられます（これは Unix シェルの機能です）。
このような `-v` でマウントするものを __ボリューム__ と言います。v は volume の頭文字です。

`--name webserver` は生成するコンテナに名前をつけます。名前をつけずにコンテナIDを利用することもできますが、指定するのが面倒なので名前をつけた方が作業しやすいです。
最後に指定している `nginx` が生成に利用するイメージの名前です。

__本当は最後（イメージ名の後）に実行するコマンドを指定する__ のですが、nginx のイメージはデフォルトで nginx を起動するコマンドが実行されるようになっているため必要ありません。

以下に概念図を示します（カレントディレクトリが /Users/myname/Documents と仮定しています）。

![docker_run.png](https://qiita-image-store.s3.amazonaws.com/0/53805/d84e3d44-d5e7-bfa0-0146-5128153be139.png)

これでホスト側でブラウザを立ち上げて

http://localhost/

にアクセスすれば、"Welcome to nginx!" のページが表示されるはずです。

nginx はアクセスログを `/var/log/nginx/access.log` に出力する設定になっています。
ホスト側の `~/nginx/log` をコンテナ側の `/var/log/nginx` にマウントしましたので、以下でアクセスログを確認できます。

```console
$ cat nginx/log/access.log
```

Docker ではコンテナを毎回削除するような使い方が好まれます（run の時に指定した内容は後から変更できないし・・・）。
__コンテナを削除すると起動後に作成されたコンテナ内のファイルは消えてしまう__ （PC捨てたらバックアップ取っとかない限り搭載されてるHDDの内容ごと捨てられるのは当たり前ですよね？）ので、データの保存場所は `-v` で別に指定しておくのがよくやる使い方になります。
ホスト側のディレクトリをマウントしてもいいし、名前付きボリュームを指定する方法もありますがそれは後述。


## コンテナ内で作業する

このコンテナのコマンドラインで作業してみましょう。

```console
$ docker exec -it webserver /bin/bash
root@1f99d8d3072d:/#
```

`exec` は指定したコンテナで指定したコマンドを実行します。
`-it` はコンテナ側のコマンドの入出力を手元のターミナルに接続します。
`webserver` がコンテナの名前です。
`/bin/bash` が実行するコマンドです。

root でログインした状態でコマンドプロンプトが表示されます。

apt コマンドが使えるので nginx イメージは debian 系みたいですね。
Docker は CPU とメモリのオーバーヘッドは小さいけれど、ディスクサイズは増えるので、目的のプロセスを動作させるのに最低限のシステムにするのが好ましいです。最近は Alpine Linux がよく使われます。
Alpine Linux ベースだと __bash が入っていなかったりするので、その場合は /bin/sh を指定__ します。

これで設定ファイルを変更したりといった様々な作業を行うことができます。

__`exec` で接続した場合は、`exit` してもコンテナは起動したまま__ になります。
え？当たり前じゃんって思いますか？ ところが __`run` の時にシェルを実行して `exit` した場合は、コンテナは停止します__。

```console
root@1f99d8d3072d:/# exit
```

実行中のコンテナ一覧を表示すると、ちゃんと実行されているのが確認できます。

```console
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
1f99d8d3072d        nginx               "nginx -g 'daemon of…"   17 minutes ago      Up 17 minutes       0.0.0.0:80->80/tcp   webserver
```

`-it` は手元のシェルに入出力を繋ぐために指定していますが、単に何かコマンドを実行するだけで結果表示すらいらないなら指定しなくてもいいです。

```console
$ docker exec webserver ls /etc/nginx
conf.d
fastcgi_params
koi-utf
koi-win
mime.types
modules
nginx.conf
scgi_params
uwsgi_params
win-utf
```

`docker cp` を使ってホスト側にファイルをコピーしてみましょう。

```console
$ docker cp webserver:/etc/nginx/nginx.conf .
```

`webserver:/etc/nginx/nginx.conf` が「どのコンテナの」「どのファイルか」を指定しています。
その後にホスト側のどこにコピーするかを指定します。上記では `.` でカレントディレクトリを指定してます。

## コンテナを停止する

停止するには `stop` を使います。シャットダウンして電源を切ると思ってもらえればいいかと。

```console
$ docker stop webserver
```

`ps` で実行中のコンテナを確認してみると、表示されなくなっています。

```console
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```

`ps -a` で停止中のものも含めて全て表示すれば見つかります。

```console
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS               NAMES
1f99d8d3072d        nginx               "nginx -g 'daemon of…"   44 minutes ago      Exited (0) 20 seconds ago                       webserver
```

## コンテナをイメージ化

何か色々設定した後のものをイメージ化するには `commit` コマンドを使います。

イメージの命名規則は以下です。

```
<Docker Hubのユーザ名>/イメージ名:[タグ名]
```

タグ名は自分が分かりやすい名前を付ければいいです。公開されているものは大抵はバージョン番号になっています。

今回は Docker Hub に `push` しないので、ユーザー名は省略します。
なお一応コンテナを停止していなくても作成できます。

```console
$ docker commit webserver webserver:0.0
```

イメージが作成されたか確認してみましょう。

```console
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
webserver           0.0                 943dcf35eaf4        7 seconds ago       108MB
nginx               latest              3f8a4339aadd        2 weeks ago         108MB
```

注意点としては __`-v` で指定したボリュームは `commit` でのイメージ化の対象外__ になります。
ボリュームは外付けハードディスクやネットワークドライブ的なもので、`commit` は内臓ハードディスクしかバックアップしないと考えると分かりやすいかと思います。

## コンテナを起動する

`run` を実行してしまうと新しくコンテナを作成してしまいます。
停止している既存のコンテナを起動するには `start` を使います。

```console
$ docker start webserver
```

実行中のコンテナを確認してみましょう。ちゃんと起動していますね。

```console
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
1f99d8d3072d        nginx               "nginx -g 'daemon of…"   2 hours ago         Up 4 seconds        0.0.0.0:80->80/tcp   webserver
```

## コンテナを削除する

起動しているコンテナを停止します（シャットダウン）。
停止しないでいきなり削除もできますが・・・。

```console
$ docker stop webserver
```

コンテナを削除します（マシンを廃棄）。

```console
$ docker rm webserver
```

全てのコンテナを表示して削除されたか確認してみます。

```console
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```

## イメージを削除する

いらなくなったイメージを削除してみましょう。
コンテナの削除が `rm` に対してイメージの削除は `rmi` を利用します。混同しやすいので注意してください。
タグを省略すると `latest` 指定と同じになります。

```console
$ docker rmi webserver:0.0
$ docker rmi nginx
```

削除されたか確認してみます。

```console
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
```

# おまけ

## privileged モード

CentoOS でサーバーと同じ開発環境を整えようとして躓いたので previleged モードについて書いておきます。

systemd を利用するディストリビューションでは、privileged モードを指定して `run` しないと `sytemctl` コマンドが実行できません。

参考: [Docker privileged オプションについて](https://qiita.com/muddydixon/items/d2982ab0846002bf3ea8)

参考: [Dockerで立ち上げたCentOSでsystemctlを使うとFailed to get D-Bus connection: Operation not permittedと出る](https://qiita.com/setouchi/items/6bfa116fc5a266856177)

また centos のイメージからコンテナを作った場合、`/sbin/init` を実行すると普通に起動したのと同じような感じに扱えます。
（nginx イメージは nginx を起動するコマンドが実行されるように設定されているため、コマンドを指定する必要はありませんでした）

```console
$ docker run -d --name mycentos --privileged centos /sbin/init
```

この記事では「軽量な Linux 仮想マシン」として扱う想定ですので、このような使い方を紹介しておきます。
続編の記事で解説しますが、実際には Docker の文化（利点を活かした使い方）では、１つのコンテナで１つのアプリケーションしか実行しないため、systemd を使わずに（nginx イメージのように）直接目的のサービスを実行する設定にするのが普通です。

## 名前付きボリューム

`run` の時に -v で指定するものを __ボリューム__ と呼ぶと前述しました。
ホスト側のディレクトリと同期する方法が良く用いられますが、Docker が管理する __名前付きボリューム__ を使う方法もあります。

```console
$ docker run -d -p 80:80 -v webserver_log:/var/log/nginx --name webserver nginx
```

これで `webserver_log` という名前付きボリュームがコンテナ側の `/var/log/nginx` にマウントされ、そこにログが出力されます。

名前付きボリュームはコンテナを削除しても残ります。
仮想外付けハードディスクと思ってもらえばいいです。ただしコンテナ作成時にしか接続できないので「リムーバブル」じゃないですけど。

以下に名前付きボリュームも加えた Docker の概念図を示します。

![docker_with_volume.png](https://qiita-image-store.s3.amazonaws.com/0/53805/a226d4c6-cada-abc5-89a6-b11ab8b95f40.png)

名前付きボリュームの一覧は

```console
$ docker volume ls
DRIVER              VOLUME NAME
local               webserver_log
```

で見ることができます。

削除するのは以下で。

```console
$ docker volume rm webserver_log
```

## 実は今までのコマンドは省略形？

`docker --help` で用意されているコマンドをみると、Management Commands というのがあります。

```console
$ docker --help
（略）
Management Commands:
  checkpoint  Manage checkpoints
  config      Manage Docker configs
  container   Manage containers
  image       Manage images
  network     Manage networks
  node        Manage Swarm nodes
  plugin      Manage plugins
  secret      Manage Docker secrets
  service     Manage services
  swarm       Manage Swarm
  system      Manage Docker
  trust       Manage trust on Docker images
  volume      Manage volumes

Commands:
  attach      Attach local standard input, output, and error streams to a running container
  build       Build an image from a Dockerfile
  commit      Create a new image from a container's changes
  cp          Copy files/folders between a container and the local filesystem
（略）
```

実は既に `docker volume ls` や `docker volume rm` で Management Commands は利用しています。

自分は基本的に Commands: に記載されているコマンドを使って作業していますが（楽なので）、もっと複雑なことをやる人や、どんなコマンドがあるのか `--help` をつけて確認する場合は、これらの Management Commands を使うと良いのではないでしょうか。
[Docker 本家の Get Started](https://docs.docker.com/get-started/) でも Management Commands を使って解説されています。

| Commands利用 | Management Commands利用 |
| --- | ---- |
| docker pull | docker image pull |
| docker images | docker image ls |
| docker rmi | docker image rm |
| docker ps | docker container ls |
| docker ps -a | docker container ls --all |
| docker rm | docker container rm |
| docker commit | docker container commit |
| docker run | docker container run |
| docker start | docker container start |
| docker stop | docker container stop |

このように「何に対する操作なのか」が体系的にまとまっていて、コマンド入力は面倒ですが分かりやすいです。
`docker image --help` で表示してみると、イメージに対する操作コマンドは以下があるようです。
使ってないイメージを削除してくれる `prune` とか便利そうです。

```console
$ docker image --help

Usage:  docker image COMMAND

Manage images

Options:


Commands:
  build       Build an image from a Dockerfile
  history     Show the history of an image
  import      Import the contents from a tarball to create a filesystem image
  inspect     Display detailed information on one or more images
  load        Load an image from a tar archive or STDIN
  ls          List images
  prune       Remove unused images
  pull        Pull an image or a repository from a registry
  push        Push an image or a repository to a registry
  rm          Remove one or more images
  save        Save one or more images to a tar archive (streamed to STDOUT by default)
  tag         Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE

Run 'docker image COMMAND --help' for more information on a command.
```


# まとめ

Docker を「Linuxしか使えない軽量な仮想マシン」を動作させる環境として捉え、そのための `docker` コマンドの使い方を学びました。

この記事の内容だけでも、Linux仮想マシンが欲しくなった時に Docker を利用できると思います。

# この次は？

コンテナを作ったらあれこれインストールしたり設定を変更したりするかと思います。
作業後に `commit` してイメージとして保存してもいいんですが、それらの作業内容を Dockerfile に記載してテキスト化しておくと管理しやすいです。

次は Dockerfile の記述方法と、Dockerfile からイメージをビルドする方法について説明する予定です。