Watch
1
0
Fork
You've already forked RedFlag
0
RedFlag/server/Dockerfile
Fimeg e2dab2845a supply-chain: gate our own deps, ship the verdict signed
dep-scan.sh gates go/npm/cargo on push and bakes an attested posture into the
release — embedded in the server, signed into the manifest. Reasoning and the
two Moby exceptions are in SECURITY.md.

(posture-builder runs rustup; bookworm's cargo is too old for cargo-audit.)
2026-06-14 11:43:23 -04:00

200 lines
No EOL
7.3 KiB
Docker

# Stage 0: Build the web dashboard for embedding into the server binary
FROM node:20-alpine AS web-builder
WORKDIR /web
COPY web/package.json web/package-lock.json ./
RUN npm ci --ignore-scripts
COPY web/ ./
RUN npx vite build
# Desktop variant (Tauri frontend) — built here too because this stage has
# Node 20; bookworm's packaged Node 18 is below Vite's floor.
RUN npx tsc && npx vite build --config vite.desktop.config.ts
# Stage 0b: Supply-chain self-attestation. Runs the SAME scripts/dep-scan.sh the
# CI/release pipeline runs, so an operator's own docker-compose build gates its
# dependency supply chain and produces the attested posture embedded into the
# server below. A real un-accepted vulnerability fails the build here; an offline
# build (advisory DBs unreachable) degrades to an honest unattested posture
# rather than blocking an air-gapped operator.
FROM golang:1.25-bookworm AS posture-builder
WORKDIR /src
# Toolchains for the three ecosystems + python3 for the gate scripts. Debian's
# packaged node is enough for `npm audit` (no front-end build happens here).
# Rust comes from rustup, NOT apt: bookworm's cargo (~1.65) is too old to build
# a current cargo-audit (it needs the 2024 edition), and an old scanner misses
# new advisories. This mirrors CI's rust-toolchain@stable so the docker
# self-attest path and the pipeline can't drift on the toolchain.
RUN apt-get update && apt-get install -y --no-install-recommends \
nodejs npm python3 pkg-config libssl-dev curl \
&& rm -rf /var/lib/apt/lists/*
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --profile minimal --default-toolchain stable
ENV PATH="/go/bin:/root/.cargo/bin:${PATH}"
# Scanners installed in their own layer so they cache until this line changes.
RUN go install golang.org/x/vuln/cmd/govulncheck@latest \
&& cargo install cargo-audit --locked
COPY server/ ./server/
COPY agent/ ./agent/
COPY web/ ./web/
COPY helper/ ./helper/
COPY scripts/ ./scripts/
COPY .govulncheck-allow ./
# exit 1 = real block (fail the build); exit 2 = infra/offline (posture written
# unattested, continue). Guarantee a posture file exists regardless.
RUN rc=0; bash scripts/dep-scan.sh --posture-out /posture-build.json || rc=$?; \
if [ "$rc" = "1" ]; then echo "[ERROR] dep-scan blocked the build"; exit 1; fi; \
test -f /posture-build.json || \
printf '%s' '{"attested":false,"generated_at":0,"substrate":{},"scans":[],"exceptions":[]}' > /posture-build.json
# Stage 1: Build server binary
FROM golang:1.25-alpine AS server-builder
ARG BUILD_VERSION=dev
WORKDIR /app
# Copy go.mod and go.sum
COPY server/go.mod server/go.sum ./
RUN go mod download
# Copy server contents to /app
COPY server/ ./
# Embed the dashboard build — server/internal/webui picks this up via go:embed
COPY --from=web-builder /web/dist ./internal/webui/dist
# Embed the attested supply-chain posture over the committed attested:false stub
# (services/posture.go picks this up via go:embed).
COPY --from=posture-builder /posture-build.json ./internal/services/posture-build.json
# Build server with version injection
RUN echo "Building server version: $BUILD_VERSION" && \
CGO_ENABLED=0 go build \
-ldflags "-X github.com/Fimeg/RedFlag/server/internal/version.AgentVersion=$BUILD_VERSION" \
-o redflag-server ./cmd/server/
# Stage 2: Build agent binaries for all platforms
FROM golang:1.25-alpine AS agent-builder
ARG BUILD_VERSION=dev
WORKDIR /build
# Copy agent source code
COPY agent/ ./
# Build for Linux amd64
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags "-X github.com/Fimeg/RedFlag/agent/internal/version.Version=$BUILD_VERSION" \
-o binaries/linux-amd64/redflag-agent ./cmd/agent
# Build for Linux arm64
RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build \
-ldflags "-X github.com/Fimeg/RedFlag/agent/internal/version.Version=$BUILD_VERSION" \
-o binaries/linux-arm64/redflag-agent ./cmd/agent
# Build for macOS amd64
RUN CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build \
-ldflags "-X github.com/Fimeg/RedFlag/agent/internal/version.Version=$BUILD_VERSION" \
-o binaries/darwin-amd64/redflag-agent ./cmd/agent
# Build for macOS arm64
RUN CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build \
-ldflags "-X github.com/Fimeg/RedFlag/agent/internal/version.Version=$BUILD_VERSION" \
-o binaries/darwin-arm64/redflag-agent ./cmd/agent
# Build for Windows amd64
RUN CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build \
-ldflags "-X github.com/Fimeg/RedFlag/agent/internal/version.Version=$BUILD_VERSION" \
-o binaries/windows-amd64/redflag-agent.exe ./cmd/agent
# Build for Windows arm64
RUN CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build \
-ldflags "-X github.com/Fimeg/RedFlag/agent/internal/version.Version=$BUILD_VERSION" \
-o binaries/windows-arm64/redflag-agent.exe ./cmd/agent
# Stage 2b: Build the Rust capability-gate executor (redflag-helper)
FROM rust:1-alpine AS helper-builder
WORKDIR /helper
# musl-dev provides the C runtime cargo links against on alpine.
RUN apk --no-cache add musl-dev
# Copy helper crate and build a static release binary. ed25519-dalek/sha2/serde
# are pure Rust, so the default x86_64-unknown-linux-musl target builds clean.
COPY helper/ ./
RUN cargo build --release && \
mkdir -p /out/helper-linux-amd64 && \
cp target/release/redflag-helper /out/helper-linux-amd64/redflag-helper
# Stage 2c: Build the Tauri desktop app (system tray + local UI)
FROM rust:1-bookworm AS desktop-builder
ARG BUILD_VERSION=dev
RUN apt-get update && apt-get install -y --no-install-recommends \
libwebkit2gtk-4.1-dev \
libjavascriptcoregtk-4.1-dev \
libappindicator3-dev \
librsvg2-dev \
libgtk-3-dev \
libsoup-3.0-dev \
libglib2.0-dev \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
# Tauri's build embeds frontendDist (../web/dist-desktop) at compile time —
# take the bundle from the Node 20 stage instead of building it here.
COPY --from=web-builder /web/dist-desktop web/dist-desktop
# Copy desktop Tauri source
COPY desktop/ desktop/
# Build the Tauri app in release mode
RUN cd desktop && cargo build --release && \
mkdir -p /out && \
cp target/release/redflag-desktop /out/redflag-desktop
# Stage 3: Final image with server and all agent binaries
FROM alpine:3.21
RUN apk --no-cache add ca-certificates tzdata bash
WORKDIR /app
# Copy server binary
COPY --from=server-builder /app/redflag-server .
COPY --from=server-builder /app/internal/database ./internal/database
# Copy all agent binaries
COPY --from=agent-builder /build/binaries ./binaries
# Copy the signed-at-startup capability-gate executor alongside the agent
# binaries so BuildOrchestrator.SignExistingBinary can find it at
# binaries/helper-linux-amd64/redflag-helper.
COPY --from=helper-builder /out/helper-linux-amd64 ./binaries/helper-linux-amd64
# Copy the Tauri desktop app (system tray + local UI shell)
COPY --from=desktop-builder /out/redflag-desktop ./binaries/linux-amd64/redflag-desktop
# Copy and setup entrypoint script
COPY server/docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/api/health || exit 1
EXPOSE 8080
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["./redflag-server"]