# ============================================================================ # STAGE 1: Build Frontend (React 18 + Bun) # ============================================================================ # This stage builds the React SPA with TypeScript, Tailwind CSS, and Vite # Output: frontend/dist/ with optimized static assets # ============================================================================ FROM oven/bun:latest AS frontend-builder WORKDIR /app/frontend # Copy frontend source COPY frontend/ . # Install dependencies and build frontend # - bun install: Fast package installation # - bun run build: TypeScript type check + Vite optimization RUN bun install && \ bun run build # Verify build output exists RUN test -f dist/index.html || (echo "Frontend build failed!" && exit 1) # ============================================================================ # STAGE 2: Build Backend (Go 1.25) with embedded frontend # ============================================================================ # This stage builds the Go server with the React SPA embedded # Uses Go's embed package to include all static assets # Output: filebrowser binary (statically linked, no dependencies) # ============================================================================ FROM golang:1.25-alpine AS backend-builder WORKDIR /app # Install build dependencies RUN apk add --no-cache git ca-certificates # Copy the entire project COPY . . # Copy built frontend from stage 1 into the frontend Go package # The frontend/frontend.go file uses //go:embed dist to include these files COPY --from=frontend-builder /app/frontend/dist ./frontend/dist # Download Go dependencies RUN go mod download # Build the Go binary with embedded frontend # - CGO_ENABLED=0: Static linking (no libc dependency) # - GOOS=linux: Linux target # - GOARCH=amd64: 64-bit x86 architecture # - The frontend/dist directory is embedded via the //go:embed directive RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o filebrowser . # Verify binary was created and contains embedded assets RUN test -f filebrowser || (echo "Go build failed!" && exit 1) && \ strings filebrowser | grep -q "index.html" || (echo "Frontend not embedded!" && exit 1) # ============================================================================ # STAGE 3: Fetch dependencies and base tools # ============================================================================ FROM alpine:3.23 AS fetcher # install and copy ca-certificates, mailcap, and tini-static; download JSON.sh RUN apk update && \ apk --no-cache add ca-certificates mailcap tini-static && \ wget -O /JSON.sh https://raw.githubusercontent.com/dominictarr/JSON.sh/0d5e5c77365f63809bf6e77ef44a1f34b0e05840/JSON.sh # ============================================================================ # STAGE 4: Final Runtime Image (Minimal) # ============================================================================ # Lightweight runtime using busybox (~50MB total image size) # Only includes the compiled binary and essential runtime dependencies # ============================================================================ FROM busybox:1.37.0-musl # Define non-root user UID and GID for security ENV UID=1000 ENV GID=1000 # Create unprivileged user and group RUN addgroup -g $GID user && \ adduser -D -u $UID -G user user # Copy compiled binary from backend builder # The binary includes the React SPA (frontend/dist) via Go's embed package COPY --chown=user:user --from=backend-builder /app/filebrowser /bin/filebrowser # Copy initialization and configuration scripts COPY --chown=user:user docker/common/ / COPY --chown=user:user docker/alpine/ / # Copy runtime dependencies from fetcher stage COPY --chown=user:user --from=fetcher /sbin/tini-static /bin/tini COPY --from=fetcher /JSON.sh /JSON.sh COPY --from=fetcher /etc/ca-certificates.conf /etc/ca-certificates.conf COPY --from=fetcher /etc/ca-certificates /etc/ca-certificates COPY --from=fetcher /etc/mime.types /etc/mime.types COPY --from=fetcher /etc/ssl /etc/ssl # Create persistent data directories with proper ownership RUN mkdir -p /config /database /srv && \ chown -R user:user /config /database /srv && \ chmod +x /healthcheck.sh /init.sh # Health check configuration # Verifies the frontend is being served correctly HEALTHCHECK --start-period=2s --interval=5s --timeout=3s CMD /healthcheck.sh # Run as non-root user USER user # Define persistent volumes # /srv: User file storage # /config: Configuration files # /database: SQLite database VOLUME /srv /config /database # Expose port 80 (internal server runs on 8080, init.sh proxies if needed) EXPOSE 80 # Use tini as init process for proper signal handling # Then run init.sh which starts the filebrowser binary ENTRYPOINT [ "tini", "--", "/init.sh" ]