はじめに
こんにちは。
プログラミング初心者wakinozaと申します。
Java勉強中に調べたことを記事にまとめています。
十分気をつけて執筆していますが、なにぶん初心者が書いた記事なので、理解が浅い点などあるかと思います。
間違い等あれば、指摘いただけると助かります。
記事を参考にされる方は、初心者の記事であることを念頭において、お読みいただけると幸いです。
対象読者
- Dockerを勉強中の方
動作環境
- Windows11
- Visual Studio Code
- Docker 29
記事のテーマ
- Dockerを勉強しています
- この記事では、シンプルなJava開発環境を構築するという手順から、Dockerfileの書き方やビルド方法についてまとめていきます
目次
1. Dockerとは
2. カスタムなイメージの作り方
3. Dockerfileの書き方
4. ビルドと実行
5. コンテナの後始末
1. Dockerとは
Dockerは、「コンテナ」というシステム環境から隔離された実行環境を提供する技術です。
開発を行っていると、1つのPC内で複数のシステムを実行させる場合がよく発生します。複数のシステムは、互いに依存し、複雑な関係を構築します。そのような状態になると、システムAが必要とするライブラリCのバージョンアップが、同一環境で動作するシステムBの依存要件と衝突し、共存不能になる「依存関係の競合(Dependency Hell)」が発生します。
このような競合状態を解消する手段は「コンテナ」です。
Dockerでは、1つのPCに複数のコンテナを作成することができます。それぞれのコンテナは互いに独立しており、影響を及ぼさないため、システムの競合状態を解消できます。
例えば、先ほどの例の競合状態では、システムA(+新ライブラリC)とシステムB(+旧ライブラリC)を独立した環境に分離できるため、競合を完全に回避できます。
このように、コンテナは互いに独立しているため、それぞれのコンテナに同じシステムの別のバージョンを格納することも可能です。無関係なシステムから影響を受けることがなくなったため、不具合のリスクを減らすことができます。
また、Dockerは、コンテナを別のPCに簡単にコピーできるという特徴があります。
コンテナには、コンテナの元ともいえる「イメージ」が存在します。「イメージ」は、コンテナに必要なファイルをすべてまとめたアーカイブパッケージで、Docker社が運営している「Docker Hub」というサイトで多数の汎用的なイメージが公開されており、自由にダウンロードできます。
2. カスタムなイメージの作り方
しかし、時に汎用的なイメージをカスタムして利用したい場合があります。
例えば、開発環境をDockerイメージとしてプロジェクトメンバーに配布する場合などは、汎用的なイメージを利用するよりも、環境変数の設定など必要な操作が完了した状態のイメージを配布したほうが効率的に開発に取り掛かれるでしょう。
カスタムなイメージを作成する方法は主に2つありますが、再現性と透明性の観点から、実務では後述するDockerfileによる管理が一般的です。
1つ目は、すでに自作したコンテナからイメージを作成する方法です。
ベースとなる汎用的なイメージからコンテナを起動し、必要な操作を完了した状態でイメージを作成します。すでに操作済みのコンテナが存在し、そのコンテナをそのまま配布したい場合はこの方法がよいでしょう。しかし、配布されたイメージからコンテナを起動しても、イメージ作成者がどのような操作をしたのかは、コンテナ利用者からはわかりません。そのため、誤操作や悪意のある変更が加えられている可能性も否定できないというデメリットが存在します。
2つ目は、Dockerfileを作成する方法です。Dockerfileは、ベースとなるイメージに対する変更指示をまとめたファイルです。これを見れば、ベースとなるイメージにどのような変更を加えたのかが一目瞭然であるため、操作ミスや悪意のある操作が加えられていないかをあらかじめ確認することができます。
また、Dockerfileはシンプルな記述であるため、改良が容易です。誰かが作ったイメージにさらに改良を加えたい場合は、そのDockerfileを入手して、必要個所を変更するだけに済みます。
3. Dockerfileの書き方
この記事では、シンプルなJavaプログラムを実行するコンテナをDockerfileから作成していきます。
3-1. Javaプログラムを作成
まずは、実行するJavaプログラムを作成します。
ターミナルもしくはPowerShellで、PC内の適当な場所に作業用ディレクトリ(docker_test)を作成します。
mkdir docker_test
次に、docker_testディレクトリの中に以下のJavaファイルを作成します。
public class Main {
public static void main(String[] args) {
System.out.println("Dockerfile!!");
}
}
3-2. Dockerfileを作成
次に、先ほどの作業ディレクトリ内に「Dockerfile」というファイルを作成して、以下の内容を記述します。(拡張子は不要です)
FROM eclipse-temurin:21-jdk-alpine
WORKDIR /app
COPY Main.java .
RUN javac Main.java
CMD ["java", "Main"]
1つずつのコマンドを説明していきます。
3-2-1. FROM
FROM eclipse-temurin:21-jdk-alpine
「FROM」で基本となる、ベースイメージを指定します。
ゼロからイメージを構築するのは大変なので、Docker Hub公式で公開されているイメージを利用します。
Dockerfileが実行されると、まず自分のPC内に指定されたイメージがあるかを確認しに行き、ない場合は「Docker Hub」から指定されたイメージを自動的にダウンロードします。
Dockerfileは基本的に、FROMから始めます。
今回は、「eclipse-temurin:21-jdk-alpine」というJDKとLinuxがセットになっているイメージを利用します。
「eclipse-temurin」はEclipse財団が提供しているJDKで、更新頻度も高く、商用利用でも無償で利用できるため、非常に利用しやすいJDKです。
「alpine」は、OSの機能を最小限に絞った軽量なLinuxです。
FROMでイメージを指定する場合は、バージョンを指定することが推奨されます。バージョンを指定しなかったり、「latest」タグをつけると、ビルドを実行した時点の最新版がダウンロードされます。そのため、バージョン情報を指定しないと、同じDockerfileを利用しているのに、利用するタイミングによって異なるバージョンがダウンロードされてしまう事態を招き、予期せぬ不具合の原因となります。
3-2-2. WORKDIR
WORKDIR /app
「WORKDIR」で、コンテナ内作業用ディレクトリを設定します。指定されたディレクトリがない場合は、自動的に新しいディレクトリを新規作成します。
3-2-3. COPY
COPY Main.java .
「COPY」で、ローカル環境にあるファイルやディレクトリをコンテナ内にコピーします。
「COPY コピー元 コピー先」という順番でそれぞれのパスを指定します。
コピー元などローカル環境のパスを指定する場合は、基本的に相対パスを記述します。それは、もし絶対パスを記述してしまうと、Dockerfileを他の人に配布した際、環境によってパスが異なるためビルドが失敗する可能性があるからです。
また、コピー元として指定したディレクトリの内部に不要なファイルがある場合は注意が必要です。大容量ファイルがあると、ビルドが遅くなりますし、機密情報が記載されたファイルまで誤ってコピーしてしまえば、セキュリティリスクもあります。ディレクトリに不要なファイルがある場合は、予め「.dockerignore」で不要なファイルを指定し、ビルド時のコピー対象から外しておきます。
今回は、Dockerfileと同じディレクトリ内にあるMainファイル「Main.java」を、コンテナの作業用ディレクトリ「.」にコピーしています。
3-2-4. RUN
RUN javac Main.java
「RUN」で、イメージをビルドしている最中に実行したいコマンドを指定します。環境構築や、インストール、コンパイルなどのコマンドはRUN命令に記述します。
本来、Javaプログラムの実行にはOSに適合したJDKを個別に用意する必要があります。しかし、コンテナ内にビルド環境を閉鎖的に構築することで、ホストOSに依存しない一貫したビルド・実行プロセスを保証できます。
RUN命令は複数書くことができますが、RUN命令が増えるたびに新しいレイヤーが追加されます。レイヤーが増えすぎると、イメージサイズが増大し、ビルドパフォーマンスやデプロイ効率を悪化させます。そのため、複数のコマンドを記述する場合は、キャッシュ効率を考慮に入れつつ、まとめられるコマンドは「&&」で1つのRUN命令にまとめることが必要です。
今回は、Javaファイルをコンパイルする「javac Main.java」コマンドを記載しています。
3-2-5. CMD
CMD ["java", "Main"]
「CMD」は、コンテナ実行時に、実行するコマンドを記載します。アプリケーションの開始や実行に関わるコマンドなどを記述します。コンテナを起動するたびに毎回実行されます。また、複数の CMD 命令を記述しても、有効になるのは最後に記述したCMDのみである点に注意が必要です。
CMDの記述方法は Exec式とShell式の2種類がありますが、Exec式で記載することが一般的です。
Exec式は、上のコード例のように、コマンドを1単語ずつJSON配列で記述します。今回では、「java Main」というコマンドを実行するために["java", "Main"]と記載しています。Exec式は、指定したコマンドが直接実行されるのが特徴で、「docker stop」などの停止信号を送った際にも正しく終了処理ができるメリットがあります。一方で、環境変数の展開などは、この記述方法ではうまくいかない場合があります。
一方、Shell式は、カッコやクオテーションマークを使わず、コマンドをそのまま記載します。上のコード例をShell式に変換すると、「CMD java Main」という書き方になります。Shell式は、文字通り、裏側でShellを介して実行されます。環境変数なども正しく展開されるというメリットもありますが、Shellを介したコマンドであるため、「docker stop」などの停止信号を送った際に不自然な強制終了になってしまう可能性があります。
4. ビルドと実行
最後に、イメージをビルドし、コンテナを実行する工程に移ります。
まず、ターミナルもしくはPowerShellで先ほどの作業用ディレクトリに移動後、以下のコマンドを実行し、イメージをビルドします。
[Building FINISHED]と出ればビルドは完了です。
docker build -t java21alpine .
- [docker build] : ビルドを開始する命令です。このコマンドを実行すると、Dockerfileと「ビルドコンテキスト」からイメージをビルドします。「ビルドコンテキスト」とは、DockerクライアントがDockerデーモンに送信するファイル群のルートディレクトリPATH、またはURLを指します。
- [-t イメージ名] : 「-t」で作成するイメージに名前(タグ)をつけることができます。イメージ名は任意で指定できます。イメージ名をつけ忘れると、イメージを指定する際に長いIDを記述しなければならなくなります。
- [.] : 「.(カレントディレクトリ)」をビルドコンテキストとして指定しています。ビルドコンテキストのパスを指定することにより、ビルドコンテキストの Main.java がDockerデーモンに送られ、COPY 命令で参照可能になります。
次に、ビルドしたイメージからコンテナを起動し、実行します。
標準出力に Dockerfile!! と表示されれば、コンテナ内でのJavaプログラムの実行は成功です。
docker run --name java-test java21alpine
- [docker run] : コンテナを作成して起動する命令です。
- [--name コンテナ名] : 起動したコンテナにコンテナ名を指定します。
- [イメージ名] : docker buildのときにつけたイメージ名を指定します。
今回は簡潔さのために、 [-d]コマンドなしで起動しましたが、実務では、バックグラウンドで実行する[-d]コマンドを実行し、「docker logs」でログを確認します。
5. コンテナ・イメージの後始末
不要になったイメージやコンテナをそのままにすると、ディスク容量を圧迫してしまいます。
そのため、不要になったコンテナは停止・削除し、イメージも削除します。
コンテナを停止・削除する場合は、ターミナルで以下のコマンドを実行します。
# 1. 動いているコンテナのIDや名前を確認
docker ps -a
# 2. コンテナを停止する
docker stop java-test
# 3. コンテナを削除する
docker rm java-test
不要になったイメージを削除する場合は、同様に以下のコマンドを実行します。
# 1. イメージの一覧を確認
docker images
# 2. イメージを削除する
docker rmi java21alpine
まとめ
-
環境の隔離と解消: コンテナ技術により、ホストOSや他のプロジェクトとの依存関係の競合(Dependency Hell)を回避し、クリーンな実行環境を維持できる。
-
Dockerfileによる構成管理: 手動構築を避け、Dockerfileで手順をコード化することで、環境構築の透明性・再現性・ポータビリティを担保できる。
-
一貫したビルドプロセス: コンテナ内でコンパイルを行うことで、開発者のローカル環境に依存しない成果物の作成が可能になる。
-
ビルドコンテキストの最適化: docker build 時のビルドコンテキストを意識し、.dockerignore 等で適切に制御することがパフォーマンス向上の鍵となる。
記事は以上です。
次は、Dockerfileのベストプラクティスについてまとめる予定です。
最後までお読みいただき、ありがとうございました。
参考情報一覧
この記事は以下の情報を参考にして執筆しました。
- [仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん]
- [さわって学ぶクラウドインフラ docker 基礎からのコンテナ構築]
- Dockerfileの書き方 (最終更新 2025-08-16) (参照 2026-01-08)
- 公式ドキュメント (参照 2026-01-08)