知っておくべきDockerカスタムイメージの作成【初心者OK！Docker入門＋応用シリーズ】

# 前回の記事
https://qiita.com/kenny_J_7/items/ef4a5eb412ff943410e3

# 本記事の内容
Dockerイメージを入手する手段は大きく２通りあります。

- DockerHub などのイメージレジストリサービスからダウンロードする
- Dockerfile を使って自分でイメージを作成する

今回は後者の **「Dockerfile を使って自分でイメージを作成する」** について詳しく説明します。

あと、いいねくれると喜びます🙇‍♂️

# Docker講義動画を Udemy で配信中（クーポンあり）
今回の内容を含むUdemyの動画コースを配信しています。
ぜひ興味のある方はご覧ください。
動画版では、**完全ゼロ知識を想定して説明しています。**
また、**動画版にしかないコンテンツも多数あります。**
たとえば、動画版ではDockerの基礎部分だけでなく実際の業務に即したアプリ開発を体験するセクションも用意してあります。


以下リンクからだと割引で買えます🙇‍♂️

https://dockercourse.page.link/coupon-qiita

では、以下本題です。


# Dockerfileとは？
Dockerfileは、自分好みのカスタマイズされたイメージを作るのに必要な設計書のようなものです。

**Dockerfile ---> ビルド ---> Dockerイメージ**

このように、Dockerfileに対して、「ビルド」という作業をすることで、設計書であるDockerfileに記載された通りにイメージが作成されます。

では、**そもそもDockerfileっていうものが何で必要なのか？**

## なぜDockerfileが必要なのか？
Dockerfileは、コンテナを作るための設計図で、環境の設定やファイルなどを含みます。
つまり、 **自分が作りたいコンテナ環境を作ることは、イメージをカスタマイズすることと同じ** だと言えます。

イメージをカスタマイズする方法はいくつかありますが、最も代表的で手軽な方法はDockerfileを使ってイメージをカスタマイズすることです。
DockerHubからイメージをダウンロードして使うこともできますが、実務においてはほとんど成り立ちません。
なぜなら、DockerHubに登録されているイメージの多くは、汎用的に利用できるように最低限の機能しか含まれていないためです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181439/6ae5c2d3-2bd9-c647-862b-8d74a02213de.png)
たとえば、Ubuntuのイメージであれば、Ubuntuが成り立つのに必要最低限なソフトウェアしか含まれていません。

そのため、Ubuntu上で動くPythonアプリを作りたい場合は、自分でUbuntuのイメージ上にPythonをインストールする必要があります。同様に、Ubuntu上でNode.jsが動く環境を作りたい場合は、自分でUbuntuのイメージをベースにしてNode.jsをインストールする必要があります。

つまり、DockerHubに登録されたイメージは、それをベースとしてカスタマイズすることを前提とされています。


そして、そのカスタマイズされたイメージを作成するのに、Dockerfileが使われます。

# Dockerfileからイメージを作成する
```sh
# Dockerfileからイメージを作成する
$ docker image build {Dockerfileが存在するディレクトリのパス}
```
このコマンドによって、Dockerfileに記述された通りのイメージを作成することができます。

実際にやってみましょう。

以下のような内容を持つ、最小限のDockerfileを作ってみます。

```dockerfile
FROM ubuntu:20.04
```

`FROM` は作ろうとしているイメージのベースとなるDockerイメージを指定するものです。
今回は Ubuntu の20.04をベースイメージとしたいのでこうしてみました。

こうすることで、DockerHubに登録されている、 `ubuntu:20.04` をベースとしてカスタマイズされたイメージが作成できます。

そして、以下のコマンドで、「ビルド」を実行してDockerfileからイメージを作成できます。
```sh
$ docker image build .
```
`.` というのはDockerfileが配置されたディレクトリです。
では実行してみます。

```sh
$ docker image build .
[+] Building 4.3s (5/5) FINISHED                                                                                                                                                                                                            
 => [internal] load build definition from Dockerfile                                                                                                                                                                                   0.0s
 => => transferring dockerfile: 60B                                                                                                                                                                                                    0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                      0.0s
 => => transferring context: 2B                                                                                                                                                                                                        0.0s
 => [internal] load metadata for docker.io/library/ubuntu:20.04                                                                                                                                                                        2.4s
 => [1/1] FROM docker.io/library/ubuntu:20.04@sha256:24a0df437301598d1a4b62ddf59fa0ed2969150d70d748c84225e6501e9c36b9                                                                                                                  1.9s
 => => resolve docker.io/library/ubuntu:20.04@sha256:24a0df437301598d1a4b62ddf59fa0ed2969150d70d748c84225e6501e9c36b9                                                                                                                  0.0s
 => => sha256:24a0df437301598d1a4b62ddf59fa0ed2969150d70d748c84225e6501e9c36b9 1.13kB / 1.13kB                                                                                                                                         0.0s
 => => sha256:f92b9de5f10b6e1fcda653f5d02bd6c816386dfb830c71cef95df52b2280cd1c 424B / 424B                                                                                                                                             0.0s
 => => sha256:625d192afcdc5add2f097cd05a177ab19f18e69473911dbb479fc5eba56ba8fd 2.32kB / 2.32kB                                                                                                                                         0.0s
 => => sha256:a774dfcdedc7c7c4f0009ddadc2f33557d60452450684534df5625f9693df81a 25.97MB / 25.97MB                                                                                                                                       0.9s
 => => extracting sha256:a774dfcdedc7c7c4f0009ddadc2f33557d60452450684534df5625f9693df81a                                                                                                                                              0.8s
 => exporting to image                                                                                                                                                                                                                 0.0s
 => => exporting layers                                                                                                                                                                                                                0.0s
 => => writing image sha256:6c2d21432de5d96fbfab04e504e1cd97ffc777d5dfeaf1f5cdd17c276a713100                                                                                                                                           0.0
```
これでイメージを作成できたはずです。
作成されたイメージIDは、最終行にある
`6c2d21432de5d96fbfab04e504e1cd97ffc777d5dfeaf1f5cdd17c276a713100` 
です。

一応確認してみます。
```sh
$ docker image ls
REPOSITORY                   TAG       IMAGE ID       CREATED        SIZE
<none>                       <none>    6c2d21432de5   2 weeks ago    65.7MB
```
このように、 `IMAGE ID` に該当するIDが存在しているのがわかります。

ちなみに、今回使ったDockerfileは単に `ubuntu:20.04` をベースイメージに指定するだけで何もカスタマイズをしていません。
ですから、作成されたイメージは `ubuntu:20.04` と全く同じイメージになっているはずです。

それを確認してみます。

```sh
$ docker image pull ubuntu:20.04
20.04: Pulling from library/ubuntu
a774dfcdedc7: Already exists 
Digest: sha256:24a0df437301598d1a4b62ddf59fa0ed2969150d70d748c84225e6501e9c36b9
Status: Downloaded newer image for ubuntu:20.04
docker.io/library/ubuntu:20.04
```
まずは、`ubuntu:20.04` をPull（ダウンロード）してきます。
そして、確認をすると、
```sh
$ docker image ls
REPOSITORY                   TAG       IMAGE ID       CREATED        SIZE
<none>                       <none>    6c2d21432de5   2 weeks ago    65.7MB
ubuntu                       20.04     625d192afcdc   2 weeks ago    65.7MB
```
はい、違うIMAGE IDになっていますが、SIZEは全く同じになっているのがわかります。

# イメージに名前をつける
`ubuntu:20.04` をベースイメージとして自分のイメージを Dockerfileから作ることができました。
この **自作イメージに、REPOSITORY や TAG をつける方法**です。

```sh
# 名前をつけながらイメージを作成する
$ docker image build -t {REPOSITORY}:{TAG} {Dockerfileが存在するディレクトリのパス}
```

実際にやってみます。
今回は、`myubuntu:v1` とでも名前をつけてみます。
```sh
$ docker image build -t myubuntu:v1 .
[+] Building 0.1s (5/5) FINISHED                                                                                                                                                                                                            
 => [internal] load build definition from Dockerfile                                                                                                                                                                                   0.0s
 => => transferring dockerfile: 36B                                                                                                                                                                                                    0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                      0.0s
 => => transferring context: 2B                                                                                                                                                                                                        0.0s
 => [internal] load metadata for docker.io/library/ubuntu:20.04                                                                                                                                                                        0.0s
 => CACHED [1/1] FROM docker.io/library/ubuntu:20.04                                                                                                                                                                                   0.0s
 => exporting to image                                                                                                                                                                                                                 0.0s
 => => exporting layers                                                                                                                                                                                                                0.0s
 => => writing image sha256:6c2d21432de5d96fbfab04e504e1cd97ffc777d5dfeaf1f5cdd17c276a713100                                                                                                                                           0.0s
 => => naming to docker.io/library/myubuntu:v1                                                                                                                                                                                         0.0s
```
確認します。
```sh
$ docker image ls
REPOSITORY                   TAG       IMAGE ID       CREATED        SIZE
ubuntu                       20.04     625d192afcdc   2 weeks ago    65.7MB
myubuntu                     v1        6c2d21432de5   2 weeks ago    65.7MB
```
🙌 🙌 🙌

# RUN で任意のコマンドを実行する
実際には、ベースイメージに対して必要な改造を加えて、欲しいイメージを作成することになります。

そこでよく使うのが、 `RUN` です。
RUN を使うことで、好きなシェルコマンドを実行して、必要なソフトウェアを追加インストールすることができます。

たとえば、ベースイメージである、`ubuntu:20.04` に `curl` というツールをインストールしたイメージを作る場合は以下のようになります。

```dockerfile
FROM ubuntu:20.04

RUN apt update
RUN apt install -y curl
```

こうすることで、ベースイメージに対して、`RUN` で指定したコマンドを実行した後のイメージを入手することができます。

ちなみに、
```dockerfile
RUN apt install -y curl
```
こちらで書いている `-y` ですが、これを無しにするとうまくいきません。
`apt install` によって新しいソフトウェアをインストールする場合、実行したユーザに対して、「本当に入れても良いのか？」という質問が投げかけられ、それに対して「y(=yes)」と回答する必要があります。

しかし、Dockerビルドする際には、このようなインタラクティブなやり取りを差し込む余地がありません。
そこで、`apt install` に用意されている、「質問には全てYESの扱いで回答する」というオプションである `-y` を利用することでこのようなやり取りをスキップしています。
`apt install` 以外でも似たようなインタラクティブなやり取りが求められるケースがあるかもしれません。
その際には同じようなオプションがないかを調べて対応することになります。

さて、では上記のDockerfileをビルドしてみましょう。
```sh
$ docker image build -t ubuntu-and-curl .
```
そして、できたイメージからコンテナを作成してBashを起動してみます。
```sh
$ docker container run -it ubuntu-and-curl bash

# コンテナにBashでアクセスしている状態で、curl を実行
root@ecce5f6ed1ed:/# curl
curl: try 'curl --help' or 'curl --manual' for more information
```
このように、 `curl` を実行すると反応しているのがわかります。
つまり、ちゃんと `RUN` で指定したインストール処理が走っていることがわかります。

## TIPS
ちなみに、`RUN` の書き方によって、同じ内容でもDockerのイメージサイズが変わります。
どういう書き方が適切なのかはケースバイケースです。

この辺のノウハウは、[Udemyの動画](https://dockercourse.page.link/coupon-qiita)で詳しく説明しています。

# COPYで好きなファイルをイメージに配置する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181439/21a13fda-de41-b091-70ea-cf89ce88ad7b.png)
Dockerfileで `COPY` というものがあります。

`COPY` は自分の手元ホストマシンにあるファイルやディレクトリを、自分の作るイメージの中にコピーします。
上図では、ファイルをコピーしている様子を書きましたが、ディレクトリを丸ごとコピーすることも可能です。

例えば、現在のディレクトリの配下がこうなってるとしましょう。
```
.
├── Dockerfile
└── hello.txt
```
現在のディレクトリの配下には、`Dockerfile` と `hello.txt` をおきました。

Dockerfileの中身は以下の通りにします。
```Dockerfile
FROM ubuntu:22.04

COPY ./hello.txt /app/
```
こうすることで、作成したイメージの中に、 `hello.txt` を埋め込むことができます。

```dockerfile
COPY ./hello.txt /app/
```
ここの意味は、
**「`./hello.txt`を作成したイメージの`/app/`というディレクトリにコピーする」**
ということです。

ちなみに、
「`/app/`なんていうディレクトリは、ベースイメージであるUbuntuイメージに存在しないのでは？」
という疑問を持つ方もいるかもしれません。

**`COPY` のコピー先ディレクトリについては、存在しなくても自動的にディレクトリを作成した上でコピーしてくれます。**
なので、存在しない `/app/`をコピー先に指定しても問題ありません。

実際にやってみましょう。
上記のDockerfileをビルドします。
```dockerfile
$ docker image build .
[+] Building 0.1s (7/7) FINISHED                                                                                                 
 => [internal] load build definition from Dockerfile                                                       0.0s
 => => transferring dockerfile: 85B                                                                        0.0s
 => [internal] load .dockerignore                                                                          0.0s
 => => transferring context: 2B                                                                            0.0s
 => [internal] load metadata for docker.io/library/ubuntu:20.04                                            0.0s
 => [internal] load build context                                                                          0.0s
 => => transferring context: 30B                                                                           0.0s
 => CACHED [1/2] FROM docker.io/library/ubuntu:20.04                                                       0.0s
 => [2/2] COPY ./hello.txt /app/                                                                           0.0s
 => exporting to image                                                                                     0.0s
 => => exporting layers                                                                                    0.0s
 => => writing image sha256:c0835d2b93d2b4ad274d2539d3477629bf9085c417ed8b0f30f5d94b89ac20f9               0.0s
```
そして、このイメージからコンテナを作成してBashを実行します。
```shell
$ docker container run -it c0835d2b93d2b4ad274d2539d3477629bf9085c417ed8b0f30f5d94b89ac20f9 bash

# コンテナにBashでアクセスしている状態
root@fec3440d432a:/# ls /app
hello.txt
```
`ls` を使って、`/app`のディレクトリ内のファイル一覧を出力させました。
そうすると、ちゃんと`hello.txt`が入っているのがわかります🚀

つまり、コピーがうまくいっていますね。

さらに、元々のベースイメージ（`ubuntu:20.04`）には存在しなかったはずの、`/app`というディレクトリももちろん作成されていますね。

# Docker講義動画を Udemy で配信中（クーポンあり）
今回の内容を含むUdemyの動画コースを配信しています。
ぜひ興味のある方はご覧ください。
動画版では、**完全ゼロ知識を想定して説明しています。**
また、**動画版にしかないコンテンツも多数あります。**
たとえば、動画版ではDockerの基礎部分だけでなく実際の業務に即したアプリ開発を体験するセクションも用意してあります。


以下リンクからだと割引で買えます🙇‍♂️

https://dockercourse.page.link/coupon-qiita