🦀

docker compose watchとrustとの相性を確認してみる

2023/10/20に公開

はじめに

普段、Rustで開発する時にcargo watchを使ってホットリロードしながら開発をしております。
先日、docker compose watchの記事を見かけて、cargo watchのようにできるなら面白そうと思い、この記事を書きました。

合わせて、少し前にdocker initにrustがサポートされていたなので、こちらも気になっていたので、合わせて試してみます。

この記事ではよく使っているActix-web + askama + htmx を使った簡単なサンプルの作成になります。(htmxは完全におまけ)

リポジトリはこちらです

色々、蛇足が多いので、結論から見たい場合は「docker compose watchの設定」から見ていただければと思います。

環境

docker-desktop 4.24.2
rust 1.73.0

内容

rustプロジェクト作成

cargo init

dockerファイル作成

docker init

以下の選択をしました。

? What application platform does your project use? `Rust`

? What version of Rust do you want to use? `1.73.0`

? What port does your server listen on? `8080`

このコマンドで以下のファイルが作成されました。

  • .dockerignore
  • Dockerfile
  • compose.yaml

編集箇所はこちらです。

(最近はdocker-compose.yamlではなく、compose.yamlになっているんですね。)

作成されたファイルの中身をみてみます。

.dockerignore

コンテナにコピーされたくないファイルやディレクトリが自動で貼り付けられてます。
自動で便利です。

Dockerfile

以下の内容が生成されてました。コメントは略してます。

Dockerfile
# -----------------------------------------------
ARG RUST_VERSION=1.73.0
ARG APP_NAME=docker_watch_demo_with_rust
FROM rust:${RUST_VERSION}-slim-bullseye AS build
ARG APP_NAME
WORKDIR /app

RUN --mount=type=bind,source=src,target=src \
    --mount=type=bind,source=Cargo.toml,target=Cargo.toml \
    --mount=type=bind,source=Cargo.lock,target=Cargo.lock \
    --mount=type=cache,target=/app/target/ \
    --mount=type=cache,target=/usr/local/cargo/registry/ \
    <<EOF
set -e
cargo build --locked --release
cp ./target/release/$APP_NAME /bin/server
EOF

# -----------------------------------------------
FROM debian:bullseye-slim AS final

ARG UID=10001
RUN adduser \
    --disabled-password \
    --gecos "" \
    --home "/nonexistent" \
    --shell "/sbin/nologin" \
    --no-create-home \
    --uid "${UID}" \
    appuser
USER appuser

COPY --from=build /bin/server /bin/

EXPOSE 8080

CMD ["/bin/server"]

自分で書かずともマルチステージビルドの記載になっているのはかなり便利ですね。
ビルドの部分では、--mount=type=cacheなどを使って高速化を図っていますね。

imageを作成する部分で、adduser作成にセキュリティ向上のテクニックがあるので、少しみてみます。

  1. --disabled-password
    このオプションは、新しいユーザーのパスワードを無効にするので、誰もこのユーザーでログインできなくなリます。

  2. --gecos ""
    ユーザー名や電話番号などの情報を格納するフィールドのようで、このオプションで空文字列を指定して、不要な情報を残していないです。

  3. --home "/nonexistent"
    新しいユーザーのホームディレクトリを作成しないという意味で、万が一、不正にログインされても、ホームディレクトリにアクセスできなくなります。

  4. --shell "/sbin/nologin"
    シェルを起動できなくなるため、実質ログインできなくなります。

  5. --no-create-home
    このオプションを使用すると、ホームディレクトリが作成されなくなります。これでホームディレクトリが存在しないユーザを作れます。

  6. --uid "${UID}"
    初回UID1000以外を設定します。

かなり強固な設定に見えますね。流石と思います。

compose.yaml

compose.yaml
services:
  server:
    build:
      context: .
      target: final
    ports:
      - 8080:8080

シンプルな設定ですね。特に補足はなさそうです。

actix-webのサンプル作成

編集箇所はこちらです。

シンプルにしたいのと、actix-webに興味持って欲しいので、actix-webの公式ドキュメントの最初のサンプルを使いました。

このサンプルでcargo runで実行することでapiサーバが起動します。

curl -i localhost:8080
curl -i localhost:8080/hey

などをすると動作確認できます。
シンプルで簡単に実装できるので、actix-webに興味を持ってもらえれば幸いです。

docker向けにアドレスを修正

src/main.rs
-    .bind(("127.0.0.1", 8080))?
+    .bind(("0.0.0.0", 8080))?

テンプレートエンジンを導入

askamaというjinja2ライクなテンプレートエンジンがあり、それを入れました。
jinja経験者であれば、すんなり理解できると思います。
お気に入りポイントとしては、構造体とhtmlファイルの紐付けるような書き方であるところです。
比較的プロジェクトが大きくなったとしても、index.htmlとファイル名検索することですぐに構造体が見つかるのが非常に便利と思っています。

編集箇所はこちらです。

試しに、この時点でcargo runで実行し、ブラウザでlocalhost:8080を開いてみます。

actix-webとaskamaの組み合わせを見てみてもらえればと思います。
おまけとして、webページにhtmxを使ったボタンを配置しています。
js書かずにajax通信できるので便利です!

docker compose watchの設定

お待たせしました。ここでやっと本題になります。
ここまで蛇足に突き合わせてしまいましたが、設定はかなりシンプルにでした。

まず、テンプレートエンジン用にhtmlファイルのディレクトリを追加したのでDockerfileを修正します。

Dockerfile
RUN --mount=type=bind,source=src,target=src \
+    --mount=type=bind,source=templates,target=templates \
    --mount=type=bind,source=Cargo.toml,target=Cargo.toml \

次にメインですが、6行を追加だけになります。

compose.yaml
services:
  server:
    build:
      context: .
      target: final
    ports:
      - 8080:8080
+   develop:
+     watch:
+       - action: rebuild
+         path: ./src
+       - action: rebuild
+         path: ./templates

内容はシンプルで、pathで指定した./srcまたは./templatesディレクトリ配下のファイルの変更があると、actionが行われます。
actionのrebuildは字の如く、ビルドしなおしてコンテナに配置します。

actionにはrebuildの他にsyncがあります。
syncは

    - action: sync
        path: ./web
        target: /src/web

のように書きますが、指定したディレクトリのファイルが変更されると、コンテナのtargetにファイルを配置するというものです。
reactなどのフロントエンド開発であれば、かなり便利かと思いますが、
今回のrustではテンプレートエンジンであるため、ビルド時にindex.htmlごとのバイナリに含まれるような形になります。
よって、今回の構成ではsyncが使えないことになります。

ではこの状態で以下のコマンド実行します

docker compose watch

そして、ブラウザでlocalhost:8080にアクセスしてみます。

動作確認

watchが起動している最中にファイルを変更してみてください。
rebuildが走り、ブラウザの表示やrust側の変更が適用されます。

まとめ

いかがだったでしょうか。
rust開発でdocker compose watchを使う場合は、基本rebuildにすれば良さそうです。
cargo watchでいいじゃんと思われますが、コンテナ上でcargo watchしているようなものですので、挙動は似ていますが、環境差分はなくなるといったメリットがあるかと思います。

また今回はrustのみの開発を想定していますが、
DBサーバを追加したりすると話が変わってくるので、docker compose watchの恩恵を受けれるかと思います。

docker compose watchの可能性を知りつつ、rustの魅力も伝わればと思います!

この記事が役に立てれば幸いです!ありがとうございました!!

GitHubで編集を提案

Discussion