# --- Build stage --- FROM rust:1.88-alpine AS builder RUN apk add --no-cache musl-dev pkgconfig openssl-dev WORKDIR /app COPY Cargo.toml Cargo.lock* ./ # Pre-fetch deps with a dummy build for layer caching RUN mkdir src && echo "fn main(){}" > src/main.rs && \ cargo build --release && \ rm -rf src COPY src ./src COPY static ./static COPY migrations ./migrations # Copied WITH the sources, not with Cargo.toml above: cargo auto-detects `build.rs` by presence, so # putting it in the dependency-cache layer would make the dummy build run it too and invalidate a # layer that is otherwise stable. Copied at all because without it the image builds a subtly # DIFFERENT package from the one developers build — no build script, hence none of the # rerun-if-changed tracking for `static/export-viewer` and `migrations`. Harmless here (every image # build is clean, so there is no stale cache to reuse) and confusing everywhere else. COPY build.rs ./ RUN touch src/main.rs && cargo build --release # --- Runtime stage --- FROM alpine:3.21 RUN apk add --no-cache ca-certificates ffmpeg # Run as a non-root user. Pre-create and chown the media + export mount paths so # the fresh named volumes inherit the non-root ownership (Docker seeds an empty # named volume from the image directory, preserving its uid/gid) and uploads + # export archives can be written. Exports live OUTSIDE /media on purpose so the # public media ServeDir can't reach them. RUN addgroup -S app && adduser -S app -G app WORKDIR /app COPY --from=builder /app/target/release/eventsnap-backend ./ RUN mkdir -p /media /exports && chown -R app:app /app /media /exports USER app EXPOSE 3000 CMD ["./eventsnap-backend"]