🐳

RustのDockerビルド時の依存関係のキャッシュで詰まった話

2024/12/01に公開

最初に結論

Hello,World!プログラムが実行されてしまうのは、RUN cargo buildのまえにRUN touch src/main.rsを挟むことで解決できる

はじめに

RustのプログラムをDockerで動かすときに、素朴にDockerfileを書くと、依存関係のビルドにとてつもなく時間がかかってしまうので、Cargo.lockCargo.tomlだけをコピーしてからビルドして、その後にソースコードをコピーしてビルドすることによって依存関係が変わっていない場合にDockerのキャッシュを効かせるテクニックがあります。

参考: https://zenn.dev/ucwork/articles/acec204571362b の修正ポイント1

しかし、その方法を試したものの二度目のcargo buildが(cargoによって?)キャッシュされてしまい本当にビルドしたいコードのビルドがされないという問題が発生したのでその解決策について書きます。

問題が発生するDockerfile

cargo new --bin dbwriterとして、dbwriterディレクトリにDockerfileなどもあり、コンテナ化したいソースコードもある状況を想定しています。

# Use the official Rust image as the build stage
FROM rust:1.82.0 AS builder
# Set the working directory inside the builder stage
WORKDIR /app

## Dependencies build part
RUN cargo new dbwriter --bin
WORKDIR /app/dbwriter

# 先に依存関係(を記述するファイル)をコピー
COPY Cargo.toml Cargo.lock ./
# src/main.rsはHello, Worldのままだがビルド
RUN cargo build --release

# 本当にビルドしたいソースコードをコピーする
COPY ./src ./src
# 本当にビルドしたいソースコードをビルドする(ここがキャッシュされてしまい、Hello Worldのままになってしまうのが問題)
RUN cargo build --release

## Build stage
# Use a minimal base image for the runtime stage
FROM debian:stable-slim AS deployment

# For OpenSSL
RUN apt-get update -y
RUN apt-get install -y libssl-dev ca-certificates

# Set the working directory inside the runtime stage
WORKDIR /app
# Copy the built binary from the builder stage to the runtime stage
COPY --from=builder /app/dbwriter/target/release/dbwriter  /app/

# Set the startup command to run the application
CMD ["./dbwriter"]

実際に上のDockerfileをビルドして実行すると、Hello, Worldプログラムが実行されるのみで、本当に実行したいプログラムは実行されません。

解決策

この問題は、二回目のRUN cargo build --releaseが(cargoによって?)キャッシュされてしまうことが原因のようで、

二回目のRUN cargo buildの前にRUN touch src/main.rsをしてソースファイルのタイムスタンプを更新することで、きちんとビルドしたいプログラムのビルドがされるようになります

以下のようなDockerfileを使うことで、解決できます

# Use the official Rust image as the build stage
FROM rust:1.82.0 AS builder
# Set the working directory inside the builder stage
WORKDIR /app

## Dependencies build part
RUN cargo new dbwriter --bin
WORKDIR /app/dbwriter

# 先に依存関係(を記述するファイル)をコピー
COPY Cargo.toml Cargo.lock ./
# src/main.rsはHello, Worldのままだがビルド
RUN cargo build --release

# 本当にビルドしたいソースコードをコピーする
COPY ./src ./src
# ソースコードのタイムスタンプを更新する
RUN touch src/main.rs
# 本当にビルドしたいソースコードをビルドする(ビルドしたいプログラムがきちんとビルドされる)
RUN cargo build --release

## Build stage
# Use a minimal base image for the runtime stage
FROM debian:stable-slim AS deployment

# For OpenSSL
RUN apt-get update -y
RUN apt-get install -y libssl-dev ca-certificates

# Set the working directory inside the runtime stage
WORKDIR /app
# Copy the built binary from the builder stage to the runtime stage
COPY --from=builder /app/dbwriter/target/release/dbwriter  /app/

# Set the startup command to run the application
CMD ["./dbwriter"]

参考: https://www.reddit.com/r/rust/comments/126xeyx/comment/jebodib/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button

さいごに

この記事では、Dockerを使用したRustのプログラムのビルドが長くなってしまう問題と、その対処法である依存関係を先にビルドする方法と、その方法がうまく働かない理由とその対処法を紹介しました。

最後まで読んでいただきありがとうございます! RustとDockerの組み合わせで同じ問題に悩んでいた方は、ぜひ「いいね♥️」で教えてください!

間違いの指摘や感想コメントなど、どんなコメントもお待ちしています!気軽にコメントしてくださいね💬

Discussion