Image builds keep compiled crates between builds (#438)

The ipx Dockerfile built its dependencies in a layer of stub crates,
which any change to Cargo.toml or Cargo.lock invalidated: all of them
compiled again in release, 405 crates, 170 s at the builder's cap. The
directory's had no such layer and compiled everything every time. Both
now build with cargo's registry and target/ as BuildKit cache mounts.
Measured on Tower: a Cargo.toml change rebuilds three crates in 52 s
instead of 405 in 170 s. The peak is about the same, since the ipx
crate itself is most of it; what goes is the minutes at the cap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-09 14:05:12 +00:00
parent f9e4c1d800
commit 3a3900e959
3 changed files with 24 additions and 21 deletions

View File

@@ -114,7 +114,9 @@ split lands means the frontend and directory too. Run `pull` before `up`, becaus
not Docker Hub, which rate-limits this host with `429 Too Many Requests`. The capped builder not Docker Hub, which rate-limits this host with `429 Too Many Requests`. The capped builder
cannot see images the Docker daemon has stored, so pulling and tagging them locally does nothing cannot see images the Docker daemon has stored, so pulling and tagging them locally does nothing
for it. If `buildkitd.toml` changes, remove the builder (`docker buildx rm ipx-capped`) so the for it. If `buildkitd.toml` changes, remove the builder (`docker buildx rm ipx-capped`) so the
next build makes it again with the new config. next build makes it again with the new config. The builder also keeps the compiled crates
between builds (cache mounts, #438), so a `Cargo.lock` change compiles only what changed; removing
it throws them away, and the next build of each image compiles all of them again.
The containers have no Docker healthcheck (#218): Docker runs one with `exec`, which on Tower The containers have no Docker healthcheck (#218): Docker runs one with `exec`, which on Tower
fails whenever root is logged out, because elogind removes `/run/user/0`, where containerd puts fails whenever root is logged out, because elogind removes `/run/user/0`, where containerd puts

View File

@@ -7,25 +7,22 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
WORKDIR /src WORKDIR /src
# Dependencies first, so editing the source does not rebuild librqbit every time: each crate of
# the workspace as an empty stub, its manifest real.
COPY Cargo.toml Cargo.lock ./ COPY Cargo.toml Cargo.lock ./
COPY crates/ipx/Cargo.toml crates/ipx/
COPY crates/feed/Cargo.toml crates/feed/
COPY crates/directory/Cargo.toml crates/directory/
COPY crates/tui/Cargo.toml crates/tui/
RUN mkdir -p crates/ipx/src crates/feed/src crates/directory/src crates/tui/src \
&& echo 'fn main(){}' > crates/ipx/src/main.rs && touch crates/feed/src/lib.rs \
&& echo 'fn main(){}' > crates/directory/src/main.rs && touch crates/directory/src/lib.rs \
&& echo 'fn main(){}' > crates/tui/src/main.rs \
&& cargo build --release --locked -p ipx \
&& rm -rf crates/ipx/src crates/feed/src crates/directory/src crates/tui/src
COPY crates ./crates COPY crates ./crates
# ipx builds the directory crate in, whose public pages serve these (#215). # ipx builds the directory crate in, whose public pages serve these (#215).
COPY web/logo.svg web/logo-dark.svg web/favicon.png ./web/ COPY web/logo.svg web/logo-dark.svg web/favicon.png ./web/
# cargo skips a rebuild if mtimes look untouched; make sure it does not. # cargo's registry and target/ are BuildKit cache mounts, kept in the builder between builds, so
RUN touch crates/ipx/src/main.rs crates/ipx/src/lib.rs crates/feed/src/lib.rs && cargo build --release --locked -p ipx # a Cargo.lock change compiles only the crates that changed (#438): a dependency layer of stub
# crates went with any change to it and compiled all 500 again, holding the builder's memory cap
# for minutes. A cache mount is not part of the image, so the binary is copied out in the same
# step. The workspace's own crates are touched because cargo goes by mtimes and skips a rebuild
# if they look untouched.
RUN --mount=type=cache,id=ipx-cargo-registry,target=/usr/local/cargo/registry,sharing=locked \
--mount=type=cache,id=ipx-target,target=/src/target,sharing=locked \
touch crates/ipx/src/main.rs crates/ipx/src/lib.rs crates/feed/src/lib.rs \
crates/directory/src/lib.rs \
&& cargo build --release --locked -p ipx \
&& cp target/release/ipxd /usr/local/bin/ipxd
FROM debian:trixie-slim FROM debian:trixie-slim
# ffmpeg is DeadAir's (#171): it finds the silences and copies the frames kept. Debian's adds # ffmpeg is DeadAir's (#171): it finds the silences and copies the frames kept. Debian's adds
@@ -33,7 +30,7 @@ FROM debian:trixie-slim
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates gosu ffmpeg \ ca-certificates gosu ffmpeg \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
COPY --from=build /src/target/release/ipxd /usr/local/bin/ipxd COPY --from=build /usr/local/bin/ipxd /usr/local/bin/ipxd
COPY docker-entrypoint.sh /usr/local/bin/ COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh RUN chmod +x /usr/local/bin/docker-entrypoint.sh

View File

@@ -6,18 +6,22 @@ RUN apt-get update && apt-get install -y --no-install-recommends build-essential
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
WORKDIR /src WORKDIR /src
# Every member of the workspace, since cargo reads them all to build one. # Every member of the workspace, since cargo reads them all to build one.
# ponytail: no dependency layer, so each build compiles sea-orm again; add the stub trick from
# the main Dockerfile if builds get slow enough to matter.
COPY Cargo.toml Cargo.lock ./ COPY Cargo.toml Cargo.lock ./
COPY crates ./crates COPY crates ./crates
# The logo and icon the public pages serve (#215), compiled into the binary. # The logo and icon the public pages serve (#215), compiled into the binary.
COPY web/logo.svg web/logo-dark.svg web/favicon.png ./web/ COPY web/logo.svg web/logo-dark.svg web/favicon.png ./web/
RUN cargo build --release --locked -p ipx-directory # Compiled crates kept in the builder between builds, as in the main Dockerfile (#438). A target
# of its own: built with ipx's, the two would rebuild shared crates for each other's features.
RUN --mount=type=cache,id=ipx-cargo-registry,target=/usr/local/cargo/registry,sharing=locked \
--mount=type=cache,id=ipx-directory-target,target=/src/target,sharing=locked \
touch crates/directory/src/main.rs crates/directory/src/lib.rs crates/feed/src/lib.rs \
&& cargo build --release --locked -p ipx-directory \
&& cp target/release/ipx-directory /usr/local/bin/ipx-directory
FROM debian:trixie-slim FROM debian:trixie-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \ RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
COPY --from=build /src/target/release/ipx-directory /usr/local/bin/ipx-directory COPY --from=build /usr/local/bin/ipx-directory /usr/local/bin/ipx-directory
ENV DIRECTORY_CONFIG=/config/directory.toml ENV DIRECTORY_CONFIG=/config/directory.toml
# Unraid's nobody:users. It writes nothing to disk: the database is elsewhere. # Unraid's nobody:users. It writes nothing to disk: the database is elsewhere.
USER 99:100 USER 99:100