初学者向けDockerfileハンズオン

この記事は [Docker Advent Calendar 2019](https://qiita.com/advent-calendar/2019/docker) 2日目の記事です。
昨日は @kai_kou さんによる [AWSとGCPのインスタンスやコンテナサービスをDockerコンテナ上のUnixBenchでベンチマークして比較してみた](https://qiita.com/kai_kou/items/92f5d12547da4dc3da07) でした。

# 初学者向けDockerfileハンズオン

Docker初学者向けにDockerfileハンズオンを実施しましたので、その時に作ったハンズオン資料を公開します。

DockerコマンドおよびDockerfileに慣れることが目的のハンズオンの資料ですので、Dockerの詳しい説明はしません。

また、お使いのPCにDockerはインストール済みという前提で進めます。

文章の手順通りに進めていけば、DockerコマンドとDockerfileの体験ができます！



# 軽量なLinux・・・それがalpine
Linuxといえば、CentOSやUbuntuが有名ですが、Docker界隈では軽量なalpineを使うのが定番です。

以後、alpineを使う前提で話を進めます。



# Windows/Mac上で動くalpineマシンがほしい！

なぜDockerを使うのか？ **WindowsやMac上でLinuxを使いたいからです！**

ここからはWindows/Mac上でalpineを使う体験をしていきたいと思います。



## alpine入りのDockerイメージをpullする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/92be24af-1514-9921-5981-e7af1c4a6423.png)

まずは、Dockerイメージというものを入手しましょう。Dockerイメージというのは、Linuxマシンのひな型だと思ってください。

Docker Hub にDockerイメージが公開されていますので、それを以下のコマンドでダウンロードします。

```bash
# alpineのDockerイメージをダウンロードする
docker pull alpine:3.9
```

もしネットワークがプロキシ配下にある場合、ここでエラーが発生するかもしれません！

Dockerのプロキシの設定を確認してみてください。



## alpine入りのDockerコンテナを生成する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/ea9e3ef4-bb6c-c96e-8a78-cf0dac3ab632.png)

次にDockerコンテナを生成します。Dockerコンテナというのは、Linuxマシン本体のことだと思ってください。

以下のコマンドで、コンテナを生成し、そのコンテナに入ります。


```bash
# Dockerコンテナを生成して入る
docker run -it alpine:3.9 /bin/ash
```

これだけで、alpineマシンに入ることができます！

`docker run` というのはコンテナを起動するというコマンドです。

`-i` は標準入力を受け付けるというオプションです。

`-t` は疑似TTYを割り当てるというオプションで、 `-it` のようにくっつけることで、コンテナ内に入力ができるようになります。

コンテナに対し入力できる状態のことを **コンテナに入る** と本記事では呼ぶことにします。

`bin/ash` というのは、コンテナ内で使用するシェルが `bin/ash` であるという意味です。alpineでは `bin/bash` が使えないので、代わりに `/bin/ash` を使います。 （なので、タイプミスではありませんｗ）

コンテナに入れたら、 `exit` で抜けましょう。

```bash
# コンテナから抜ける
exit
```



## 作成したコンテナのIDを確認する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/32f7db40-6a71-becf-3682-7f0abaa4ac22.png)

`exit` で抜けましたが、コンテナは消えたわけではなく、まだ存在しています。

以下のコマンドでIDを確認してみましょう！

```bash
# 作成したDockerコンテナを確認する
docker ps -af ancestor=alpine:3.9
```

以下のような情報が表示がされます。

```dockerfile
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
0cf65c560abb        alpine:3.9          "/bin/ash"               4 minutes ago       Exited (0) 4 minutes ago                       vigilant_knuth
```

`docker ps` というのは、Dockerコンテナの一覧を表示するコマンドです。

`-a` は停止中のものも含めてすべてのコンテナを表示する候補にするというオプションです。

`-f ancestor=alpine:3.9` は、候補のうち、イメージ名が `alpine:3.9` のものを表示するという意味です。

`-a` と `-f` は `-af` のようにくっつけることができます。



## もう一度起動してコンテナのIDを確認する

もう一度コンテナを起動します。

```bash
# Dockerコンテナを生成して入る
docker run -it alpine:3.9 /bin/ash
```

コンテナに入れたら、すぐに抜けます。

```bash
# コンテナから抜ける
exit
```

再び、コンテナのIDを確認しましょう。

```bash
# 作成したDockerコンテナを確認する
docker ps -af ancestor=alpine:3.9
```

今度は以下のように表示されます。

```dockerfile
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
a4a95f438cbc        alpine:3.9          "/bin/ash"               12 seconds ago      Exited (0) 2 seconds ago                        serene_dubinsky
0cf65c560abb        alpine:3.9          "/bin/ash"               4 minutes ago       Exited (0) 4 minutes ago                       vigilant_knuth
```

あれ？ **２つに増えてる？？**

はい、そうなんです。実は、 `docker run` する度に、以下のように新しいコンテナが生成されるんです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/a01813b0-7e4c-c85b-f3bc-870276d3b65f.png)

先ほど、コンテナはLinuxマシンに相当すると説明しました。

現在のこの状況は、つまりLinuxマシンが2台あるということになります。

私が初めてこの `docker run` コマンドを触ったときは、「Linuxマシンを2台も作るなんて贅沢だ！1台を使いまわせばいいじゃないか」 と思いました。

しかし、Docker界隈には、 **１つのコンテナを使いまわさずに、毎回新品のコンテナを使おう！** という考え方があるんです。

１つのコンテナを使いまわすと、メモリエラー等が累積していき、徐々に動きがおかしくなっていきます。

毎回、まっさらなコンテナを用意することで、そういったリスクを回避することができるんです。



## 作成したコンテナを削除する

今度は作成したコンテナを削除してみましょう。

以下のようにコンテナIDを指定してコンテナを削除してください。

```bash
# Dockerコンテナを削除する
docker rm a4a95f438cbc
docker rm 0cf65c560abb

# 作成したDockerコンテナを確認する
docker ps -af ancestor=alpine:3.9
```

綺麗になくなりました！



# Node.jsを使いたい

次に、Node.jsが入ったDockerイメージを入手しましょう。



## Node.js入りのDockerイメージを入手して起動する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/f90d0bcb-2f36-5580-cf41-41f5626a0b46.png)

Node.js入りのDockerイメージが公開されていますのでプルします。

```bash
# Node.js入りのDockerイメージをダウンロードする
docker pull node:12.3.1-alpine

# Dockerコンテナを生成して入る
docker run -it node:12.3.1-alpine /bin/ash
```

コンテナに入れたら、Node.jsがインストールされているか確認してみましょう。

```bash
# Node.jsのバージョンを確認する
node --version
```

```
v12.3.1
```

今度はNode.jsが入ってました！

`exit` で抜けましょう。

```bash
# コンテナから抜ける
exit
```



# Dockerコンテナを一括削除したい

先ほどは、コンテナIDを指定してコンテナを削除しました。ただ、毎回コンテナIDを手作業で調べるのはかなり面倒です。

これから説明するコマンドを使えば、停止用のコンテナを一括して削除することができます。

ただし、 **利用には細心の注意が必要です。**

**データベースやログ等、大事なデータが格納されているコンテナ等が停止していた場合、そのデータ毎コンテナが消えてしまいます。**

それを理解した上で、停止中のコンテナを一括で削除したい場合は、以下のコマンドを実行してください。

```bash
# 停止中のコンテナを一括削除する
docker container prune
```

「コンテナを使いまわさない」というルールで運用しているのであれば、上記のコマンドを叩いても基本的には問題はおこらないはずですが、念のため、注意を記載させていただきました。



# Dockerコンテナ内にフォルダを作りたい

コンテナ内に `/src/app` というフォルダを作成したいとします。

まずは、ダメなやり方から紹介します。

```bash
# Dockerコンテナを生成して入る
docker run -it node:12.3.1-alpine /bin/ash
```

上記のようにコンテナを生成してから、以下のようにLinuxコマンドでフォルダを作成します。

```bash
# /src フォルダを作成する
mkdir /src

# /src/app フォルダを作成する
mkdir /src/app

# 終了
exit
```

このようにフォルダを作成したコンテナを閉じた場合、次にコンテナを生成した時に、同じフォルダはあるのでしょうか？もう一度、Dockerコンテナを生成しフォルダがあるか確認します。

```bash
# Dockerコンテナを生成して入る
docker run -it node:12.3.1-alpine /bin/ash
```

コンテナに入れたら、フォルダがあるか確認します。

```bash
# フォルダがあるか確認する
ls -al /src/app
```

```
ls: /src/app: No such file or directory
```

エラーが発生しました！ **作成したはずのフォルダがありません！**

何度も言いますが、 `docker run` する度に、新しいコンテナが生成されるので、前のコンテナの状態は引き継がれないのです。

毎回新品のコンテナを使うという考え方は理解できますけど、状態が引き継げないのは不便ですよね。



# フォルダ作成済みのDockerイメージを作りたい

実は、 `Dockerfile` という仕組みを使えば、フォルダが作成済みの状態のDockerイメージを作ることができるんです。



## 作業ディレクトリを作る

まずは、 `Dockerfile` を置くためのディレクトリを作ります。

とりあえず、今回は

- Windows: `D:\Docker\DockerHandson` 
- Mac: `~/Docker/DockerHandson`

とします。上記に限らず、お好きな場所で結構です。

このディレクトリを以後、 `作業ディレクトリ` と呼ぶことにします。



## Dockerfileを書いてみる

作業ディレクトリに `Dockerfile` という名前のファイルを作成します。拡張子はありません。

```dockerfile:Dockerfile
# Node.js
FROM node:12.3.1-alpine

# フォルダ作成＆移動
WORKDIR /src/app
```

`FROM` は、ベースにするDockerイメージを指定するコマンドです。

`WORKDIR` は、Dockerイメージ内にフォルダを作成し、かつ、カレントディレクトリをそのフォルダにするコマンドです。



## Dockerイメージを作ってみる

カレントディレクトリを作業ディレクトリにした状態で、以下のコマンドを叩きます。

```bash
# DockerfileをもとにDockerイメージを作成する
docker build -t hoge:0.1.0 ./
```

すると、 `hoge:0.1.0` という名前のDockerイメージが生成されます。

`docker build` というのは、 `Dockerfile` をもとにDockerイメージを生成するコマンドです。

`-t hoge:0.1.0` は、作成するイメージに `hoge:0.1.0` という名前を付けています。

最後の `./` は `Dockerfile` が設置されているディレクトリへの相対パスです。

これで、 `hoge:0.1.0` が生成されます。

本当に生成されたのでしょうか？以下のコマンドで確認しましょう。

```bash
# hoge
docker images hoge
```

以下のように表示されます。

```bash
REPOSITORY          TAG                 IMAGE ID            CREATED
SIZE
hoge                0.1.0               3e9a3b5598db        12 seconds ago
79.6MB
```

Dockerイメージが作成されていますね！



## Dockerコンテナに入ってみる

作成した `hoge:0.1.0` のコンテナを生成して入ってみます。

```bash
# 作ったDockerイメージからDockerコンテナを生成して入る
docker run -it hoge:0.1.0 /bin/ash
```

フォルダがあるか確認します。

```bash
# フォルダがあるか確認する
ls -al /src/app
```

以下のように表示されると思います。

```bash
total 8
drwxr-xr-x    2 root     root          4096 Nov 12 10:26 .
drwxr-xr-x    3 root     root          4096 Nov 12 10:26 ..
```

**今度はフォルダがありました！**

今後は、このイメージを元にコンテナを作成すれば、フォルダが作成済みの状態で作業ができそうです。



# ホスト（Win/Mac）のファイル群をDockerイメージにコピーしたい

ここまでは、フォルダ作成済みのDockerイメージを作りました。

次は、そのフォルダにホスト（Windows/Mac）側のファイルをコピーしたDockerイメージを作ってみます。



## ホスト側にappフォルダを作る

作業ディレクトリ直下にappフォルダを作り、その中に `index.html` を作りましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/db4e2524-0dfa-8d82-78ef-0e1e1fb6db89.png)

`index.html` の中身はこんな感じです。

```html:index.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>タイトル</title>
</head>

<body>
    本文
</body>

</html>
```



## Dockerfileを改良する

作業ディレクトリ直下の `Dockerfile` を以下のように書き換えます。

```dockerfile:Dockerfile
# Node.js
FROM node:12.3.1-alpine

# フォルダ作成＆移動
WORKDIR /src/app

# 必要なファイルをコピー
COPY ./app /src/app
```

`COPY` というコマンドが登場しました。これはホスト（Windows/Mac）側にある `./app` というフォルダの中身を、Dockerイメージ側の `/src/app` にコピーするという意味になります。

この `Dockerfile` をもとにDockerイメージを作成しましょう。今度はタグを `0.2.0` にしてます。

```bash
# DockerfileをもとにDockerイメージを作成する
docker build -t hoge:0.2.0 ./
```

これで `hoge:0.2.0` というDockerイメージが作成されました。



## Dockerコンテナを確認する

いつも通りにDockerコンテナを生成してみます。

```bash
# 作ったDockerイメージからDockerコンテナを生成して入る
docker run -it hoge:0.2.0 /bin/ash
```

Dockerコンテナ内に `index.html` があるか確認してみましょう！

```bash
# ファイルがあるか確認する
cat /src/app/index.html
```

HTMLの中身が表示されると思います！



# Dockerコンテナ内のHTMLをホスト側のブラウザから見れないの？

せっかくHTMLファイルを置いたので、今度はホスト側のブラウザからHTMLファイルを閲覧する方法を考えます。

以下の作業をすればできます。

- ポートを開放する
- Expressをインストールする

Expressの導入は、長くなってしまいますので、今回は省略することにしました。申し訳ありません。



# さいごに

`Dockerfile` を触ってみていかがでしたでしょうか？

実際に手を動かしてみることで、Dockerの雰囲気が何となくわかったかなと思います。

最後に、いらなくなったコンテナを削除しましょう！

```bash
# 未使用コンテナを一括削除する
docker container prune
```

以上、おつかれさまでした。

この記事は [Docker Advent Calendar 2019](https://qiita.com/advent-calendar/2019/docker) 2日目の記事でした。
明日は @ProjectEuropa さんによる [PHPやRubyとNode.jsを同一コンテナ内に手っ取り早く管理したい人のためのマルチステージビルド](https://qiita.com/ProjectEuropa/items/7435976d92793c8e9fe3) です。