Skip to content

Commit 3b175bc

Browse files
committed
refactor: switch devctr build to be multi-staged
Switch from a single stage build to a multi-staged build for the devctr. Although we don't often rebuild this, this should reduce the time taken when we do, due to unchanged layers now being cached. In my testing, this cut the full build time from ~1300s to ~600s without inflating the image size further. We also don't have to manually clean up after earlier build stages, as they are not included in the final image. Stage dependency graph: +--- qemu-builder -------+ | | +--- libseccomp-builder -+ | | base-image +--------------------+--- iperf3-builder -----+--- devctr | | | | +--- git-secrets-builder + | | +--- apt-base | | | | | python-deps +--- crosvm-builder -----+ | | | rust-toolchain ---+------------------------+ Parallel builders (from base-image): qemu-builder, libseccomp-builder, iperf3-builder, git-secrets-builder Sequential base chain: base-image -> apt-base -> python-deps -> rust-toolchain Rust fork (from rust-toolchain): crosvm-builder (build deps thrown away, only binary copied) devctr (cargo tools, kani, nightly, then COPY from all builders) Signed-off-by: James Curtis <jxcurtis@amazon.co.uk>
1 parent 8ca334d commit 3b175bc

1 file changed

Lines changed: 175 additions & 123 deletions

File tree

tools/devctr/Dockerfile

Lines changed: 175 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,56 @@
1-
FROM public.ecr.aws/lts/ubuntu:24.04
2-
3-
# TODO: use a multi-stage build to reduce the download size when updating this container.
4-
# The Rust toolchain layer will get updated most frequently, but we could keep the system
5-
# dependencies layer intact for much longer.
1+
# Building the devctr is a multi-stage process. The dependency graph is as follows:
2+
#
3+
# Stage dependency graph:
4+
#
5+
# +--- qemu-builder -------+
6+
# | |
7+
# +--- libseccomp-builder -+
8+
# | |
9+
# base-image +--------------------+--- iperf3-builder -----+--- devctr
10+
# | | |
11+
# | +--- git-secrets-builder +
12+
# | |
13+
# +--- apt-base |
14+
# | |
15+
# | |
16+
# python-deps +--- crosvm-builder -----+
17+
# | | |
18+
# rust-toolchain ---+------------------------+
19+
#
20+
# Parallel builders (from base-image):
21+
# qemu-builder, libseccomp-builder, iperf3-builder, git-secrets-builder
22+
#
23+
# Sequential base chain:
24+
# base-image -> apt-base -> python-deps -> rust-toolchain
25+
#
26+
# Rust fork (from rust-toolchain):
27+
# crosvm-builder (build deps thrown away, only binary copied)
28+
# devctr (cargo tools, kani, nightly, then COPY from all builders)
29+
#
630

7-
ARG RUST_TOOLCHAIN="1.94.1"
8-
ARG TMP_BUILD_DIR=/tmp/build
31+
# ============================================================
32+
# Base image: Shared between builders
33+
# ============================================================
34+
FROM public.ecr.aws/lts/ubuntu:24.04 AS base-image
935
ARG DEBIAN_FRONTEND=noninteractive
10-
ARG PIP_BREAK_SYSTEM_PACKAGES=1
1136
ARG ARCH
37+
RUN apt-get update \
38+
&& apt-get -y install --no-install-recommends \
39+
git gcc make libc-dev ca-certificates musl-tools
1240

13-
ENV CARGO_HOME=/usr/local/rust
14-
ENV RUSTUP_HOME=/usr/local/rust
15-
ENV PATH="$PATH:$CARGO_HOME/bin"
16-
ENV LC_ALL=C.UTF-8
41+
# help musl-gcc find linux headers
42+
RUN cd /usr/include/$ARCH-linux-musl \
43+
&& ln -s ../$ARCH-linux-gnu/asm asm \
44+
&& ln -s ../linux linux \
45+
&& ln -s ../asm-generic asm-generic
46+
47+
# ============================================================
48+
# Builder: Qemu vhost-user-blk backend
49+
# ============================================================
50+
FROM base-image AS qemu-builder
51+
ARG PIP_BREAK_SYSTEM_PACKAGES=1
1752
ENV QEMU_VER="8.1.1"
18-
ENV CROSVM_VER="9d542e6dafa3a85acd1fb6cd6f1adfa1331c4e96"
19-
ENV CROSVM_TOOLCHAIN_VER="1.68.2"
20-
ENV LIBSECCOMP_VER="v2.6.0"
2153

22-
# Build and install Qemu vhost-user-blk backend
23-
#
2454
RUN apt-get update \
2555
&& apt-get -y install --no-install-recommends \
2656
curl gpg gpg-agent \
@@ -35,27 +65,65 @@ RUN apt-get update \
3565
&& tar xf qemu-${QEMU_VER}.tar.xz && cd qemu-${QEMU_VER} \
3666
&& ./configure && make -j $(nproc) contrib/vhost-user-blk/vhost-user-blk \
3767
&& strip ./build/contrib/vhost-user-blk/vhost-user-blk \
38-
&& cp -a ./build/contrib/vhost-user-blk/vhost-user-blk /usr/local/bin \
39-
&& pip3 uninstall -y meson \
40-
&& apt-get purge -y \
41-
curl gpg gpg-agent \
42-
build-essential ninja-build libglib2.0-dev libpixman-1-dev flex bison \
43-
&& apt-get autoremove -y \
44-
&& cd && rm -r /tmp/qemu_build
68+
&& cp -a ./build/contrib/vhost-user-blk/vhost-user-blk /usr/local/bin
69+
70+
# ============================================================
71+
# Builder: static libseccomp (musl)
72+
# ============================================================
73+
FROM base-image AS libseccomp-builder
74+
ENV LIBSECCOMP_VER="v2.6.0"
75+
76+
# Install static version of libseccomp
77+
# We need to compile from source because
78+
# libseccomp provided by the distribution is not
79+
# compiled with musl-gcc and we need this
80+
# for our musl builds.
81+
# We specify the tag in order to have a fixed version
82+
# of the library.
83+
RUN apt-get -y install --no-install-recommends \
84+
libtool gperf autoconf automake \
85+
&& git clone https://github.com/seccomp/libseccomp /tmp/libseccomp \
86+
&& cd /tmp/libseccomp \
87+
&& git checkout tags/${LIBSECCOMP_VER} \
88+
&& ./autogen.sh \
89+
&& CC="musl-gcc -static" ./configure --enable-static=yes --enable-shared=false \
90+
&& make DESTDIR=/staging install
91+
92+
# ============================================================
93+
# Builder: iperf3-vsock
94+
# ============================================================
95+
FROM base-image AS iperf3-builder
96+
RUN git clone https://github.com/stefano-garzarella/iperf-vsock /tmp/iperf-vsock \
97+
&& cd /tmp/iperf-vsock && git checkout 9245f9a \
98+
&& mkdir build && cd build \
99+
&& ../configure "LDFLAGS=--static" --disable-shared && make \
100+
&& cp src/iperf3 /usr/local/bin/iperf3-vsock
101+
102+
# ============================================================
103+
# Builder: git-secrets
104+
# ============================================================
105+
FROM base-image AS git-secrets-builder
106+
RUN git clone https://github.com/awslabs/git-secrets /tmp/git-secrets \
107+
&& cd /tmp/git-secrets \
108+
&& make DESTDIR=/staging install
109+
110+
# ============================================================
111+
# Base: apt packages (changes very rarely)
112+
# ============================================================
113+
FROM base-image AS apt-base
114+
ARG ARCH
115+
ENV LC_ALL=C.UTF-8
45116

46-
# Install system dependencies
47-
#
48117
RUN apt-get update \
49118
&& apt-get -y install --no-install-recommends \
50119
# essential build tools
51-
gcc make libc-dev binutils-dev libssl-dev \
120+
binutils-dev libssl-dev \
52121
# Useful utilities
53122
gdbserver \
54123
# Needed in order to be able to compile `userfaultfd-sys`.
55124
clang \
56125
curl \
57126
file \
58-
git \
59127
jq \
60128
less \
61129
libbfd-dev \
@@ -80,18 +148,44 @@ RUN apt-get update \
80148
python3-seccomp \
81149
# for aws-lc-rs
82150
cmake \
83-
# for Qemu vhost-user-blk backend
84-
libglib2.0-dev \
85151
# for crosvm (vhost-user-blk backend)
86152
libcap2 \
87153
# for debugging
88154
gdb strace trace-cmd \
89155
&& rm -rf /var/lib/apt/lists/*
90156

157+
158+
# Add cross-compile toolchain for devtool checkbuild command (rust target added in devctr stage)
159+
RUN case "${ARCH}" in \
160+
"aarch64") \
161+
apt-get update && apt install -y gcc-x86-64-linux-gnu libc6-dev-amd64-cross linux-libc-dev-amd64-cross \
162+
;; \
163+
"x86_64") \
164+
apt-get update && apt install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross linux-libc-dev-arm64-cross \
165+
;; \
166+
*) echo "Unsupported arch ${ARCH}" && exit 1 ;; \
167+
esac \
168+
&& rm -rf /var/lib/apt/lists/*
169+
170+
171+
# Download codecov uploader
172+
RUN cd /usr/local/bin \
173+
&& (if [ "$ARCH" = "x86_64" ]; then \
174+
curl -O https://uploader.codecov.io/latest/linux/codecov; else \
175+
curl -O https://uploader.codecov.io/latest/aarch64/codecov; fi) \
176+
&& chmod +x codecov
177+
178+
# ============================================================
179+
# Base: Python/Poetry deps (changes with poetry.lock)
180+
# ============================================================
181+
FROM apt-base AS python-deps
182+
ARG PIP_BREAK_SYSTEM_PACKAGES=1
183+
ARG ARCH
184+
ARG VENV="/opt/venv"
185+
91186
RUN curl -sSL https://install.python-poetry.org | python3 -
92187
ENV PATH="/root/.local/bin:$PATH"
93188

94-
ARG VENV="/opt/venv"
95189
COPY tools/devctr/poetry.lock /tmp/poetry/
96190
COPY tools/devctr/pyproject.toml /tmp/poetry/
97191
RUN cd /tmp/poetry \
@@ -106,24 +200,50 @@ ENV PATH=$VENV/bin:$PATH
106200
# apt-get installs it globally, to manually copy it into the venv
107201
RUN cp /usr/lib/python3/dist-packages/seccomp.cpython-312-"$ARCH"-linux-gnu.so "$VENV"/lib/python3.12/site-packages/
108202

109-
RUN git clone https://github.com/awslabs/git-secrets /tmp/git-secrets \
110-
&& cd /tmp/git-secrets \
111-
&& make install \
112-
&& cd - \
113-
&& rm -rf /tmp/git-secrets
203+
# ============================================================
204+
# Base: Rust toolchain (current version)
205+
# ============================================================
206+
FROM python-deps AS rust-toolchain
207+
ARG RUST_TOOLCHAIN="1.94.1"
208+
ENV CARGO_HOME=/usr/local/rust
209+
ENV RUSTUP_HOME=/usr/local/rust
210+
ENV PATH="$PATH:$CARGO_HOME/bin"
211+
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --default-toolchain "$RUST_TOOLCHAIN" \
212+
&& rustup target add x86_64-unknown-linux-musl \
213+
&& rustup target add aarch64-unknown-linux-musl \
214+
&& rustup component add llvm-tools-preview clippy rustfmt
215+
216+
# ============================================================
217+
# Builder: crosvm (used as vhost-user-blk backend)
218+
# ============================================================
219+
FROM rust-toolchain AS crosvm-builder
220+
ENV CROSVM_VER="9d542e6dafa3a85acd1fb6cd6f1adfa1331c4e96"
221+
RUN apt-get update && \
222+
apt-get -y install --no-install-recommends \
223+
libcap2 \
224+
libcap-dev \
225+
protobuf-compiler \
226+
&& git clone https://github.com/google/crosvm.git /tmp/crosvm \
227+
&& cd /tmp/crosvm && git checkout ${CROSVM_VER} \
228+
&& git submodule update --init \
229+
&& cargo build --no-default-features --release \
230+
&& strip ./target/release/crosvm \
231+
&& cp -a ./target/release/crosvm /usr/local/bin
114232

115-
# Running the three as a single dockerfile command to avoid inflation of the image:
116-
# - Install the Rust toolchain.
233+
# ============================================================
234+
# Devctr (final): Kani, cargo targets and assembled artifacts
235+
# ============================================================
236+
FROM rust-toolchain AS devctr
237+
ARG ARCH
238+
ENV CARGO_HOME=/usr/local/rust
239+
240+
# Run these as a single dockerfile command to avoid inflation of the image:
241+
# - Install required binary crates, including cargo-afl (fuzzer) and Kani (formal verification)
117242
# - Kani always installs _some_ nightly toolchain, we reuse it for the seccomp filter analysis test. Dynamically
118243
# determine the exact toolchain name, and install more components into it.
119-
# - Build and install crosvm (used as vhost-user-blk backend)
120-
# - Clean up cargo compilation directories
121244
# - Always install both x86_64 and aarch64 musl targets, as our rust-toolchain.toml would force on-the-fly installation of both anyway
122-
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --default-toolchain "$RUST_TOOLCHAIN" \
123-
&& rustup target add x86_64-unknown-linux-musl \
124-
&& rustup target add aarch64-unknown-linux-musl \
125-
&& rustup component add llvm-tools-preview clippy rustfmt \
126-
&& cargo install --locked grcov cargo-sort cargo-afl \
245+
# - Clean up cargo compilation directories
246+
RUN cargo install --locked grcov cargo-sort cargo-afl \
127247
&& cargo install --locked cargo-deny --version 0.19.0 \
128248
&& cargo install --locked kani-verifier --version 0.67.0 && cargo kani setup \
129249
\
@@ -132,88 +252,20 @@ RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --default-too
132252
&& rustup target add "$ARCH"-unknown-linux-musl --toolchain "$NIGHTLY_TOOLCHAIN" \
133253
&& cargo +"$NIGHTLY_TOOLCHAIN" install cargo-udeps \
134254
\
135-
&& apt-get update \
136-
&& apt-get -y install --no-install-recommends \
137-
libcap-dev \
138-
protobuf-compiler \
139-
&& git clone https://github.com/google/crosvm.git /tmp/crosvm \
140-
&& cd /tmp/crosvm && git checkout ${CROSVM_VER} \
141-
&& git submodule update --init \
142-
&& cargo build --no-default-features --release \
143-
&& strip ./target/release/crosvm \
144-
&& cp -a ./target/release/crosvm /usr/local/bin \
145-
&& apt-get purge -y \
146-
libcap-dev \
147-
protobuf-compiler \
148-
&& apt-get autoremove -y \
149-
&& rm -rf /var/lib/apt/lists/* \
150-
&& rustup toolchain uninstall ${CROSVM_TOOLCHAIN_VER}-${ARCH}-unknown-linux-gnu \
151-
&& cd && rm -r /tmp/crosvm \
255+
&& case "${ARCH}" in \
256+
"aarch64") rustup target add x86_64-unknown-linux-gnu ;; \
257+
"x86_64") rustup target add aarch64-unknown-linux-gnu ;; \
258+
esac \
152259
\
153260
&& rm -rf "$CARGO_HOME/registry" \
154261
&& rm -rf "$CARGO_HOME/git"
155262

156-
# help musl-gcc find linux headers
157-
RUN cd /usr/include/$ARCH-linux-musl \
158-
&& ln -s ../$ARCH-linux-gnu/asm asm \
159-
&& ln -s ../linux linux \
160-
&& ln -s ../asm-generic asm-generic
161-
162-
# Install static version of libseccomp
163-
# We need to compile from source because
164-
# libseccomp provided by the distribution is not
165-
# compiled with musl-gcc and we need this
166-
# for our musl builds.
167-
# We specify the tag in order to have a fixed version
168-
# of the library.
169-
RUN apt-get update \
170-
&& apt-get -y install \
171-
libtool gperf \
172-
&& git clone https://github.com/seccomp/libseccomp /tmp/libseccomp \
173-
&& cd /tmp/libseccomp \
174-
&& git checkout tags/${LIBSECCOMP_VER} \
175-
&& ./autogen.sh \
176-
&& CC="musl-gcc -static" ./configure --enable-static=yes --enable-shared=false \
177-
&& make install \
178-
&& cd \
179-
&& apt-get purge -y \
180-
libtool gperf \
181-
&& apt-get autoremove -y \
182-
&& rm -rf /tmp/libseccomp
183-
184-
# Build iperf3-vsock
185-
RUN mkdir "$TMP_BUILD_DIR" && cd "$TMP_BUILD_DIR" \
186-
&& git clone https://github.com/stefano-garzarella/iperf-vsock \
187-
&& cd iperf-vsock && git checkout 9245f9a \
188-
&& mkdir build && cd build \
189-
&& ../configure "LDFLAGS=--static" --disable-shared && make \
190-
&& cp src/iperf3 /usr/local/bin/iperf3-vsock \
191-
&& cd / \
192-
&& rm -rf "$TMP_BUILD_DIR"
193-
194-
# Download the codecov.io uploader
195-
RUN cd /usr/local/bin \
196-
&& (if [ "$ARCH" = "x86_64" ]; then \
197-
curl -O https://uploader.codecov.io/latest/linux/codecov; else \
198-
curl -O https://uploader.codecov.io/latest/aarch64/codecov; fi) \
199-
&& chmod +x codecov \
200-
&& cd -
201-
202-
# Add cross-compile toolchain for devtool checkbuild command
203-
RUN case "${ARCH}" in \
204-
"aarch64") \
205-
apt install -y gcc-x86-64-linux-gnu libc6-dev-amd64-cross linux-libc-dev-amd64-cross && \
206-
rustup target add x86_64-unknown-linux-gnu \
207-
;; \
208-
"x86_64") \
209-
apt install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross linux-libc-dev-arm64-cross && \
210-
rustup target add aarch64-unknown-linux-gnu \
211-
;; \
212-
*) \
213-
echo "Unsupported arch ${ARCH}" && \
214-
exit 1 \
215-
;; \
216-
esac
263+
# Copy pre-built artifacts LAST — builder changes don't invalidate Rust layer
264+
COPY --from=qemu-builder /usr/local/bin/vhost-user-blk /usr/local/bin/
265+
COPY --from=libseccomp-builder /staging /
266+
COPY --from=iperf3-builder /usr/local/bin/iperf3-vsock /usr/local/bin/
267+
COPY --from=git-secrets-builder /staging /
268+
COPY --from=crosvm-builder /usr/local/bin/crosvm /usr/local/bin/
217269

218270
ADD tools/devctr/ctr_gitconfig /root/.gitconfig
219271

0 commit comments

Comments
 (0)