コンテナ技術 ハンズオン Vol.2  Dockerイメージ編

# 本題の前に
---

## 位置付け・前提
本記事は、社内ワークショップ のために用意したものです。そのため、次の前提で進めることをご理解ください。

また、本稿はこちらの続きとなります。

[コンテナ技術 ハンズオン Vol.1  Docker操作編](https://qiita.com/syamane/items/65570b41b3d6d32691a5)

目的や前提なども上記の内容に準拠します。

---

## docker ハンズオン環境

今回も [Play with Docker](https://labs.play-with-docker.com/) を活用してハンズオンを行います。

[![playwithdocker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107190/bc635cbe-c24f-6cec-c871-f4a8073cfffb.png)](https://labs.play-with-docker.com/)

※要：[Docker Hub](https://hub.docker.com/) アカウント

注意点として、この環境は4時間の制限があります。４時間経過すると環境が削除されてしまう点を注意して下さい。

もちろんローカル環境でも構いません。

---

## 参加者の前提知識

Dockerfile および Docker レイヤーの考え方を理解していることが前提とします。参考までに下記の資料を一読しておくことをお奨めします。

[![Dockerfile を書くためのベストプラクティス解説編](https://image.slidesharecdn.com/explainingbestpracticesforwritingdockerfiles-190604090734/95/slide-1-1024.jpg)](https://www.slideshare.net/zembutsu/explaining-best-practices-for-writing-dockerfiles)

HTMLページが良ければ下記でもOKです。

[Dockerfile のベストプラクティス — Docker-docs-ja 1.9.0b ドキュメント](http://docs.docker.jp/engine/articles/dockerfile_best-practice.html)

---

# ハンズオンの前に

---

## 前回の問題

前回のハンズオンで最後に出した問題はこちら。

1. 起動しているコンテナはホストのどのディレクトリにあるでしょうか。
2. dockerイメージはどこにあるでしょうか。
3. dockerのプロセスはどれか。

うまく ~~ググれカス~~ 答えられましたでしょうか。

---

## 前回の問題の解答

### 解答1. コンテナの実態ディレクトリの調べ方はこちら。

```bash
$ docker inspect {NAME|ID}
```

このコマンドを実行して、表示されるJSONデータのうち、下記の該当箇所を確認すればわかります。

```JSON
"HostnamePath": "/var/lib/docker/containers/8c059e84473033b3a0474b8a9a4beb8f848f003492d87ba6865f93863ce889b2/hostname",
"HostsPath": "/var/lib/docker/containers/8c059e84473033b3a0474b8a9a4beb8f848f003492d87ba6865f93863ce889b2/hosts",
"LogPath": "/var/lib/docker/containers/8c059e84473033b3a0474b8a9a4beb8f848f003492d87ba6865f93863ce889b2/8c059e84473033b3a0474b8a9a4beb8f848f003492d87ba6865f93863ce889b2-json.log",
```

この場合、/var/lib/docker/containers/8c059e84473033b3a0474b8a9a4beb8f848f003492d87ba6865f93863ce889b2 配下が実態のディレクトリとなります。

---
## 前回の問題の解答

### 解答2. イメージの場所は解答1から予想が付きます。

```bash
$ ls /var/lib/docker/image
overlay2
```

しかし、これは外れです。メタデータなどしかありません。

実態のデータはこちらにあります。

```bash
/var/lib/docker/overlay2
```

配下に複雑な文字列のディレクトリがならんでいて、その中に実態となるデータが保存されます。

---
## 前回の問題の解答

### 解答3. 例として、２つインスタンスが起動しているdockerのプロセスを見てみます。

```bash
PID   USER     TIME  COMMAND
    8 root      0:14 dockerd
   35 root      0:02 containerd --config /var/run/docker/containerd/containerd.toml --log-level debug
 2860 root      0:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.co
 2877 root      0:00 /bin/sh
 7628 root      0:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.co
 7648 root      0:00 /bin/bash
```

PID が 8 と 35 は Docker エンジン自体のプロセスです。

一方、 PID 2866 と 2877、7628 と 7648 は別のインスタンスです。

変な感じですが、OSから直接見たプロセス（の実行コマンド）には差が無いように見えます。

この辺は、今後 学習を進めていく中で疑問が解消できると良いかと思います。

---

# ハンズオン

---

## 今回のゴール

* docker インスタンスから新たなイメージを作成して、そのインスタンスを作成できるようになる。
* dockerfile から新たなイメージを作成すること
* dockerコンテナ同士を連携させること

---

## 今回やらないこと

* コンテナ/イメージを分析したり、たくさん操作したりはしません。
* kubernetes / docker-compose のようなオーケストレーションツールの使いません。

---

## これから進める中で必ず理解しておいて欲しいこと

**Docker イメージはレイヤーになっている**

[![Dockerコンテナ実行とは、イメージ内のプログラムを実行](https://image.slidesharecdn.com/explainingbestpracticesforwritingdockerfiles-190604090734/95/slide-11-1024.jpg)](https://www.slideshare.net/zembutsu/explaining-best-practices-for-writing-dockerfiles/11)

この概念は、イメージを作成していく上で重要な概念となります。理解できていない人は、上の画像をクリックして該当箇所を学習した方が良いでしょう。

---

## イメージ・レイヤ作成

---

### インスタンスからイメージ・レイヤを作成する

Docker イメージ・レイヤを作成するイメージとコマンドは下記です。

[![Dockerイメージを作る"docker commit"](https://image.slidesharecdn.com/explainingbestpracticesforwritingdockerfiles-190604090734/95/slide-12-1024.jpg)](https://www.slideshare.net/zembutsu/explaining-best-practices-for-writing-dockerfiles/12)

利用したコンテナからイメージを生成します。

```shell
docker commit {コンテナ名/インスタンス名} {イメージ名}
```

このとき、実態として**イメージからコンテナ化した後の作業・設定などを反映した状態**がイメージとして保存されます。

---

### docker commit ハンズオン

下記の通り、docker コンテナを作成および操作します。※コメントは無視して入力してください。

```shell
# 軽量Linux "Alpine Linux"の最新版コンテナを pull して、起動(run)する
docker run -it --name alpine-linux-test alpine:latest

# パッケージ群を最新化する
apk update

# 開発に便利なパッケージを追加する
apk --update add python3 python3-dev zsh vim tmux git tig wget

# Pythonのパッケージ管理ツールを最新化する
pip3 install --upgrade pip

# Pythonでよく使うモジュールをインストールする
pip install flask requests

# Alpineパッケージのキャッシュデータを削除する（軽量化）
rm -rf /var/cache/apk/*
```

ここまで実行したら、Ctrl-p Ctrl-q でデタッチして、ホストのシェルに戻ってインスタンス状態を確認します。

```shell
$ docker ps      
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
d813f16ccdf5        alpine              "/bin/sh"           7 minutes ago       Up 7 minutes                            alpine-linux-test
```

この状態のインスタンスから、現時点のイメージを生成します。

```shell
docker commit alpine-linux-test mydev-tools
```

そうした上で、Docker イメージの状態を確認します。

```shell
$ docker images
REPOSITORY       TAG        IMAGE ID            CREATED             SIZE
mydev-tools      latest     50ad99bbf7b5        34 seconds ago      158MB
```

このまま、新たにインスタンスを生成して、今度はバックグラウンド起動します。

```shell
$ docker run -it -d --name dev-env1 mydev-tools
4d68811389f344265f82c923d00d264524b14fb507d42b199b241be6f1f73ec3
```

再度インスタンスの一覧を表示します。

```shell
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
589bdf4b45c4        mydev-tools         "/bin/sh"           15 seconds ago      Up 14 seconds                           dev-env1
d813f16ccdf5        alpine              "/bin/sh"           14 minutes ago      Up 14 minutes                           alpine-linux-test
```

これでインスタンスを複製することに成功しました。
NAMES値だけ変えれば大量のインスタンスを短時間に生成することも可能です。

---
### 補足：docker commit コマンド

```shell
$ docker commit --help 

Usage:	docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

Create a new image from a container's changes

Options:
  -a, --author string    Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")
  -c, --change list      Apply Dockerfile instruction to the created image
  -m, --message string   Commit message
  -p, --pause            Pause container during commit (default true)
```

実際には、-a で作成者を、-m で作成時の記録を残すのが実用的ですね。

---

### dockerイメージを別サーバへ移す

別のサーバへ移行して、コンテナを動作させたい場合があるはずです。

そのような場合、下記の手順で実行します。

1. dockerインスタンスを export する
2. exportした1のデータを移行先に移動/複製する
3. 新たなイメージとして、2で配置したデータを import する
4. import したイメージからコンテナを生成・起動する

export / import の書式はこちらです。

```shell
docker export [Container ID | NAMES] -o [出力ファイル名].tar
docker import [ファイル名|URL] [REPOSITORY[:TAG]]
```
---

### docker export / import ハンズオン

ここでは、「docker commit ハンズオン」で生成したイメージを export / import してみます。

まず、イメージを export します。

```shell
docker export dev-env1 -o dev-env.tar
```
---

本来であれば、scp や sftp などでサーバ間でデータを移動させるはずですが、今回は同じ docker 内で実施します。

次に、異なる名前でイメージとしてインポートします。

```shell
docker import dev-env.tar dev-env-copy
```
---

再度イメージを確認すると、下記のとおりにインポートに成功しています。

```shell
$ docker images
REPOSITORY       TAG        IMAGE ID            CREATED             SIZE
dev-env-copy     latest     98d5d7aeebb1        10 seconds ago      158MB
mydev-tools      latest     50ad99bbf7b5        34 minutes ago      158MB
```

ここからインスタンスを生成すれば、そのまま異なるサーバの docker で同じ内容のインスタンスを動作させることが可能です。


---
### 補足：docker export コマンド

```shell
$ docker export --help

Usage:	docker export [OPTIONS] CONTAINER

Export a container's filesystem as a tar archive

Options:
  -o, --output string   Write to a file, instead of STDOUT
```

実際には、-o 以外は指定するものがほぼありません。

---

### 補足：docker import コマンド

```shell
$ docker import --help

Usage:	docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]

Import the contents from a tarball to create a filesystem image

Options:
  -c, --change list      Apply Dockerfile instruction to the created image
  -m, --message string   Set commit message for imported image
```

オプション無しでも十分に動作しますが、実用的には -m で記録を残すのが良いでしょう。

---

### 補足：docker save コマンド

```shell
$ docker save --help  

Usage:	docker save [OPTIONS] IMAGE [IMAGE...]

Save one or more images to a tar archive (streamed to STDOUT by default)

Options:
  -o, --output string   Write to a file, instead of STDOUT

```

[こちらの記事](https://qiita.com/chroju/items/739c77b30d47e5225392)によると、export との違いはこちらだそうです。

> ・save 上記のレイヤーやタグといったメタ情報含めてコンテナをtarでまとめる。
> ・export ファイルシステムを愚直にtarでまとめ、メタ情報は無視される。

また、上記の主語として、save はイメージ、export はインスタンス（コンテナ）が対象になります。

通常は export の方が容量が少なくて良いですが、より正確さ（再現性？）を求めるなら save が良さそうです。

---

### 補足：docker load コマンド

```shell
$ docker load --help

Usage:	docker load [OPTIONS]

Load an image from a tar archive or STDIN

Options:
  -i, --input string   Read from tar archive file, instead of STDIN
  -q, --quiet          Suppress the load output
```

save したデータは load コマンドでしかインポートできません。

---

### 練習問題

どれかインスタンスを save した後、
同じホストの docker で load すると、
どのようなことが起こるか確認してください。

---

## イメージを設計書から作る

---

### dockerfile = イメージの設計書

dockerfile とは、下記のように説明されています。

> Docker Image は Dockerfile というファイルを記述し、そのファイルを元にビルドすることでスナップショットの作成ができます。

[![dockerfile のイメージ](https://y-ohgi.com/introduction-docker/2_component/imgs/dockerfile.png)](https://y-ohgi.com/introduction-docker/2_component/dockerfile/)

引用元： [dockerfile - 入門 Docker](https://y-ohgi.com/introduction-docker/2_component/dockerfile/)

つまり、端的には**イメージを作る元ファイル**と考えましょう。

---
### dockerfile の書き方

サンプルとして、すべての命令を書いた dockerfile をご覧ください。

```dockerfile:dockerfile
# ベースとなるイメージを指定する
# ローカルになければ Docker Hub から Pull
FROM ubuntu:latest

# 生成するイメージの作者を設定する
MAINTAINER syamane

# apt で最新化後、vim をインストールコマンドを実行する（シェル方式）
RUN apt update && apt install -y vim

# vim の設定ファイルを作成（JSON形式で指定）
RUN ["/bin/bash", "-c", "echo", "set", "nowrap", ">", "/root/.vimrc"]

# リッスンするポートを指定する
EXPOSE 5900

# 環境変数を設定する
ENV PATH=/root:$PATH

# 命令実行時の作業ディレクトリを指定する
WORKDIR /root

# ローカルファイルをコンテナ内にコピーする（今回はコメントアウト）
# ダウンロード元URL もしくは 実行ディレクトリ配下のパスを指定可
# 圧縮ファイルを指定すれば自動解凍する
# ADD *.txt /root/

# ローカルファイルをコンテナ内にコピーする（今回はコメントアウト）
# 実行ディレクトリ配下のみ指定可
# COPY *.log /root/

# vim をヘルプが開いた状態でフォアグラウンド起動する
# シェルを指定すれば、シェルでの操作ができる
CMD ["vim", "-c", "help"]
```

他にも VOLUME / LABEL / USER / ARG / ONBUILD / STOPSIGNAL / HEALTHCHECK / SHELL がありますが、書かなくてもイメージは作れます。（もちろん書けた方が良いですが）

各命令の詳細な解説は、下記を参考にしましょう。
* [Dockerfile リファレンス — Docker-docs-ja 1.9.0b ドキュメント](http://docs.docker.jp/v1.9/engine/reference/builder.html)
* [Dockerfile のベストプラクティス — Docker-docs-ja 1.9.0b ドキュメント](http://docs.docker.jp/engine/articles/dockerfile_best-practice.html)
* [Dockerfile を書くためのベストプラクティス解説編](https://www.slideshare.net/zembutsu/explaining-best-practices-for-writing-dockerfiles)

---
### dockerイメージのビルド

dockerfile と イメージに ADD/COPY したいファイルがあるディレクトリで、下記のコマンドを実行します。

```shell
docker build -t testimage:1.0 .
```

この書式は下記の通りです。

```shell
docker build [-t {イメージ名}[:{タグ名}]] {Dockerfileのあるディレクトリ}
```

最後のディレクトリ指定ですが、「.」つまりカレントディレクトリを指定することが多いです。 

このコマンドを実行して、エラーが出なければ下記のように成功を示すメッセージを出力して完了です。

```shell
$ docker build -t test:1.0 .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM alpine:latest
 ---> 961769676411
Step 2/2 : CMD echo hello
 ---> Running in e1c85ea06aba
Removing intermediate container e1c85ea06aba
 ---> 73e67f49d233
Successfully built 73e67f49d233
Successfully tagged test:1.0

$$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
test                1.0                 73e67f49d233        47 seconds ago      5.58MB
```

---
### dockerfile 書き方あるある

* [Dockerfile の ADD と COPY の違いを結論から書く - Qiita](https://qiita.com/YumaInaura/items/1647e509f83462a37494)
* [DockerのRUNとCMDの違い - Qiita](https://qiita.com/YusukeHigaki/items/044164837daa5e845d50)
* [Dockerfile内のENTRYPOINTとCMD（とRUN） の使い分け](https://shinkufencer.hateblo.jp/entry/2019/01/28/225553)
* [Dockerfileを書く時の注意とかコツとかハックとか | kim hirokuni](http://kimh.github.io/blog/jp/docker/gothas-in-writing-dockerfile-jp/)
    * CMDでコンテナをバイナリのように扱う
    * CMDとENTRYPOINTの違い
* [Dockerで複数CMDを実行する方法 | エンジニアの眠れない夜](https://sleepless-se.net/2018/05/26/docker%E3%81%A7%E8%A4%87%E6%95%B0cmd%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/)

---
### docker build や docker run が上手くいかない場合は？

下記の記事を参考に問題を調査してみましょう。

* [dockerでコンテナが立ち上がらないときやってみること - Qiita](https://qiita.com/mom0tomo/items/35dfacb628df1bd3651e)
* [Dockerイメージのビルド中にExitedしたコンテナに入る方法 - Namiking.net](https://blog.namiking.net/post/2015/09/docker-exec-exited/)
* [Docker調査　~ログ編~ - Qiita](https://qiita.com/HommaHomma/items/f943fa3397bc3f386057)

---
### 補足：docker build コマンド

非常に沢山のオプションがあって複雑です。

```shell
$ docker build --help

Usage:	docker build [OPTIONS] PATH | URL | -

Build an image from a Dockerfile

Options:
      --add-host list           Add a custom host-to-IP mapping (host:ip)
      --build-arg list          Set build-time variables
      --cache-from strings      Images to consider as cache sources
      --cgroup-parent string    Optional parent cgroup for the container
      --compress                Compress the build context using gzip
      --cpu-period int          Limit the CPU CFS (Completely Fair Scheduler) period
      --cpu-quota int           Limit the CPU CFS (Completely Fair Scheduler) quota
  -c, --cpu-shares int          CPU shares (relative weight)
      --cpuset-cpus string      CPUs in which to allow execution (0-3, 0,1)
      --cpuset-mems string      MEMs in which to allow execution (0-3, 0,1)
      --disable-content-trust   Skip image verification (default true)
  -f, --file string             Name of the Dockerfile (Default is 'PATH/Dockerfile')
      --force-rm                Always remove intermediate containers
      --iidfile string          Write the image ID to the file
      --isolation string        Container isolation technology
      --label list              Set metadata for an image
  -m, --memory bytes            Memory limit
      --memory-swap bytes       Swap limit equal to memory plus swap: '-1' to enable unlimited swap
      --network string          Set the networking mode for the RUN instructions during build (default "default")
      --no-cache                Do not use cache when building the image
      --pull                    Always attempt to pull a newer version of the image
  -q, --quiet                   Suppress the build output and print image ID on success
      --rm                      Remove intermediate containers after a successful build (default true)
      --security-opt strings    Security options
      --shm-size bytes          Size of /dev/shm
  -t, --tag list                Name and optionally a tag in the 'name:tag' format
      --target string           Set the target build stage to build.
      --ulimit ulimit           Ulimit options (default [])
```

単純にイメージを生成する以外に、リソースを制御することも可能です。

---

### dockerfile からイメージを作るハンズオン

「docker commit ハンズオン」で作成したコンテナと同じイメージを作る dockerfile は下記です。

```Dockerfile:Dockerfile
FROM alpine:latest
CMD apk update \
 && apk --update add python3 python3-dev zsh vim tmux git tig wget \
 && pip3 install --upgrade pip \
 && pip install flask requests \
CMD rm -rf /var/cache/apk/*
COPY test.txt /test.txt
```

なお、イメージ内にホスト上のファイルを追加する記述をいれましたので、そのファイルを作成します。

```test.txt
DOCKER HANDSON TEST
```

先程の dockerfile と test.txt を同じディレクトリに配置して、ビルドします。

```bash
docker build -t dev-tools-img .
```

実行するとこんな感じです。

```docker
Sending build context to Docker daemon  3.072kB
Step 1/5 : FROM alpine:latest
 ---> 961769676411
Step 2/5 : CMD apk update  && apk --update add python3 python3-dev zsh vim tmux git tig wget  && pip3 install --upgrade pip  && pip install flask requests CMD rm -rf /var/cache/apk/*
 ---> Running in 8e625e558d82
Removing intermediate container 8e625e558d82
 ---> eb9b1153bdd2
Step 3/5 : COPY test.txt /tmp/
 ---> 90ff3bf6c49c
Step 4/5 : CMD cat /test.txt
 ---> Running in 297faf85f943
Removing intermediate container 297faf85f943
 ---> 24bce8916152
Step 5/5 : CMD echo "Complete!"
 ---> Running in 93d43c43f9a9
Removing intermediate container 93d43c43f9a9
 ---> 600d60d001e1
Successfully built 600d60d001e1
Successfully tagged dev-tools-img:latest
```

実際にインスタンスを作ってみましょう。

```bash
$ docker run --name dev-env3 dev-tools-img
```

```shell
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
7b29bbf49e46        mydev-tools         "/bin/sh"           29 minutes ago      Up 29 minutes                           dev-env1
```

これでオリジナルのイメージを作成して、そこからインスタンスを生成することができました。

あとは、どんな dockerfile を書くかにかかっているわけです。

---

### dockerfile ハンズオン

---

### dockerfile に慣れるハンズオン

とにかく沢山の Dockerfile からイメージを作って、コンテナを作成してみましょう。

---

### 意味のない ubuntu イメージ

```Dockerfile:Dockerfile
FROM ubuntu
COPY hello.txt /tmp/hello.txt
CMD ["cat", "/tmp/hello.txt"]
```

もちろんビルドします。

```bash
docker build -t hello .
```

出典：[dockerfile - 入門 Docker](https://y-ohgi.com/introduction-docker/2_component/dockerfile/)

---

### サービスをいくつか追加した ubuntu イメージ

```Dockerfile:Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server
```

今回は dockerfile を指定してみます。

```bash
docker build -t build:sample -f dockerfile .
```

追加しただけで、普通のOSと同じ状態でサービス起動は設定してません。

出典：[Dockerfileを使った基本的なDockerの動作 - デベロッパー・コラボ](https://developer-collaboration.com/2018/08/02/dockerfile/)

---

### ApacheでHTTPサーバを実行する。

```Dockerfile:Dockerfile
FROM centos
MAINTAINER Admin <admin@admin.com>
RUN echo "now running..." \
    yum update \
    yum install -y httpd
EXPOSE 80
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
```
※元の内容を一部改変しました。

ビルドします。

```bash
docker build -t samplehttp .
```

実際にインスタンスを作ってみます。

```bash
docker run --name ap1 -d -p 80:80 samplehttp
```

80番ポートにアクセスして、動作できればコンテナを停止しましょう。

出典：[Dockerfileを書いてみる - Qiita](https://qiita.com/nl0_blu/items/1de829288db2670276e8)

---

##  ip-api.com へのリバースプロキシ

```Dockerfile:Dockerfile
FROM centos:6
RUN set -x && \
    yum install -y epel-release && \
    yum install -y nginx && \
    sed -i -e "s/index  index.html index.htm/proxy_pass http:\/\/ip-api.com\/json/" \
        /etc/nginx/conf.d/default.conf && \
    ln -sf /dev/stdout /var/log/nginx/access.log && \
    ln -sf /dev/stderr /var/log/nginx/error.log
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```

キチンとビルドします。

```bash
docker build -t reverse-proxy .
```

実際に走らせてみましょう。

```bash
docker run --name rp1 -d -p 80:80 reverse-proxy
```

80番ポートにアクセスして、動作できればコンテナを停止しましょう。（前回のキャッシュが残っていないかは要注意）

出典：[効率的に安全な Dockerfile を作るには - Qiita](https://qiita.com/pottava/items/452bf80e334bc1fee69a)


---
## 複雑な nginx サーバーのイメージ

下記の２つのファイルを作成します。

```default.conf
server {
        listen 8080 default_server;
        listen [::]:8080 default_server;

        root /app;

        location / {
        }
}
```

``` index.html
<html>
<body>
<h1>TEST PAGE</h1>
sample index file
</body>
</html>
```

```Dockerfile:Dockerfile
FROM alpine:3.6

# nginxのインストール
RUN apk update && \
    apk add --no-cache nginx

# ドキュメントルート
ADD app /app
ADD default.conf /etc/nginx/conf.d/default.conf

# ポート設定
EXPOSE 8080

RUN mkdir -p /run/nginx

# フォアグラウンドでnginx実行
CMD nginx -g "daemon off;"
```

ビルドします。

```shell
docker build -t alp_nginx .
```

インスタンスを作って、実行してみます。

```shell
docker run -it --name nginx2 -p 80:8080 alp_nginx
```

80番ポートに接続して、動作できればコンテナを停止しましょう。（前回のキャッシュが残っていないかは要注意）

出典：[お気楽にnginxのDockerイメージを作ってみる - Qiita](https://qiita.com/rspepe/items/c0c0119032ccc79ff7a1)

---
## 実用的な開発環境のイメージ

下記の記事の内容を dockerfile で用意してみます。

[Dockerでサクッと使い捨ての開発環境を用意する ｜ DevelopersIO](https://dev.classmethod.jp/tool/docker/docker-develop-container/)

```Dockerfile:Dockerfile
FROM ubuntu:latest
RUN apt update -y \
 && apt install curl vim git net-tools build-essential -y
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \
 && apt install nodejs -y \
 && node -v
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt update -y && apt install yarn -y
RUN yarn -v
```

ビルドします。結構長い時間かかります。

```shell
docker build -t dev_container .
```

インスタンスを作って、実行してみます。

```shell
docker run -it --name devcon1 dev_container
```

無事に開発環境に入れたら、Node.js のバージョンを確認してみます。

```shell
node -v
```

バージョンが表示されたらOKです。

---
## NGINX公式イメージ

GitHubの下記のリポジトリから dockerfile をダウンロードして実行してみます。

[nginxinc/docker-nginx: Official NGINX Dockerfiles](https://github.com/nginxinc/docker-nginx)

```shell
git clone https://github.com/nginxinc/docker-nginx.git
cd docker-nginx/stable/alpine/
docker build -t ngx1 .
```

あとはインスタンスを作って、実行してみます。

```shell
docker run -it -d --name n1 -p 80:80 ngx1
```

無事に80番ポートにアクセスできたらOKです。

このように、GitHub にはたくさんの Dockerfile が公開されています。ぜひ参考にしてみて下さい。

[Search · Dockerfile - GitHub](https://github.com/search?o=desc&q=Dockerfile&s=stars&type=Repositories)

---

## 問題（宿題）

CentOS を最新状態にアップデートして、nginx をインストール＆起動した状態のDockerイメージ「nginx1」を作成してください。

また、完了した後は下記を調べて、後始末をしましょう。

1. 起動しているインスタンスを一括停止するコマンドは？
2. 起動しているインスタンスを一括削除するコマンドは？
3. 起動しているイメージを一括削除するコマンドは？

---
## さらに Dockerfile 力をレベルアップするためには

下記のようなコンテンツを読むと良いかもしれません。

* [Dockerfileを改善するためのBest Practice 2019年版](https://www.slideshare.net/zembutsu/dockerfile-bestpractices-19-and-advice)
* [【翻訳】いいDockerイメージを構築するには？ ーDockerfileのベストプラクティス | POSTD](https://postd.cc/building-good-docker-images/)
* [docker buildを速くするコツ - keroxpのScrapbox](https://scrapbox.io/keroxp/docker_build%E3%82%92%E9%80%9F%E3%81%8F%E3%81%99%E3%82%8B%E3%82%B3%E3%83%84)
* [改めてDockerfileのベストプラクティスを振り返ろう｜JapanContainerDays｜動画 | crash.academy](https://crash.academy/video/603/1931)

---
# おわりに

今回は docker イメージを作成する方法について、ハンズオンをしました。

ここまで自由にイメージが作れるようになると、だんだん楽しくなってきたのではないでしょうか。

こうして作成したイメージから生成するコンテナの真骨頂は、**大量のコンテナ操作**や**定型的な運用操作の効率化**にあります。今後はDocker-compose や Kubernetes といったツールを学んでいきたいところですが、

なお、次はコンテナを管理していく方法について、ハンズオンを行っていきます。

[コンテナ技術 ハンズオン Vol.3 Docker Compose編](https://qiita.com/syamane/items/dcea30a7794f9755a59d)

---

# 参考

これまでに紹介した記事以外に、本ハンズオン資料を作成する上で下記の記事を参考にさせていただきました。

* [Dockerコンテナをexportして別のサーバでimportして動作させる - かべぎわブログ](https://www.kabegiwablog.com/entry/2018/07/31/100000)
* [Dockerハンズオン基礎編：コンテナとイメージのライフサイクルを理解 - Qiita](https://qiita.com/zembutsu/items/ca0fa7b874c8375be8a4)
* [Dockerハンズオン資料 - Qiita](https://qiita.com/wantonbe/items/54ea5fc076bb28937037)
* [Docker ハンズオン - 基本コマンド編 - Qiita](https://qiita.com/hihihiroro/items/6dda871dc2566801a6da)
* [こうしたいんだぜ！ という時の逆引きdockerコマンド | 株式会社ビヨンド](https://beyondjapan.com/blog/2016/08/docker-command-reverse-resolutions/)

大変ありがとうございました。