Dockerfile：実践体験＆シェルを使ったインタラクティブな使い方

このチュートリアルでは、**Alibaba Cloud**上での**Dockerfile**の使用方法について実践的な経験を積むことに焦点を当てています。

*本ブログは英語版からの翻訳です。オリジナルは[こちら](https://www.alibabacloud.com/blog/getting-to-know-dockerfile-instructions-part-1_594330)からご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。*

#前提条件
最近のバージョンのDockerが既にインストールされているAlibaba Cloud [Elastic Compute Service](https://www.alibabacloud.com/ja/product/ecs)インスタンスにアクセスする必要があります。

私はCentOSを使ってこのチュートリアルを書いています。Debian / Ubuntuを使用することができます。[このチュートリアル](https://www.alibabacloud.com/blog/how-to-install-and-use-docker-on-ubuntu-16-04_470612?spm=a2c65.11461447.0.0.23253c29cnylho)の99%は、ほとんどがDockerコマンドを使用しているので、どのようなLinuxディストロでも動作します。このチュートリアルを参照して、LinuxサーバにDockerをインストールする方法を学ぶことができます。

あなたは、**docker run**と**docker ps -a**を使用したり...Docker、イメージ、コンテナの非常に基本的な理解と知識が必要です。

このチュートリアルの目的は、非常にシンプルなDockerfileをいくつか構築してもらうことです。使っていくうちに、Dockerの概念の理解が深まっていくでしょう。

#準備
コンテナをいくつか(できれば何もない)実行している場合には、本当に役立つでしょう。そうすれば、**docker ps -a** の出力リストで簡単にチュートリアルコンテナを見つけることができます。

そこで、実行している必要のないすべてのコンテナを停止してプルーニングしてください。

（開発環境で）次のようにしてすばやく実行できます。

```
docker stop $(docker ps -a -q) #stop all container
```

すべてのコンテナを削除するには、次のように実行します。

```
docker rm -f $(docker ps -a -q) # remove ALL containers
```

#Dockerfile FROM 命令
このチュートリアルの最初の部分は、あまりエキサイティングなものではありません。Alpine version 3.8のイメージをダウンロードして、それをベースに独自のコンテナを構築するだけです。

DockerfileのFROM命令は、コンテナを構築するためのベースとなるイメージ/Linuxディストロを指定します。

すべてのDockerfileには必ずFROM命令が必要です。

Dockerfileのチュートリアルでは、小さなAlpineのベースイメージで十分です。

[https://hub.docker.com/_/alpine/](https://hub.docker.com/_/alpine/?spm=a2c65.11461447.0.0.23253c298U1gTw)

私は必要になる前に画像をpull / downloadしたいと思っています。( Dockerfileで画像を参照すると自動的にダウンロードされます。)

実行：

```
docker pull alpine:3.8
```

期待される出力

```
3.8: Pulling from library/alpine
4fe2ade4980c: Already exists
Digest: sha256:621c2f39f8133acb8e64023a94dbdf0d5ca81896102b9e57c0dc184cadaf5528
Status: Downloaded newer image for alpine:3.8
```

Dockerfilesは別のディレクトリに置いた方が良いでしょう。同じディレクトリにあるすべてのファイルとディレクトリは、Dockerビルド中に使用されます。イメージのビルドに必要なファイルとディレクトリだけを入れてください。画像ごとに1つのディレクトリ：これらすべての属性により、きちんと整理され、小さく、効率的で、完全に理解できます。

したがって、入力します。

```
mkdir dockerfile-tutorials
cd dockerfile-tutorials
```

Linuxディストロでよく使われるエディタにはvimとnanoの2つがあります。私は nano の方が好きなので、nano のみを使うことになります。

nanoをインストールするには

Debian / Ubuntu ：

apt install nano

CentOS ：

yum install nano

Nano初心者ガイド：
1、nano file-to-edit ... これはnanoエディタを起動します。
2、カーソルキーが期待通りに動きます。
3、alibabaチュートリアルのウェブページからDockerfileのテキストをカットすると、期待通りに動作します。
4、Dockerfileへの貼り付けは期待通りに動作します。
5、F3を押して作業を保存します。
6、F2を押してエディタを終了します。
これであなたは nano のエキスパートです。このチュートリアルの4つのチュートリアルでnanoを使うために必要な知識はこれだけです。

nano を使って、以下のような Dockerfile を作成してみましょう。

nano Dockerfile

```
FROM alpine:3.8
```

実行：

```
docker build --file Dockerfile 
```

期待される出力

```
Sending build context to Docker daemon  84.48kB
Step 1/1 : FROM alpine:3.8
 ---> 196d12cf6ab1
Successfully built 196d12cf6ab1
Successfully tagged tutorial:demo
```

**Sending build context to Docker daemon 84.48kB**に注目してください。これは、DockerfileがあるディレクトリのFULLコンテンツがビルドコンテキストとして使用されていることを意味します。ファイルやディレクトリを選択的に最終的なビルドイメージにコピーすることができます。

**FROM alpine:3.8**は、ダウンロードしたAlpine 3.8イメージを読み込んで、ビルドイメージに追加します。ビルドイメージの中には、小さなAlpine Linuxディストロが入っています。

ビルドコンテキストとは、Dockerfileと同じディレクトリにある全てのファイルとディレクトリのことです。そのため、Dockerfileは別のディレクトリに配置する必要があります。もしDockerfileをルートディレクトリに置いていると、フルリナックスディストロがビルドコンテキストの一部になってしまいます。

Dockerfileが必要なときに自動的にダウンロードしてくれることを証明するために、Alpineのイメージを削除してみましょう。

```
docker image remove alpine:3.8
```

期待される出力

```
Untagged: alpine:3.8
Untagged: alpine@sha256:621c2f39f8133acb8e64023a94dbdf0d5ca81896102b9e57c0dc184cadaf5528
Deleted: sha256:196d12cf6ab19273823e700516e98eb1910b03b17840f9d5509f03858484d321
```

rerun

```
docker build --file Dockerfile .
```

下の**Pulling from library/alpine**に気付きました。インターネットから画像をダウンロードすると時間がかかります。

```
Sending build context to Docker daemon  2.048kB
Step 1/1 : FROM alpine:3.8
3.8: Pulling from library/alpine
4fe2ade4980c: Already exists
Digest: sha256:621c2f39f8133acb8e64023a94dbdf0d5ca81896102b9e57c0dc184cadaf5528
Status: Downloaded newer image for alpine:3.8
 ---> 196d12cf6ab1
Successfully built 196d12cf6ab1
```

Dockerfileで画像を参照すると自動的にダウンロードされます。alpine:3.8のイメージを削除したので、Dockerfileで参照すると自動的にダウンロードされるようになりました。

提案です。私はイメージをローカルマシンに引っ張ってくる方が好きです。そうすれば、ライブのインターネット接続を必要とせずに一日中作業することができます。

まとめ：
1、Nano the editor
2、ビルドコンテキストと個別のディレクトリの必要性
3、docker pullを使って画像をダウンロードする
4、アルパインバージョン3.8のイメージを使用するには、FROM alpine:3.8を使用してください。
5、docker build --file Dockerfile .を使用して、ベースとして使用するイメージを構築します。

#追加とコピー
コンテナが便利な機能を提供するためには、いくつかのソフトウェアを含まなければなりません。

このステップでは、コンテナにファイルやディレクトリを追加するためのADDとCOPYの使い方を学びます。

まず、いくつかのファイルとtarred / zipファイルを作成する必要があります。

実行:

```
touch in-tar-file-1
touch in-tar-file-2
touch in-tar-file-3

tar -cvf tarredfiles.tar in-tar-*
```

touch コマンドは空のファイルを作成します。

tar コマンドは in-tar- で始まるすべてのファイルを tarredfiles.tar に追加します。

1、c フラグは新しい .tar アーカイブファイルを作成します。
2、v フラグは、ファイル上の .tar ファイルの進行状況を一度に表示します。
3、f フラグは、このアーカイブ操作の出力ファイルの名前です。
さらに3つのファイルを作成して、別の tar ( zip アーカイブ ) に追加する必要があります: **more-tarredfiles.tar**

```
touch more-in-tar-file-1 ; touch more-in-tar-file-2 ; touch more-in-tar-file-3 ;
tar -cvf more-tarredfiles.tar more-in-tar-*
```

現在、tarredfiles.tar とmore-tarredfiles.tarの2つのtar化されたアーカイブがあります。

を使ってDockerfileに追加します。

nano Dockerfile

```
FROM alpine:3.8
ADD tarredfiles.tar /root
COPY more-tarredfiles.tar /root
```

を使用して画像を構築します。

```
docker build --tag tutorial:demo --file Dockerfile  .
```

期待される出力

```
Sending build context to Docker daemon  84.48kB
Step 1/3 : FROM alpine:3.8
 ---> 196d12cf6ab1
Step 2/3 : ADD tarredfiles.tar /root
 ---> f2ac949250b2
Step 3/3 : COPY more-tarredfiles.tar /root
 ---> 972dbad70e51
Successfully built 972dbad70e51
Successfully tagged tutorial:demo
```

このチュートリアルでは、実行する非常に小さなDockerfileのサンプルがたくさん含まれています。

次の実行のたびに、実行中の前のコンテナを停止してプルーニングしなければなりません。そうしないと、このようなエラーメッセージが表示されます。

```
docker: Error response from daemon: Conflict. The container name "/tutorial" is already in use by container "d2f5d27b1edc16e33bb8378b555fd61fd1e3ca244b5e8c1b682e79b76e39c660". You have to remove (or rename) that container to be able to reuse that name.
```

したがって

```
docker stop -t 0 tutorial   ;   docker container prune -f   ;   docker ps -a
```

そのため、これらのコマンドを頻繁に目にすることになるでしょう: 彼らは前のコンテナを停止し、それを削除/プルーニングします。

結果を見るためにコンテナを起動してみましょう。

```
docker stop -t 0 tutorial ;   docker container prune -f  
docker run -ti -d --name tutorial tutorial:demo /bin/sh -c 'while true; do sleep 60; done'
docker exec -it tutorial /bin/sh
```

/# shell promptの /# で** ls /root/** と入力して、カレントディレクトリ内のファイルを一覧表示します。

期待される出力

```
/ # ls /root
in-tar-file-1         in-tar-file-2         in-tar-file-3         more-tarredfiles.tar
/ # exit
```

予想通り、ADDはtarファイルのアンタースを自動化します。
COPYはtarファイルを自動化しません。

覚えやすいですね。ADDは二重の役割を果たします...追加とtarファイルの展開 / 解凍を行います。

COPYはコピー＆ペーストのようにコピーするだけで、余分な処理は行われません。写真のコピー機はコピーするだけで、二重の役割はありません。

**ベストプラクティス**: この二重の役割を果たす機能が必要な場合のみADDを使用してください。

DockerfileのADD tarredfiles.tar /rootは、tarredfiles.tarを自動で/root/に展開します。

DockerfileのCOPY more-tarredfiles.tar /rootは単にmore-tarredfiles.tarを/root/にコピーするだけです。

個々のファイルをコンテナにコピーする際にも全く同じ構文を使用します。

ADDとCOPYについては、[https://docs.docker.com/engine/reference/builder/#add](https://docs.docker.com/engine/reference/builder/?spm=a2c65.11461447.0.0.23253c29YqQd5h#add) でかなり詳しい情報を得ることができます。

少なくともこれで、この2つのコマンドを使った実践的な経験ができるようになりました。公式のDockerリファレンスドキュメントを読むことで、より意味のあるものになるでしょう。

#ファイルの所有権を変更するための chown の使用
ここでは、CHOWNを使ってADDやCOPYでファイルの所有権を変更する方法を説明します。

を使って2つのファイルを作成してみましょう。

```
touch gamefile
touch anothergame
```

を使ってDockerfileに追加します。

nano Dockerfile

```
FROM alpine:3.8
COPY --chown=games:games gamefile /root/
COPY --chown=35:35 anothergame /root/
```

を使用して画像を構築します。

```
docker build --tag tutorial:demo --file Dockerfile  .
```

コンテナを起動して結果を見てみましょう。

```
docker stop -t 0 tutorial ;   docker container prune -f  
docker run -ti -d --name tutorial tutorial:demo /bin/sh -c 'while true; do sleep 60; done'
docker exec -it tutorial /bin/sh
```

期待される出力

```
/ # ls -l /root
total 0
-rw-r--r--    1 games    games            0 Oct 16 14:26 anothergame
-rw-r--r--    1 games    games            0 Oct 16 14:26 gamefile
/ # exit
```

何が起こったのか : Dockerfileから解凍します。

```
COPY --chown=games:games gamefile /root
COPY --chown=35:35 anothergame /root
```

最初のコピーコマンドでは、所有権を変更するためにユーザ名とグループ名を使用していることに注意してください。

2 番目のコピーでは UID と GID を使用して所有権を変更しています。35はゲームのユーザーIDのUIDです。

GID = グループ識別子 (GID)

UID = ユーザー識別子（GID)

[https://en.wikipedia.org/wiki/User_identifier](https://en.wikipedia.org/wiki/User_identifier?spm=a2c65.11461447.0.0.23253c29MNq2hR)

パスワードファイルを覗いてみると、長いテキストのユーザ ID とそれらの UID を見つけることができるでしょう。私たち人間は簡単なテキストのユーザ名を使いますが、カーネルでは短くて効率的な数字だけを使います。UIDsです。

Dockerfileにこれら2つのコマンドを入力し、イメージから実行中のコンテナを作成するだけで、5段落の理論を読むのに比べて、--chgownについてより多くのことを学ぶことができました。

私はaddではなく、各行に1つのファイルをコピーするためにCOPYを使用しました。

#Dockerfile COPYを使用したdirsの再帰的コピー
目的：Dockerfile COPYを使用してdirsの再帰的なコピーをデモする

まず、入れ子になったディレクトリを表示するための小さな、しかし便利なツールをインストールする必要があります。

tree コマンドは、入れ子になったディレクトリをツリー構造としてきれいに表示するのに便利なツールです。

私はCentOSを使っているので、実行する必要があります。

```
yum install tree
```

( Ubuntu / Debian の場合は apt install tree )が必要です。

```
mkdir -p demo-directories/dir-a/dir-a1/dir-a2
```

mkdir はディレクトリを作成します。p は必要に応じて自動的に親ディレクトリを作成します。

これらのディレクトリを作成するための初心者の方法は

```
mkdir demo-directories
cd demo-directories
mkdir dir-a
cd dir-a
mkdir dir-a1
cd dir-a1
mkdir dir-a2
```

シェルで **man mkdir** を使って、mkdir のすべての機能を読むことができます。

入れ子になったディレクトリがどのように見えるか見てみましょう

実行：

```
tree demo-directories
```

期待される出力

```
demo-directories/
+-- dir-a
    +-- dir-a1
        +-- dir-a2
3 directories, 0 files
```

シェルで** ls -R demo-directories** を実行してみてください。違いがわかります。ツリーがよりわかりやすくなりました。

それぞれのディレクトリにファイルを追加してみましょう。

```
touch demo-directories/demo-file
touch demo-directories/dir-a/file-a
touch demo-directories/dir-a/dir-a1/file-a1
touch demo-directories/dir-a/dir-a1/dir-a2/file-a2
```

touch が自動的にネストされたディレクトリを作成できるようになれば、これらの **touch** コマンドのみを使用することができれば良いですが残念ながら、それはできません。そのため、まずディレクトリを作成してから touch でディレクトリ内のファイルを作成する必要があります。

実行：

```
tree demo-directories
```

期待される出力

```
demo-directories
+-- demo-file
+-- dir-a
    +-- dir-a1
    ��� +-- dir-a2
    ��� ��� +-- file-a2
    ��� +-- file-a1
    +-- file-a
3 directories, 4 files
```

これで、それぞれのディレクトリに1つのファイルができました。

Dockerfileを以下のように編集します。

```
FROM alpine:3.8

WORKDIR demo-work-dir
COPY demo-directories/ .
```

COPYコマンドの最後の .は現在のdirを意味します。**WORKDIR demo-work-dir**を使用しているので、現在のディレクトリは**demo-work-dir**です。

そのため、COPYはdemo-directoriesからすべてのファイルをdemo-work-dirにコピーします。

実行：

```
docker build --tag tutorial:demo --file Dockerfile .
```

期待される出力

```
Sending build context to Docker daemon  84.48kB
Step 1/3 : FROM alpine:3.8
 ---> 196d12cf6ab1
Step 2/3 : WORKDIR demo-work-dir
 ---> Using cache
 ---> c9996f2cdedc
Step 3/3 : COPY demo-directories/ .
 ---> Using cache
 ---> 350a93964377
Successfully built 350a93964377
Successfully tagged tutorial:demo
```

ビルド完了、実行

```
docker stop -t 0 tutorial ; docker container prune -f;docker ps -a

docker run -ti -d --name tutorial tutorial:demo /bin/sh -c 'while true; do sleep 60; done'
```

Dockerでは実行中のコンテナのコンテナIDが表示されます。

ビルドの効果を確認するためにコンテナに入ってみましょう。# shell promptで ls -R と入力します。

```
docker exec -it tutorial /bin/sh
```

期待される出力

```
/demo-work-dir # ls -R
.:
demo-file  dir-a
./dir-a:
dir-a1  file-a
./dir-a/dir-a1:
dir-a2   file-a1
./dir-a/dir-a1/dir-a2:
file-a2
```

このディレクトリはDockerfileの**WORKDIR demo-work-dir**で作成されたものです。

Dockerfileの**COPY demo-directories/ .**コマンドは、demo-directories/の再帰的な内容をすべて**demo-work-dir**にコピーしています。

注: demo-directoriesディレクトリ自体はコピーされず、中身だけがコピーされることに注意してください。

コンテナ内で作成された demo-directories ディレクトリ自体が必要な場合は、Dockerfile で **WORKDIR  demo-directories** を指定します。WORKDIRは存在しなければdirを作成します。

上記のことを本当に理解しているかどうかをテストするために、両方の名前を変更します。

demo-directories

demo-work-dir

次に、Dockerfileを新しいdir名で動作するように変更します。その後、イメージを再構築して実行し、コンテナに入り、新しいディレクトリにあるコンテンツを探してください。

これでうまくいっていれば問題ありません。次に、これらのdir名のうちの1つを意図的に間違えてください。上記のすべてを再実行してください。表示されるエラーメッセージを読んで、修正してください。これで、どんな理論が教えてくれるよりも、WORKDIRとCOPYがよく理解できるようになりました。

これでパート1の4つのうちの1つ、Dockerfileの説明書をすべて知ることができました。もっと学ぶために[パート2](https://www.alibabacloud.com/blog/getting-to-know-dockerfile-instructions-part-2_594331?spm=a2c65.11461447.0.0.23253c29HnXjGm)を読み続けてください。

*アリババクラウドは日本に２つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
[アリババクラウドジャパン公式ページ](https://www.alibabacloud.com/ja)*