最近色々なところでdockerfileに出会う機会が増え、なんとなく使えるようにはなったけど、実用性重視で理解できないまま使っていたので、年の瀬に駆け込みで理解したいと思う。
Dockerとは?
よく見る↓の絵。

参考
docker以外の仮想環境を使ったことないと、イマイチ良さが伝わらない。。。
よくわからなすぎて、とりあえず別の仮想環境(VirtualBox)を使ってみた後の今の理解は
- 仮想環境:
- 普段使っているOSに追加で別のOSをインストールする感じ
- インストール方法も大体は最初にPCを購入したときの設定と同じ
- 入れれば入れるほど各OSのスペックが下がる気がした
- OSを追加するたびに初期の環境構築がだるい
- そもそもOSなので「みんなで環境を共有」するにはファイルサイズ大きすぎ(ファイル出力は一応できた)
- docker:
- dockerエンジンの上で新規OSをアプリ感覚で動かせる
- OSの全機能というより一部の必要なのみ取捨選択してDLするので、動作が軽い気がする
- 環境構築後の設定まで含めて一つのアプリ(dockerイメージ)扱いなので、環境構築はしなくて良いのが楽
- いくつかの文字ファイルで共有できるので、「みんなで環境を共有」が簡単。しかもgithub管理できる
- 初心者殺しの環境構築を避けられる一方、dockerを起動できるようになるまで&共有ファイルを作るまでが難しい
以上のように、仮想環境は手間がある分、各OSの洗練された誘導画面に沿って環境構築していけば良いので、プログラミングはじめまして!な人には圧倒的に親切なもの。
そして仮想環境で何度も環境構築した結果、その親切さに嫌気が差し始め3た貴方にはdockerがオススメです。
docker事始め
以下、ubuntu上でdockerを使う方法についてのみ触れます。
(私のwindowsはdockerが起動できない子なので)
dockerのインストール
いくら楽だからと言っても、本当に最初の環境構築(インストール)は避けられない。
自分がdockerを使える状態かどうか忘れた人は以下のコマンドで確認。何かしらバージョン情報が返ってきたら、インストール済みの合図。
docker -v
permission deniedと表示された人はインストールはできてるけど、権限変更ができていない人なので、最後の権限変更のみ実施するのをオススメします。
では、まだインストールできていない人は以下のコマンドを無心で入力。(詳しい説明は他の人が色々書いてるので省略)
sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
docker権限変更
dockerはデフォルトだと管理者権限(sudo)を必要とするので、sudoをつけなくても使えるよう権限変更をしたほうが、使いがって良い(変更しなくても使える)
sudo usermod -aG docker $USER
su - ${USER}
dockerインストール確認
最初に書いた以下のコマンドでも確認できる。
docker -v
もう少しちゃんと動作確認をするには、以下のコマンドを実行。
インストールとともに入ったhello-worldコンテナが起動されて、Hello from Docker! なんちゃらかんちゃらとターミナルに表示される。
docker run hello-world
docker image? docker container??
次に、ダウンロードしてきたDockerイメージを使ってDockerコンテナを起動して・・・
dockerを始めるときに挫折する最初のポイントが↑の呪文。急に日本語じゃなくなった感がすごい。
- docker image:
- コンテナを作成するための指示が記載された、いわば設計書
- 使用するOS、pythonやC++などのプログラミング言語ごとに必要なライブラリ、ROSやWebアプリ開発のために必要なライブラリ・・・など「どのOSにどれをインストールしたものか」などが記載されている。
-
Dockerfileに書かれていて、これを手動で作成したり、DockerHubで共有されているものをダウンロードして使ったりできる
- docker container:
- dockerイメージから作成され、アプリケーションを実行する環境、いわば第2のコンピュータ
- コンテナ上で色々追加・変更したものをdocker imageとして出力することもできる
Docker imageを使いこなす
DockerHubからダウンロードもできるが、それはそのまま使えるので、つまらない。
わからなくても、取り敢えずこんなファイルがダウンロードされるんだなと思えるように、先にDockerfileそのものを理解したい。
Dockerfileを作る
より詳しい情報は以下の公式ページを参照
DockerではDockerfileから命令を読み込み、自動的にイメージをビルドする。Dockerfileはテキストファイルであり、イメージを作り上げるために実行するコマンドライン命令がすべてこのファイルに含まれている。
docker buildコマンドを実行すると、順次コマンドラインで命令処理を行い、ビルド結果となるイメージが得らる。
Dockerfileの最小構成
Dockerfileに記載されているコマンドはインストラクションと呼ばれる。
FROM ros:humble-ros-base
COPY . /app
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
iproute2 \
net-tools \
build-essential \
python3-colcon-common-extensions \
python3-vcstool \
python3-rosdep \
CMD ["bash"]
- FROM:
- Dockerfileの中で一番最初に記述されるべき命令
-
FROM [イメージ] [タグ]の形で、新たに作成するDockerイメージのベースとなるイメージを指定する(このベースイメージはDockerHubを参考する) - 例では 「ros」イメージの「humble-ros-base」のタグを利用するよう指定
- COPY:
-
COPY [コピー元][コピー先]の形で、ソースコードや設定ファイルなど、ローカルのファイルやディレクトリをDockerイメージにコピー - これによってローカルで開発しているプログラムをdocker上で実行可能になる
-
- RUN:
- Dockerイメージのビルド時にシェルコマンドを実行するための命令
-
RUN [コマンド]の形で、イメージにソフトウェアをインストールしたり、セットアップの手順を実行したりする
- CMD:
-
CMD ["実行ファイル", "パラメータ1","パラメータ2"]の形で、Dockerコンテナが実行されたときにデフォルトで実行するコマンドを定義 - 実行ファイルをbashに設定すると、ターミナルが自動起動するようにできる
-
「RUN」インストラクションは実行ごとに新しいレイヤを作成してその上でコマンドを実行する。そのため、多くの「RUN」を持つDockerfileは多くのレイヤから成るイメージを作成し、イメージのサイズが大きくなってしまう。
つまり、レイヤ数が多いと、PCの容量が圧迫されたり、イメージのビルドに時間がかかったりするため、「&&」で繋げるなどしてなるべく複数コマンドをまとめて記載し、レイヤ数は最小限にすることが望ましい。
RUN と CMD の違い
「RUNはレイヤを作る」が「CMDはレイヤを作らない」ため、ダウンロードなどの実施はRUNでレイヤを作成し起動のたびにダウンロードが実行されるのを避ける必要がある。
その他インストラクション
- ENV:
-
ENV [キー] = [値]の形で、環境変数を設定できる - 設定された環境変数は、それ以降の行に書かれたインストラクションで使用できる
- 設定された環境変数は、作成されたコンテナ内で常に利用できる
-
- WORKDIR:
-
WORKDIR [ディレクトリのパス]の形で、コマンドを実行する作業ディレクトリを指定できる - 絶対パスを指定する手間が省ける
-
Docker pull
Dockerfileを自作しない場合、DockerHubからイメージをダウンロードしてくることができる。
先程のDockerfileに記載されていたイメージをダウンロードするには以下のコマンドを実行する。
docker image pull ros:humble-ros-base
Dockerビルド
dockerイメージを用意できたら、ビルドを実行する。
docker buildコマンドは、Dockerfileとコンテキストからイメージを 構築 (ビルド)する。
コンテキスト内のファイルだけがDockerfileに記述されたCOPYやADDで読み込まれる対象となる。したがって、コンテキストを指定することで、ビルド時に必要なファイルのみを明確に識別し、安全かつ効率的にイメージを作成できる。
Dockerfileの名前がDockerfileで、コンテキストがビルド実行パス直下の場合、以下のような最短コマンドでdockerビルドを実行できる。
docker build .
Dockerfileの名前がDockerfileでなくとも、-fオプションを使えばどこに置いてあっても実行可能。
コンテキストも同様にパス指定できるが、あまり上位のパスを指定するのはオススメしない。
また、-tオプションでimageにタグ付けすることも可能
docker build -t image_name:tag_name -f path/to/Dockerfile path/to/context
Dockerを起動する
dockerの起動方法を調べると以下の3つのコマンドが現れる。これらの違いがイマイチ分かりにくい。
-
docker run:docker create+docker startを一つのコマンドでまとめて実行するコマンド
createでは、ビルドイメージをもとにコンテナを新規作成する。そしてstartでは作成されたコンテナを起動する。
そのため、ビルド直後の(初回の)docker起動ではこのコマンドを利用する必要がある。 -
docker start:作成されたコンテナを起動するコマンド
起動後、新たなコマンドの入力は受付ない(バックグラウンドで実行される)ので、通常のターミナルのようにコマンド入力したい場合は↓のexecコマンドと併用する必要がある。 -
docker exec:起動中のdockerターミナルにアクセスして、コマンド入力を可能にするコマンド
基本的な違いは以上。
しかし、コンテナを作成〜削除まで毎回実施する派と、一度作成したら起動と停止のみ実施して削除しない派に分かれているため、ネット上の「起動コマンド」話が複雑になっている感が否めない。
コンテナを作成〜削除まで毎回実施する派
以下のような順序を毎回繰り返す。
ただし、以下のオプションを必ず設定する必要がある
※-itと--nameはdocker run使用時に必要な基本オプション
-
--rm:Ctrl+Cで停止した場合(正常終了時)にコンテナ削除を自動実行するオプション -
-it:dockerがキーボード入力を受け付けられるようにする -
-v "$PWD":/workspace:rw:ヴォリュームマウント設定
"$PWD"現在のディレクトリ配下のファイルをdockerコンテナ上の/workspaceディレクトリ配下でも開けるようにする設定。ただし、権限設定はrw(読み書き)権限 -
--name container-name image-name:tag:どのビルドイメージから、何という名前のコンテナを作成するか指定
# dockerの作成&起動
docker run --rm -it -v "$PWD":/workspace:rw --name container-name image-name:tag
# 追加でターミナルにコマンド入力したい場合は
docker exec container-name bash
一度作成したら起動と停止のみ実施して削除しない派
削除しない場合、コンテナ内のファイルは停止後もご存されているので、ヴォリュームマウントする必要はない。(マウントしておいたほうが、ローカルでのファイル編集が可能なので楽だが)
# dockerの作成&起動(初回起動時のみ)
docker run -it --name container-name image-name:tag
# 2回目以降の起動
docker start container-name
# 追加でターミナルにコマンド入力したい場合は
docker exec -it container-name bash
# コンテナの停止(Ctrl+Cだけでは完全停止しない場合があるので注意)
docker stop container-name
貴方はどっち派?
藪をつついてキノコ竹の子戦争みたいなのに巻き込まれるのは嫌なので、どっちつかずなコメントだけ残そうと思う。
- 毎回削除するのは、ちょいと面倒そうな響きだが、これまでの起動時に発生した謎のバグを引きずってしまうリスクを回避できる点ではありかもしれない
- ただ、初心者としては「削除」なんて怖いことはしないで、せっかく頑張って作ったものを使い続けたい気もする