Docker初心者向けに、DockerfileとDocker Composeの基本をまとめました。
「Dockerfileって何を書くの?」「compose.yamlはどう使うの?」という方の参考になれば幸いです。
ざっくり理解
Dockerfile
- イメージの「作り方」を定義。
- 料理のレシピのような感じ。
- イメージをビルドする。
compose.yaml
- 複数のコンテナの構成・連携方法を定義。
- レストランのオペレーション表のような感じ。
開発環境と本番環境の違い
- 開発環境
- Dockerfile.dev(別名でも良い)には、「どんな環境を作るか」を定義する。
- compose.yamlでボリュームマウントの設定により、ローカルのファイルとコンテナ内のファイルがリアルタイムで同期される(ホットリロード)。
- 本番環境
- Dockerfileでコードをイメージにコピーして「イメージ単体で完結」させる。
- これにより、どの環境でも同じイメージ = 同じ動作が保証される。
Dockerのコマンド
| コマンド | やること | 例えると |
|---|---|---|
| build | Dockerfile→イメージ | レシピから冷凍食品を作る |
| create | イメージ→コンテナ(停止状態) | 冷凍食品を皿に出す(まだ温めない) |
| run | イメージ→コンテナ(起動状態) | 冷凍食品を温めて食べられる状態に |
| start | 停止中のコンテナを起動 | 冷めた料理を温め直す |
もう少し詳しく
dockerの基本概念
-
イメージ
- 「こういう環境で作ってね」という設計図
- 静的で変わらない
- ファイルとして保存される
-
コンテナ
- イメージを元に実際に動いている環境
- 動的(プロセスが動いている)
- 複数作れる、消せる、作り直せる
Dockerfileの書き方
- DockerはDockerfileから命令を読み、自動的にイメージを構築する。
- イメージを作るために実行するコマンドライン命令は全てこのファイルに記述する。
FROM: ベースイメージを指定(必須・最初に書く)
FROM node:20
- ゼロから作るのではなく、既存のイメージの上に追加していく
WORKDIR: 作業ディレクトリを設定
WORKDIR /app
- 以降のコマンドは全て
/appの中で実行される - フォルダがなければ自動で作られる(今回はnode:20ベースイメージに指定しており、これには
/appというディレクトリは最初から存在しないが、mkdirをしなくても勝手にappディレクトリを作成してくれる)
COPY: ファイルをコピー
COPY package.json ./
- ローカルのファイルをイメージの中にコピー
-
./はWORKDIRで指定した場所(=/app) - 書き方は
COPY 元(ローカル) 先(コンテナ内)
RUN: コマンドを実行(ビルド時)
RUN npm install
- イメージを作る時に実行される
- パッケージのインストールなどに使う
つまり、イメージにはパッケージが全て含まれている。
今回の場合だと、npm install実行によりnode_modulesが全てイメージに含まれる。
イメージのサイズは大きくなるが、それを上回るメリットがある。
- 一度ダウンロードすればキャッシュされる
-
起動が爆速
毎回インストールしていると起動に何分もかかってしまう -
本番環境で安定
どのサーバーでも「全く同じイメージ」を使う
CMD: 起動時のコマンド
CMD ["npm", "start"]
- コンテナが起動した時に実行される
- RUNとの違い
- RUNはビルド時、CMDは起動時
EXPOSE: ポート番号を明示
EXPOSE 3000
- このコンテナが何番ポートを利用するかという情報を残す
- 実際の公開は
docker run -p 3000:3000で行う
EXPOSEはただの宣言(ドキュメント)であって、実際の動作には影響しない。
Dockerfileに EXPOSE 3000 と書いてあっても
docker run -p 8080:3000 myapp
と書いてしまえば、8080でアクセス → コンテナの3000に届く。
なぜ書くのか?
- Dockerfileを見た人が「このコンテナは3000番を使う」とわかる
- docker ps で確認しやすい
# Dockerfile
EXPOSE 3000 # 「3000番使うよ」と宣言
# 実行時
docker run -p 3000:3000 myapp # 実際に公開
ENV: 環境変数を設定
# ENV <キー>=<値> ...
ENV NODE_ENV=production
ENV PORT=3000
- コンテナ内で使える環境変数を設定
- TOKENなどの機密情報はDockerfileに書かないように注意(イメージの中に残るため誰でも見れてしまう)
ARG: ビルド時の変数
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}
- ビルド時にだけ使える変数
-
docker build --build-arg NODE_VERSION=18 .で上書き可能 - 例えば「普段はNode 20でビルドするが、Node 18でも動くか試したい」ときなどに使用する
ENTRYPOINT: イメージに対してメインとなるコマンドの設定
ENTRYPOINT ["npm"]
- ENTRYPOINTは固定、CMDは上書き可能なデフォルト引数
ENTRYPOINT ["npm"]
CMD ["start"]
# 引数なしで実行
docker run myapp
# → npm start(ENTRYPOINTのnpm + CMDのstart)
# 引数ありで実行
docker run myapp test
# → npm test(ENTRYPOINTのnpm + 渡したtest)
# CMDが上書きされた
Dockerfileの全体例
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}
WORKDIR /app
COPY package.json ./
RUN npm install
ENV NODE_ENV=production
EXPOSE 3000
CMD ["npm", "start"]
compose.yamlの書き方
- 複数のコンテナをまとめて管理するためのファイル
- 例えば、Webアプリを作る時、Webサーバー(Node.js), データベース(PostgreSQL), キャッシュ(Redis)、これらを毎回
docker runで1つずつ起動するのは面倒。compose.yamlに書いておけば、docker compose up一発で全部起動できる。
基本構造
services:
#サービス名(自由につけられる)
web:
build: .
ports:
- "3000:3000"
db:
image: postgres:15
servicesの下に、起動したいコンテナを並べていく。
build
- Dockerfileからイメージを作る場合に使用する
services:
web:
build: . #カレントディレクトリのDockerfileを使う
image
- 既存のイメージを使う場合に使用する
services:
db:
image: postgres:15 # Docker Hubから取得
ports
- ポートマッピング
ports:
- "3000:3000"
書き方: "ホスト側:コンテナ側"
localhost:3000でアクセス → コンテナの3000番ポートに届く
別のポートにしたい場合:
ports:
- "8080:3000"
localhost:8080 → コンテナの3000に届く
volumes
- コンテナとホスト(またはDockerの管理領域)の間でデータを共有・保存する仕組み
- コンテナは使い捨てが基本
コンテナ作成 → 使う → 削除- 削除するとコンテナ内のデータも消える。しかし消えてほしくないデータがある。
- コード → 編集したらすぐ反映してほしい
- DBのデータ → コンテナ消してもデータは残したい
- ボリューム = コンテナの外にデータを置く仕組み
- 【ボリュームなし】
- コンテナ削除 → 中のデータも消える
- 【ボリュームあり】
- コンテナ削除 → データは外にあるから残る
- 【ボリュームなし】
- バインドマウントと名前付きボリューム(=ボリュームマウント)がある
services:
web:
volumes:
- ./src:/app/src # これはバインドマウント (1)
db:
volumes:
- db-data:/var/lib/postgresql/data # これは名前付きボリューム(2)
volumes:
db-data: # 名前付きボリュームを定義(3)
書き方:
- バインドマウントの書き方:
"ホスト側のパス:コンテナ側のパス" - 名前付きボリュームの書き方:
"ボリューム名:コンテナ側のパス"
(1)バインドマウント
- ローカルの
./srcフォルダ →(同期) コンテナの/app/srcフォルダ - これが「ホットリロード」の仕組み
- ローカルでコードを編集 → コンテナ内にも即反映される
本番環境ではバインドマウントは不要
バインドマウントは開発環境でコードを即時反映するためのもので、本番環境ではイメージ自体にコードが入っているため同期する必要がない。
そのため、基本的に本番環境ではバインドマウントは不要。
(2)名前付きボリューム
- dbをコンテナ内に持っていると、コンテナが消えたときにdbも消え、データが消えてしまう。
- そのためコンテナ外にデータを持たせる場所をここで明示する。
(3)名前付きボリュームの定義
-
servicesと同じ階層にvolumesを定義する。 - Dockerが
db-dataでdocker内の別の場所にデータの保存場所を用意してくれる。 - これにより、コンテナが消えてもデータは永続的に残る。
environment
- 環境変数
environment:
- NODE_ENV=development
- DATABASE_URL=postgres://db:5432/myapp
または
environment:
NODE_ENV: development
DATABASE_URL: postgres://db:5432/myapp
どちらの書き方でもOK。
depends_on
- 起動順序
services:
web:
depends_on:
- db # dbが先に起動してからwebを起動
db:
image: postgres:15
注意
「起動順序」であって「準備完了を待つ」わけではない。 dbコンテナが起動しても、PostgreSQLが接続可能になるまで少し時間がかかる。
compose.yamlの全体例
services:
web:
build: .
ports:
- "3000:3000"
environment:
NODE_ENV: development
depends_on:
- db
db:
image: postgres:15
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
dockerコマンドとdocker composeコマンド
| やりたいこと | docker | docker compose |
|---|---|---|
| 操作 | 単一のコンテナを操作 | 複数のコンテナをまとめて操作 |
| 起動 | docker run |
docker compose up |
| 停止 | docker stop |
docker compose stop |
| 停止+削除 |
docker stop + docker rm
|
docker compose down |
| ログ確認 | docker logs コンテナ名 |
docker compose logs サービス名 |
| 中に入る | docker exec -it コンテナ名 bash |
docker compose exec サービス名 bash |