diff --git a/.github/workflows/official-pr.yml b/.github/workflows/official-pr.yml index a1429eb090..2c6aeaa125 100644 --- a/.github/workflows/official-pr.yml +++ b/.github/workflows/official-pr.yml @@ -10,7 +10,7 @@ on: - '**/Dockerfile' - '**/docker-entrypoint.sh' - 'versions.json' - - 'stackbrew.js' + - 'stackbrew.mjs' jobs: pr: @@ -36,7 +36,7 @@ jobs: - name: Generate Stackbrew for diff run: | cd docker-node - ./stackbrew.js > ../official-images/library/node + ./stackbrew.mjs ../official-images/library/node - name: Create PR in official-images id: create-pr diff --git a/27/alpine3.23/Dockerfile b/27/alpine3.23/Dockerfile new file mode 100644 index 0000000000..5f71eee2b7 --- /dev/null +++ b/27/alpine3.23/Dockerfile @@ -0,0 +1,87 @@ +FROM alpine:3.23 + +ENV NODE_VERSION=27.0.0-alpha.0.0.0 + +RUN addgroup -g 1000 node \ + && adduser -u 1000 -G node -s /bin/sh -D node \ + && apk add --no-cache \ + libatomic \ + libstdc++ \ + && apk add --no-cache --virtual .build-deps \ + gnupg \ + curl \ + && ARCH= OPENSSL_ARCH='linux*' && alpineArch="$(apk --print-arch)" \ + && case "${alpineArch##*-}" in \ + x86_64) ARCH='x64' OPENSSL_ARCH=linux-x86_64;; \ + aarch64) OPENSSL_ARCH=linux-aarch64;; \ + *) echo "unsupported architecture"; exit 1 ;; \ + esac \ + # use pre-existing gpg directory, see https://github.com/nodejs/docker-node/pull/1895#issuecomment-1550389150 + && export GNUPGHOME="$(mktemp -d)" \ + # gpg keys listed at https://github.com/nodejs/node#release-keys + && set -ex \ + && for key in \ + 5BE8A3F6C8A5C01D106C0AD820B1A390B168D356 \ + DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7 \ + CC68F5A3106FF448322E48ED27F5E38D5B0A215F \ + 890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4 \ + C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C \ + 108F52B48DB57BB0CC439B2997B01419BD92F80A \ + 655F3B5C1FB3FA8D1A0CA6BDE4A7D232B936D2FD \ + A363A499291CBBC940DD62E41F10027AF002F8B0 \ + ; do \ + { gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" && gpg --batch --fingerprint "$key"; } || \ + { gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" && gpg --batch --fingerprint "$key"; } ; \ + done \ + && if [ "$ARCH" = "x64" ]; then \ + curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz" \ + && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \ + && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \ + && gpgconf --kill all \ + && rm -rf "$GNUPGHOME" \ + && grep " node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz\$" SHASUMS256.txt | sha256sum -c - \ + && tar -xJf "node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \ + && rm "node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \ + && ln -s /usr/local/bin/node /usr/local/bin/nodejs; \ + else \ + echo "Building from source" \ + && apk add --no-cache --virtual .build-deps-full \ + binutils-gold \ + g++ \ + gcc \ + libgcc \ + linux-headers \ + make \ + python3 \ + py-setuptools \ + rust \ + cargo \ + && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION.tar.xz" \ + && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \ + && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \ + && gpgconf --kill all \ + && rm -rf "$GNUPGHOME" \ + && grep " node-v$NODE_VERSION.tar.xz\$" SHASUMS256.txt | sha256sum -c - \ + && tar -xf "node-v$NODE_VERSION.tar.xz" \ + && cd "node-v$NODE_VERSION" \ + && ./configure \ + && make -j$(getconf _NPROCESSORS_ONLN) V= \ + && make install \ + && apk del .build-deps-full \ + && cd .. \ + && rm -Rf "node-v$NODE_VERSION" \ + && rm "node-v$NODE_VERSION.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt; \ + fi \ + # Remove unused OpenSSL headers to save ~34MB. See this NodeJS issue: https://github.com/nodejs/node/issues/46451 + && find /usr/local/include/node/openssl/archs -mindepth 1 -maxdepth 1 ! -name "$OPENSSL_ARCH" -exec rm -rf {} \; \ + && apk del .build-deps \ + # smoke tests + && node --version \ + && npm --version \ + && rm -rf /tmp/* + + +COPY docker-entrypoint.sh /usr/local/bin/ +ENTRYPOINT ["docker-entrypoint.sh"] + +CMD [ "node" ] diff --git a/27/alpine3.23/docker-entrypoint.sh b/27/alpine3.23/docker-entrypoint.sh new file mode 100755 index 0000000000..1b3116e53b --- /dev/null +++ b/27/alpine3.23/docker-entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +# Run command with node if the first argument contains a "-" or is not a system command. The last +# part inside the "{}" is a workaround for the following bug in ash/dash: +# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=874264 +if [ "${1#-}" != "${1}" ] || [ -z "$(command -v "${1}")" ] || { [ -f "${1}" ] && ! [ -x "${1}" ]; }; then + set -- node "$@" +fi + +exec "$@" diff --git a/27/alpine3.24/Dockerfile b/27/alpine3.24/Dockerfile new file mode 100644 index 0000000000..713db86e90 --- /dev/null +++ b/27/alpine3.24/Dockerfile @@ -0,0 +1,87 @@ +FROM alpine:3.24 + +ENV NODE_VERSION=27.0.0-alpha.0.0.0 + +RUN addgroup -g 1000 node \ + && adduser -u 1000 -G node -s /bin/sh -D node \ + && apk add --no-cache \ + libatomic \ + libstdc++ \ + && apk add --no-cache --virtual .build-deps \ + gnupg \ + curl \ + && ARCH= OPENSSL_ARCH='linux*' && alpineArch="$(apk --print-arch)" \ + && case "${alpineArch##*-}" in \ + x86_64) ARCH='x64' OPENSSL_ARCH=linux-x86_64;; \ + aarch64) OPENSSL_ARCH=linux-aarch64;; \ + *) echo "unsupported architecture"; exit 1 ;; \ + esac \ + # use pre-existing gpg directory, see https://github.com/nodejs/docker-node/pull/1895#issuecomment-1550389150 + && export GNUPGHOME="$(mktemp -d)" \ + # gpg keys listed at https://github.com/nodejs/node#release-keys + && set -ex \ + && for key in \ + 5BE8A3F6C8A5C01D106C0AD820B1A390B168D356 \ + DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7 \ + CC68F5A3106FF448322E48ED27F5E38D5B0A215F \ + 890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4 \ + C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C \ + 108F52B48DB57BB0CC439B2997B01419BD92F80A \ + 655F3B5C1FB3FA8D1A0CA6BDE4A7D232B936D2FD \ + A363A499291CBBC940DD62E41F10027AF002F8B0 \ + ; do \ + { gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" && gpg --batch --fingerprint "$key"; } || \ + { gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" && gpg --batch --fingerprint "$key"; } ; \ + done \ + && if [ "$ARCH" = "x64" ]; then \ + curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz" \ + && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \ + && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \ + && gpgconf --kill all \ + && rm -rf "$GNUPGHOME" \ + && grep " node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz\$" SHASUMS256.txt | sha256sum -c - \ + && tar -xJf "node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \ + && rm "node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \ + && ln -s /usr/local/bin/node /usr/local/bin/nodejs; \ + else \ + echo "Building from source" \ + && apk add --no-cache --virtual .build-deps-full \ + binutils-gold \ + g++ \ + gcc \ + libgcc \ + linux-headers \ + make \ + python3 \ + py-setuptools \ + rust \ + cargo \ + && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION.tar.xz" \ + && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \ + && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \ + && gpgconf --kill all \ + && rm -rf "$GNUPGHOME" \ + && grep " node-v$NODE_VERSION.tar.xz\$" SHASUMS256.txt | sha256sum -c - \ + && tar -xf "node-v$NODE_VERSION.tar.xz" \ + && cd "node-v$NODE_VERSION" \ + && ./configure \ + && make -j$(getconf _NPROCESSORS_ONLN) V= \ + && make install \ + && apk del .build-deps-full \ + && cd .. \ + && rm -Rf "node-v$NODE_VERSION" \ + && rm "node-v$NODE_VERSION.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt; \ + fi \ + # Remove unused OpenSSL headers to save ~34MB. See this NodeJS issue: https://github.com/nodejs/node/issues/46451 + && find /usr/local/include/node/openssl/archs -mindepth 1 -maxdepth 1 ! -name "$OPENSSL_ARCH" -exec rm -rf {} \; \ + && apk del .build-deps \ + # smoke tests + && node --version \ + && npm --version \ + && rm -rf /tmp/* + + +COPY docker-entrypoint.sh /usr/local/bin/ +ENTRYPOINT ["docker-entrypoint.sh"] + +CMD [ "node" ] diff --git a/27/alpine3.24/docker-entrypoint.sh b/27/alpine3.24/docker-entrypoint.sh new file mode 100755 index 0000000000..1b3116e53b --- /dev/null +++ b/27/alpine3.24/docker-entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +# Run command with node if the first argument contains a "-" or is not a system command. The last +# part inside the "{}" is a workaround for the following bug in ash/dash: +# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=874264 +if [ "${1#-}" != "${1}" ] || [ -z "$(command -v "${1}")" ] || { [ -f "${1}" ] && ! [ -x "${1}" ]; }; then + set -- node "$@" +fi + +exec "$@" diff --git a/27/bookworm-slim/Dockerfile b/27/bookworm-slim/Dockerfile new file mode 100644 index 0000000000..64cadb3836 --- /dev/null +++ b/27/bookworm-slim/Dockerfile @@ -0,0 +1,63 @@ +FROM debian:bookworm-slim + +RUN groupadd --gid 1000 node \ + && useradd --uid 1000 --gid node --shell /bin/bash --create-home node + +ENV NODE_VERSION=27.0.0-alpha.0.0.0 + +RUN ARCH= OPENSSL_ARCH= && dpkgArch="$(dpkg --print-architecture)" \ + && case "${dpkgArch##*-}" in \ + amd64) ARCH='x64' OPENSSL_ARCH='linux-x86_64';; \ + ppc64el) ARCH='ppc64le' OPENSSL_ARCH='linux-ppc64le';; \ + arm64) ARCH='arm64' OPENSSL_ARCH='linux-aarch64';; \ + *) echo "unsupported architecture"; exit 1 ;; \ + esac \ + && set -ex \ + && apt-get update && apt-get install -y ca-certificates curl wget gnupg dirmngr xz-utils libatomic1 --no-install-recommends \ + && rm -rf /var/lib/apt/lists/* \ + # use pre-existing gpg directory, see https://github.com/nodejs/docker-node/pull/1895#issuecomment-1550389150 + && export GNUPGHOME="$(mktemp -d)" \ + # gpg keys listed at https://github.com/nodejs/node#release-keys + && for key in \ + 5BE8A3F6C8A5C01D106C0AD820B1A390B168D356 \ + DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7 \ + CC68F5A3106FF448322E48ED27F5E38D5B0A215F \ + 890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4 \ + C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C \ + 108F52B48DB57BB0CC439B2997B01419BD92F80A \ + 655F3B5C1FB3FA8D1A0CA6BDE4A7D232B936D2FD \ + A363A499291CBBC940DD62E41F10027AF002F8B0 \ + ; do \ + { gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" && gpg --batch --fingerprint "$key"; } || \ + { gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" && gpg --batch --fingerprint "$key"; } ; \ + done \ + && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH.tar.xz" \ + && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \ + && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \ + && gpgconf --kill all \ + && rm -rf "$GNUPGHOME" \ + && grep " node-v$NODE_VERSION-linux-$ARCH.tar.xz\$" SHASUMS256.txt | sha256sum -c - \ + && tar -xJf "node-v$NODE_VERSION-linux-$ARCH.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \ + && rm "node-v$NODE_VERSION-linux-$ARCH.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \ + # Remove unused OpenSSL headers to save ~34MB. See this NodeJS issue: https://github.com/nodejs/node/issues/46451 + && find /usr/local/include/node/openssl/archs -mindepth 1 -maxdepth 1 ! -name "$OPENSSL_ARCH" -exec rm -rf {} \; \ + && apt-mark auto '.*' > /dev/null \ + && find /usr/local -type f -executable -exec ldd '{}' ';' \ + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); print so }' \ + | sort -u \ + | xargs -r dpkg-query --search \ + | cut -d: -f1 \ + | sort -u \ + | xargs -r apt-mark manual \ + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && ln -s /usr/local/bin/node /usr/local/bin/nodejs \ + # smoke tests + && node --version \ + && npm --version \ + && rm -rf /tmp/* + + +COPY docker-entrypoint.sh /usr/local/bin/ +ENTRYPOINT ["docker-entrypoint.sh"] + +CMD [ "node" ] diff --git a/27/bookworm-slim/docker-entrypoint.sh b/27/bookworm-slim/docker-entrypoint.sh new file mode 100755 index 0000000000..1b3116e53b --- /dev/null +++ b/27/bookworm-slim/docker-entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +# Run command with node if the first argument contains a "-" or is not a system command. The last +# part inside the "{}" is a workaround for the following bug in ash/dash: +# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=874264 +if [ "${1#-}" != "${1}" ] || [ -z "$(command -v "${1}")" ] || { [ -f "${1}" ] && ! [ -x "${1}" ]; }; then + set -- node "$@" +fi + +exec "$@" diff --git a/27/bookworm/Dockerfile b/27/bookworm/Dockerfile new file mode 100644 index 0000000000..fff3fb8b6c --- /dev/null +++ b/27/bookworm/Dockerfile @@ -0,0 +1,50 @@ +FROM buildpack-deps:bookworm + +RUN groupadd --gid 1000 node \ + && useradd --uid 1000 --gid node --shell /bin/bash --create-home node + +ENV NODE_VERSION=27.0.0-alpha.0.0.0 + +RUN ARCH= && dpkgArch="$(dpkg --print-architecture)" \ + && case "${dpkgArch##*-}" in \ + amd64) ARCH='x64';; \ + ppc64el) ARCH='ppc64le';; \ + arm64) ARCH='arm64';; \ + *) echo "unsupported architecture"; exit 1 ;; \ + esac \ + # use pre-existing gpg directory, see https://github.com/nodejs/docker-node/pull/1895#issuecomment-1550389150 + && export GNUPGHOME="$(mktemp -d)" \ + # gpg keys listed at https://github.com/nodejs/node#release-keys + && set -ex \ + && for key in \ + 5BE8A3F6C8A5C01D106C0AD820B1A390B168D356 \ + DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7 \ + CC68F5A3106FF448322E48ED27F5E38D5B0A215F \ + 890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4 \ + C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C \ + 108F52B48DB57BB0CC439B2997B01419BD92F80A \ + 655F3B5C1FB3FA8D1A0CA6BDE4A7D232B936D2FD \ + A363A499291CBBC940DD62E41F10027AF002F8B0 \ + ; do \ + { gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" && gpg --batch --fingerprint "$key"; } || \ + { gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" && gpg --batch --fingerprint "$key"; } ; \ + done \ + && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH.tar.xz" \ + && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \ + && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \ + && gpgconf --kill all \ + && rm -rf "$GNUPGHOME" \ + && grep " node-v$NODE_VERSION-linux-$ARCH.tar.xz\$" SHASUMS256.txt | sha256sum -c - \ + && tar -xJf "node-v$NODE_VERSION-linux-$ARCH.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \ + && rm "node-v$NODE_VERSION-linux-$ARCH.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \ + && ln -s /usr/local/bin/node /usr/local/bin/nodejs \ + # smoke tests + && node --version \ + && npm --version \ + && rm -rf /tmp/* + + +COPY docker-entrypoint.sh /usr/local/bin/ +ENTRYPOINT ["docker-entrypoint.sh"] + +CMD [ "node" ] diff --git a/27/bookworm/docker-entrypoint.sh b/27/bookworm/docker-entrypoint.sh new file mode 100755 index 0000000000..1b3116e53b --- /dev/null +++ b/27/bookworm/docker-entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +# Run command with node if the first argument contains a "-" or is not a system command. The last +# part inside the "{}" is a workaround for the following bug in ash/dash: +# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=874264 +if [ "${1#-}" != "${1}" ] || [ -z "$(command -v "${1}")" ] || { [ -f "${1}" ] && ! [ -x "${1}" ]; }; then + set -- node "$@" +fi + +exec "$@" diff --git a/27/trixie-slim/Dockerfile b/27/trixie-slim/Dockerfile new file mode 100644 index 0000000000..baef31f621 --- /dev/null +++ b/27/trixie-slim/Dockerfile @@ -0,0 +1,64 @@ +FROM debian:trixie-slim + +RUN groupadd --gid 1000 node \ + && useradd --uid 1000 --gid node --shell /bin/bash --create-home node + +ENV NODE_VERSION=27.0.0-alpha.0.0.0 + +RUN ARCH= OPENSSL_ARCH= && dpkgArch="$(dpkg --print-architecture)" \ + && case "${dpkgArch##*-}" in \ + amd64) ARCH='x64' OPENSSL_ARCH='linux-x86_64';; \ + ppc64el) ARCH='ppc64le' OPENSSL_ARCH='linux-ppc64le';; \ + s390x) ARCH='s390x' OPENSSL_ARCH='linux*-s390x';; \ + arm64) ARCH='arm64' OPENSSL_ARCH='linux-aarch64';; \ + *) echo "unsupported architecture"; exit 1 ;; \ + esac \ + && set -ex \ + && apt-get update && apt-get install -y ca-certificates curl wget gnupg dirmngr xz-utils libatomic1 --no-install-recommends \ + && rm -rf /var/lib/apt/lists/* \ + # use pre-existing gpg directory, see https://github.com/nodejs/docker-node/pull/1895#issuecomment-1550389150 + && export GNUPGHOME="$(mktemp -d)" \ + # gpg keys listed at https://github.com/nodejs/node#release-keys + && for key in \ + 5BE8A3F6C8A5C01D106C0AD820B1A390B168D356 \ + DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7 \ + CC68F5A3106FF448322E48ED27F5E38D5B0A215F \ + 890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4 \ + C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C \ + 108F52B48DB57BB0CC439B2997B01419BD92F80A \ + 655F3B5C1FB3FA8D1A0CA6BDE4A7D232B936D2FD \ + A363A499291CBBC940DD62E41F10027AF002F8B0 \ + ; do \ + { gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" && gpg --batch --fingerprint "$key"; } || \ + { gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" && gpg --batch --fingerprint "$key"; } ; \ + done \ + && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH.tar.xz" \ + && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \ + && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \ + && gpgconf --kill all \ + && rm -rf "$GNUPGHOME" \ + && grep " node-v$NODE_VERSION-linux-$ARCH.tar.xz\$" SHASUMS256.txt | sha256sum -c - \ + && tar -xJf "node-v$NODE_VERSION-linux-$ARCH.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \ + && rm "node-v$NODE_VERSION-linux-$ARCH.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \ + # Remove unused OpenSSL headers to save ~34MB. See this NodeJS issue: https://github.com/nodejs/node/issues/46451 + && find /usr/local/include/node/openssl/archs -mindepth 1 -maxdepth 1 ! -name "$OPENSSL_ARCH" -exec rm -rf {} \; \ + && apt-mark auto '.*' > /dev/null \ + && find /usr/local -type f -executable -exec ldd '{}' ';' \ + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); print so }' \ + | sort -u \ + | xargs -r dpkg-query --search \ + | cut -d: -f1 \ + | sort -u \ + | xargs -r apt-mark manual \ + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && ln -s /usr/local/bin/node /usr/local/bin/nodejs \ + # smoke tests + && node --version \ + && npm --version \ + && rm -rf /tmp/* + + +COPY docker-entrypoint.sh /usr/local/bin/ +ENTRYPOINT ["docker-entrypoint.sh"] + +CMD [ "node" ] diff --git a/27/trixie-slim/docker-entrypoint.sh b/27/trixie-slim/docker-entrypoint.sh new file mode 100755 index 0000000000..1b3116e53b --- /dev/null +++ b/27/trixie-slim/docker-entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +# Run command with node if the first argument contains a "-" or is not a system command. The last +# part inside the "{}" is a workaround for the following bug in ash/dash: +# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=874264 +if [ "${1#-}" != "${1}" ] || [ -z "$(command -v "${1}")" ] || { [ -f "${1}" ] && ! [ -x "${1}" ]; }; then + set -- node "$@" +fi + +exec "$@" diff --git a/27/trixie/Dockerfile b/27/trixie/Dockerfile new file mode 100644 index 0000000000..5168c624fa --- /dev/null +++ b/27/trixie/Dockerfile @@ -0,0 +1,51 @@ +FROM buildpack-deps:trixie + +RUN groupadd --gid 1000 node \ + && useradd --uid 1000 --gid node --shell /bin/bash --create-home node + +ENV NODE_VERSION=27.0.0-alpha.0.0.0 + +RUN ARCH= && dpkgArch="$(dpkg --print-architecture)" \ + && case "${dpkgArch##*-}" in \ + amd64) ARCH='x64';; \ + ppc64el) ARCH='ppc64le';; \ + s390x) ARCH='s390x';; \ + arm64) ARCH='arm64';; \ + *) echo "unsupported architecture"; exit 1 ;; \ + esac \ + # use pre-existing gpg directory, see https://github.com/nodejs/docker-node/pull/1895#issuecomment-1550389150 + && export GNUPGHOME="$(mktemp -d)" \ + # gpg keys listed at https://github.com/nodejs/node#release-keys + && set -ex \ + && for key in \ + 5BE8A3F6C8A5C01D106C0AD820B1A390B168D356 \ + DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7 \ + CC68F5A3106FF448322E48ED27F5E38D5B0A215F \ + 890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4 \ + C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C \ + 108F52B48DB57BB0CC439B2997B01419BD92F80A \ + 655F3B5C1FB3FA8D1A0CA6BDE4A7D232B936D2FD \ + A363A499291CBBC940DD62E41F10027AF002F8B0 \ + ; do \ + { gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" && gpg --batch --fingerprint "$key"; } || \ + { gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" && gpg --batch --fingerprint "$key"; } ; \ + done \ + && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH.tar.xz" \ + && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \ + && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \ + && gpgconf --kill all \ + && rm -rf "$GNUPGHOME" \ + && grep " node-v$NODE_VERSION-linux-$ARCH.tar.xz\$" SHASUMS256.txt | sha256sum -c - \ + && tar -xJf "node-v$NODE_VERSION-linux-$ARCH.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \ + && rm "node-v$NODE_VERSION-linux-$ARCH.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \ + && ln -s /usr/local/bin/node /usr/local/bin/nodejs \ + # smoke tests + && node --version \ + && npm --version \ + && rm -rf /tmp/* + + +COPY docker-entrypoint.sh /usr/local/bin/ +ENTRYPOINT ["docker-entrypoint.sh"] + +CMD [ "node" ] diff --git a/27/trixie/docker-entrypoint.sh b/27/trixie/docker-entrypoint.sh new file mode 100755 index 0000000000..1b3116e53b --- /dev/null +++ b/27/trixie/docker-entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +# Run command with node if the first argument contains a "-" or is not a system command. The last +# part inside the "{}" is a workaround for the following bug in ash/dash: +# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=874264 +if [ "${1#-}" != "${1}" ] || [ -z "$(command -v "${1}")" ] || { [ -f "${1}" ] && ! [ -x "${1}" ]; }; then + set -- node "$@" +fi + +exec "$@" diff --git a/genMatrix.js b/genMatrix.js index fc10f8d994..a1249d9e61 100644 --- a/genMatrix.js +++ b/genMatrix.js @@ -52,7 +52,7 @@ const getAffectedDockerfiles = (filesAdded, filesModified, filesRenamed) => { }; const getFullNodeVersionFromDockerfile = (file) => - fs.readFileSync(file, 'utf8').match(/^ENV NODE_VERSION=(\d*\.*\d*\.\d*)/m)[1]; + fs.readFileSync(file, 'utf8').match(/^ENV NODE_VERSION=(\S*)/m)[1]; const getDockerfileMatrixEntry = (file) => { const [variant] = path.dirname(file).split(path.sep).slice(-1); diff --git a/stackbrew.js b/stackbrew.js deleted file mode 100755 index 5a00e332ee..0000000000 --- a/stackbrew.js +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); - -// Grab last git commit -function getCommitHasForPath(path) { - return require('child_process') - .execSync(`git log -1 --format=%H HEAD -- ${path}`) - .toString() - .trim(); -} - -const stackbrewPath = path.basename(__filename); - -// Header -let stackbrew = `# this file is generated via https://github.com/nodejs/docker-node/blob/${getCommitHasForPath(stackbrewPath)}/${stackbrewPath} - -Maintainers: The Node.js Docker Team (@nodejs) -GitRepo: https://github.com/nodejs/docker-node.git -GitFetch: refs/heads/main\n`; - -// Loop versions - -const config = require('./versions.json'); - -const versions = Object.keys(config).reverse(); - -let midnight = new Date(); -midnight.setHours(0, 0, 0, 0); -const now = midnight.getTime(); -const alpineRE = new RegExp(/alpine*/); -const slimRE = new RegExp(/\*-slim/); -let foundLTS = false; -let foundCurrent = false; - -for (const version of versions) { - let lts = new Date(`${config[version].lts}T00:00:00.00`).getTime(); - let isCurrent = foundCurrent ? false : isNaN(lts) || lts >= now; - foundCurrent = isCurrent || foundCurrent; - let isLTS = foundLTS ? false : now >= lts; - foundLTS = isLTS || foundLTS; - let codename = config[version].codename; - let defaultAlpine = config[version]['alpine-default']; - let defaultDebian = config[version]['debian-default']; - let variants = config[version].variants; - for (const variant in variants) { - let dockerfilePath = path.join(version, variant, 'Dockerfile'); - let isAlpine = alpineRE.test(variant); - let isSlim = slimRE.test(variant); - let isDefaultSlim = new RegExp(`${defaultDebian}-slim`).test(variant); - - // Get full version from the Dockerfile - let dockerfile = fs.readFileSync(dockerfilePath, 'utf-8'); - let fullVersion = dockerfile.match( - /ENV NODE_VERSION=(?\d+)\.(?\d+)\.(?\d+)/, - ); - - let tags = [ - `${fullVersion.groups.major}.${fullVersion.groups.minor}.${fullVersion.groups.patch}-${variant}`, - `${fullVersion.groups.major}.${fullVersion.groups.minor}-${variant}`, - `${fullVersion.groups.major}-${variant}`, - ]; - - if (codename) { - tags.push(`${codename}-${variant}`); - } - - if (variant === defaultAlpine) { - tags.push( - `${fullVersion.groups.major}.${fullVersion.groups.minor}.${fullVersion.groups.patch}-alpine`, - ); - tags.push( - `${fullVersion.groups.major}.${fullVersion.groups.minor}-alpine`, - ); - tags.push(`${fullVersion.groups.major}-alpine`); - if (codename) { - tags.push(`${codename}-alpine`); - } - } - - if (variant === defaultDebian) { - tags.push( - `${fullVersion.groups.major}.${fullVersion.groups.minor}.${fullVersion.groups.patch}`, - ); - tags.push(`${fullVersion.groups.major}.${fullVersion.groups.minor}`); - tags.push(`${fullVersion.groups.major}`); - if (isSlim) { - tags.push( - `${fullVersion.groups.major}.${fullVersion.groups.minor}.${fullVersion.groups.patch}-slim`, - ); - tags.push( - `${fullVersion.groups.major}.${fullVersion.groups.minor}-slim`, - ); - tags.push(`${fullVersion.groups.major}-slim`); - } - if (codename) { - tags.push(`${codename}`); - } - } - if (isDefaultSlim) { - tags.push( - `${fullVersion.groups.major}.${fullVersion.groups.minor}.${fullVersion.groups.patch}-slim`, - ); - tags.push(`${fullVersion.groups.major}.${fullVersion.groups.minor}-slim`); - tags.push(`${fullVersion.groups.major}-slim`); - if (codename) { - tags.push(`${codename}-slim`); - } - } - - if (isCurrent) { - if (variant === defaultAlpine) { - tags.push(variant); - tags.push( - `${fullVersion.groups.major}.${fullVersion.groups.minor}.${fullVersion.groups.patch}-alpine`, - ); - tags.push( - `${fullVersion.groups.major}.${fullVersion.groups.minor}-alpine`, - ); - tags.push(`${fullVersion.groups.major}-alpine`); - tags.push('alpine'); - tags.push('current-alpine'); - } - if (variant === defaultDebian) { - tags.push(variant); - tags.push('latest'); - tags.push('current'); - } - if (isAlpine) { - tags.push(`${variant}`); - tags.push(`current-${variant}`); - } - if (!isAlpine) { - tags.push(`${variant}`); - tags.push(`current-${variant}`); - } - if (isDefaultSlim) { - tags.push('slim'); - tags.push('current-slim'); - } - } - - if (isLTS) { - tags.push(`lts-${variant}`); - if (variant === defaultDebian) { - tags.push('lts'); - if (codename) { - tags.push(`lts-${codename}`); - } - } - if (isDefaultSlim) { - tags.push(`lts-slim`); - } - if (variant === defaultAlpine) { - tags.push(`lts-alpine`); - } - } - - // remove duplicates - tags = tags.filter((x, i, a) => a.indexOf(x) == i); - tags = tags.sort(); - let directory = `${version}/${variant}`; - stackbrew += `\nTags: ${tags.join(', ')}\n`; - stackbrew += `Architectures: ${config[version].variants[variant].join(', ')}\n`; - stackbrew += `GitCommit: ${getCommitHasForPath(directory)}\n`; - stackbrew += `Directory: ${directory}\n`; - } -} - -// output -console.log(stackbrew); diff --git a/stackbrew.mjs b/stackbrew.mjs new file mode 100644 index 0000000000..62e4c78a64 --- /dev/null +++ b/stackbrew.mjs @@ -0,0 +1,184 @@ +#!/usr/bin/env node + +import { readFileSync, writeFileSync } from 'node:fs'; +import { basename, resolve } from 'node:path'; +import { execSync } from 'node:child_process'; + +// Grab last git commit +function getCommitHasForPath(path) { + return execSync(`git log -1 --format=%H HEAD -- ${path}`).toString().trim(); +} + +// Grab nodejs/Release values as the authoritative start/maintenance/end dates +const releaseTimes = await fetch( + 'https://raw.githubusercontent.com/nodejs/Release/refs/heads/main/schedule.json', +); +const releaseJson = await releaseTimes.json(); + +// Grab the nodejs.org index.json as the authoritative Codename values, since that can lag the Release repo JSON +const availableVersions = await fetch( + 'https://nodejs.org/download/release/index.json', +); +const officialIndexJson = await availableVersions.json(); + +const stackbrewPath = basename(import.meta.filename); + +// Header +let stackbrew = `# this file is generated via https://github.com/nodejs/docker-node/blob/${getCommitHasForPath(stackbrewPath)}/${stackbrewPath} + +Maintainers: The Node.js Docker Team (@nodejs) +GitRepo: https://github.com/nodejs/docker-node.git +GitFetch: refs/heads/main\n`; + +const config = JSON.parse(readFileSync('versions.json')); + +const localVersions = Object.keys(config).reverse(); + +let midnight = new Date(); +midnight.setHours(0, 0, 0, 0); +const now = midnight.getTime(); +let foundLTS = false; +let foundCurrent = false; + +for (const majorVersion of localVersions) { + let releaseRepoInfo = releaseJson[`v${majorVersion}`]; + let nodejsOrgReleaseInfo = officialIndexJson.find((indexVersion) => + indexVersion.version.startsWith(`v${majorVersion}`), + ); + let currentStart = new Date(`${releaseRepoInfo.start}T00:00:00.00`).getTime(); + // First Alpha build is the only active one, and when the "start" of the release hasn't cut over + let isAlpha = nodejsOrgReleaseInfo.version.includes('alpha'); + // The Current/Latest release is the first non-Alpha major that has hit it's "start" date + let isCurrent = foundCurrent + ? false + : !isAlpha && (isNaN(currentStart) || now >= currentStart); + foundCurrent |= isCurrent; + let codename = nodejsOrgReleaseInfo.lts + ? nodejsOrgReleaseInfo.lts.toLowerCase() + : ''; + // The LTS tag is only set on the most recent LTS release + let isLTS = foundLTS ? false : codename !== ''; + foundLTS |= isLTS; + let defaultAlpine = config[majorVersion]['alpine-default']; + let defaultDebian = config[majorVersion]['debian-default']; + let defaultSlim = `${config[majorVersion]['debian-default']}-slim`; + let variants = config[majorVersion].variants; + for (const variant in variants) { + let tags; + + if (isAlpha) { + // Only push explicit variants during the Alpha phase + tags = [`${nodejsOrgReleaseInfo.version.substring(1)}-${variant}`]; + } else { + let fullVersion = nodejsOrgReleaseInfo.version.match( + /v(?\d+)\.(?\d+)\.(?\d+)/, + ); + tags = [ + `${fullVersion.groups.major}.${fullVersion.groups.minor}.${fullVersion.groups.patch}-${variant}`, + `${fullVersion.groups.major}.${fullVersion.groups.minor}-${variant}`, + `${fullVersion.groups.major}-${variant}`, + ]; + + if (codename) { + tags.push(`${codename}-${variant}`); + } + + if (variant === defaultAlpine) { + tags.push( + `${fullVersion.groups.major}.${fullVersion.groups.minor}.${fullVersion.groups.patch}-alpine`, + ); + tags.push( + `${fullVersion.groups.major}.${fullVersion.groups.minor}-alpine`, + ); + tags.push(`${fullVersion.groups.major}-alpine`); + + if (codename) { + tags.push(`${codename}-alpine`); + } + + if (isCurrent) { + tags.push('alpine'); + tags.push('current-alpine'); + } + + if (isLTS) { + tags.push(`lts-alpine`); + } + } else if (variant === defaultDebian) { + tags.push( + `${fullVersion.groups.major}.${fullVersion.groups.minor}.${fullVersion.groups.patch}`, + ); + tags.push(`${fullVersion.groups.major}.${fullVersion.groups.minor}`); + tags.push(`${fullVersion.groups.major}`); + + if (codename) { + tags.push(`${codename}`); + } + + if (isCurrent) { + tags.push(variant); + tags.push('latest'); + tags.push('current'); + } + + if (isLTS) { + tags.push('lts'); + if (codename) { + tags.push(`lts-${codename}`); + } + } + } else if (variant === defaultSlim) { + tags.push( + `${fullVersion.groups.major}.${fullVersion.groups.minor}.${fullVersion.groups.patch}-slim`, + ); + tags.push( + `${fullVersion.groups.major}.${fullVersion.groups.minor}-slim`, + ); + tags.push(`${fullVersion.groups.major}-slim`); + + if (codename) { + tags.push(`${codename}-slim`); + } + + if (isCurrent) { + tags.push('slim'); + tags.push('current-slim'); + } + + if (isLTS) { + tags.push(`lts-slim`); + } + } + + if (isCurrent) { + tags.push(`${variant}`); + tags.push(`current-${variant}`); + } + + if (isLTS) { + tags.push(`lts-${variant}`); + } + } + + stackbrew += ` +Tags: ${tags + .filter((x, i, a) => a.indexOf(x) == i) + .sort() + .join(', ')} +Architectures: ${config[majorVersion].variants[variant].join(', ')} +GitCommit: ${getCommitHasForPath(`${majorVersion}/${variant}`)} +Directory: ${`${majorVersion}/${variant}`}\n`; + } +} + +try { + const outputFile = process.argv[2]; + + if (outputFile) { + writeFileSync(resolve(outputFile), stackbrew); + } else { + console.log(stackbrew); + } +} catch (ex) { + console.error("Couldn't handle output", ex); +} diff --git a/versions.json b/versions.json index 479c550111..3f3b72fc52 100644 --- a/versions.json +++ b/versions.json @@ -1,10 +1,41 @@ { + "27": { + "alpine-default": "alpine3.24", + "debian-default": "trixie", + "variants": { + "alpine3.23": [ + "amd64", + "arm64v8" + ], + "alpine3.24": [ + "amd64", + "arm64v8" + ], + "bookworm": [ + "amd64", + "arm64v8", + "ppc64le" + ], + "bookworm-slim": [ + "amd64", + "arm64v8", + "ppc64le" + ], + "trixie": [ + "amd64", + "arm64v8", + "ppc64le", + "s390x" + ], + "trixie-slim": [ + "amd64", + "arm64v8", + "ppc64le", + "s390x" + ] + } + }, "26": { - "start": "2026-05-05", - "lts": "2026-10-28", - "maintenance": "2027-10-20", - "end": "2029-04-30", - "codename": "lithium", "alpine-default": "alpine3.24", "debian-default": "trixie", "variants": { @@ -41,11 +72,6 @@ } }, "24": { - "start": "2025-05-06", - "lts": "2025-10-28", - "maintenance": "2026-10-20", - "end": "2028-04-30", - "codename": "krypton", "alpine-default": "alpine3.24", "debian-default": "bookworm", "variants": { @@ -84,11 +110,6 @@ } }, "22": { - "start": "2024-04-23", - "lts": "2024-10-29", - "maintenance": "2025-10-21", - "end": "2027-04-30", - "codename": "jod", "alpine-default": "alpine3.24", "debian-default": "bookworm", "variants": {