Dockerfile
Rama master · réplica de lectura del repositorio original.
# syntax=docker/dockerfile:1.28
# Unified multi-target Dockerfile for all pillar-csi components.
#
# Targets:
# controller — CSI controller (distroless, no runtime deps)
# agent — pillar-agent gRPC server (alpine + ZFS + LVM2 + NFS server)
# node — CSI node plugin (alpine + mount/NFS utils + e2fsprogs + xfsprogs)
#
# Usage:
# docker buildx bake # build all 3 images
# docker buildx bake controller # build controller only
# docker build --target=agent . # build agent only
#
# Security posture (all targets):
# - Non-root default user: UID/GID 65532 ("nonroot" convention)
# - Statically-linked Go binary (CGO_ENABLED=0), -trimpath
# - Versions pinned; update SHA digests when bumping
#
# See docker-bake.hcl for the build matrix and CI integration.
# ── Builder stage ─────────────────────────────────────────────────────────────
FROM --platform=$BUILDPLATFORM golang:1.27.3-alpine3.24 AS builder
WORKDIR /workspace
# Download dependencies first to maximise build-cache hits on source changes.
COPY go.mod go.sum ./
RUN go mod download
COPY . .
ARG TARGETOS
ARG TARGETARCH
# TARGETVARIANT is set by buildx for sub-architecture levels (e.g. "v3" for
# linux/amd64/v3, "v7" for linux/arm/v7, "v8.2" for linux/arm64/v8.2). Empty
# for the architecture default (linux/amd64, linux/arm64).
ARG TARGETVARIANT
# Build all binaries under cmd/ in one invocation. The -o flag with a
# directory target writes each binary (named after its package directory)
# into that directory. This works with cross-compilation unlike go install
# which rejects GOBIN when GOOS/GOARCH differ from the host.
#
# Map the BuildKit TARGETVARIANT to the matching Go toolchain env var so each
# manifest in the multi-platform image carries a binary tuned for that
# instruction-set level:
# amd64/v1..v4 → GOAMD64 (Go 1.21+; default v1)
# arm/v6,v7 → GOARM (strip the leading 'v')
# arm64/v8.0..v9.5 → GOARM64 (Go 1.23+; default v8.0)
RUN --mount=type=cache,target=/root/.cache/go-build set -eu; \
GOAMD64=""; GOARM=""; GOARM64=""; \
case "$TARGETARCH" in \
amd64) GOAMD64="${TARGETVARIANT:-v1}" ;; \
arm) GOARM="${TARGETVARIANT#v}" ;; \
arm64) GOARM64="${TARGETVARIANT:-v8.0}" ;; \
esac; \
CGO_ENABLED=0 GOOS="${TARGETOS:-linux}" GOARCH="${TARGETARCH}" \
GOAMD64="$GOAMD64" GOARM="$GOARM" GOARM64="$GOARM64" \
go build \
-trimpath \
-pgo=auto \
-ldflags="-s -w" \
-o /workspace/bin/ \
./cmd/...
# ── Runtime: controller ───────────────────────────────────────────────────────
# Distroless — no shell, no package manager, minimal attack surface.
FROM gcr.io/distroless/static:nonroot AS controller
COPY --from=builder --link /workspace/bin/controller /usr/bin/manager
USER 65532:65532
ENTRYPOINT ["/usr/bin/manager"]
# ── Runtime: agent ────────────────────────────────────────────────────────────
# Alpine + ZFS + LVM2 + nfs-utils userspace tools. The agent invokes zfs(8),
# zpool(8), lvcreate(8), lvremove(8), rpc.mountd(8) and nfsdcld(8), plus
# exportfs(8) from its relocated /usr/libexec/pillar-csi path (see below).
# NVMe-oF uses configfs directly; NFS uses the host kernel nfsd and a supervised
# userspace NFSv4 client-recovery daemon with private persistent state.
#
# Runtime security (enforced in the DaemonSet manifest):
# --security-opt=no-new-privileges:true
# --read-only (combine with tmpfs mounts for /tmp, /run)
# --cap-drop ALL --cap-add SYS_ADMIN (ZFS + configfs need SYS_ADMIN)
FROM alpine:3.24.3 AS agent
RUN set -eux \
&& apk add --no-cache 'zfs~=2.4' lvm2 nfs-utils blkid \
# OpenZFS libshare runs the hard-coded `/usr/sbin/exportfs -ra` after every
# zfs create/mount/destroy whenever /usr/sbin/exportfs exists
# (lib/libshare/os/linux/nfs.c), rewriting /var/lib/nfs/etab from the empty
# /etc/exports and wiping pillar's export rows. Move exportfs out of that
# path so libzfs treats NFS sharing as unavailable (a no-op commit) while
# the agent keeps calling it via its absolute relocated path. The `test`
# is the build-time guard: this is the final agent layer, apk is removed
# below, and no later instruction reinstalls nfs-utils.
&& install -d -m 0755 /usr/libexec/pillar-csi \
&& mv /usr/sbin/exportfs /usr/libexec/pillar-csi/exportfs \
&& test ! -e /usr/sbin/exportfs \
# Configure LVM for container environments where udevd is not running.
&& sed -i 's/obtain_device_list_from_udev = 1/obtain_device_list_from_udev = 0/' /etc/lvm/lvm.conf \
&& sed -i 's/udev_sync = 1/udev_sync = 0/' /etc/lvm/lvm.conf \
&& sed -i 's/udev_rules = 1/udev_rules = 0/' /etc/lvm/lvm.conf \
&& addgroup -g 65532 nonroot \
&& adduser -u 65532 -G nonroot -s /sbin/nologin -D nonroot \
# Strip SUID/SGID bits from every file on the root filesystem.
&& (find / -xdev \( -perm -4000 -o -perm -2000 \) -exec chmod a-s {} + 2>/dev/null || true) \
# Remove package manager (prevents `apk add` at runtime).
&& rm -rf /sbin/apk /etc/apk /lib/apk /usr/share/apk /var/lib/apk \
# Remove shell (no interactive escape path).
&& rm -f /bin/sh /bin/bash /usr/bin/env
COPY --from=builder --link --chmod=0555 /workspace/bin/agent /usr/bin/pillar-agent
USER 65532:65532
EXPOSE 2049/tcp
EXPOSE 9500
ENTRYPOINT ["/usr/bin/pillar-agent"]
# ── Runtime: node ─────────────────────────────────────────────────────────────
# Alpine + mount utilities (util-linux) + ext4/XFS formatting and resize tools
# (e2fsprogs, xfsprogs). xfsprogs-extra carries xfs_growfs on Alpine, and the
# mkfs.xfs LTS profiles under /usr/share/xfsprogs/mkfs: the node plugin formats
# XFS with the Linux 5.15 one so every supported node kernel can mount the
# volume. hack/verify-mkfs-baseline.sh fails the build when the image's mkfs
# would create a filesystem Linux 5.15 cannot mount.
# device-mapper carries dmsetup, which holds the backend device of a local
# attach on the storage node through a linear target.
# nfs-utils supplies mount.nfs for NFSv4.2 stages without host package installs.
#
# Runtime security (enforced in the DaemonSet manifest):
# --security-opt=no-new-privileges:true
# --read-only (combine with tmpfs mounts for /tmp, /run)
# --cap-drop ALL --cap-add SYS_ADMIN (mount(8) and NVMe-oF need SYS_ADMIN)
FROM alpine:3.24.3 AS node
RUN --mount=type=bind,source=hack/verify-mkfs-baseline.sh,target=/tmp/verify-mkfs-baseline.sh \
set -eux \
&& apk add --no-cache \
'util-linux~=2.42' \
'e2fsprogs~=1.47' \
'e2fsprogs-extra~=1.47' \
'xfsprogs~=7.0' \
'xfsprogs-extra~=7.0' \
'device-mapper~=2.03' \
'quota-tools~=4.11' \
nfs-utils; \
# A separate command: the "|| true" below would mask a failure in the chain.
sh /tmp/verify-mkfs-baseline.sh; \
addgroup -g 65532 nonroot \
&& adduser -u 65532 -G nonroot -s /sbin/nologin -D nonroot \
# Strip SUID/SGID bits from every file on the root filesystem.
&& find / -xdev \( -perm -4000 -o -perm -2000 \) -exec chmod a-s {} + 2>/dev/null || true \
# Remove package manager (prevents `apk add` at runtime).
&& rm -rf /sbin/apk /etc/apk /lib/apk /usr/share/apk /var/lib/apk \
# Remove shell (no interactive escape path).
&& rm -f /bin/sh /bin/bash /usr/bin/env
COPY --from=builder --link --chmod=0555 /workspace/bin/node /usr/bin/pillar-node
USER 65532:65532
ENTRYPOINT ["/usr/bin/pillar-node"]