Konubinix' opinionated web of thoughts

Issues With Docker Multi Stage Build and Cache Invalidation in Rust

Fleeting

Context: I want to build a wasm package from a project consisting on a rust library.

Layout

  • ./common
    • ./Cargo.lock
    • ./Cargo.toml
    • ./Earthfile
    • ./src
  • ./wasm-client
    • ./Cargo.lock
    • ./Cargo.toml
    • ./Earthfile
    • ./src

first try

In common

src:
    FROM rust
    RUN rustup component add clippy rustfmt
    WORKDIR /app
    COPY Cargo.lock Cargo.toml  .
    RUN mkdir -p src && touch src/lib.rs
    RUN cargo build
    COPY --dir src .
    SAVE ARTIFACT /app src

In wasm-client

wasm:
    FROM rust
    RUN rustup component add clippy rustfmt
    WORKDIR /app
    RUN apt update && apt install -y build-essential
    WORKDIR /app/wasm-client
    RUN cargo install wasm-pack wasm-opt
    COPY Cargo.lock Cargo.toml .
    COPY ../common+deps/common ../common
    RUN mkdir -p src && touch src/lib.rs
    RUN wasm-pack build --target web --release # to have a cache of the dependencies
    COPY --dir src /app/wasm-client
    COPY ../common+src/src /app/common
    RUN wasm-pack build --target web --release

This is what happens

  • when COPYing stuff, the dates are put to “Apr 16 2020”.
  • when running the caching build, the target files have the date of now.
  • then, when copying the actual sources, they get the date “apr 16 2020” and
  • the build will ignore them.

second try, with –keep-ts

I can add a –keep-ts instruction when moving the sources.

In common

src:
    FROM rust
    RUN rustup component add clippy rustfmt
    WORKDIR /app
    COPY Cargo.lock Cargo.toml  .
    RUN mkdir -p src && touch src/lib.rs
    RUN cargo build
    COPY --keep-ts --dir src .
    SAVE ARTIFACT --keep-ts /app src

In wasm-client

wasm:
    FROM rust
    RUN rustup component add clippy rustfmt
    WORKDIR /app
    RUN apt update && apt install -y build-essential
    WORKDIR /app/wasm-client
    RUN cargo install wasm-pack wasm-opt
    COPY Cargo.lock Cargo.toml .
    COPY ../common+deps/common ../common
    RUN mkdir -p src && touch src/lib.rs
    RUN wasm-pack build --target web --release # to have a cache of the dependencies
    COPY --keep-ts --dir src /app/wasm-client
    COPY --keep-ts ../common+src/src /app/common
    RUN wasm-pack build --target web --release

Consider that the sources have a date like “now-1h” this is what happens:

  • when COPYing stuff, the dates are put to “now-1h”.
  • when running the caching build, the target files have the date of now.
  • then, when copying the actual sources, they get the date “now-1h” that is still < now and
  • the build will ignore them.

trying using a target dating to “Apr 16 2020”

src:
    FROM rust
    RUN rustup component add clippy rustfmt
    WORKDIR /app
    COPY Cargo.lock Cargo.toml  .
    RUN mkdir -p src && touch src/lib.rs
    RUN cargo build
    COPY --keep-ts --dir src .
    SAVE ARTIFACT --keep-ts /app src

In wasm-client

cached-target:
    FROM rust
    RUN rustup component add clippy rustfmt
    WORKDIR /app
    RUN apt update && apt install -y build-essential
    WORKDIR /app/wasm-client
    RUN cargo install wasm-pack wasm-opt
    COPY Cargo.lock Cargo.toml .
    COPY ../common+deps/common ../common
    RUN mkdir -p src && touch src/lib.rs
    RUN wasm-pack build --target web --release # to have a cache of the dependencies
    SAVE ARTIFACT target

wasm:
    FROM rust
    COPY --keep-ts --dir src /app/wasm-client
    COPY --keep-ts ../common+src/src /app/common
    COPY --dir +cached-target/target .
    RUN wasm-pack build --target web --release

This happens

  • when COPYing stuff, the dates are put to “now-1h”,
  • when running the caching build, the target files have the date of now,
  • when copying its artifact, the target gets the date “Apr 16 2020”,
  • then, when copying the actual sources, they get the date “now-1h” that is always > “Apr 16 2020”
  • the build will be triggered everytime, but at least the dependencies won’t be built again.

trying with the cache instruction

src:
    FROM rust
    RUN rustup component add clippy rustfmt
    WORKDIR /app
    COPY Cargo.lock Cargo.toml  .
    RUN mkdir -p src && touch src/lib.rs
    RUN cargo build
    COPY --keep-ts --dir src .
    SAVE ARTIFACT --keep-ts /app src

In wasm-client

cached-target:
    FROM rust
    RUN rustup component add clippy rustfmt
    WORKDIR /app
    RUN apt update && apt install -y build-essential
    WORKDIR /app/wasm-client
    RUN cargo install wasm-pack wasm-opt
    COPY Cargo.lock Cargo.toml .
    COPY ../common+deps/common ../common
    RUN mkdir -p src && touch src/lib.rs
    RUN cargo fetch
    COPY --keep-ts --dir src /app/wasm-client
    COPY --keep-ts ../common+src/src /app/common
    RUN --mount=type=cache,target=/app/wasm-client/target wasm-pack build --target web --release

Note that we only fetched the dependencies before hand. We did not run the build.

This happens:

  • when COPYing stuff, the dates are put to “now-1h”,
  • when building, the cache will keep the dates
  • when building again, only the new things are built

With this, we loose a bit of control about where the data resides, as there is a cache somewhere that we need to deal with. We also cannot take a look at this cache as easily as using the trick of RUN false and earthly -i .

But it provides the most correct build-when-needed behavior.

Note also that doing this, we NEED to –keep-ts every sources, or the build system will always think that the target is up-to-date.