はじめに
こんにちは!ITスクールRareTECHにてCS(Customer Support)を担当している池村です。今回の記事はDockerfileについてです。DockerfileはDockerの基本コマンドとかなり密接に関わっていますので、最初からここに手をつけるのはお勧めしません。
①〜⑥まで読んでいない方はまずそちらを読んでからをおすすめします。
①はこちら
前回の記事はこちら
Dockerfileとは
まずDockerfileとはって何?というお話しですが、今までのややこしい環境構築等を一つのファイルにまとめて、イメージの作成をしちゃおうというものです。
例えば、前回の記事でWebサーバー(Flask)を立ち上げて環境構築するまでを一つのコマンドで実行していたわけですが、長ったらしくて面倒です。
docker run --network my_network --name flask_container -v "$(pwd)/flask_app:/app" -w /app -p 5000:5000 python:3.9-slim sh -c "ls && pip install -r requirements.txt && python app.py"
これをファイルに書き込んでおいて、イメージ化し、実行するだけで終わらせることができます。
短縮できない記述もあります。
DockerHubにあるイメージ類も、基本的にはこのDockerfileで作られていることが多いです。
Dockerfileの基本的な要素
サンプルDockerfile
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt requirements.txt
COPY app.py app.py
RUN pip install -r requirements.txt
EXPOSE 5000
CMD ["python", "app.py"]
このDockerfileはサンプルであり、いい書き方ではないですが、初心者が順序よく理解するためにはちょうどいいものとして作成しています。
1. FROM
まずFROMですが、これはどのイメージをベースにするコンテナか決めるための項目です。search
やpull
で検索や取得をしていたイメージたちですね。もちろんタグ付けもできるので、今回はPythonの軽量なコンテナイメージである3.9-slim
を選択しています。
Python以外で色々コンテナを作るとしたら、以下のような例があります。
# Ubuntuの最新版を使いたい場合
FROM ubuntu:latest
# MySQLのコンテナを使いたい場合
FROM mysql:latest
# Nginxを使いたい場合
FROM nginx:latest
# ReactとかExpressを使いたい場合
FROM node:16-alpine
# Goを使いたい場合
FROM golang:1.20-alpine
# Javaを使いたい場合
FROM openjdk:17-jdk-slim
と、様々あります。作りたいコンテナによって、ここは変わってきますね。
一つのDockerfileに複数のイメージを書いているように見えますが、本来FROMで指定するのは一つですのでご注意ください。例として一つのファイルに複数書いているだけです。
2. RUN
次はRUNです。Dockerfileはイメージを作成するためのものです。このRUNは、イメージの作成中(build中)に実行されるものです。今回のサンプルのファイルの中ではpip install -r requirements.txt
を実行していますね。
全てではありませんが、先ほどFROMで見てきたイメージに合わせたRUNを考えると以下が考えられます。
Ubuntu
FROM ubuntu:latest
RUN apt-get update && apt-get install -y vim git curl python3
Ubuntuをどう使いたいかにもよりますが、まずはパッケージ管理システムのアップデートが最初に来ることが多いです。その状態で色々なツールをインストールしています。
Nginx
FROM nginx:latest
RUN apt-get update && apt-get install -y openssl certbot
今回入れているのはSSL証明書の作成用openssl
と無料かつ自動でSSL証明書を発行できるcertbot
というツールです。apt-get
を使っているのは、NginxのベースイメージがDebianやUbuntuを使用しているためです。
Node.js
FROM node:16-alpine
RUN apk add --no-cache git && npm install -g create-react-app
ベースがAlpineLinuxなので、パッケージ管理システムはapk
を使います。
--no-cache
をつけると、キャッシュを残しません。
本来、パッケージ管理システムはインストールした履歴のようなものを残していて、再インストール時に高速で取得できるようになっているのですが、正直一回入れてしまえば再インストールすることはほぼないので、だったら余計なキャッシュは残さず、その分イメージを軽量にします。
あとはReactのプロジェクトを作成しています。
Golang
FROM golang:1.20-alpine
RUN apk add --no-cache git && go install github.com/gin-gonic/gin@latest
こちらもAlpineなのでapk
でgitも入れておいて、Ginというフレームワークをインストールしておきます。
色々書きましたが、RUN
はイメージを作成する時に実行してくれるコマンドを記述する場所です。それを覚えておいてください。
3. COPY(ADDも一応)とWORKDIR
COPYはホストのローカルにあるディレクトリ構成をコンテナ内にコピーする項目です。
これはボリュームとも関連してきます。特定のディレクトリにバインドマウントしたい場合、ここに書いていきます。
初学者にとって、ここはイメージがつきにくい部分だと思っています。じっくり理解していきたいです。
COPY <ソース> <ターゲット>
基本は上記👆です。
# ローカルのapp.pyをコンテナ内の/appの下にコピー
COPY app.py /app
ここで注意点です。
コンテナの中に/app
なんてディレクトリあるの?という疑問ですね。
結論:ないです。
なので作る必要があります。
ここでWORKDIR
が必要になってきます。
FROM python:3.9-slim
WORKDIR /app
WORKDIR
を指定することで、コンテナ内での作業ディレクトリが決まります。これを設定してあげることで、COPYやRUNがその指定した作業ディレクトリで行われるようになります。
ここですごいのは、勝手に/app
のディレクトリを作ってくれることです。
なのでWORKDIR
はCOPY
やRUN
よりも前に書かれることが多いです。
順番的にこの辺りで説明した方が良さそうなのでここにしました。(迷走)
ということは
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt requirements.txt
COPY app.py app.py
RUN pip install -r requirements.txt
- イメージはPythonの軽量イメージ
- 作業ディレクトリは
/app
(コンテナ内のルートディレクトリにあるapp
というディレクトリの中) - その中にrequirements.txtと、app.pyを同じ名前でコピー
- その後のそのrequirements.txtを使って必要なライブラリをインストール
requirements.txtとapp.pyは一緒にCOPYもできます。
COPY requirements.txt app.py .
としてください。これによって、同じ名前で.(/app)
にコピーされます。
ADDについて
COPY
を拡張した記法としてADD
があります。
これはCOPYと違って、圧縮ファイルなどを解凍してくれます。基本的な使い方はCOPY
と同様です。
ADD <ソース> <ターゲット>
COPYと同じく、ローカルのファイルやディレクトリをコンテナ内の指定の場所にコピーします。
# /app/に解凍されたファイルが展開
ADD my_archive.tar.gz /app/
# URLから直接持ってくることも可能
ADD https://example.com/file.txt /app/
特徴 | COPY | ADD |
---|---|---|
ローカルファイルのコピー | ✅ 可能 | ✅ 可能 |
圧縮ファイルの解凍 | ❌ 不可能 | ✅ 自動で解凍 |
リモートリソースの取得 | ❌ 不可能 | ✅ 可能 |
推奨される使用ケース | シンプルなファイルやディレクトリのコピー | 圧縮ファイルの解凍やリモートリソース取得 |
個人的にはCOPYがしっかり使えればOKだと思っています。
4. EXPOSE
EXPOSEはポート番号をどうするか決める部分です。今回のサンプルの場合、EXPOSE 5000
としているので、5000番ポートを開けておくよ〜といった形ですね。
# Flaskのデフォルトポート番号
EXPOSE 5000
# Djangoのデフォルトポート番号
EXPOSE 8000
# SSHなら
EXPOSE 22
# ファイルサーバーとかなら(Samba)
EXPOSE 445
ポートフォワーディングをしているわけではないので、起動時に外部との通信を行う際は、ホストポート:コンテナポート
を指定して起動する必要があります。
5. CMD
次にCMD
の書き方です。これはRUN
と違って、コンテナの起動時に実行されるコマンドを書く部分です。大体はDockerfileの最後に記述されることが多い印象です。
これも例を見つつじっくり確認していきましょう。
Ubuntu
CMD ["bash"]
Ubuntuの場合、これを最後に書くことで、Bashが立ち上がります。なので、今まで打ってきたコマンドの一部を省略できます。
docker run -it my_container
/bin/bash
を省略することができました。ただ、-it
は必須ですのでご注意ください。
Flask
CMD ["python", "app.py"]
これを最後に書いてあげると、app.pyに記述しているWebサーバーがコンテナ起動と同時に立ち上がります。
ReactやExpress
CMD ["npm", "start"]
CMD ["node", "app.js"]
これを最後に書いてあげると、JS関係のアプリケーションを起動可能です。
ENV
ENV
は環境変数です。.env
ファイルのように、大事な情報等を管理するために使われます。ただ、こういったファイルに入れるパスワードやトークンの情報は危険な情報です。使い方に注意です。
ENV APP_PORT=5000
CMD ["python", "app.py", "--port", "$APP_PORT"]
上記のような使い方は特に問題はないです。コンテナ内でしか使われない環境変数ですが、ここで機密情報を入れた状態のイメージを作成して、DockerHubに入れちゃったりしたら大変なことです。
結論、機密情報を入れないようにしましょう。
その他
LABEL
:初学者は知らなくて良い。イメージの情報を入れられます。誰が作ったとかそういうの。
VOLUME
:ボリュームを設定。Composeを使えるようになったら、正直使わない。
USER
:コンテナ内でのユーザー管理用。デフォルトではrootユーザーになる。これを変えたい場合に使うが、まあ使わないですね。
ENTRYPOINT
:CMD
と似ているが、docker run
の時に上書きできるかできないかの違い。
docker run my_app # 実行: python app.py
docker run my_app ls # 実行: ls
docker run my_app # 実行: python app.py
docker run my_app --version # 実行: python app.py --version
長くなってしまいましたが、ここまでが基本的なDockerfileの要素です。
作ったDockerfileからイメージを作って起動しよう
ではここからは作ったDockerfileを実際にイメージ化して、コンテナとして立ち上げていきましょう。ここでは新しくbuild
というコマンドが出てきます。
環境準備
まずは適当なディレクトリにDockerfileを作成しましょう。(ホーム以外)
mkdir test_flask && cd test_flask && touch Dockerfile app.py requirements.txt
.
├── Dockerfile
├── app.py
└── requirements.txt
次にDockerfileの中身を編集していきます。エディタはなんでも良いです。
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt requirements.txt
COPY app.py app.py
RUN pip install -r requirements.txt
EXPOSE 5000
CMD ["python", "app.py"]
記述が終わったら次にapp.pyの中身を書いていきましょう。
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello, Qiita!"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
できたら、次はrequirements.txtです。
Flask
これで準備は完了です。
イメージの作成
ではイメージを作成していきます。
docker build -t test_flask_app .
必ずDockerfileのあるところで実行してください。
-t
はタグ付けです。イメージに名前をつける作業と思ってください。
今回はtest_flask_app
という名前をつけました。
できたイメージを確認するときはdocker images
を叩いてみてください。
では次に、作ったイメージからコンテナを動かしていきます。
docker run -d -p 5000:5000 test_flask_app
Macの方は5001:5000
の方が良いかも。
では動いているか確認といきましょう。
curlコマンドで行う場合
curl http://localhost:5000
成功するとこんなレスポンスが帰ってくるはずです。(私は5001番ポートを使用)
ブラウザの場合
以下のリンクをクリックしてみてください。
コンテナが動いているかどうかは、docker ps
で確認してみてください。Upなら動いているし、Exitedなら動いていないです。
おわりに
今回はDockerfileについて解説しました。
これがあるだけで今までの苦労がかなり短縮された気がしています。
Dockerfileにもたくさんの書き方がありますが、そこはいったん置いておいて、次はいよいよDockerComposeの話をしていきたいと思います。
これができたらアプリ開発の環境構築は怖くないです!頑張りましょう💪
余談
前のDockerネットワークの記事がやたら伸びているのを見て震えています。