Files
tuliprox/docker/cross.Dockerfile

271 lines
10 KiB
Docker

# =================================================================
#
# Part 1: Prerequisite Build Stages
#
# These stages are used as building blocks for the final images.
# They prepare the Rust binary and other resources.
#
# =================================================================
# -----------------------------------------------------------------
# Stage 0: Fetch static FFmpeg binaries
# -----------------------------------------------------------------
# We use a static build to ensure it works in the scratch image without libraries
# Static ffmpeg image is multi-arch, so it pulls correct arch automatically
FROM mwader/static-ffmpeg:7.1 AS ffmpeg-static
# The cross image provides the target compiler and sysroot, but no Rust toolchain.
FROM rust:1.95 AS rust-toolchain
WORKDIR /src
COPY Cargo.toml Cargo.lock ./
COPY shared ./shared/
COPY frontend/Cargo.toml frontend/Trunk.toml ./frontend/
COPY backend/app/Cargo.toml backend/app/build.rs ./backend/app/
COPY backend/auth/Cargo.toml ./backend/auth/
COPY backend/btree/Cargo.toml ./backend/btree/
COPY backend/config-loader/Cargo.toml ./backend/config-loader/
COPY backend/core/Cargo.toml ./backend/core/
COPY backend/curation/Cargo.toml ./backend/curation/
COPY backend/dvr/Cargo.toml ./backend/dvr/
COPY backend/hls/Cargo.toml ./backend/hls/
COPY backend/iptv/Cargo.toml ./backend/iptv/
COPY backend/library/Cargo.toml ./backend/library/
COPY backend/media-server/Cargo.toml ./backend/media-server/
COPY backend/metadata/Cargo.toml ./backend/metadata/
COPY backend/messaging/Cargo.toml ./backend/messaging/
COPY backend/mpegts/Cargo.toml ./backend/mpegts/
COPY backend/parser/Cargo.toml ./backend/parser/
COPY backend/processing/Cargo.toml ./backend/processing/
COPY backend/repository/Cargo.toml ./backend/repository/
COPY backend/session/Cargo.toml ./backend/session/
RUN mkdir -p frontend/src backend/app/src/tools && \
printf 'fn main() {}\n' > frontend/src/main.rs && \
printf 'fn main() {}\n' > backend/app/src/main.rs && \
printf 'fn main() {}\n' > backend/app/src/tools/flags_builder.rs && \
for crate in auth btree config-loader core curation dvr hls iptv library media-server metadata messaging mpegts parser processing repository session; do \
mkdir -p "backend/${crate}/src"; \
printf '\n' > "backend/${crate}/src/lib.rs"; \
done
# -----------------------------------------------------------------
# Stage 1: Build the Rust binary for production
# -----------------------------------------------------------------
FROM ghcr.io/cross-rs/x86_64-unknown-linux-musl:main AS rust-build
COPY --from=rust-toolchain /usr/local/cargo /usr/local/cargo
COPY --from=rust-toolchain /usr/local/rustup /usr/local/rustup
COPY --from=rust-toolchain /src /src
ENV CARGO_HOME=/usr/local/cargo \
RUSTUP_HOME=/usr/local/rustup \
PATH=/usr/local/cargo/bin:$PATH
# Get target architecture
ARG RUST_TARGET
# Add the requested compilation target to the pinned Rust toolchain
RUN rustup target add $RUST_TARGET
# Set Rust compiler flags for better optimization and reproducibility
ENV RUSTFLAGS='--remap-path-prefix $HOME=~ -C target-feature=+crt-static'
WORKDIR /src
# Build dependencies (this layer will be cached unless dependencies change)
RUN cargo build -p tuliprox --target $RUST_TARGET --release
# Copy the backend source only after its dependency layer is complete.
COPY backend ./backend/
RUN find backend -type f -name '*.rs' -exec touch {} + && \
cargo build -p tuliprox --target $RUST_TARGET --release
# -----------------------------------------------------------------
# Stage 2: Build the rust frontend
# -----------------------------------------------------------------
FROM rust-toolchain AS trunk-build
ARG RUST_TARGET=wasm32-unknown-unknown
# Install dependencies for Trunk and WebAssembly
RUN apt-get update && apt-get install -y --no-install-recommends \
pkg-config libssl-dev curl binaryen
# Add wasm target & install trunk
RUN rustup target add wasm32-unknown-unknown
RUN cargo install --locked trunk wasm-bindgen-cli
# Pre-build dependencies
WORKDIR /src/frontend
RUN cargo build --target wasm32-unknown-unknown --release
# Now copy the actual frontend source and build the project
WORKDIR /src
COPY frontend ./frontend/
WORKDIR /src/frontend
# Run Trunk build
RUN find src -type f -name '*.rs' -exec touch {} + && trunk build --release
# -----------------------------------------------------------------
# Stage 3: Build video resources with ffmpeg
# -----------------------------------------------------------------
FROM linuxserver/ffmpeg:latest AS resource-build
WORKDIR /src
COPY resources ./resources
# Combine ffmpeg commands into a single layer to reduce image size
# Convert every .jpg file in resources into a matching .ts file.
RUN set -eu; \
find ./resources -maxdepth 1 -type f -name '*.jpg' | sort | while IFS= read -r image; do \
output="${image%.jpg}.ts"; \
resource_name="$(basename "${image%.jpg}")"; \
ffmpeg -y -nostdin -loop 1 -framerate 30 -i "${image}" \
-f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000 \
-t 10 -shortest \
-c:v libx264 -pix_fmt yuv420p -preset veryfast -crf 23 \
-x264-params "keyint=30:min-keyint=30:scenecut=0:bframes=0:open_gop=0" \
-c:a aac -b:a 128k -ac 2 -ar 48000 \
-mpegts_flags +resend_headers \
-muxdelay 0 -muxpreload 0 \
-f mpegts "${output}" || exit 1; \
if [ "${resource_name}" = "panel_api_provisioning" ]; then \
rm -f ./resources/panel_api_provisioning_hls_*.ts ./resources/panel_api_provisioning_hls.m3u8; \
ffmpeg -y -nostdin -loop 1 -framerate 30 -i "${image}" \
-f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000 \
-t 12 -shortest \
-c:v libx264 -pix_fmt yuv420p -preset veryfast -crf 23 \
-g 60 -keyint_min 60 \
-force_key_frames "expr:gte(t,n_forced*2)" \
-x264-params "scenecut=0:bframes=0:open_gop=0" \
-c:a aac -b:a 128k -ac 2 -ar 48000 \
-mpegts_flags +resend_headers \
-muxdelay 0 -muxpreload 0 \
-f hls \
-hls_time 2 \
-hls_list_size 0 \
-hls_segment_type mpegts \
-hls_segment_filename "./resources/panel_api_provisioning_hls_%03d.ts" \
./resources/panel_api_provisioning_hls.m3u8 || exit 1; \
rm -f ./resources/panel_api_provisioning_hls.m3u8; \
fi; \
done
# -----------------------------------------------------------------
# Stage 4: Prepare timezone data
# -----------------------------------------------------------------
FROM alpine:latest AS tz-prep
ARG TZ=UTC
ENV TZ=${TZ}
RUN apk add --no-cache tzdata \
&& mkdir -p /output/etc \
&& mkdir -p /output/usr/share \
&& cp -r /usr/share/zoneinfo /output/usr/share/zoneinfo \
&& ln -sf /usr/share/zoneinfo/${TZ} /output/etc/localtime
# =================================================================
#
# Part 2: Final Image Stages
#
# These stages build the final, runnable images by assembling
# the artifacts from the prerequisite stages above.
#
# =================================================================
# -----------------------------------------------------------------
# Final Image #1: Debugging Environment (Alpine-based)
# -----------------------------------------------------------------
FROM rust:alpine AS debug
# For Alpine, the correct target architecture uses 'musl'
ARG RUST_TARGET=x86_64-unknown-linux-musl
ARG TZ=UTC
# Make the build-time argument available as a run-time environment variable
ENV RUST_TARGET=${RUST_TARGET}
ENV TZ=${TZ}
# Install build dependencies and the lldb debugger for Alpine
# Added ffmpeg for probing
RUN apk add --no-cache bash build-base lldb gdb perl ffmpeg
# Update Rust toolchain and add the necessary target
RUN rustup update && rustup target add $RUST_TARGET
# Create the final application layout to mimic production
WORKDIR /app
COPY --from=trunk-build /src/frontend/dist ./web
COPY --from=resource-build /src/resources ./resources
# Create a separate directory for the source code
WORKDIR /usr/src/tuliprox
COPY . .
# Copy the entrypoint script and set it as the entrypoint for the container.
COPY ./docker/debug/debug-entrypoint.sh /usr/local/bin/entrypoint.sh
# Ensure the script is executable
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
# The CMD will be passed as arguments to the entrypoint script.
CMD ["tail", "-f", "/dev/null"]
# -----------------------------------------------------------------
# Final Image #2: Production image based on scratch
# -----------------------------------------------------------------
FROM scratch AS scratch-final
ARG RUST_TARGET
# Copy timezone data and localtime from tz-prep
COPY --from=tz-prep /output/usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=tz-prep /output/etc/localtime /etc/localtime
# Certificates
COPY --from=rust-build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Copy static ffmpeg/ffprobe binaries
COPY --from=ffmpeg-static /ffmpeg /usr/local/bin/
COPY --from=ffmpeg-static /ffprobe /usr/local/bin/
# RUN ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime
# App
WORKDIR /app
COPY --from=rust-build /src/target/${RUST_TARGET}/release/tuliprox ./tuliprox
COPY --from=trunk-build /src/frontend/dist ./web
COPY --from=resource-build /src/resources ./resources
ENTRYPOINT ["/app/tuliprox"]
CMD ["-s", "-p", "/app/config"]
# -----------------------------------------------------------------
# Final Image #3: Production image based on Alpine
# -----------------------------------------------------------------
FROM alpine:latest AS alpine-final
ARG RUST_TARGET
ARG TZ=UTC
ENV TZ=${TZ}
# Install dependencies including ffmpeg
RUN apk add --no-cache bash curl ca-certificates tini ffmpeg
COPY --from=rust-build /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=rust-build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Default timezone fallback
RUN ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime
WORKDIR /app
COPY --from=rust-build /src/target/${RUST_TARGET}/release/tuliprox ./tuliprox
COPY --from=trunk-build /src/frontend/dist ./web
COPY --from=resource-build /src/resources ./resources
ENTRYPOINT ["/sbin/tini", "--", "/app/tuliprox"]
CMD ["-s", "-p", "/app/config"]