mirror of
https://github.com/Finsys/dockhand.git
synced 2026-06-17 19:09:33 +03:00
v1.0.19
This commit is contained in:
+40
-68
@@ -1,30 +1,21 @@
|
||||
# syntax=docker/dockerfile:1.4
|
||||
# =============================================================================
|
||||
# Dockhand Docker Image - Security-Hardened Build
|
||||
# Dockhand Docker Image - Node.js Runtime (Security-Hardened Build)
|
||||
# =============================================================================
|
||||
# This Dockerfile builds a custom Wolfi OS from scratch using apko, ensuring:
|
||||
# - Full transparency (no dependency on pre-built Chainguard images)
|
||||
# - Reproducible builds from open-source Wolfi packages
|
||||
# - Minimal attack surface with only required packages
|
||||
#
|
||||
# Bun is copied from the official oven/bun image (app-builder stage).
|
||||
# For CPUs without AVX support (Celeron, Atom, pre-Haswell), build with:
|
||||
# docker build --build-arg BUN_VARIANT=baseline -t dockhand:baseline .
|
||||
# Uses Node.js instead of Bun to eliminate BoringSSL native memory leaks
|
||||
# on mTLS connections. Same Wolfi-based security-hardened OS.
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 1: OS Generator (Alpine + apko tool)
|
||||
# -----------------------------------------------------------------------------
|
||||
# We use Alpine because it has a shell. This lets us download and run apko
|
||||
# to build our custom Wolfi OS from scratch using open-source packages.
|
||||
FROM alpine:3.21 AS os-builder
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
# Install apko tool (latest stable release)
|
||||
# apko is the tool Chainguard uses to build their images - we use it directly
|
||||
# Install apko tool
|
||||
ARG APKO_VERSION=0.30.34
|
||||
RUN apk add --no-cache curl unzip \
|
||||
&& ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "arm64" || echo "amd64") \
|
||||
@@ -32,9 +23,7 @@ RUN apk add --no-cache curl unzip \
|
||||
| tar -xz --strip-components=1 -C /usr/local/bin \
|
||||
&& chmod +x /usr/local/bin/apko
|
||||
|
||||
# Generate apko.yaml for current target architecture only
|
||||
# We build single-arch to avoid multi-arch layer confusion in extraction
|
||||
# Note: Bun is NOT included here - it's copied from app-builder stage for CPU compatibility
|
||||
# Generate apko.yaml — Node.js instead of Bun
|
||||
RUN APKO_ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "aarch64" || echo "x86_64") \
|
||||
&& printf '%s\n' \
|
||||
"contents:" \
|
||||
@@ -47,6 +36,7 @@ RUN APKO_ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "aarch64" || echo "x86_64")
|
||||
" - ca-certificates" \
|
||||
" - busybox" \
|
||||
" - tzdata" \
|
||||
" - nodejs-24" \
|
||||
" - docker-cli" \
|
||||
" - docker-compose" \
|
||||
" - docker-cli-buildx" \
|
||||
@@ -58,6 +48,8 @@ RUN APKO_ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "aarch64" || echo "x86_64")
|
||||
" - curl" \
|
||||
" - tini" \
|
||||
" - su-exec" \
|
||||
" - glibc" \
|
||||
" - libstdc++" \
|
||||
"entrypoint:" \
|
||||
" command: /bin/sh -l" \
|
||||
"archs:" \
|
||||
@@ -65,7 +57,6 @@ RUN APKO_ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "aarch64" || echo "x86_64")
|
||||
> apko.yaml
|
||||
|
||||
# Build the OS tarball and extract rootfs
|
||||
# apko creates an OCI tarball - we need to extract the actual filesystem layer
|
||||
RUN apko build apko.yaml dockhand-base:latest output.tar \
|
||||
&& mkdir -p rootfs \
|
||||
&& tar -xf output.tar \
|
||||
@@ -73,67 +64,46 @@ RUN apko build apko.yaml dockhand-base:latest output.tar \
|
||||
&& tar -xzf "$LAYER" -C rootfs
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 2: Application Builder
|
||||
# Stage 2: Application Builder (pure Node.js)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Using Debian to avoid Alpine musl thread creation issues
|
||||
# Alpine's musl libc causes rayon/tokio thread pool panics during svelte-adapter-bun build
|
||||
FROM oven/bun:1.3.5-debian AS app-builder
|
||||
|
||||
# Build argument for Bun variant (regular or baseline)
|
||||
# baseline is for CPUs without AVX support (Celeron, Atom, pre-Haswell)
|
||||
ARG BUN_VARIANT=regular
|
||||
ARG TARGETARCH
|
||||
FROM node:24-slim AS app-builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install build dependencies
|
||||
# libnss-wrapper: needed for git SSH with arbitrary UIDs on read-only containers (getpwuid workaround)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends jq git curl unzip ca-certificates libnss-wrapper && rm -rf /var/lib/apt/lists/* \
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
jq git curl python3 make g++ libnss-wrapper \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& cp "$(dpkg -L libnss-wrapper | grep 'libnss_wrapper\.so$')" /usr/local/lib/libnss_wrapper.so
|
||||
|
||||
# Copy package files and install ALL dependencies (needed for build)
|
||||
COPY package.json bun.lock* bunfig.toml ./
|
||||
RUN bun install --frozen-lockfile
|
||||
# Copy package files and install dependencies
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Copy source code and build
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Build the application
|
||||
RUN NODE_OPTIONS="--max-old-space-size=8192 --max-semi-space-size=128" bun run build
|
||||
# Production dependencies only (rebuilds native addons like better-sqlite3)
|
||||
RUN rm -rf node_modules \
|
||||
&& npm ci --omit=dev \
|
||||
&& rm -rf node_modules/@types
|
||||
|
||||
# Prepare production node_modules (do this in builder where we have compilers)
|
||||
# This ensures native addons compile correctly before copying to hardened runtime
|
||||
RUN rm -rf node_modules && bun install --production --frozen-lockfile \
|
||||
&& rm -rf node_modules/@types node_modules/bun-types
|
||||
|
||||
# Download baseline Bun binary if BUN_VARIANT=baseline (for CPUs without AVX)
|
||||
# Only applies to amd64 - ARM64 doesn't have AVX concept
|
||||
ARG BUN_VERSION=1.3.5
|
||||
RUN if [ "$BUN_VARIANT" = "baseline" ] && [ "$TARGETARCH" = "amd64" ]; then \
|
||||
echo "Downloading Bun baseline binary for CPUs without AVX support..." && \
|
||||
curl -fsSL "https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/bun-linux-x64-baseline.zip" -o /tmp/bun.zip && \
|
||||
unzip -o /tmp/bun.zip -d /tmp && \
|
||||
cp /tmp/bun-linux-x64-baseline/bun /usr/local/bin/bun && \
|
||||
chmod +x /usr/local/bin/bun && \
|
||||
rm -rf /tmp/bun.zip /tmp/bun-linux-x64-baseline && \
|
||||
echo "Bun baseline binary installed successfully"; \
|
||||
fi
|
||||
# Build Go collector
|
||||
FROM golang:1.24 AS go-builder
|
||||
WORKDIR /app
|
||||
COPY collector/ ./collector/
|
||||
RUN cd collector && CGO_ENABLED=0 go build -o /app/bin/collection-worker .
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 3: Final Image (Scratch + Custom Wolfi OS)
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM scratch
|
||||
|
||||
# Install our custom-built Wolfi OS (now we have /bin/sh!)
|
||||
# Install custom Wolfi OS with Node.js
|
||||
COPY --from=os-builder /work/rootfs/ /
|
||||
|
||||
# Copy Bun from official image - ensures compatibility with all x86_64 CPUs (no AVX2 requirement)
|
||||
# Wolfi's bun package requires AVX2 which breaks on Celeron/Atom CPUs
|
||||
# For baseline builds (BUN_VARIANT=baseline), this contains the baseline binary (no AVX requirement)
|
||||
# For regular builds, this contains the standard oven/bun binary
|
||||
COPY --from=app-builder /usr/local/bin/bun /usr/bin/bun
|
||||
|
||||
# Copy libnss_wrapper for git SSH with arbitrary UIDs (same cross-copy pattern as Bun above)
|
||||
# Copy libnss_wrapper for git SSH with arbitrary UIDs
|
||||
COPY --from=app-builder /usr/local/lib/libnss_wrapper.so /usr/lib/libnss_wrapper.so
|
||||
|
||||
WORKDIR /app
|
||||
@@ -149,20 +119,22 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
|
||||
PUID=1001 \
|
||||
PGID=1001
|
||||
|
||||
# Create docker compose plugin symlink (we use `docker compose` syntax, Wolfi has standalone binary)
|
||||
# Note: docker-cli-buildx package already creates the buildx symlink
|
||||
# Create docker compose plugin symlink
|
||||
RUN mkdir -p /usr/libexec/docker/cli-plugins \
|
||||
&& ln -s /usr/bin/docker-compose /usr/libexec/docker/cli-plugins/docker-compose
|
||||
&& ln -sf /usr/bin/docker-compose /usr/libexec/docker/cli-plugins/docker-compose
|
||||
|
||||
# Create dockhand user and group (using busybox commands)
|
||||
# Create dockhand user and group
|
||||
RUN addgroup -g 1001 dockhand \
|
||||
&& adduser -u 1001 -G dockhand -h /home/dockhand -D dockhand
|
||||
|
||||
# Copy application files with correct ownership (avoids layer duplication from chown -R)
|
||||
# Copy application files with correct ownership
|
||||
COPY --from=app-builder --chown=dockhand:dockhand /app/node_modules ./node_modules
|
||||
COPY --from=app-builder --chown=dockhand:dockhand /app/package.json ./
|
||||
COPY --from=app-builder --chown=dockhand:dockhand /app/build ./build
|
||||
COPY --from=app-builder --chown=dockhand:dockhand /app/build/subprocesses/ ./subprocesses/
|
||||
COPY --from=app-builder --chown=dockhand:dockhand /app/server.js ./
|
||||
|
||||
# Copy Go collector binary
|
||||
COPY --from=go-builder --chown=dockhand:dockhand /app/bin/collection-worker ./bin/collection-worker
|
||||
|
||||
# Copy database migrations
|
||||
COPY --chown=dockhand:dockhand drizzle/ ./drizzle/
|
||||
@@ -171,15 +143,15 @@ COPY --chown=dockhand:dockhand drizzle-pg/ ./drizzle-pg/
|
||||
# Copy legal documents
|
||||
COPY --chown=dockhand:dockhand LICENSE.txt PRIVACY.txt ./
|
||||
|
||||
# Copy entrypoint script (root-owned, executable)
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
# Copy entrypoint script
|
||||
COPY docker-entrypoint-node.sh /usr/local/bin/docker-entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
# Copy emergency scripts
|
||||
COPY --chown=dockhand:dockhand scripts/emergency/ ./scripts/
|
||||
RUN chmod +x ./scripts/*.sh ./scripts/**/*.sh 2>/dev/null || true
|
||||
|
||||
# Create data directories with correct ownership
|
||||
# Create data directories
|
||||
RUN mkdir -p /home/dockhand/.dockhand/stacks /app/data \
|
||||
&& chown dockhand:dockhand /app/data /home/dockhand /home/dockhand/.dockhand /home/dockhand/.dockhand/stacks
|
||||
|
||||
@@ -189,4 +161,4 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:3000/ || exit 1
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]
|
||||
CMD ["bun", "run", "./build/index.js"]
|
||||
CMD ["node", "/app/server.js"]
|
||||
|
||||
Reference in New Issue
Block a user