From 21cde8dce1e0cdcecbac2452fceff238ae829527 Mon Sep 17 00:00:00 2001 From: zodern Date: Thu, 13 Aug 2026 12:56:09 -0500 Subject: [PATCH] fix: better error message when using unsupported Docker version --- image/scripts/check_clone3.sh | 45 +++++++++++++++++++++++++++++++++ image/scripts/setup_nvm.sh | 5 ++++ readme.md | 3 +++ tests/clone3-eperm-seccomp.json | 10 ++++++++ tests/test.sh | 37 +++++++++++++++++++++++++++ 5 files changed, 100 insertions(+) create mode 100644 image/scripts/check_clone3.sh create mode 100644 tests/clone3-eperm-seccomp.json diff --git a/image/scripts/check_clone3.sh b/image/scripts/check_clone3.sh new file mode 100644 index 0000000..240736e --- /dev/null +++ b/image/scripts/check_clone3.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +# Debian 13 uses a glibc that creates threads with the clone3 syscall. +# It expects an ENOSYS error before falling back to the old method. +# +# Docker started returning ENOSYS in 20.10.10. Older versions +# used EPERM instead. + +# Check if clone3 is denied. It is syscall 435 on every architecture that both +# Debian 13 and node are available for. Giving it a size of 0 is rejected with +# EINVAL before the kernel creates anything, so this only tells us whether the +# syscall can be reached. +# +# Exit 3 means it was denied. If the status is different, it either passed +# or failed for an unknown reason, and we assume it works. +python3 -c " +import ctypes, errno, sys +libc = ctypes.CDLL(None, use_errno=True) +ctypes.set_errno(0) +libc.syscall(435, None, 0) +sys.exit(3 if ctypes.get_errno() == errno.EPERM else 0) +" 2>/dev/null + +if [ $? -ne 3 ]; then + exit 0 +fi + +cat >&2 <<'EOF' + +=> ERROR: The Docker version is outdated + +This version of zodern/meteor uses Debian 13, which requires Docker 20.10.10 or newer. + +To fix this, either: + + * Update Docker to 20.10.10 or newer, or + * Use an older version of zodern/meteor based on Debian 11: + zodern/meteor:0 (or zodern/meteor:0-root) + +Adding "--security-opt seccomp=unconfined" to docker run also works, but only +when running the app. It can not be used for docker build. + +EOF + +exit 1 diff --git a/image/scripts/setup_nvm.sh b/image/scripts/setup_nvm.sh index 48d5a72..b393832 100644 --- a/image/scripts/setup_nvm.sh +++ b/image/scripts/setup_nvm.sh @@ -1,4 +1,9 @@ set -e + +# On older docker versions, `nvm use` errors with a confusing message. +# First check if we are using a compatible docker version. +sh /home/app/scripts/check_clone3.sh + export NVM_DIR="/home/app/.nvm" [ -s "$NVM_DIR/nvm.sh" ] . "$NVM_DIR/nvm.sh" diff --git a/readme.md b/readme.md index 4aa9747..1532069 100644 --- a/readme.md +++ b/readme.md @@ -23,6 +23,9 @@ Docker image to run Meteor apps. Version 1 changed the base image from Debian 11 (bullseye) to Debian 13 (trixie). This is needed to support the uws DDP transport in Meteor 3.5. Old Meteor versions are still compatible. If the new base image causes problems for your app, you can use `zodern/meteor:0` or `zodern/meteor:0-root`. +Version 1 needs Docker 20.10.10 or newer, due to changes in Debian 13. Use `zodern/meteor:0` if you can not update Docker. Using an older version of Docker can result in `node[122]: pthread_create: Operation not permitted. nvm is not compatible with the npm config "prefix" option: currently set to "" ` + + ## How To Use ### Permissions diff --git a/tests/clone3-eperm-seccomp.json b/tests/clone3-eperm-seccomp.json new file mode 100644 index 0000000..eedaca9 --- /dev/null +++ b/tests/clone3-eperm-seccomp.json @@ -0,0 +1,10 @@ +{ + "defaultAction": "SCMP_ACT_ALLOW", + "syscalls": [ + { + "names": ["clone3"], + "action": "SCMP_ACT_ERRNO", + "errnoRet": 1 + } + ] +} diff --git a/tests/test.sh b/tests/test.sh index cc3beff..0342742 100644 --- a/tests/test.sh +++ b/tests/test.sh @@ -10,6 +10,7 @@ sudo rm -rf /tmp/docker-meteor-tests mkdir /tmp/docker-meteor-tests cp -r ../ /tmp/docker-meteor-tests cd /tmp/docker-meteor-tests/tests +TESTS_DIR="$PWD" rm -rf ./app rm -rf ./bundle @@ -188,6 +189,37 @@ test_latest() { test_uws_transport } +# Docker older than 20.10.10 denies the clone3 syscall with EPERM, which stops +# node from creating threads on the Debian 13 base image. We can't fix that from +# inside the image, but we should explain it instead of letting nvm report the +# npm "prefix" option as "". clone3-eperm-seccomp.json reproduces the old +# default seccomp profile. +test_clone3_check() { + echo "=> Checking clone3 detection" + + set +e + output=$(docker run --rm \ + --security-opt seccomp="$TESTS_DIR/clone3-eperm-seccomp.json" \ + --entrypoint bash \ + "$DOCKER_IMAGE" /home/app/scripts/setup_nvm.sh 2>&1) + code=$? + set -e + + if [ "$code" -eq 0 ]; then + echo "FAIL: expected a runtime without clone3 to be rejected" + echo "$output" + exit 1 + fi + + if ! echo "$output" | grep -q "20.10.10"; then + echo "FAIL: the error did not say which Docker version is needed" + echo "$output" + exit 1 + fi + + echo "SUCCESS" +} + test_versions() { echo "--- Testing Docker Image $DOCKER_IMAGE ---" @@ -232,6 +264,11 @@ export DISABLE_REACT_FAST_REFRESH="true" DOCKER_IMAGE="zodern/meteor" NPM_OPTIONS="" + +if [[ -z ${METEOR_TEST_OPTION+x} || "$METEOR_TEST_OPTION" == "latest" ]]; then + test_clone3_check +fi + test_versions DOCKER_IMAGE="zodern/meteor:root"