はじめに
前回は、Raspberry PiにDockerをインストールし、
-
hello-worldを動かす - ImageとContainerの違いを確認する
- nginx Containerを起動する
- 別PCのブラウザからnginxへアクセスする
- Containerを削除して、同じImageから作り直す
ところまで試しました。
前回の記事では、Docker Hubに用意されている nginx Imageをそのまま使用しました。
今回は一歩進んで、自分で作ったHTMLをnginxから配信するところから始めます。
その途中で、
- Bind Mount
- Dockerfile
docker build- 自作Image
について確認していきます。
最終的には、次のような流れを実際に作ってみます。
nginx公式Image
+
自分のindex.html
↓
Dockerfile
↓
自分専用Image
↓
Container
作業用ディレクトリを用意する
まず、Raspberry Pi上に作業用ディレクトリを作ります。
mkdir -p ~/docker-lab/html
cd ~/docker-lab
今回の途中では、最終的に次のような構成になります。
docker-lab/
├── html/
│ └── index.html
└── Dockerfile
自分のHTMLを作る
まずは、自分で用意したHTMLをnginxから配信してみます。
index.html を作成します。
nano ~/docker-lab/html/index.html
内容は次のようにしました。
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Docker on Raspberry Pi</title>
</head>
<body>
<h1>Hello Docker!</h1>
<p>Raspberry Pi上のDockerコンテナから配信しています。</p>
</body>
</html>
Bind MountでHTMLをContainerから見えるようにする
前回使ったnginx Containerが動いている場合は削除します。
sudo docker rm -f web
次に、今度は -v オプションを付けてnginxを起動します。
sudo docker run -d \
--name web \
-p 8080:80 \
-v ~/docker-lab/html:/usr/share/nginx/html:ro \
nginx
今回新しく追加したのが、次の部分です。
-v ~/docker-lab/html:/usr/share/nginx/html:ro
この指定によって、次の対応関係が作られます。
Raspberry Pi側
~/docker-lab/html
│
│ Bind Mount
▼
Container側
/usr/share/nginx/html
nginxは /usr/share/nginx/html にあるファイルをWebページとして配信します。
そのため、Raspberry Pi側の html ディレクトリをここへBind Mountすることで、自分で作成した index.html をnginxから配信できます。
最後の :ro はRead Onlyの意味です。
Container側から、このディレクトリへ書き込めないようにしています。
ブラウザから確認する
Containerが起動していることを確認します。
sudo docker ps
その後、別PCのブラウザから次のアドレスへアクセスします。
http://Raspberry PiのIPアドレス:8080
すると、作成したページが表示されました。
Hello Docker!
Raspberry Pi上のDockerコンテナから配信しています。
ここで重要なのは、HTMLファイルをContainer内へコピーしたわけではないという点です。
Raspberry Pi側にあるファイルを、Container側から見えるようにしています。
HTMLを変更するとすぐに反映される
Bind Mountの動きを確認するため、Raspberry Pi側のHTMLを変更してみます。
nano ~/docker-lab/html/index.html
見出しを、
<h1>Hello Docker!</h1>
から、
<h1>Hello Docker from Raspberry Pi!</h1>
へ変更しました。
ここではDocker Containerを再起動しません。
そのままブラウザを更新すると、
Hello Docker from Raspberry Pi!
へ表示が変わりました。
なぜContainerを再起動していないのに変更されたのでしょうか。
今回の構成は次のようになっています。
Raspberry Pi
└── ~/docker-lab/html/index.html
│
│ Bind Mount
▼
nginx Container
│
└── /usr/share/nginx/html/index.html
Containerの中に別のHTMLが存在するのではなく、Raspberry Pi側のファイルをContainerから参照しています。
そのため、Raspberry Pi側のファイルを変更すると、その変更がそのままContainerからも見えます。
開発中にコードやHTMLを頻繁に変更する場合には便利そうです。
Containerを削除してもHTMLは残る
次に、Containerを完全に削除してみます。
sudo docker rm -f web
Containerが消えたことを確認します。
sudo docker ps -a
一方で、Raspberry Pi側のHTMLを確認します。
cat ~/docker-lab/html/index.html
HTMLはそのまま残っています。
今回の構成では、
Container
↓
削除
Raspberry Pi
└── docker-lab/
└── html/
└── index.html ← 残っている
となります。
HTMLの実体はContainerではなくRaspberry Pi側にあるため、Containerを削除しても消えません。
もう一度同じコマンドでContainerを作れば、
sudo docker run -d \
--name web \
-p 8080:80 \
-v ~/docker-lab/html:/usr/share/nginx/html:ro \
nginx
再び同じHTMLを配信できます。
Bind MountではImage自体は変わっていない
ここまででは、nginx Imageそのものは変更していません。
使用しているのはDocker Hubから取得した公式の nginx Imageです。
nginx Image
│
▼
web Container
│
│ Bind Mount
▼
Raspberry Pi上のindex.html
自分のHTMLは nginx Imageの中には入っていません。
ここで、
自分のHTMLまで含めたImageを作るにはどうするのか?
という疑問が出てきます。
そこで使うのが Dockerfile です。
Dockerfileを作る
まず、今動いているContainerを削除します。
sudo docker rm -f web
作業ディレクトリへ移動します。
cd ~/docker-lab
Dockerfile を作成します。
nano Dockerfile
内容は次の2行です。
FROM nginx:latest
COPY html/index.html /usr/share/nginx/html/index.html
かなり短いですが、これだけで自分専用のDocker Imageを作ることができます。
Dockerfileの内容を確認する
まず1行目です。
FROM nginx:latest
これは、nginx公式Imageを土台として使用するという意味です。
次に、
COPY html/index.html /usr/share/nginx/html/index.html
では、html/index.html をImage内の /usr/share/nginx/html/index.html へコピーします。
つまり今回のDockerfileは、
nginx:latest
│
│ FROM
▼
nginxを土台にする
│
│ COPY
▼
自分のindex.htmlを追加
│
▼
自分専用Image
というレシピになっています。
Docker ImageをBuildする
DockerfileからImageを作成します。
sudo docker build -t my-nginx .
ここで、
-t my-nginx
は、作成するImageへ my-nginx という名前を付ける指定です。
また、最後の . は現在のディレクトリをBuild Contextとして使用するという指定です。
実行すると、今回の環境では次のような処理が確認できました。
[1/2] FROM docker.io/library/nginx:latest
[2/2] COPY html/index.html /usr/share/nginx/html/index.html
Dockerfileに書いたとおり、
- nginx Imageを土台にする
-
index.htmlをコピーする
という処理が行われています。
作成されたImageを確認する
Image一覧を確認します。
sudo docker images
今回の環境では、次のImageが確認できました。
IMAGE
hello-world:latest
my-nginx:latest
nginx:latest
nginx:latest はDocker Hubから取得した公式Imageです。
一方、
my-nginx:latest
は、今回Dockerfileから自分で作成したImageです。
自作ImageからContainerを起動する
作成した my-nginx Imageを使ってContainerを起動します。
sudo docker run -d --name web -p 8080:80 my-nginx
ここで重要なのは、今回は -v を指定していないことです。
Bind Mountを使用したときは、
-v ~/docker-lab/html:/usr/share/nginx/html:ro
を指定していました。
しかし今回は、
sudo docker run -d --name web -p 8080:80 my-nginx
だけです。
それでもブラウザへアクセスすると、
Hello Docker from Raspberry Pi!
が表示されました。
これは index.html がすでに my-nginx Imageの中に組み込まれているためです。
Bind Mountとの違いを実験する
ここでBind Mountとの違いを実際に確認してみます。
Raspberry Pi側のHTMLを変更します。
nano ~/docker-lab/html/index.html
見出しを次のように変更します。
<h1>Changed on Raspberry Pi!</h1>
保存したあと、そのままブラウザを更新します。
しかし、今回は表示が変わりません。
Bind Mountを使用していたときとは動きが違います。
理由は、現在の構成が次のようになっているためです。
Raspberry Pi上のindex.html
│
│ docker buildした時点でCOPY
▼
my-nginx Image
│
▼
web Container
docker build を実行した時点のHTMLがImage内へコピーされています。
その後、Raspberry Pi側の元ファイルを変更しても、すでに作成済みのImageの内容は変わりません。
変更後のHTMLをImageへ反映する
変更したHTMLを反映するため、Containerを削除します。
sudo docker rm -f web
そしてImageをもう一度Buildします。
sudo docker build -t my-nginx .
新しいImageからContainerを作成します。
sudo docker run -d --name web -p 8080:80 my-nginx
ブラウザを更新すると、
Changed on Raspberry Pi!
と表示されました。
つまり、DockerfileでファイルをImageへ組み込む場合は、
index.htmlを変更
↓
docker build
↓
新しいImageを作る
↓
Containerを作り直す
↓
変更が反映される
という流れになります。
Docker Buildではキャッシュも使われる
2回目のBuildでは、出力に次のような表示がありました。
CACHED [1/2] FROM docker.io/library/nginx:latest
[2/2] COPY html/index.html /usr/share/nginx/html/index.html
FROM nginx:latest の部分は変更されていないため、Dockerが以前のBuild結果を再利用しています。
一方、index.html は変更されているため、COPY の処理は再実行されています。
つまりDockerは毎回すべてを最初からBuildするのではなく、変更されていない部分についてはキャッシュを利用できます。
Bind MountとDockerfileの違いを整理する
今回、同じHTMLを配信するために2つの方法を試しました。
Bind Mount
Raspberry Pi
└── index.html
│
│ Bind Mount
▼
Container
└── nginx
ホスト側のファイルをContainerから直接参照します。
そのため、Raspberry Pi側のファイルを変更すると、Containerを作り直さなくても変更が反映されました。
開発中に何度もファイルを書き換える場合には便利そうです。
Dockerfile + COPY
index.html
│
│ docker build
▼
自作Image
│
▼
Container
Build時にファイルをImage内へ組み込みます。
そのため、元ファイルを変更しただけでは作成済みImageや実行中Containerには反映されません。
変更を反映するには、もう一度BuildしてContainerを作り直す必要があります。
一方で、Image自体に必要なファイルが含まれるため、同じImageから同じ内容のContainerを作ることができます。
DockerfileはImageを作るためのレシピ
今回Dockerfileを書いてみて、個人的には、
DockerfileはDocker Imageを作るためのレシピ
と考えると分かりやすく感じました。
今回作成したDockerfileは、
FROM nginx:latest
COPY html/index.html /usr/share/nginx/html/index.html
だけです。
文章にすると、
nginx公式Imageを土台にする
↓
自分のindex.htmlを追加する
↓
新しいImageを作る
となります。
第1回では既存ImageからContainerを作りましたが、今回はさらに一歩進んで、自分でImageを作り、そのImageからContainerを作るところまで確認できました。
まとめ
今回は、自分で作成したHTMLをDocker上のnginxから配信しながら、
- Bind Mount
- Dockerfile
docker build- 自作Image
- Build Cache
について確認しました。
特に重要だったのは、Bind MountとDockerfileではファイルの扱い方が異なることです。
Bind Mountでは、
ホスト側のファイル
↓
Containerから直接参照
となるため、元ファイルの変更がすぐに反映されました。
一方、Dockerfileの COPY では、
元ファイル
↓
docker build
↓
Imageへ組み込む
↓
Container
となります。
Imageを作ったあとに元ファイルを変更しても、作成済みImageには影響しません。
変更を反映するには、もう一度Buildする必要があります。
今回の実験で、
Docker HubからImageを取得して使う
↓
自分のファイルをContainerから参照する
↓
Dockerfileを書く
↓
自分でImageを作る
↓
自分のImageからContainerを作る
という流れを一通り体験できました。
第1回よりも、Imageが「Containerを作る元になるもの」という意味が具体的に見えてきました。
次回
ここまでの操作では、Containerを起動するたびに、
sudo docker run -d --name web -p 8080:80 my-nginx
のようなコマンドを入力していました。
Containerが1つだけならそれほど大変ではありません。
しかし、Webアプリやデータベースなど、複数のContainerを組み合わせるようになると設定が増えていきます。
次回は Docker Compose を使って、
compose.yaml
│
├── Web Container
│
└── Redis Container
という複数Container構成を作ります。
さらに、Container同士が同じDockerネットワーク上で、IPアドレスを直接指定せずにサービス名で通信できることも確認します。
次回:【Docker編 第3回】Docker Composeで複数Containerをまとめて動かす