Claude Codeで古いプロジェクトをビルドする — 当時のJDKをDockerで再現
いまのJDKでは通らない10年前のAntやMaven 2のJavaプロジェクトを、当時のJDKを入れたdevcontainerで動かします。失敗ログを読ませて直す往復と、ホストを守る隔離の切り方を設定例つきで示します。
古いJavaプロジェクトを引き継いだとき、最初の壁は「そもそもビルドが通らない」ことです。いまのJDKとMavenに合わせて直し始める前に、当時の環境をコンテナで作り、まず元のまま動かします。Claude Codeはそのコンテナの中で、ビルドの失敗ログを読み、Dockerfileやビルドファイルを直す役を担います。
この記事は「まず動かす」ところまでが範囲です。動いたあとの移行の進め方はコードモダナイゼーションの記事にあります。
先に環境を固定すると、直す対象が減る
10年前のプロジェクトが落ちる原因は、コードの不具合とは限りません。JDKのバージョン、ビルドツールのバージョン、依存ライブラリの取得先という3つの環境差だけで、ほとんどのビルドは落ちます。
この3つを当時のままに固定してから失敗を見ると、残った失敗はプロジェクト自身の問題だと分かります。逆に、最新のJDKで最初から直し始めると、環境差とコードの不具合が混ざり、Claude Codeが直すべき範囲を見失います。
最初に固定する3つ
JDKのバージョン
pomの
source/target、READMEの記載、CIの設定から当時の値を探します。見つからなければ、手元でビルドが通る最古の候補から試します。ビルドツールのバージョン
AntかMavenか、Mavenなら2系か3系かを決めます。
build.xmlとpom.xmlの書き方に差が出ます。依存の取得先
Maven Centralを指す古い設定は、そのままでは取得に失敗します。この点は後の節で、実際に起きる挙動と合わせて扱います。
当時のツールをどこから入れるか
JDKとMaven 3系は、Docker Hubの公式イメージで揃います。Maven 2とAntは、Apacheのアーカイブから配布物を取って入れます。
| 必要なもの | 入手元 | タグ・ファイル名 |
|---|---|---|
| JDK 8 | 入手元eclipse-temurin イメージ | タグ・ファイル名8-jdk-jammy、8-jdk-noble などのタグ |
| JDK 7とMaven 3.2.5 | 入手元maven イメージ | タグ・ファイル名3.2.5-jdk-7 のタグ |
| Maven 2.2.1 | 入手元Apacheのアーカイブ | タグ・ファイル名maven/maven-2/2.2.1/binaries/ の apache-maven-2.2.1-bin.tar.bz2 |
| Ant 1.8系・1.9系 | 入手元Apacheのアーカイブ | タグ・ファイル名ant/binaries/ の apache-ant-1.9.12-bin.tar.gz など |
maven イメージには 3.2.5-jdk-7 のような旧い組み合わせのタグが残っています。Maven 3.2系とJDK 7で足りるプロジェクトなら、Dockerfileを書かずにこのタグを image に指定するだけで済みます。
JDK 6が要る案件でも、Docker Hubの maven イメージには 3.2.5-jdk-6 や 3-jdk-6 といったタグが残っています。
devcontainerを組む
Claude Codeをコンテナに入れるには、公式のDev Container Featureを使います。.devcontainer/ に次の2ファイルを置きます。JDK 8とMaven 2.2.1とAntを同居させる例です。
# .devcontainer/Dockerfile
FROM eclipse-temurin:8-jdk-jammy
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl ca-certificates bzip2 git \
&& rm -rf /var/lib/apt/lists/*
# Maven 2.2.1(Apacheのアーカイブから)
RUN curl -fsSL \
https://archive.apache.org/dist/maven/maven-2/2.2.1/binaries/apache-maven-2.2.1-bin.tar.bz2 \
| tar xj -C /opt \
&& ln -s /opt/apache-maven-2.2.1/bin/mvn /usr/local/bin/mvn
# Ant 1.9.12(Apacheのアーカイブから)
RUN curl -fsSL \
https://archive.apache.org/dist/ant/binaries/apache-ant-1.9.12-bin.tar.gz \
| tar xz -C /opt \
&& ln -s /opt/apache-ant-1.9.12/bin/ant /usr/local/bin/ant
# Claude Codeは root では --dangerously-skip-permissions を拒否する
RUN useradd -m -s /bin/bash dev{
"build": { "dockerfile": "Dockerfile" },
"remoteUser": "dev",
"features": {
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}
},
"mounts": [
"source=claude-code-config-${devcontainerId},target=/home/dev/.claude,type=volume"
],
"containerEnv": {
"CLAUDE_CONFIG_DIR": "/home/dev/.claude",
"DISABLE_AUTOUPDATER": "1"
}
}設定の意図は次のとおりです。
remoteUserを非rootにします。公式ドキュメントによると、rootで起動した場合、--dangerously-skip-permissionsはCLIに拒否されますmountsとCLAUDE_CONFIG_DIRの組み合わせで、再構築しても認証と設定が消えません。${devcontainerId}を名前に入れると、プロジェクトごとに状態が分かれますDISABLE_AUTOUPDATERを1にすると、コンテナ内でClaude Code自身の自動更新が止まります。古い環境の再現では、道具の側が途中で変わらないほうが比較しやすくなります
Featureのバージョン指定 :1.0 はインストールスクリプトのバージョンで、Claude Code本体の版ではありません。本体を固定したいときは、Featureを使わずDockerfileで npm install -g @anthropic-ai/claude-code@X.Y.Z と書く方法が、公式に案内されています。
ベースイメージにNode.jsが無くても、Featureが自分で入れます。それでも Failed to install Node.js and npm で止まったら、features の先頭に ghcr.io/devcontainers/features/node:1 を足して再構築します。
ビルドを回す: 失敗、ログ、直す、の繰り返し
コンテナの中で claude を起動したら、最初のビルドを実行させます。ここでは、直す前に現状の失敗をそのまま記録させることが重要です。
claude -p "ant clean jar を実行して。失敗したら直さず、
エラーの先頭行と原因の候補を3行で報告して"失敗の理由が分かってから、1回に1つだけ直させます。一度に複数を直すと、どの変更がビルドを通したのか後から追えません。この運用は、プロジェクトの CLAUDE.md に書いておくと毎回言い直さずに済みます。
## ビルド復旧の作業ルール
- ビルド環境は .devcontainer/ のJDK・Maven・Antで固定済み。コンテナ内でだけ実行する
- 1回の修正で変えるのは1か所。変更後は同じコマンドを再実行し、結果を報告する
- ソースコードは直さない。直すのは Dockerfile、settings.xml、build.xml、pom.xml の順
- 直した内容は理由つきで RECOVERY.md に1行ずつ追記する「ソースコードは直さない」という縛りが効きます。環境側で解決できるものを先に潰さないと、コードを書き換えるほうへ流れやすいためです。
依存の取得先が http のままだと落ちる
古いMaven 2のプロジェクトで、最初に当たる壁の一つがMaven Centralの取得です。Centralは、http:// のアクセスを https:// のページへ転送します。手元から確認できる形は次のとおりです。
curl -s -i http://repo1.maven.org/maven2/junit/junit/maven-metadata.xml | head -3返ってくるのは 301 Moved Permanently で、Location は https://repo1.maven.org/501-https-required.html です。メタデータのXMLは返りません。http で取りに行く古いビルドは、依存を解決できずに止まります。
Claude Codeには、このときのログ全体を読ませたうえで、~/.m2/settings.xml にミラーを足す案を出させます。
<settings>
<mirrors>
<mirror>
<id>central-https</id>
<mirrorOf>central</mirrorOf>
<url>https://repo1.maven.org/maven2</url>
</mirror>
</mirrors>
</settings>これで通るかは、JDK側のTLS設定にも左右されます。次の失敗が証明書やプロトコルのエラーなら、そのログをそのまま読ませて、JDKを変えるか、設定を足すかを決めます。ここでも直すのは1か所ずつです。
Mavenのビルドを回す際の出力の絞り方は、MavenビルドをClaude Codeで回す記事で扱っています。-ntp(--no-transfer-progress)はMaven 3.6.1のリリースノートに載っているオプションで、Maven 2や3.6.0以前では使えないため、その場合は -B だけで回します。
Antの失敗は、ログの先頭ではなく「どのタスクで落ちたか」を読む
Antのログは、[javac] や [junit] のようにタスク名が行頭に付きます。Claude Codeには、失敗した行ではなく、直前に成功していたタスクの名前から原因を絞らせます。
たとえば javac のタスクで、source と target の値が現行のJDKで受け付けられないことがあります。この場合は build.xml の該当属性を直すよりも先に、コンテナのJDKを当時の値に近づけられないかを検討します。環境で解決できる問題を、ビルドファイルの書き換えで解決しないための順番です。
ホストに触れさせない隔離の切り方
古いプロジェクトは、出所や依存の素性が分かりにくいものです。ビルドスクリプトが外部のファイルを取得したり、ホストのファイルを読んだりしても被害が出ないように、隔離を意識して組みます。
公式ドキュメントは、devcontainerの保護は強いものの完全ではないと警告しています。--dangerously-skip-permissions で動かした場合、悪意あるプロジェクトはコンテナ内で到達できるものを外へ持ち出せます。そこには ~/.claude に保存された認証情報も含まれます。信頼できるリポジトリで使うことが前提です。
実際に守る範囲は、次のように決めます。
隔離で決める4点
マウントしない
ホストの
~/.sshやクラウドの認証情報ファイルは、コンテナにマウントしません。リポジトリ専用の短命なトークンを使います。書き込みは作業ツリーだけ
バインドマウントされたワークスペースは、そのままホストのリポジトリに反映されます。親ディレクトリ全体を渡さず、対象プロジェクトの1つだけをマウントします。
外向き通信を絞る
古いビルドが任意のURLから何かを取得しないよう、通信先を限定します。参照構成のファイアウォール
init-firewall.shが使えます。権限確認を外すなら通信も絞る
--dangerously-skip-permissionsは、通信の制限と組み合わせて使います。通信制限が無いまま外すと、到達できる範囲が広くなります。
通信の絞り方は2通りあります。コンテナ側のファイアウォールはinit-firewall.shの解説にあり、NET_ADMIN と NET_RAW の権限が必要です。もう一つは、Claude Codeのサンドボックスを重ねる方法です。許可ドメインは空から始まるので、ビルドに要る取得先だけを足します。
{
"sandbox": {
"enabled": true,
"network": {
"allowedDomains": ["repo1.maven.org", "archive.apache.org"]
}
}
}これは .claude/settings.json に置きます。サンドボックスが包むのはBashツールが実行するコマンドで、ReadやEditなどの組み込みツールは権限ルールに従います。権限を絞ったコンテナでサンドボックスが起動しないときは、enableWeakerNestedSandboxの記事の手順で切り分けます。
隔離の方式を選び直したいときは、公式の「Choose a sandbox environment」が、devcontainer、自前のコンテナ、仮想マシンの違いを並べています。ビルドスクリプトの中身を全く信用できない案件では、カーネルまで分ける仮想マシンのほうが向きます。
脆弱性診断の再現をdevcontainerで隔離する記事は、同じ隔離の考え方を別の作業に当てたものです。
動いたあとに残すもの
ビルドが通ったら、コンテナの定義そのものが成果物になります。残す対象は次の3つです。
.devcontainer/Dockerfileとdevcontainer.json。次に開く人は、再構築するだけで同じ環境に入れますRECOVERY.md。1か所ずつ直した内容と理由の記録です。後で「なぜこのミラーが要るのか」を説明できます- 通ったビルドコマンド。
ant clean jarやmvn -B packageを、READMEの先頭に書いておきます
この3つがあれば、次の作業は「当時の環境で動く状態」を起点にできます。移行を進める段階では、サポート切れの依存や実行環境の期限を洗うことになります。その突き合わせはendoflife.dateでの棚卸しの記事が扱っています。
つまずきやすい点
- 再構築のたびにログインを求められる:
containerEnvにCLAUDE_CONFIG_DIRが無いと、~/.claude.jsonがボリュームの外に書かれ、再構築で消えます - ブラウザの認証が戻ってこない: ポート転送がコールバックを中継しないことがあります。ブラウザに出たコードを、ターミナルの
Paste code here if promptedに貼ります - rootで
--dangerously-skip-permissionsを付けて拒否される:remoteUserが非rootか確認します。Dockerfileのuseraddだけでは足りず、devcontainer.jsonの指定が要ります
コンテナ内のビルド成果物はホストに残るか
バインドマウントしたワークスペースの変更は、ホストのリポジトリに現れます。target/ や build/ もそこに出力されます。ホストのIDEで同じ場所を開くと、ホストのJDKで作ったクラスファイルと混ざるので、成果物の置き場は .gitignore に入れたうえで、コンテナ内だけでビルドします。
JDK 6が要るときはどうするか
JDK 6限定の案件は、上で挙げた 3.2.5-jdk-6 のタグから始めます。そのJDKで動く必要が本当にあるのかは、source / target の指定から見直せます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。