はじめに#
Dockerfileのベストプラクティスを理解しても、実際に自分の開発言語でどう書けばよいか迷うことがあります。Node.jsとGoではビルドプロセスが異なり、PythonとJavaでは依存関係の管理方法も違います。各言語の特性を無視してDockerfileを書くと、ビルド時間の増加やイメージサイズの肥大化を招きかねません。
本記事では、Node.js、Python、Go、Javaの4言語について、それぞれの特性を活かしたDockerfile作成例を解説します。この記事を読み終えると、以下のことができるようになります。
- 各言語に最適化されたDockerfileを作成できる
- 依存関係のキャッシュ戦略を言語ごとに適用できる
- 開発用と本番用でDockerfileを適切に分離できる
- マルチステージビルドを言語特性に合わせて実装できる
前提として、Dockerfileの基本構文とベストプラクティスを理解していることを想定しています。まだ基本を押さえていない場合は、Dockerfile入門 - 基本構文とビルドの流れとDockerfileベストプラクティスを参照してください。
言語別Dockerfileのポイント#
各言語でDockerfileを作成する際、共通して意識すべきポイントと、言語固有の考慮事項があります。
共通で意識すべき観点#
| 観点 |
説明 |
| ベースイメージの選定 |
公式イメージを使用し、用途に応じてslim/alpineを検討 |
| 依存関係のキャッシュ |
依存定義ファイルを先にコピーしてキャッシュを活用 |
| マルチステージビルド |
ビルド環境と実行環境を分離して軽量化 |
| non-rootユーザー |
セキュリティのため一般ユーザーで実行 |
| 開発/本番の分離 |
環境に応じた構成で効率と安全性を両立 |
言語ごとの特性比較#
| 言語 |
ビルド成果物 |
実行環境 |
キャッシュ対象 |
| Node.js |
トランスパイル後のJS |
Node.jsランタイム |
node_modules |
| Python |
.pyファイル |
Pythonインタプリタ |
site-packages |
| Go |
シングルバイナリ |
不要(scratch可) |
go mod cache |
| Java |
JARファイル |
JREのみ |
Maven/Gradleキャッシュ |
これらの特性を踏まえ、各言語のDockerfile実践例を見ていきましょう。
Node.jsアプリのDockerfile例#
Node.jsアプリケーションでは、node_modulesのキャッシュ活用と、開発依存関係の除外が軽量化のポイントとなります。
ベースイメージの選択#
Node.js公式イメージには複数のバリエーションがあります。
| イメージ |
サイズ |
用途 |
| node:22 |
約1.1GB |
フル機能が必要な場合 |
| node:22-slim |
約200MB |
一般的な本番環境向け |
| node:22-alpine |
約140MB |
最小構成を目指す場合 |
Alpineはmusl libcを使用しているため、一部のネイティブモジュールで互換性の問題が発生する可能性があります。問題が発生した場合はslimを検討してください。
本番用Dockerfile(Express/Fastify等)#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
# ビルドステージ
FROM node:22-alpine AS builder
WORKDIR /app
# 依存関係の定義ファイルを先にコピー(キャッシュ活用)
COPY package.json package-lock.json ./
RUN npm ci
# ソースコードをコピーしてビルド
COPY . .
RUN npm run build
# 本番ステージ
FROM node:22-alpine AS production
ENV NODE_ENV=production
WORKDIR /app
# 本番用依存関係のみインストール
COPY package.json package-lock.json ./
RUN npm ci --only=production && npm cache clean --force
# ビルド成果物をコピー
COPY --from=builder /app/dist ./dist
# non-rootユーザーで実行
USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]
|
キャッシュ戦略のポイント#
Node.jsではpackage.jsonとpackage-lock.jsonを先にコピーすることで、依存関係に変更がない限りnpm ciのキャッシュが有効になります。
ファイル変更時のビルド挙動
package.jsonが変更された場合:
npm ci --> 再実行(キャッシュ無効)
COPY . --> 再実行
npm run build --> 再実行
ソースコードのみ変更された場合:
npm ci --> キャッシュ利用(高速)
COPY . --> 再実行
npm run build --> 再実行
開発用Dockerfile#
開発時はホットリロードやデバッグ機能が必要です。開発用の構成を追加しましょう。
1
2
3
4
5
6
7
8
9
10
11
12
|
# 開発ステージ
FROM node:22-alpine AS development
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
# 開発サーバー起動(ホットリロード有効)
CMD ["npm", "run", "dev"]
|
開発時はDocker Composeと組み合わせ、ソースコードをバインドマウントすることで、コンテナ再ビルドなしにコード変更を反映できます。
1
2
3
4
5
6
7
8
9
10
11
|
# compose.yaml(開発用)
services:
app:
build:
context: .
target: development
volumes:
- .:/app
- /app/node_modules
ports:
- "3000:3000"
|
PythonアプリのDockerfile例#
Pythonアプリケーションでは、仮想環境の取り扱いとpipキャッシュの活用が重要です。
ベースイメージの選択#
| イメージ |
サイズ |
用途 |
| python:3.13 |
約1GB |
フル機能が必要な場合 |
| python:3.13-slim |
約150MB |
一般的な本番環境向け |
| python:3.13-alpine |
約50MB |
最小構成(C拡張に注意) |
Alpine版はコンパイルが必要なパッケージ(NumPy、Pandas等)でビルド時間が大幅に増加する場合があります。データサイエンス系のライブラリを使用する場合はslimを推奨します。
本番用Dockerfile(FastAPI/Flask等)#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
# ビルドステージ
FROM python:3.13-slim AS builder
WORKDIR /app
# 仮想環境を作成
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# 依存関係をインストール
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 本番ステージ
FROM python:3.13-slim AS production
WORKDIR /app
# 仮想環境をコピー
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# アプリケーションコードをコピー
COPY . .
# non-rootユーザーを作成して実行
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
Poetryを使用する場合#
依存関係管理にPoetryを使用している場合のDockerfileパターンです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
FROM python:3.13-slim AS builder
WORKDIR /app
# Poetryをインストール
RUN pip install poetry==2.1.2
# 依存関係の定義ファイルをコピー
COPY pyproject.toml poetry.lock ./
# 仮想環境をプロジェクト内に作成し、依存関係をインストール
RUN poetry config virtualenvs.in-project true && \
poetry install --only=main --no-interaction --no-ansi
FROM python:3.13-slim AS production
WORKDIR /app
# 仮想環境をコピー
COPY --from=builder /app/.venv /app/.venv
ENV PATH="/app/.venv/bin:$PATH"
COPY . .
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
開発用Dockerfile#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
FROM python:3.13-slim AS development
WORKDIR /app
# 開発ツールをインストール
RUN pip install poetry==2.1.2
COPY pyproject.toml poetry.lock ./
RUN poetry config virtualenvs.in-project true && \
poetry install --no-interaction --no-ansi
COPY . .
# 開発サーバー起動(ホットリロード有効)
CMD ["poetry", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
GoアプリのDockerfile例#
Go言語はシングルバイナリにコンパイルされるため、マルチステージビルドによる軽量化効果が最も大きい言語です。最終イメージをscratchやdistrolessにすることで、数MB程度のイメージを作成できます。
ベースイメージの選択#
| ビルド用 |
実行用 |
最終サイズ |
| golang:1.24 |
scratch |
約5-15MB |
| golang:1.24 |
gcr.io/distroless/static |
約7-20MB |
| golang:1.24-alpine |
alpine |
約10-25MB |
scratchは完全に空のイメージで最も軽量ですが、シェルやデバッグツールが含まれません。トラブルシューティングが必要な場合はdistrolessを検討してください。
本番用Dockerfile#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
# ビルドステージ
FROM golang:1.24 AS builder
WORKDIR /app
# 依存関係の定義ファイルを先にコピー(キャッシュ活用)
COPY go.mod go.sum ./
RUN go mod download
# ソースコードをコピーしてビルド
COPY . .
# 静的リンクでビルド(scratchで動作させるため)
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-w -s" -o /app/main .
# 本番ステージ(scratchを使用)
FROM scratch AS production
# SSL証明書をコピー(HTTPS通信が必要な場合)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# バイナリをコピー
COPY --from=builder /app/main /main
# non-rootユーザーで実行(数値指定)
USER 65534:65534
EXPOSE 8080
ENTRYPOINT ["/main"]
|
ビルドオプションの解説#
| オプション |
説明 |
| CGO_ENABLED=0 |
Cライブラリへの動的リンクを無効化 |
| GOOS=linux |
Linux向けにクロスコンパイル |
| GOARCH=amd64 |
amd64アーキテクチャ向けにビルド |
| -ldflags="-w -s" |
デバッグ情報を削除してバイナリサイズを削減 |
distrolessを使用する場合#
scratchではシェルアクセスやデバッグが困難です。本番環境でのトラブルシューティングを考慮する場合は、Googleが提供するdistrolessイメージを使用できます。
1
2
3
4
5
6
7
8
|
FROM gcr.io/distroless/static:nonroot AS production
COPY --from=builder /app/main /main
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/main"]
|
distrolessは最小限のランタイムのみを含み、シェルやパッケージマネージャが存在しないため、セキュリティ面でも優れています。
開発用Dockerfile#
Go開発ではAirなどのホットリロードツールを使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
FROM golang:1.24 AS development
WORKDIR /app
# Airをインストール(ホットリロード用)
RUN go install github.com/air-verse/air@latest
COPY go.mod go.sum ./
RUN go mod download
COPY . .
CMD ["air", "-c", ".air.toml"]
|
JavaアプリのDockerfile例#
Javaアプリケーションでは、JDKとJREの分離、およびビルドツール(Maven/Gradle)のキャッシュ活用が重要です。
ベースイメージの選択#
| 用途 |
イメージ |
サイズ |
| ビルド用 |
eclipse-temurin:21-jdk |
約460MB |
| 実行用 |
eclipse-temurin:21-jre |
約270MB |
| 軽量実行用 |
eclipse-temurin:21-jre-alpine |
約180MB |
| 最軽量 |
gcr.io/distroless/java21 |
約220MB |
Eclipse Temurinは、Eclipse Foundationが提供するOpenJDKディストリビューションで、本番環境での使用が推奨されています。
本番用Dockerfile(Maven)#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
# ビルドステージ
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /app
# Mavenをインストール
RUN apt-get update && apt-get install -y maven && rm -rf /var/lib/apt/lists/*
# 依存関係の定義ファイルを先にコピー(キャッシュ活用)
COPY pom.xml .
RUN mvn dependency:go-offline -B
# ソースコードをコピーしてビルド
COPY src ./src
RUN mvn package -DskipTests -B
# 本番ステージ
FROM eclipse-temurin:21-jre AS production
WORKDIR /app
# JARファイルをコピー
COPY --from=builder /app/target/*.jar app.jar
# non-rootユーザーを作成して実行
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
|
Gradleを使用する場合#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
# ビルドステージ
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /app
# Gradle Wrapperと設定ファイルをコピー
COPY gradlew .
COPY gradle gradle
COPY build.gradle.kts settings.gradle.kts ./
# 依存関係をダウンロード(キャッシュ活用)
RUN chmod +x gradlew && ./gradlew dependencies --no-daemon
# ソースコードをコピーしてビルド
COPY src ./src
RUN ./gradlew build -x test --no-daemon
# 本番ステージ
FROM eclipse-temurin:21-jre AS production
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
|
Spring Boot最適化(Layered JAR)#
Spring Boot 2.3以降では、Layered JARを使用してDockerレイヤーキャッシュを最適化できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
# ビルドステージ
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /app
COPY gradlew .
COPY gradle gradle
COPY build.gradle.kts settings.gradle.kts ./
RUN chmod +x gradlew && ./gradlew dependencies --no-daemon
COPY src ./src
RUN ./gradlew build -x test --no-daemon
# レイヤー抽出
RUN java -Djarmode=layertools -jar build/libs/*.jar extract
# 本番ステージ
FROM eclipse-temurin:21-jre AS production
WORKDIR /app
# レイヤーごとにコピー(キャッシュ効率向上)
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
|
Layered JARでは、変更頻度の低い依存関係レイヤーをキャッシュできるため、アプリケーションコードのみ変更した場合のビルドが高速化されます。
JVMメモリ設定#
コンテナ環境では、JVMのメモリ設定を適切に行う必要があります。
1
2
3
4
|
ENTRYPOINT ["java", \
"-XX:+UseContainerSupport", \
"-XX:MaxRAMPercentage=75.0", \
"-jar", "app.jar"]
|
-XX:+UseContainerSupportはJava 10以降でデフォルト有効ですが、明示的に指定することで意図を明確にできます。
依存関係キャッシュ戦略と開発/本番分離#
各言語のDockerfile例を見てきましたが、ここで共通する重要な戦略を整理します。
依存関係キャッシュの共通パターン#
すべての言語で、依存関係の定義ファイルを先にコピーし、依存関係のインストールを行ってからソースコードをコピーするパターンが有効です。
キャッシュ効率の高いDockerfile構造
1. ベースイメージ指定
2. 作業ディレクトリ設定
3. 依存関係定義ファイルのみコピー <-- 変更頻度:低
4. 依存関係インストール <-- キャッシュ対象
5. ソースコードコピー <-- 変更頻度:高
6. ビルド実行
7. 実行設定
言語別キャッシュ対象ファイル#
| 言語 |
定義ファイル |
キャッシュ対象 |
| Node.js |
package.json, package-lock.json |
node_modules |
| Python |
requirements.txt, pyproject.toml |
site-packages, .venv |
| Go |
go.mod, go.sum |
$GOPATH/pkg/mod |
| Java |
pom.xml, build.gradle.kts |
~/.m2, ~/.gradle |
開発用と本番用の分離戦略#
開発環境と本番環境では要件が異なります。以下の表に示す観点で構成を分離しましょう。
| 観点 |
開発用 |
本番用 |
| 依存関係 |
devDependencies含む |
productionのみ |
| ホットリロード |
有効 |
不要 |
| デバッグツール |
含む |
除外 |
| ソースマップ |
生成 |
除外または最小化 |
| イメージサイズ |
大きくても可 |
最小化 |
| ビルド速度 |
重視 |
成果物の品質を重視 |
マルチターゲットビルドの活用#
単一のDockerfileで開発用と本番用の両方をサポートするには、ステージを分離し--targetオプションでビルド対象を切り替えます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
# 共通ベース
FROM node:22-alpine AS base
WORKDIR /app
COPY package*.json ./
# 開発ステージ
FROM base AS development
RUN npm ci
COPY . .
CMD ["npm", "run", "dev"]
# ビルドステージ
FROM base AS builder
RUN npm ci
COPY . .
RUN npm run build
# 本番ステージ
FROM node:22-alpine AS production
ENV NODE_ENV=production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --only=production
USER node
CMD ["node", "dist/index.js"]
|
ビルドコマンドは以下のように使い分けます。
1
2
3
4
5
|
# 開発用
docker build --target development -t myapp:dev .
# 本番用
docker build --target production -t myapp:prod .
|
まとめ#
本記事では、Node.js、Python、Go、Javaの4言語について、言語特性を活かしたDockerfile作成例を解説しました。
各言語のポイント#
| 言語 |
重要ポイント |
| Node.js |
package.jsonを先にコピー、本番では–only=production |
| Python |
仮想環境を活用、slimイメージでC拡張の問題を回避 |
| Go |
CGO_ENABLED=0で静的リンク、scratch/distrolessで最軽量化 |
| Java |
JDK/JRE分離、Layered JARでキャッシュ効率向上 |
共通するベストプラクティス#
- 依存関係定義ファイルを先にコピーしてキャッシュを活用する
- マルチステージビルドでビルド環境と実行環境を分離する
- 本番イメージはslim/alpine/distrolessで軽量化する
- non-rootユーザーでアプリケーションを実行する
--targetオプションで開発/本番を切り替え可能にする
これらのパターンを自分のプロジェクトに適用することで、効率的で安全なDockerイメージを作成できます。次のステップとして、Docker Composeを使った複数コンテナの管理を学ぶと、より実践的な開発環境を構築できるようになります。
参考リンク#