xutyxd/node-quark

By xutyxd

•Updated 1 day ago

Node-Quark delivers secure, distroless Node.js Docker images based on scratch

Image
Security
Developer tools
Web servers
0

3.1K

xutyxd/node-quark repository overview

⁠ diskio logo Node Quark

diskio logo ⁠

Base Image Docker Pulls Docker Image Size Architectures Node Version Security GitHub Actions Signed with Cosign

Ultra-minimal, multi-architecture Node.js containers built from scratch

Node-Quark delivers secure, distroless Node.js Docker images based on scratch rather than full Linux distributions. By dynamically extracting only the essential libraries required by Node.js from Alpine Linux, we achieve images that are ~35MB (compared to ~180MB+ for standard Alpine Node images) while maintaining full HTTPS, crypto, and ICU (internationalization) support.

⁠✨ Features

  • 🪶 Minimal Attack Surface: Based on scratch (empty image), not Alpine or Debian
  • šŸ“¦ Dynamic Dependency Extraction: Automatically detects and bundles only required libraries using ldd
  • šŸ—ļø Multi-Architecture: Native support for linux/amd64 and linux/arm64/v8 (Apple Silicon, AWS Graviton, Raspberry Pi)
  • šŸ”’ Security Hardened: Non-root user (uid:1000), no shell, no package manager, minimal filesystem
  • šŸ·ļø Semantic Tagging: Automatic version extraction and multi-tag strategy (major, minor, patch)
  • šŸ”„ Multi-Version Support: Node.js 20 (LTS), 22 (LTS), 24 (LTS) and Edge (latest development)
  • šŸ—œļø Compression: UPX compression support for even smaller binaries (trade-off: startup time)

ā šŸƒ Quick Start

# Pull the latest LTS version (Node 24)
docker pull xutyxd/node-quark:24

# Run a quick test
docker run --rm xutyxd/node-quark:24 -e "console.log('Hello from scratch!')"

# Check the size (should be ~60MB vs 180MB+ for node:24-alpine)
docker images xutyxd/node-quark:24

# Multi-arch support (automatically selects correct platform)
docker run --rm xutyxd/node-quark:24 -p "process.arch"

⁠Available Tags

TagNode VersionAlpine BaseSizePlatform
20, 20.15, 20.15.1, 20.15.1-alpine3.2020.x3.20Sizeamd64, arm64
22, 22.22, 22.22.2, 22.22.2-alpine3.22, latests22.x3.22Sizeamd64, arm64
24, 24.1, 24.1.0, 24.1.0-alpine3.24, latests24.x3.24Sizeamd64, arm64
edge, 20260127-edge20260127 (dev)edgeSizeamd64, arm64

ā šŸ—ļø Architecture

How It Works

Traditional Node.js images include a full OS (Alpine, Debian, etc.) with shells, package managers, and hundreds of unused files. Node-Quark takes a different approach:

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  Stage 1: Builder (Alpine Linux)            │
│  ā”œā”€ā”€ Install Node.js from Alpine repos      │
│  ā”œā”€ā”€ Install UPX                            │
│  ā”œā”€ā”€ Run `ldd /usr/bin/node`                │
│  ā”œā”€ā”€ Extract all linked libraries           │
│  └── Copy to /rootfs structure              │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
                   │
                   ā–¼
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  Stage 2: Runner (Scratch)                  │
│  ā”œā”€ā”€ Copy /rootfs (Node + libs only)        │
│  ā”œā”€ā”€ CA certificates for HTTPS              │
│  ā”œā”€ā”€ Non-root user (1000:1000)              │
│  └── No shell, no package manager           │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Dynamic Dependency Extraction

Unlike other distroless images that hardcode library paths (which break when Node.js updates), we use ldd to automatically detect the exact libraries needed:

# Capture every library Node.js links against
ldd /usr/bin/node | grep -o '/[^ ]*' | sort -u > /tmp/libs.txt

# Copy each library while preserving directory structure
while IFS= read -r lib; do
    cp -L "$lib" "/rootfs$lib"
done < /tmp/libs.txt

This ensures compatibility across Alpine versions (3.20, 3.22, edge) without manual updates when dependencies change.

Security Features

  • Non-root execution: Runs as node (uid 1000), not root
  • Read-only filesystem: No write permissions except /tmp (if mounted)
  • No shell access: scratch images have no /bin/sh
  • Minimal libraries: Only musl libc, OpenSSL, ICU, and compression libs
  • CA certificates: Bundled Mozilla CA bundle for HTTPS verification

ā šŸ› ļø Usage in Your Projects

Dockerfile integration
Node-Quark is designed to be used as runner in your Dockerfiles. It's a good idea to use a multi-stage build to reduce the size of your final image.

# Use node:alpine as base
# It has npm, bash, and other utilities
FROM alpine AS builder
# Download and install Node.js
RUN apk add --no-cache nodejs npm

WORKDIR /user/src/app

ADD . .

RUN npm install
RUN npm run openapi:bundle
RUN npm run server:build
RUN npm run clean

# --------
FROM alpine:3.24 AS tools

RUN mkdir -p /usr/src/app && \
    chown -R 1000:1000 /usr/src/app

# --------

FROM xutyxd/node-quark:24 AS runner

COPY --from=tools /usr/src/app /usr/src/app

WORKDIR /usr/src/app

COPY --from=builder /user/src/app/server/cjs /user/src/app/server/cjs
COPY --from=builder /user/src/app/server/openapi /user/src/app/server/openapi
COPY --from=builder /user/src/app/package.json /user/src/app/package.json
COPY --from=builder /user/src/app/node_modules /user/src/app/node_modules
# As ENTRYPOINT is directly /bin/node, only need to point to the main file
CMD ["./server/cjs/index.js"]

EXPOSE 8080

ā šŸ“Š Size Comparison

ImageSizeBaseSecurity
node:24~1.13GBDebian BookwormMany CVEs
node:24-alpine~164MBAlpine 3.21Minimal CVEs
node:24-slim~227MBDebian SlimMedium CVEs
xutyxd/node-quark:24SizeScratchMinimal attack surface

ā šŸ” Verifying Image Signatures

All published images are signed using Sigstore Cosign⁠ with keyless signing via GitHub Actions OIDC. You can verify the provenance and SBOM without installing anything except Docker.

⁠Verify image attestation (includes SBOM)
docker run --rm \
  ghcr.io/sigstore/cosign/cosign:v2.4.1 \
  verify-attestation \
  --certificate-identity=https://github.com/xutyxd/node-quark/.github/workflows/docker.yml@refs/heads/main \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  --type spdx \
  xutyxd/node-quark:24

⁠🚨 Known Limitations

  • No Native Compilation: If your app requires node-gyp or native modules, build them in a separate stage with node:alpine and copy the compiled .node files.
  • No npm install in Runner: The scratch image includes npm but no git, python, or build tools. Install dependencies in a builder stage.
  • File Watching: fs.watch may behave differently without inotify (some fallbacks to polling).

ā šŸ™ Acknowledgments

  • Inspired by Google Distroless but with dynamic dependency resolution
  • Built on Alpine Linux for the builder stage
  • Compression is provided by UPX⁠
  • Node.js is a trademark of the OpenJS Foundation

Tag summary

Content type

Image

Digest

sha256:dca052255…

Size

13.3 kB

Last updated

1 day ago

docker pull xutyxd/node-quark:sha256-eb67eba8a0c9172076872f37052749a49a04ca86021a0dc4aec02d8770e0d8ee.att