あっぽログ
← 記事一覧に戻る

RustのDockerコンテナ化入門:マルチステージビルドで軽量イメージを作る

はじめに

Rustで作ったアプリケーションを本番環境にデプロイするとき、Dockerは欠かせないツールです。しかし、何も考えずにDockerfileを書くと、ビルドツールチェーン一式を含んだ巨大なイメージができてしまいます。

この記事ではマルチステージビルドを使って、Rustアプリを小さく・安全にコンテナ化する方法を解説します。

単純なDockerfileの問題点

まず「よくある失敗例」を見てみましょう。

# NG例:ビルドツールがそのまま残る
FROM rust:1.78

WORKDIR /app
COPY . .
RUN cargo build --release

CMD ["./target/release/myapp"]

このイメージをビルドすると、サイズは1GB以上になることがあります。rust公式イメージにはコンパイラ・Cargo・各種ライブラリがすべて含まれているためです。本番環境で必要なのはビルド済みのバイナリだけなのに、不要なものを大量に抱えることになります。

マルチステージビルドとは

マルチステージビルドとは、1つのDockerfileに複数のFROM命令を書き、ステージをまたいでファイルをコピーできる仕組みです。

  • ビルドステージ:Rustのツールチェーンを使ってバイナリを生成する
  • 実行ステージ:バイナリだけをコピーして最小限のイメージを作る

この2段階構成により、最終イメージからビルドツールを完全に排除できます。

サンプルアプリの準備

まず、簡単なHello WorldのRustプロジェクトを用意します。

cargo new myapp
cd myapp

src/main.rs はデフォルトのままでOKです。

fn main() {
    println!("Hello, Docker!");
}

マルチステージビルドのDockerfileを書く

# ---- ビルドステージ ----
FROM rust:1.78-slim AS builder

WORKDIR /app

# 依存関係のキャッシュ(後述)
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs
RUN cargo build --release
RUN rm -f target/release/deps/myapp*

# 実際のソースコードをコピーしてビルド
COPY src ./src
RUN cargo build --release

# ---- 実行ステージ ----
FROM debian:bookworm-slim AS runtime

# 最低限の実行依存を入れる
RUN apt-get update && apt-get install -y \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# ビルドステージからバイナリだけコピー
COPY --from=builder /app/target/release/myapp ./myapp

# 非rootユーザーで実行(セキュリティ向上)
RUN useradd -m appuser
USER appuser

CMD ["./myapp"]

ポイント解説

依存関係のキャッシュ

COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs
RUN cargo build --release

ダミーのmain.rsを置いてから依存クレートだけを先にビルドします。Cargo.tomlが変わらない限り、このレイヤーはキャッシュされるため、ソースコードを変更しただけのリビルドが大幅に速くなります

実行ステージのベースイメージ

debian:bookworm-slimは最小限のDebianイメージです。Rustのバイナリはデフォルトでglibcにリンクされているため、glibc互換のイメージが必要です。ca-certificatesはHTTPS通信が必要なアプリで使います。

非rootユーザー

RUN useradd -m appuser
USER appuser

コンテナをrootで実行すると、コンテナエスケープ時のリスクが高まります。専用ユーザーを作って切り替えるのがベストプラクティスです。

ビルドと実行

# イメージをビルド
docker build -t myapp:latest .

# コンテナを実行
docker run --rm myapp:latest
# => Hello, Docker!

# イメージサイズを確認
docker images myapp

マルチステージビルドを使うと、最終イメージは100MB前後に収まります(単純なアプリなら80MB程度)。rust公式イメージの1/10以下です。

さらに小さくするには:scratch や musl を使う

もし外部ライブラリへの依存をなくせるなら、scratch(空のイメージ)をベースにできます。そのためには静的リンクバイナリが必要です。

# ---- ビルドステージ(musl static link)----
FROM rust:1.78-slim AS builder

RUN rustup target add x86_64-unknown-linux-musl
RUN apt-get update && apt-get install -y musl-tools

WORKDIR /app
COPY . .
RUN cargo build --release --target x86_64-unknown-linux-musl

# ---- 実行ステージ ----
FROM scratch

COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/myapp /myapp

CMD ["/myapp"]

muslでビルドしたバイナリはlibcを静的にリンクするため、scratchイメージで動きます。最終イメージはバイナリサイズのみ(数MB〜十数MB)となり、攻撃面も最小限になります。

まとめ

方法イメージサイズ手軽さ
シングルステージ(rust公式)1GB以上
マルチステージ + debian-slim〜100MB
マルチステージ + scratch (musl)数MB〜

Rustアプリのコンテナ化では、マルチステージビルドがほぼ必須のテクニックです。

  • 依存キャッシュでビルドを速く
  • 最小ベースイメージでイメージを軽く
  • 非rootユーザーでセキュアに

まずはdebian:bookworm-slimベースのマルチステージビルドから始めて、必要に応じてmusl + scratchに挑戦してみてください。

← 記事一覧に戻る