|
1 | | -# We split the Dockerfile into two stages: |
2 | | -# Stage 1: We copy all the Cargo.toml files and create empty lib.rs files. |
3 | | -# Stage 2: |
4 | | -# * We copy the files from the first stage |
5 | | -# * We compile all the crates. |
6 | | -# * We copy the rest of the files and compile again. |
7 | | -# The reason we compile twice is to allow caching for the first compilation (that compiles all the |
8 | | -# dependency crates) if no Cargo.toml files were changed. |
9 | | -# The reason we split it into two stages is to first copy all the files and then erase all |
10 | | -# non-Cargo.toml files. This way, non-Cargo.toml files won't affect the cache of the second stage |
11 | | -# (For more on docker stages, read https://docs.docker.com/build/building/multi-stage/). |
12 | | -FROM rust:1.78 AS copy_toml |
| 1 | +# Dockerfile with multi-stage builds for efficient dependency caching and lightweight final image. |
| 2 | +# For more on Docker stages, visit: https://docs.docker.com/build/building/multi-stage/ |
13 | 3 |
|
14 | | -COPY crates/ /app/crates/ |
15 | | -COPY Cargo.toml /app/ |
16 | | - |
17 | | -WORKDIR /app/ |
18 | | - |
19 | | -# Erase all non-Cargo.toml files. |
20 | | -RUN find /app \! -name "Cargo.toml" -type f -delete ; \ |
21 | | - find /app -empty -type d -delete; \ |
22 | | - # Create empty lib.rs files. |
23 | | - # In order for cargo init to work, we need to not have a Cargo.toml file. In each crate, we rename |
24 | | - # Cargo.toml to another name and after running `cargo init` we override the auto-generated |
25 | | - # Cargo.toml with the original. |
26 | | - mv Cargo.toml _Cargo.toml && \ |
27 | | - # TODO: Consider moving to a script. |
28 | | - for dir in crates/*; do \ |
29 | | - if [ -f "$dir/Cargo.toml" ]; then \ |
30 | | - mv $dir/Cargo.toml $dir/_Cargo.toml \ |
31 | | - && cargo init --lib --vcs none $dir \ |
32 | | - && mv -f $dir/_Cargo.toml $dir/Cargo.toml; \ |
33 | | - else \ |
34 | | - for subdir in $dir/*; do \ |
35 | | - mv $subdir/Cargo.toml $subdir/_Cargo.toml \ |
36 | | - && cargo init --lib --vcs none $subdir \ |
37 | | - && mv -f $subdir/_Cargo.toml $subdir/Cargo.toml; \ |
38 | | - done; \ |
39 | | - fi; \ |
40 | | - done && \ |
41 | | - mv _Cargo.toml Cargo.toml |
42 | | - |
43 | | -COPY Cargo.lock /app/ |
44 | | - |
45 | | -# Starting a new stage so that the first build layer will be cached if a non-Cargo.toml file was |
46 | | -# changed. |
47 | | -# Use this image to compile the project to an alpine compatible binary. |
48 | | -FROM clux/muslrust:1.78.0-stable AS builder |
49 | | -WORKDIR /app/ |
| 4 | +# We use Cargo Chef to compile dependencies before compiling the rest of the crates. |
| 5 | +# This approach ensures proper Docker caching, where dependency layers are cached until a dependency changes. |
| 6 | +# Code changes in our crates won't affect these cached layers, making the build process more efficient. |
| 7 | +# More info on Cargo Chef: https://github.com/LukeMathWalker/cargo-chef |
50 | 8 |
|
| 9 | +# We start by creating a base image using 'clux/muslrust' with additional required tools. |
| 10 | +FROM clux/muslrust:1.78.0-stable AS chef |
| 11 | +WORKDIR /app |
51 | 12 | RUN apt update && apt install -y clang unzip |
| 13 | +RUN cargo install cargo-chef |
52 | 14 | ENV PROTOC_VERSION=25.1 |
53 | 15 | RUN curl -L "https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOC_VERSION/protoc-$PROTOC_VERSION-linux-x86_64.zip" -o protoc.zip && unzip ./protoc.zip -d $HOME/.local && rm ./protoc.zip |
54 | 16 | ENV PROTOC=/root/.local/bin/protoc |
55 | | - |
56 | | - |
57 | | -# Copy all the files from the previous stage (which are Cargo.toml and empty lib.rs files). |
58 | | -COPY --from=copy_toml /app . |
59 | | - |
60 | | -# Add the proc_macro code since cargo init puts autogenerated code in lib.rs file that breaks the build. |
61 | | -COPY crates/papyrus_proc_macros /app/crates/papyrus_proc_macros |
62 | | - |
63 | | -RUN rustup target add x86_64-unknown-linux-musl && \ |
64 | | - CARGO_INCREMENTAL=0 cargo build --target x86_64-unknown-linux-musl --release --package papyrus_node |
65 | | - |
66 | | -# Copy the rest of the files. |
67 | | -COPY crates/ /app/crates |
68 | | - |
69 | | -# Touching the lib.rs files to mark them for re-compilation. Then re-compile now that all the source |
70 | | -# code is available |
71 | | -RUN touch crates/*/src/lib.rs; \ |
72 | | - CARGO_INCREMENTAL=0 cargo build --release --package papyrus_node --bin papyrus_node; |
73 | | - |
74 | | -# Starting a new stage so that the final image will contain only the executable. |
| 17 | +# Add the x86_64-unknown-linux-musl target to rustup for compiling statically linked binaries. |
| 18 | +# This enables the creation of fully self-contained binaries that do not depend on the system's dynamic libraries, |
| 19 | +# resulting in more portable executables that can run on any Linux distribution. |
| 20 | +RUN rustup target add x86_64-unknown-linux-musl |
| 21 | + |
| 22 | +##################### |
| 23 | +# Stage 1 (planer): # |
| 24 | +##################### |
| 25 | +FROM chef AS planner |
| 26 | +COPY . . |
| 27 | +# * Running Cargo Chef prepare that will generate recipe.json which will be used in the next stage. |
| 28 | +RUN cargo chef prepare |
| 29 | + |
| 30 | +##################### |
| 31 | +# Stage 2 (cacher): # |
| 32 | +##################### |
| 33 | +# Compile all the dependecies using Cargo Chef cook. |
| 34 | +FROM chef AS cacher |
| 35 | + |
| 36 | +# Copy recipe.json from planner stage |
| 37 | +COPY --from=planner /app/recipe.json recipe.json |
| 38 | + |
| 39 | +# Build dependencies - this is the caching Docker layer! |
| 40 | +RUN cargo chef cook --target x86_64-unknown-linux-musl --release --package papyrus_node |
| 41 | + |
| 42 | +###################### |
| 43 | +# Stage 3 (builder): # |
| 44 | +###################### |
| 45 | +FROM chef AS builder |
| 46 | +COPY . . |
| 47 | +COPY --from=cacher /app/target target |
| 48 | +# Disable incremental compilation for a cleaner build. |
| 49 | +ENV CARGO_INCREMENTAL=0 |
| 50 | + |
| 51 | +# Compile the papyrus_node crate for the x86_64-unknown-linux-musl target in release mode, ensuring dependencies are locked. |
| 52 | +RUN cargo build --target x86_64-unknown-linux-musl --release --package papyrus_node --locked |
| 53 | + |
| 54 | +########################### |
| 55 | +# Stage 4 (papyrus_node): # |
| 56 | +########################### |
| 57 | +# Uses Alpine Linux to run a lightweight and secure container. |
75 | 58 | FROM alpine:3.17.0 AS papyrus_node |
76 | 59 | ENV ID=1000 |
77 | | - |
78 | 60 | WORKDIR /app |
79 | | -# Copy the node executable and its config. |
| 61 | + |
| 62 | +# Copy the node executable and its configuration. |
80 | 63 | COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/papyrus_node /app/target/release/papyrus_node |
81 | | -COPY config/ /app/config |
| 64 | +COPY config config |
82 | 65 |
|
| 66 | +# Install tini, a lightweight init system, to call our executable. |
83 | 67 | RUN set -ex; \ |
84 | 68 | apk update; \ |
85 | 69 | apk add --no-cache tini; \ |
86 | 70 | mkdir data |
87 | 71 |
|
| 72 | +# Create a new user "papyrus". |
88 | 73 | RUN set -ex; \ |
89 | 74 | addgroup --gid ${ID} papyrus; \ |
90 | 75 | adduser --ingroup $(getent group ${ID} | cut -d: -f1) --uid ${ID} --gecos "" --disabled-password --home /app papyrus; \ |
91 | 76 | chown -R papyrus:papyrus /app |
92 | 77 |
|
| 78 | +# Expose RPC and monitoring ports. |
93 | 79 | EXPOSE 8080 8081 |
94 | 80 |
|
| 81 | +# Switch to the new user. |
95 | 82 | USER ${ID} |
96 | 83 |
|
| 84 | +# Set the entrypoint to use tini to manage the process. |
97 | 85 | ENTRYPOINT ["/sbin/tini", "--", "/app/target/release/papyrus_node"] |
0 commit comments