Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Official Support for Windows Docker Images #2136

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions .github/workflows/build-test-windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: build-test-windows

on:
push:
paths:
- "**/windowsservercore-ltsc2019/**"
- "**/windowsservercore-ltsc2022/**"
- ".github/workflows/build-test-windows.yml"

pull_request:
paths:
- "**/windowsservercore-ltsc2019/**"
- "**/windowsservercore-ltsc2022/**"
- ".github/workflows/build-test-windows.yml"

jobs:
build-windows-2019:
name: build-windows-2019
runs-on: windows-2019
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
version: [ "22.9.0" ]
variant: [ "windowsservercore-ltsc2019" ]

steps:
- name: Get short node version
uses: actions/github-script@v7
id: short-version
with:
result-encoding: string
script: return "${{ matrix.version }}".split('.')[0]

- name: Checkout
uses: actions/checkout@v4

# We cannot use docker/build-push-action here because it requires buildx, which is not available on Windows
- name: Build image
run: |
docker build --tag node:${{ matrix.version }}-${{ matrix.variant }} ./${{ steps.short-version.outputs.result }}/${{ matrix.variant }}

- name: Test for node version
shell: pwsh
run: |
$image_node_version = (docker run --rm node:${{ matrix.version }}-${{ matrix.variant }} node --print "process.versions.node").Trim()
Write-Host "Expected: '${{ matrix.version }}', Got: '$image_node_version'"
if ($image_node_version -ne "${{ matrix.version }}") {
exit 1
}

- name: Verify node runs regular files
shell: pwsh
run: |
$tempDir = New-Item -ItemType Directory -Path $env:TEMP -Name "tempNodeApp"
$tmp_file = Join-Path $tempDir "index.js"
"console.log('success')" | Out-File -FilePath $tmp_file -Encoding utf8
$output = (docker run --rm -w /app --mount "type=bind,src=$tempDir,target=c:\app" node:${{ matrix.version }}-${{ matrix.variant }} node C:/app/index.js)
if ($output -ne 'success') {
exit 1
}

- name: Test for npm
run: docker run --rm node:${{ matrix.version }}-${{ matrix.variant }} powershell.exe npm --version

build-windows-2022:
name: build-windows-2022
runs-on: windows-2022
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
version: [ "22.9.0" ]
variant: [ "windowsservercore-ltsc2022" ]

steps:
- name: Get short node version
uses: actions/github-script@v7
id: short-version
with:
result-encoding: string
script: return "${{ matrix.version }}".split('.')[0]

- name: Checkout
uses: actions/checkout@v4

# We cannot use docker/build-push-action here because it requires buildx, which is not available on Windows
- name: Build image
run: |
docker build --tag node:${{ matrix.version }}-${{ matrix.variant }} ./${{ steps.short-version.outputs.result }}/${{ matrix.variant }}

- name: Test for node version
shell: pwsh
run: |
$image_node_version = (docker run --rm node:${{ matrix.version }}-${{ matrix.variant }} node --print "process.versions.node").Trim()
Write-Host "Expected: '${{ matrix.version }}', Got: '$image_node_version'"
if ($image_node_version -ne "${{ matrix.version }}") {
exit 1
}

- name: Verify node runs regular files
shell: pwsh
run: |
$tempDir = New-Item -ItemType Directory -Path $env:TEMP -Name "tempNodeApp"
$tmp_file = Join-Path $tempDir "index.js"
"console.log('success')" | Out-File -FilePath $tmp_file -Encoding utf8
$output = (docker run --rm -w /app --mount "type=bind,src=$tempDir,target=c:\app" node:${{ matrix.version }}-${{ matrix.variant }} node C:/app/index.js)
if ($output -ne 'success') {
exit 1
}

- name: Test for npm
# We need to use powershell.exe to run npm because docker needs to attach to process and npm is a batch file/powershell script
run: docker run --rm node:${{ matrix.version }}-${{ matrix.variant }} powershell.exe npm --version
20 changes: 20 additions & 0 deletions 22/windowsservercore-ltsc2019/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM mcr.microsoft.com/windows/servercore:ltsc2019

SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

# PATH isn't actually set in the Docker image, so we have to set it from within the container
RUN $newPath = ('C:\nodejs;{0};{0}' -f $env:PATH); \
[Environment]::SetEnvironmentVariable('PATH', $newPath, [EnvironmentVariableTarget]::Machine)
# doing this first to share cache across versions more aggressively

ENV NODE_VERSION 22.9.0
ENV NODE_CHECKSUM 8af226c0aa71fefe5228e881f4b5c5d90a8b41c290b96f44f56990d8dc3fac1c

RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; \
Invoke-WebRequest $('https://nodejs.org/dist/v{0}/node-v{0}-win-x64.zip' -f $env:NODE_VERSION) -OutFile 'node.zip' -UseBasicParsing ; \
if ((Get-FileHash node.zip -Algorithm sha256).Hash -ne $env:NODE_CHECKSUM) { Write-Error 'SHA256 mismatch' } ; \
Expand-Archive node.zip -DestinationPath C:\ ; \
Rename-Item -Path $('C:\node-v{0}-win-x64' -f $env:NODE_VERSION) -NewName 'C:\nodejs' ; \
Remove-Item node.zip -Force ; \
node --version; \
npm --version;
20 changes: 20 additions & 0 deletions 22/windowsservercore-ltsc2022/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM mcr.microsoft.com/windows/servercore:ltsc2022

SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

# PATH isn't actually set in the Docker image, so we have to set it from within the container
RUN $newPath = ('C:\nodejs;{0};{0}' -f $env:PATH); \
[Environment]::SetEnvironmentVariable('PATH', $newPath, [EnvironmentVariableTarget]::Machine)
# doing this first to share cache across versions more aggressively

ENV NODE_VERSION 22.9.0
ENV NODE_CHECKSUM 8af226c0aa71fefe5228e881f4b5c5d90a8b41c290b96f44f56990d8dc3fac1c

RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; \
Invoke-WebRequest $('https://nodejs.org/dist/v{0}/node-v{0}-win-x64.zip' -f $env:NODE_VERSION) -OutFile 'node.zip' -UseBasicParsing ; \
if ((Get-FileHash node.zip -Algorithm sha256).Hash -ne $env:NODE_CHECKSUM) { Write-Error 'SHA256 mismatch' } ; \
Expand-Archive node.zip -DestinationPath C:\ ; \
Rename-Item -Path $('C:\node-v{0}-win-x64' -f $env:NODE_VERSION) -NewName 'C:\nodejs' ; \
Remove-Item node.zip -Force ; \
node --version; \
npm --version;
20 changes: 20 additions & 0 deletions Dockerfile-windows.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM mcr.microsoft.com/windows/servercore:version

SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

# PATH isn't actually set in the Docker image, so we have to set it from within the container
RUN $newPath = ('C:\nodejs;{0};{0}' -f $env:PATH); \
[Environment]::SetEnvironmentVariable('PATH', $newPath, [EnvironmentVariableTarget]::Machine)
# doing this first to share cache across versions more aggressively

ENV NODE_VERSION 0.0.0
ENV NODE_CHECKSUM CHECKSUM_x64
zZHorizonZz marked this conversation as resolved.
Show resolved Hide resolved

RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; \
Invoke-WebRequest $('https://nodejs.org/dist/v{0}/node-v{0}-win-x64.zip' -f $env:NODE_VERSION) -OutFile 'node.zip' -UseBasicParsing ; \
if ((Get-FileHash node.zip -Algorithm sha256).Hash -ne $env:NODE_CHECKSUM) { Write-Error 'SHA256 mismatch' } ; \
Expand-Archive node.zip -DestinationPath C:\ ; \
Rename-Item -Path $('C:\node-v{0}-win-x64' -f $env:NODE_VERSION) -NewName 'C:\nodejs' ; \
Remove-Item node.zip -Force ; \
node --version; \
npm --version;
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ The official Node.js docker image, made with love by the node community.
- [`node:bullseye`](#nodebullseye)
- [`node:bookworm`](#nodebookworm)
- [`node:slim`](#nodeslim)
- [`node:windowsservercore-ltsc<2019|2022>`](#nodewindowsservercore-ltsc20192022)
- [License](#license)
- [Supported Docker versions](#supported-docker-versions)
- [Supported Node.js versions](#supported-nodejs-versions)
Expand Down Expand Up @@ -189,12 +190,12 @@ One common issue that may arise is a missing shared library required for use of
`process.dlopen`. To add the missing shared libraries to your image:

- For Alpine v3.18 and earlier, adding the
[`libc6-compat`](https://pkgs.alpinelinux.org/package/v3.18/main/x86/libc6-compat)
package in your Dockerfile is recommended: `apk add --no-cache libc6-compat`
[`libc6-compat`](https://pkgs.alpinelinux.org/package/v3.18/main/x86/libc6-compat)
package in your Dockerfile is recommended: `apk add --no-cache libc6-compat`

- Starting from Alpine v3.19, you can use the
[`gcompat`](https://pkgs.alpinelinux.org/package/v3.19/main/x86/gcompat) package
to add the missing shared libraries: `apk add --no-cache gcompat`
[`gcompat`](https://pkgs.alpinelinux.org/package/v3.19/main/x86/gcompat) package
to add the missing shared libraries: `apk add --no-cache gcompat`

To minimize image size, it's uncommon for additional related tools
(such as `git` or `bash`) to be included in Alpine-based images. Using this
Expand Down Expand Up @@ -224,6 +225,16 @@ in an environment where *only* the Node.js image will be deployed and you have
space constraints, we highly recommend using the default image of this
repository.

### `node:windowsservercore-ltsc<2019|2022>`

This image is based on Windows Server Core and is the recommended image
for users who require Windows-based environments.
It is available in two versions:`node:windowsservercore-ltsc2019` and `node:windowsservercore-ltsc2022`.
You can run this image on Windows Server 2019 or Windows Server 2022 or on Windows desktop versions
that support Windows containers.
Keep in mind that these images are significantly larger than the Linux-based
variants due to the Windows Server Core base.

## License

[License information](https://github.com/nodejs/node/blob/master/LICENSE) for
Expand Down
2 changes: 1 addition & 1 deletion architectures
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
bashbrew-arch variants
amd64 alpine3.19,alpine3.20,bookworm,bookworm-slim,bullseye,bullseye-slim
amd64 alpine3.19,alpine3.20,bookworm,bookworm-slim,bullseye,bullseye-slim,windowsservercore-ltsc2019,windowsservercore-ltsc2022
arm32v6 alpine3.19,alpine3.20
arm32v7 alpine3.19,alpine3.20,bookworm,bookworm-slim,bullseye,bullseye-slim
arm64v8 alpine3.19,alpine3.20,bookworm,bookworm-slim,bullseye,bullseye-slim
Expand Down
10 changes: 10 additions & 0 deletions functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,16 @@ function is_debian_slim() {
return 1
}

function is_windows() {
local variant
variant=$1
shift

if [ "${variant}" = "${variant#windows}" ]; then
return 1
fi
}

function get_fork_name() {
local version
version=$1
Expand Down
13 changes: 8 additions & 5 deletions genMatrix.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,25 @@ const testFiles = [
];

const nodeDirRegex = /^\d+$/;
// Directories starting with 'windowsservercore-' are excluded from the matrix windows-2019 are excluded for example
Copy link
Preview

Copilot AI Dec 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The phrase 'windows-2019 are excluded for example' seems to be a typo and should be rephrased for clarity.

Suggested change
// Directories starting with 'windowsservercore-' are excluded from the matrix windows-2019 are excluded for example
// Directories starting with 'windowsservercore-' are excluded; 'windows-2019' is an example

Copilot is powered by AI, so mistakes are possible. Review output carefully before use.

Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
const windowsDirRegex = /^windowsservercore-/;

const areTestFilesChanged = (changedFiles) => changedFiles
.some((file) => testFiles.includes(file));

// Returns a list of the child directories in the given path
// Returns a list of the child directories in the given path, excluding those starting with 'windows-'
Copy link
Preview

Copilot AI Dec 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The comment should be 'excluding those starting with 'windowsservercore-'.

Suggested change
// Returns a list of the child directories in the given path, excluding those starting with 'windows-'
// Returns a list of the child directories in the given path, excluding those starting with 'windowsservercore-'

Copilot is powered by AI, so mistakes are possible. Review output carefully before use.

Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
const getChildDirectories = (parent) => fs.readdirSync(parent, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.filter((directory) => directory.isDirectory())
.map(({ name }) => path.resolve(parent, name));

const getNodeVerionDirs = (base) => getChildDirectories(base)
const getNodeVersionDirs = (base) => getChildDirectories(base)
.filter((childPath) => nodeDirRegex.test(path.basename(childPath)));

// Returns the paths of Dockerfiles that are at: base/*/Dockerfile
const getDockerfilesInChildDirs = (base) => getChildDirectories(base)
.map((childDir) => path.resolve(childDir, 'Dockerfile'));

const getAllDockerfiles = (base) => getNodeVerionDirs(base).flatMap(getDockerfilesInChildDirs);
const getAllDockerfiles = (base) => getNodeVersionDirs(base).flatMap(getDockerfilesInChildDirs);

const getAffectedDockerfiles = (filesAdded, filesModified, filesRenamed) => {
const files = [
Expand Down Expand Up @@ -69,7 +71,8 @@ const getDockerfileMatrixEntry = (file) => {
const generateBuildMatrix = (filesAdded, filesModified, filesRenamed) => {
const dockerfiles = [...new Set(getAffectedDockerfiles(filesAdded, filesModified, filesRenamed))];

const entries = dockerfiles.map(getDockerfileMatrixEntry);
let entries = dockerfiles.map(getDockerfileMatrixEntry);
entries = entries.filter((entry) => !windowsDirRegex.test(entry.variant));

// Return null if there are no entries so we can skip the matrix step
return entries.length
Expand Down
Loading