はじめに

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.jsonpackage-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言語はシングルバイナリにコンパイルされるため、マルチステージビルドによる軽量化効果が最も大きい言語です。最終イメージをscratchdistrolessにすることで、数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を使った複数コンテナの管理を学ぶと、より実践的な開発環境を構築できるようになります。

参考リンク