1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Docker編 第2回】Dockerfileを使って自分のDocker Imageを作る

1
Posted at

はじめに

前回は、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に書いたとおり、

  1. nginx Imageを土台にする
  2. 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をまとめて動かす

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?