diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 87d138efc..d4d029a2d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -263,6 +263,11 @@ jobs: melange_config: envoy/melange.yaml arch: aarch64 runner: ubuntu-24.04-arm + # CUDA Python builds (x86_64 only - NVIDIA redist tarballs are x86_64) + - name: cuda-python + melange_config: cuda-python/melange.yaml + arch: x86_64 + runner: ubuntu-latest runs-on: ${{ matrix.runner }} timeout-minutes: 90 @@ -648,6 +653,10 @@ jobs: apko_config: envoy/apko/envoy.yaml melange_config: envoy/melange.yaml arches: x86_64,aarch64 + - name: cuda-python + apko_config: cuda-python/apko/cuda-python.yaml + melange_config: cuda-python/melange.yaml + arches: x86_64 steps: - name: Checkout repository diff --git a/Makefile b/Makefile index b9fa31576..c091fb1d7 100644 --- a/Makefile +++ b/Makefile @@ -48,18 +48,22 @@ VICTORIA_METRICS_VERSION ?= $(call melange_version,victoria-metrics/melange.yaml QDRANT_VERSION ?= $(call melange_version,qdrant/melange.yaml) OPENSEARCH_VERSION ?= 3.6.0 +# --- AI/ML --- +CUDA_VERSION ?= 12.9.0 + .PHONY: all build scan clean help .PHONY: python jenkins jenkins-melange go node-slim nginx httpd redis-slim redis-slim-melange mysql mysql-melange mysql-local memcached memcached-melange caddy caddy-melange haproxy haproxy-melange postgres-slim bun sqlite dotnet java php php-melange rails rails-melange kafka kafka-melange keygen opensearch .PHONY: valkey valkey-melange nats nats-melange traefik traefik-melange envoy envoy-melange rabbitmq rabbitmq-melange minio minio-melange .PHONY: prometheus prometheus-melange mariadb mariadb-melange .PHONY: etcd etcd-melange victoria-metrics victoria-metrics-melange jaeger jaeger-melange otelcol otelcol-melange qdrant qdrant-melange deno -.PHONY: scan-python scan-jenkins scan-go scan-node-slim scan-nginx scan-httpd scan-redis-slim scan-mysql scan-memcached scan-caddy scan-haproxy scan-postgres-slim scan-bun scan-sqlite scan-dotnet scan-java scan-php scan-rails scan-kafka scan-valkey scan-nats scan-traefik scan-rabbitmq scan-minio scan-opensearch scan-prometheus scan-mariadb scan-etcd scan-victoria-metrics scan-jaeger scan-otelcol scan-qdrant scan-deno -.PHONY: test-python test-jenkins test-go test-node-slim test-nginx test-httpd test-redis-slim test-mysql test-memcached test-caddy test-haproxy test-postgres-slim test-bun test-sqlite test-dotnet test-java test-php test-rails test-kafka test-valkey test-nats test-traefik test-envoy test-rabbitmq test-minio test-opensearch test-prometheus test-mariadb test-etcd test-victoria-metrics test-jaeger test-otelcol test-qdrant test-deno +.PHONY: cuda-python cuda-python-melange +.PHONY: scan-python scan-jenkins scan-go scan-node-slim scan-nginx scan-httpd scan-redis-slim scan-mysql scan-memcached scan-caddy scan-haproxy scan-postgres-slim scan-bun scan-sqlite scan-dotnet scan-java scan-php scan-rails scan-kafka scan-valkey scan-nats scan-traefik scan-rabbitmq scan-minio scan-opensearch scan-prometheus scan-mariadb scan-etcd scan-victoria-metrics scan-jaeger scan-otelcol scan-qdrant scan-deno scan-cuda-python +.PHONY: test-python test-jenkins test-go test-node-slim test-nginx test-httpd test-redis-slim test-mysql test-memcached test-caddy test-haproxy test-postgres-slim test-bun test-sqlite test-dotnet test-java test-php test-rails test-kafka test-valkey test-nats test-traefik test-envoy test-rabbitmq test-minio test-opensearch test-prometheus test-mariadb test-etcd test-victoria-metrics test-jaeger test-otelcol test-qdrant test-deno test-cuda-python all: build scan # Build all images -build: python jenkins go node-slim nginx httpd redis-slim mysql memcached caddy haproxy postgres-slim bun sqlite dotnet java php rails kafka valkey nats traefik envoy rabbitmq minio opensearch prometheus mariadb etcd victoria-metrics jaeger otelcol qdrant deno +build: python jenkins go node-slim nginx httpd redis-slim mysql memcached caddy haproxy postgres-slim bun sqlite dotnet java php rails kafka valkey nats traefik envoy rabbitmq minio opensearch prometheus mariadb etcd victoria-metrics jaeger otelcol qdrant deno cuda-python #------------------------------------------------------------------------------ # SIGNING KEY (required for melange packages) @@ -691,6 +695,32 @@ deno: @rm -f deno.tar sbom-*.spdx.json @echo "✓ minimal-deno built (Wolfi package, shell-less)" +#------------------------------------------------------------------------------ +# CUDA PYTHON IMAGE (melange NVIDIA redist tarballs + Wolfi Python, x86_64 only) +#------------------------------------------------------------------------------ +cuda-python-melange: keygen + @echo "Building CUDA $(CUDA_VERSION) runtime packages via melange..." + melange build cuda-python/melange.yaml \ + --arch x86_64 \ + --signing-key melange.rsa + @echo "✓ CUDA runtime packages built" + +cuda-python: cuda-python-melange + @echo "Assembling minimal-cuda-python image with apko..." + apko build cuda-python/apko/cuda-python.yaml \ + $(REGISTRY)/$(OWNER)/minimal-cuda-python:$(VERSION) \ + cuda-python.tar \ + --arch x86_64 \ + --repository-append ./packages \ + --keyring-append melange.rsa.pub + docker load < cuda-python.tar + docker tag $(REGISTRY)/$(OWNER)/minimal-cuda-python:$(VERSION)-amd64 \ + $(REGISTRY)/$(OWNER)/minimal-cuda-python:$(VERSION) + docker tag $(REGISTRY)/$(OWNER)/minimal-cuda-python:$(VERSION)-amd64 \ + $(REGISTRY)/$(OWNER)/minimal-cuda-python:latest + @rm -f cuda-python.tar sbom-*.spdx.json + @echo "✓ minimal-cuda-python built (NVIDIA CUDA + Python, x86_64 only)" + #------------------------------------------------------------------------------ # POSTGRES SLIM IMAGE (Wolfi pre-built package) #------------------------------------------------------------------------------ @@ -876,7 +906,7 @@ kafka: kafka-melange #------------------------------------------------------------------------------ # CVE SCANNING #------------------------------------------------------------------------------ -scan: scan-python scan-jenkins scan-go scan-node-slim scan-nginx scan-httpd scan-redis-slim scan-mysql scan-memcached scan-caddy scan-haproxy scan-postgres-slim scan-bun scan-sqlite scan-dotnet scan-java scan-php scan-rails scan-kafka scan-valkey scan-nats scan-traefik scan-envoy scan-rabbitmq scan-minio scan-opensearch scan-prometheus scan-mariadb +scan: scan-python scan-jenkins scan-go scan-node-slim scan-nginx scan-httpd scan-redis-slim scan-mysql scan-memcached scan-caddy scan-haproxy scan-postgres-slim scan-bun scan-sqlite scan-dotnet scan-java scan-php scan-rails scan-kafka scan-valkey scan-nats scan-traefik scan-envoy scan-rabbitmq scan-minio scan-opensearch scan-prometheus scan-mariadb scan-cuda-python scan-python: @echo "Scanning minimal-python..." @@ -1083,6 +1113,12 @@ scan-deno: $(REGISTRY)/$(OWNER)/minimal-deno:latest @echo "✓ minimal-deno: scan passed" +scan-cuda-python: + @echo "Scanning minimal-cuda-python..." + trivy image --exit-code 1 --severity CRITICAL,HIGH \ + $(REGISTRY)/$(OWNER)/minimal-cuda-python:latest + @echo "✓ minimal-cuda-python: scan passed" + # Full scan with all severities scan-all: @echo "Full vulnerability scan..." @@ -1132,7 +1168,7 @@ size: #------------------------------------------------------------------------------ # TESTING #------------------------------------------------------------------------------ -test: test-python test-jenkins test-go test-node-slim test-nginx test-httpd test-redis-slim test-mysql test-memcached test-caddy test-haproxy test-postgres-slim test-bun test-sqlite test-dotnet test-java test-php test-rails test-kafka test-valkey test-nats test-traefik test-envoy test-rabbitmq test-minio test-opensearch test-prometheus test-mariadb +test: test-python test-jenkins test-go test-node-slim test-nginx test-httpd test-redis-slim test-mysql test-memcached test-caddy test-haproxy test-postgres-slim test-bun test-sqlite test-dotnet test-java test-php test-rails test-kafka test-valkey test-nats test-traefik test-envoy test-rabbitmq test-minio test-opensearch test-prometheus test-mariadb test-cuda-python test-python: @echo "Testing Python image..." @@ -1456,6 +1492,12 @@ test-deno: deno/test.sh @echo "✓ Deno tests passed" +test-cuda-python: + @echo "Testing CUDA Python image..." + export IMAGE="$(REGISTRY)/$(OWNER)/minimal-cuda-python:latest" && \ + cuda-python/test.sh + @echo "✓ CUDA Python tests passed" + #------------------------------------------------------------------------------ # PUSH TO REGISTRY #------------------------------------------------------------------------------ @@ -1510,6 +1552,8 @@ push: docker push $(REGISTRY)/$(OWNER)/minimal-minio:latest docker push $(REGISTRY)/$(OWNER)/minimal-opensearch:$(VERSION) docker push $(REGISTRY)/$(OWNER)/minimal-opensearch:latest + docker push $(REGISTRY)/$(OWNER)/minimal-cuda-python:$(VERSION) + docker push $(REGISTRY)/$(OWNER)/minimal-cuda-python:latest #------------------------------------------------------------------------------ # CLEANUP @@ -1591,6 +1635,9 @@ clean: docker rmi $(REGISTRY)/$(OWNER)/minimal-opensearch:$(VERSION) 2>/dev/null || true docker rmi $(REGISTRY)/$(OWNER)/minimal-opensearch:$(VERSION)-amd64 2>/dev/null || true docker rmi $(REGISTRY)/$(OWNER)/minimal-opensearch:latest 2>/dev/null || true + docker rmi $(REGISTRY)/$(OWNER)/minimal-cuda-python:$(VERSION) 2>/dev/null || true + docker rmi $(REGISTRY)/$(OWNER)/minimal-cuda-python:$(VERSION)-amd64 2>/dev/null || true + docker rmi $(REGISTRY)/$(OWNER)/minimal-cuda-python:latest 2>/dev/null || true rm -f *.tar sbom-*.spdx.json rm -rf packages/ @echo "✓ Cleanup complete" @@ -1633,6 +1680,7 @@ help: @echo " make rabbitmq Build RabbitMQ $(RABBITMQ_VERSION) (official binary + Wolfi Erlang)" @echo " make minio Build MinIO $(MINIO_VERSION) (source build)" @echo " make opensearch Build OpenSearch $(OPENSEARCH_VERSION) (Wolfi package)" + @echo " make cuda-python Build CUDA Python $(CUDA_VERSION) (NVIDIA redist + Python, x86_64)" @echo " make build Build all images" @echo "" @echo "Scanning:" @@ -1663,5 +1711,6 @@ help: @echo " TRAEFIK_VERSION=$(TRAEFIK_VERSION)" @echo " RABBITMQ_VERSION=$(RABBITMQ_VERSION)" @echo " OPENSEARCH_VERSION=$(OPENSEARCH_VERSION)" + @echo " CUDA_VERSION=$(CUDA_VERSION)" @echo " REGISTRY=$(REGISTRY)" @echo " OWNER=$(OWNER)" diff --git a/README.md b/README.md index 201b25416..637b21e2e 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Build Hardened Images Vulnerability Report License: MIT - Images: 35 + Images: 36 Architectures: amd64 | arm64

@@ -84,6 +84,8 @@ Container vulnerabilities are a top attack vector. Most base images ship with do | **HAProxy** | `docker pull ghcr.io/rtvkiz/minimal-haproxy:latest` | No | High-performance TCP/HTTP load balancer | | **Traefik** | `docker pull ghcr.io/rtvkiz/minimal-traefik:latest` | No | Cloud-native reverse proxy and load balancer, built from source | | **Envoy** | `docker pull ghcr.io/rtvkiz/minimal-envoy:latest` | No | Cloud-native service proxy and load balancer, upstream binary | +| | | **AI/ML** | | +| **CUDA Python** | `docker pull ghcr.io/rtvkiz/minimal-cuda-python:latest` | No | Python + CUDA 12.9 + cuDNN 9.10 for ML inference (x86_64 only) | | | | **CI/CD** | | | **Jenkins** | `docker pull ghcr.io/rtvkiz/minimal-jenkins:latest` | Yes | CI/CD automation | diff --git a/cuda-python/apko/cuda-python.yaml b/cuda-python/apko/cuda-python.yaml new file mode 100644 index 000000000..dfd7b326a --- /dev/null +++ b/cuda-python/apko/cuda-python.yaml @@ -0,0 +1,90 @@ +# Minimal CUDA Python image - Wolfi Python 3.13 + CUDA 12.9 runtime + cuDNN 9.10.1 +# CUDA libraries packaged from NVIDIA redistributable tarballs via melange +# For ML inference and runtime workloads (not for compiling CUDA code) + +contents: + repositories: + - https://packages.wolfi.dev/os + # Local melange-built packages (passed via --repository-append CLI flag) + keyring: + - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub + # Local signing key passed via --keyring-append CLI flag + packages: + # Base filesystem layout + - wolfi-baselayout + + # Python from Wolfi + - python-3.13 + + # Core runtime dependencies + - glibc + - glibc-locale-posix + - ld-linux + - libgcc + - libstdc++ + + # Python module dependencies + - libffi + - zlib + - libssl3 + - libcrypto3 + - readline + - ncurses + - ncurses-terminfo-base + + # Additional stdlib dependencies + - libexpat1 + - libbz2-1 + - xz + - libzstd1 + - mpdecimal + - sqlite-libs + - gdbm + - libuuid + + # TLS/SSL certificates + - ca-certificates-bundle + + # CUDA runtime libraries (built from NVIDIA redist tarballs via melange) + - cuda-runtime + +accounts: + groups: + - groupname: nonroot + gid: 65532 + users: + - username: nonroot + uid: 65532 + gid: 65532 + run-as: 65532 + +entrypoint: + command: /usr/bin/python3 + +work-dir: /app + +environment: + # Python settings + PYTHONDONTWRITEBYTECODE: "1" + PYTHONUNBUFFERED: "1" + PYTHONHASHSEED: random + LANG: C.UTF-8 + PATH: /usr/bin:/bin + # NVIDIA container runtime detection + NVIDIA_VISIBLE_DEVICES: all + NVIDIA_DRIVER_CAPABILITIES: compute,utility + +paths: + - path: /app + type: directory + uid: 65532 + gid: 65532 + permissions: 0o755 + - path: /tmp + type: directory + uid: 0 + gid: 0 + permissions: 0o1777 + +archs: + - x86_64 diff --git a/cuda-python/melange.yaml b/cuda-python/melange.yaml new file mode 100644 index 000000000..327544876 --- /dev/null +++ b/cuda-python/melange.yaml @@ -0,0 +1,300 @@ +# Melange build configuration for CUDA runtime libraries +# Downloads pre-compiled NVIDIA redistributable tarballs and packages them +# as individual subpackages. cuda-runtime is a meta-package pulling all libs. +# Source: https://developer.download.nvidia.com/compute/cuda/redist/ + +package: + name: cuda-runtime + version: 12.9.0 + epoch: 0 + description: "NVIDIA CUDA 12.9 runtime libraries + cuDNN 9.10.1 (meta-package)" + copyright: + - license: LicenseRef-NVIDIA-CUDA-EULA + dependencies: + runtime: + - cuda-runtime-cudart + - cuda-runtime-cublas + - cuda-runtime-cufft + - cuda-runtime-curand + - cuda-runtime-cusolver + - cuda-runtime-cusparse + - cuda-runtime-nvtx + - cuda-runtime-nvjitlink + - cuda-runtime-cudnn + +vars: + # CUDA 12.9.0 component versions (from redistrib_12.9.0.json) + cudart_version: "12.9.37" + cublas_version: "12.9.0.13" + cufft_version: "11.4.0.6" + curand_version: "10.3.10.19" + cusolver_version: "11.7.4.40" + cusparse_version: "12.5.9.5" + nvtx_version: "12.9.19" + nvjitlink_version: "12.9.41" + cudnn_version: "9.10.1.4" + + # SHA256 checksums (from NVIDIA redistrib JSON manifests) + cudart_sha256: "db950a2443a34daa10069c8df1bccec868af3144cb898cd1d903a9ea04cccfa4" + cublas_sha256: "bc51ee77e5891614e070066c0dabc52b609302bac2127aff850020b14f35716b" + cufft_sha256: "31c2e800afc3111b966c63918cc9c257f5e7917ae4c881e97a67850a8caf3cb9" + curand_sha256: "48281b4caadb1cf790d44ac76b23c77d06f474c0b1799814f314aafec9258ad6" + cusolver_sha256: "d641c40ff8559c456663e23682bee9cadf2e85d5cc60661213030c98ee1e61f2" + cusparse_sha256: "4f5a6ba0c95b57d0678f0033da329015c8628075e0d54a890250c3b2f12bda38" + nvtx_sha256: "bfb73b62a07d54321f700510d553f3bad87ea17838b1964b19fa742f1c670510" + nvjitlink_sha256: "cfe144b33a01e720b9b8112750e11c21ded2964abecf6c4174d160e8cd84aa5a" + cudnn_sha256: "be759754e5bd1fcd9b490e224796c87f093c1e92b2b6357854d5371b6aeeb8be" + +environment: + contents: + repositories: + - https://packages.wolfi.dev/os + keyring: + - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub + packages: + - busybox + - ca-certificates-bundle + - xz + +pipeline: + # Downloads all NVIDIA redistributable tarballs and extracts .so files. + # Uses 'fetch' pipeline steps for built-in caching via --cache-dir. + # One at a time to avoid exhausting disk space (~3.2 GB compressed total). + # Note: BusyBox tar doesn't support --wildcards, so we extract fully then copy .so files. + # After the main pipeline, subpackages move .so files into per-library packages. + + - runs: mkdir -p "${{targets.destdir}}/usr/lib" + + # --- cuda_cudart --- + - uses: fetch + with: + uri: https://developer.download.nvidia.com/compute/cuda/redist/cuda_cudart/linux-x86_64/cuda_cudart-linux-x86_64-${{vars.cudart_version}}-archive.tar.xz + expected-sha256: ${{vars.cudart_sha256}} + extract: false + - runs: | + mkdir -p /tmp/extract + tar -xf cuda_cudart-linux-x86_64-${{vars.cudart_version}}-archive.tar.xz --strip-components=1 -C /tmp/extract + find /tmp/extract/lib -name '*.so*' -exec cp -a {} "${{targets.destdir}}/usr/lib/" \; + # Extract LICENSE for SBOM compliance (stays in the meta-package) + mkdir -p "${{targets.destdir}}/usr/share/licenses/cuda-runtime" + cp /tmp/extract/LICENSE "${{targets.destdir}}/usr/share/licenses/cuda-runtime/LICENSE" 2>/dev/null || \ + echo "NVIDIA CUDA End User License Agreement - https://docs.nvidia.com/cuda/eula/" > "${{targets.destdir}}/usr/share/licenses/cuda-runtime/LICENSE" + rm -rf /tmp/extract cuda_cudart-linux-x86_64-${{vars.cudart_version}}-archive.tar.xz + # Remove driver stub - real libcuda.so comes from host via nvidia-container-runtime + rm -f "${{targets.destdir}}/usr/lib/libcuda.so" + + # --- libcublas --- + - uses: fetch + with: + uri: https://developer.download.nvidia.com/compute/cuda/redist/libcublas/linux-x86_64/libcublas-linux-x86_64-${{vars.cublas_version}}-archive.tar.xz + expected-sha256: ${{vars.cublas_sha256}} + extract: false + - runs: | + mkdir -p /tmp/extract + tar -xf libcublas-linux-x86_64-${{vars.cublas_version}}-archive.tar.xz --strip-components=1 -C /tmp/extract + find /tmp/extract/lib -name '*.so*' -exec cp -a {} "${{targets.destdir}}/usr/lib/" \; + rm -rf /tmp/extract libcublas-linux-x86_64-${{vars.cublas_version}}-archive.tar.xz + + # --- libcufft --- + - uses: fetch + with: + uri: https://developer.download.nvidia.com/compute/cuda/redist/libcufft/linux-x86_64/libcufft-linux-x86_64-${{vars.cufft_version}}-archive.tar.xz + expected-sha256: ${{vars.cufft_sha256}} + extract: false + - runs: | + mkdir -p /tmp/extract + tar -xf libcufft-linux-x86_64-${{vars.cufft_version}}-archive.tar.xz --strip-components=1 -C /tmp/extract + find /tmp/extract/lib -name '*.so*' -exec cp -a {} "${{targets.destdir}}/usr/lib/" \; + rm -rf /tmp/extract libcufft-linux-x86_64-${{vars.cufft_version}}-archive.tar.xz + + # --- libcurand --- + - uses: fetch + with: + uri: https://developer.download.nvidia.com/compute/cuda/redist/libcurand/linux-x86_64/libcurand-linux-x86_64-${{vars.curand_version}}-archive.tar.xz + expected-sha256: ${{vars.curand_sha256}} + extract: false + - runs: | + mkdir -p /tmp/extract + tar -xf libcurand-linux-x86_64-${{vars.curand_version}}-archive.tar.xz --strip-components=1 -C /tmp/extract + find /tmp/extract/lib -name '*.so*' -exec cp -a {} "${{targets.destdir}}/usr/lib/" \; + rm -rf /tmp/extract libcurand-linux-x86_64-${{vars.curand_version}}-archive.tar.xz + + # --- libcusolver --- + - uses: fetch + with: + uri: https://developer.download.nvidia.com/compute/cuda/redist/libcusolver/linux-x86_64/libcusolver-linux-x86_64-${{vars.cusolver_version}}-archive.tar.xz + expected-sha256: ${{vars.cusolver_sha256}} + extract: false + - runs: | + mkdir -p /tmp/extract + tar -xf libcusolver-linux-x86_64-${{vars.cusolver_version}}-archive.tar.xz --strip-components=1 -C /tmp/extract + find /tmp/extract/lib -name '*.so*' -exec cp -a {} "${{targets.destdir}}/usr/lib/" \; + rm -rf /tmp/extract libcusolver-linux-x86_64-${{vars.cusolver_version}}-archive.tar.xz + + # --- libcusparse --- + - uses: fetch + with: + uri: https://developer.download.nvidia.com/compute/cuda/redist/libcusparse/linux-x86_64/libcusparse-linux-x86_64-${{vars.cusparse_version}}-archive.tar.xz + expected-sha256: ${{vars.cusparse_sha256}} + extract: false + - runs: | + mkdir -p /tmp/extract + tar -xf libcusparse-linux-x86_64-${{vars.cusparse_version}}-archive.tar.xz --strip-components=1 -C /tmp/extract + find /tmp/extract/lib -name '*.so*' -exec cp -a {} "${{targets.destdir}}/usr/lib/" \; + rm -rf /tmp/extract libcusparse-linux-x86_64-${{vars.cusparse_version}}-archive.tar.xz + + # --- cuda_nvtx --- + - uses: fetch + with: + uri: https://developer.download.nvidia.com/compute/cuda/redist/cuda_nvtx/linux-x86_64/cuda_nvtx-linux-x86_64-${{vars.nvtx_version}}-archive.tar.xz + expected-sha256: ${{vars.nvtx_sha256}} + extract: false + - runs: | + mkdir -p /tmp/extract + tar -xf cuda_nvtx-linux-x86_64-${{vars.nvtx_version}}-archive.tar.xz --strip-components=1 -C /tmp/extract + find /tmp/extract/lib -name '*.so*' -exec cp -a {} "${{targets.destdir}}/usr/lib/" \; + rm -rf /tmp/extract cuda_nvtx-linux-x86_64-${{vars.nvtx_version}}-archive.tar.xz + + # --- libnvjitlink --- + - uses: fetch + with: + uri: https://developer.download.nvidia.com/compute/cuda/redist/libnvjitlink/linux-x86_64/libnvjitlink-linux-x86_64-${{vars.nvjitlink_version}}-archive.tar.xz + expected-sha256: ${{vars.nvjitlink_sha256}} + extract: false + - runs: | + mkdir -p /tmp/extract + tar -xf libnvjitlink-linux-x86_64-${{vars.nvjitlink_version}}-archive.tar.xz --strip-components=1 -C /tmp/extract + find /tmp/extract/lib -name '*.so*' -exec cp -a {} "${{targets.destdir}}/usr/lib/" \; + rm -rf /tmp/extract libnvjitlink-linux-x86_64-${{vars.nvjitlink_version}}-archive.tar.xz + + # --- cuDNN --- + - uses: fetch + with: + uri: https://developer.download.nvidia.com/compute/cudnn/redist/cudnn/linux-x86_64/cudnn-linux-x86_64-${{vars.cudnn_version}}_cuda12-archive.tar.xz + expected-sha256: ${{vars.cudnn_sha256}} + extract: false + - runs: | + mkdir -p /tmp/extract + tar -xf cudnn-linux-x86_64-${{vars.cudnn_version}}_cuda12-archive.tar.xz --strip-components=1 -C /tmp/extract + find /tmp/extract/lib -name '*.so*' -exec cp -a {} "${{targets.destdir}}/usr/lib/" \; + rm -rf /tmp/extract cudnn-linux-x86_64-${{vars.cudnn_version}}_cuda12-archive.tar.xz + +# Each subpackage moves its .so files from destdir into its own contextdir, +# producing an independent .apk. The main cuda-runtime package retains only +# the LICENSE file and acts as a meta-package via runtime dependencies above. +subpackages: + - name: cuda-runtime-cudart + description: "NVIDIA CUDA runtime library (libcudart)" + pipeline: + - runs: | + mkdir -p "${{targets.contextdir}}/usr/lib" + mv "${{targets.destdir}}"/usr/lib/libcudart.so* "${{targets.contextdir}}/usr/lib/" + test: + pipeline: + - runs: | + test -f /usr/lib/libcudart.so.12 || (echo "FAIL: libcudart.so.12 not found" && exit 1) + echo "cuda-runtime-cudart: OK" + + - name: cuda-runtime-cublas + description: "NVIDIA cuBLAS library (libcublas, libcublasLt, libnvblas)" + pipeline: + - runs: | + mkdir -p "${{targets.contextdir}}/usr/lib" + mv "${{targets.destdir}}"/usr/lib/libcublas.so* "${{targets.contextdir}}/usr/lib/" + mv "${{targets.destdir}}"/usr/lib/libcublasLt.so* "${{targets.contextdir}}/usr/lib/" + mv "${{targets.destdir}}"/usr/lib/libnvblas.so* "${{targets.contextdir}}/usr/lib/" + test: + pipeline: + - runs: | + test -f /usr/lib/libcublas.so.12 || (echo "FAIL: libcublas.so.12 not found" && exit 1) + test -f /usr/lib/libcublasLt.so.12 || (echo "FAIL: libcublasLt.so.12 not found" && exit 1) + test -f /usr/lib/libnvblas.so.12 || (echo "FAIL: libnvblas.so.12 not found" && exit 1) + echo "cuda-runtime-cublas: OK" + + - name: cuda-runtime-cufft + description: "NVIDIA cuFFT library (libcufft, libcufftw)" + pipeline: + - runs: | + mkdir -p "${{targets.contextdir}}/usr/lib" + mv "${{targets.destdir}}"/usr/lib/libcufft.so* "${{targets.contextdir}}/usr/lib/" + mv "${{targets.destdir}}"/usr/lib/libcufftw.so* "${{targets.contextdir}}/usr/lib/" + test: + pipeline: + - runs: | + test -f /usr/lib/libcufft.so.11 || (echo "FAIL: libcufft.so.11 not found" && exit 1) + test -f /usr/lib/libcufftw.so.11 || (echo "FAIL: libcufftw.so.11 not found" && exit 1) + echo "cuda-runtime-cufft: OK" + + - name: cuda-runtime-curand + description: "NVIDIA cuRAND library" + pipeline: + - runs: | + mkdir -p "${{targets.contextdir}}/usr/lib" + mv "${{targets.destdir}}"/usr/lib/libcurand.so* "${{targets.contextdir}}/usr/lib/" + test: + pipeline: + - runs: | + test -f /usr/lib/libcurand.so.10 || (echo "FAIL: libcurand.so.10 not found" && exit 1) + echo "cuda-runtime-curand: OK" + + - name: cuda-runtime-cusolver + description: "NVIDIA cuSOLVER library (libcusolver, libcusolverMg)" + pipeline: + - runs: | + mkdir -p "${{targets.contextdir}}/usr/lib" + mv "${{targets.destdir}}"/usr/lib/libcusolver.so* "${{targets.contextdir}}/usr/lib/" + mv "${{targets.destdir}}"/usr/lib/libcusolverMg.so* "${{targets.contextdir}}/usr/lib/" + test: + pipeline: + - runs: | + test -f /usr/lib/libcusolver.so.11 || (echo "FAIL: libcusolver.so.11 not found" && exit 1) + test -f /usr/lib/libcusolverMg.so.11 || (echo "FAIL: libcusolverMg.so.11 not found" && exit 1) + echo "cuda-runtime-cusolver: OK" + + - name: cuda-runtime-cusparse + description: "NVIDIA cuSPARSE library" + pipeline: + - runs: | + mkdir -p "${{targets.contextdir}}/usr/lib" + mv "${{targets.destdir}}"/usr/lib/libcusparse.so* "${{targets.contextdir}}/usr/lib/" + test: + pipeline: + - runs: | + test -f /usr/lib/libcusparse.so.12 || (echo "FAIL: libcusparse.so.12 not found" && exit 1) + echo "cuda-runtime-cusparse: OK" + + - name: cuda-runtime-nvtx + description: "NVIDIA Tools Extension Library (NVTX v3)" + pipeline: + - runs: | + mkdir -p "${{targets.contextdir}}/usr/lib" + mv "${{targets.destdir}}"/usr/lib/libnvtx3interop.so* "${{targets.contextdir}}/usr/lib/" + test: + pipeline: + - runs: | + test -f /usr/lib/libnvtx3interop.so.1 || (echo "FAIL: libnvtx3interop.so.1 not found" && exit 1) + echo "cuda-runtime-nvtx: OK" + + - name: cuda-runtime-nvjitlink + description: "NVIDIA JIT Link library" + pipeline: + - runs: | + mkdir -p "${{targets.contextdir}}/usr/lib" + mv "${{targets.destdir}}"/usr/lib/libnvJitLink.so* "${{targets.contextdir}}/usr/lib/" + test: + pipeline: + - runs: | + test -f /usr/lib/libnvJitLink.so.12 || (echo "FAIL: libnvJitLink.so.12 not found" && exit 1) + echo "cuda-runtime-nvjitlink: OK" + + - name: cuda-runtime-cudnn + description: "NVIDIA cuDNN 9.10.1 deep neural network library" + pipeline: + - runs: | + mkdir -p "${{targets.contextdir}}/usr/lib" + mv "${{targets.destdir}}"/usr/lib/libcudnn*.so* "${{targets.contextdir}}/usr/lib/" + test: + pipeline: + - runs: | + test -f /usr/lib/libcudnn.so.9 || (echo "FAIL: libcudnn.so.9 not found" && exit 1) + echo "cuda-runtime-cudnn: OK" diff --git a/cuda-python/test.sh b/cuda-python/test.sh new file mode 100755 index 000000000..877553856 --- /dev/null +++ b/cuda-python/test.sh @@ -0,0 +1,139 @@ +#!/bin/bash +set -euo pipefail + +echo "Testing Python interpreter..." +docker run --rm "$IMAGE" -c "import sys; print(f'Python {sys.version}')" + +echo "Testing TLS/SSL..." +docker run --rm "$IMAGE" -c "import ssl; print('TLS OK:', ssl.OPENSSL_VERSION)" + +echo "Testing stdlib modules..." +docker run --rm "$IMAGE" -c "import json, hashlib, http.client; print('stdlib OK')" + +echo "Verifying all CUDA libraries are present and loadable..." +docker run --rm "$IMAGE" -c " +import ctypes, os, glob + +lib_dir = '/usr/lib' + +# All libraries that should be present from our subpackages +required_libs = { + 'libcudart.so': 'cudart', + 'libcublas.so': 'cublas', + 'libcublasLt.so': 'cublasLt', + 'libnvblas.so': 'nvblas', + 'libcufft.so': 'cufft', + 'libcufftw.so': 'cufftw', + 'libcurand.so': 'curand', + 'libcusolver.so': 'cusolver', + 'libcusolverMg.so': 'cusolverMg', + 'libcusparse.so': 'cusparse', + 'libnvtx3interop.so': 'nvtx3interop', + 'libnvJitLink.so': 'nvJitLink', + 'libcudnn.so': 'cudnn', +} + +for lib_name, label in required_libs.items(): + matches = glob.glob(os.path.join(lib_dir, lib_name + '*')) + if not matches: + print(f'FAIL: {lib_name} not found in {lib_dir}') + exit(1) + lib_path = matches[0] + try: + handle = ctypes.CDLL(lib_path) + print(f' {label}: OK ({os.path.basename(lib_path)})') + except OSError as e: + # Libraries load but may fail to resolve driver deps - expected without GPU + print(f' {label}: present but missing driver dep (expected in CI): {e}') + +print(f'All {len(required_libs)} CUDA libraries verified.') +" + +echo "Verifying CUDA library symbols are real (not stubs)..." +docker run --rm "$IMAGE" -c " +import ctypes + +# Verify key function symbols exist in the libraries. +# This proves the .so files contain real code, not just empty stubs. +# We only check symbols - we don't call them (that would need a GPU). +symbol_checks = { + 'libcudart.so': ['cudaGetDeviceCount', 'cudaMalloc', 'cudaFree', 'cudaMemcpy'], + 'libcublas.so': ['cublasCreate_v2', 'cublasSgemm_v2'], + 'libcufft.so': ['cufftPlan1d', 'cufftExecC2C'], + 'libcurand.so': ['curandCreateGenerator'], + 'libcusparse.so': ['cusparseCreate'], + 'libcudnn.so': ['cudnnCreate', 'cudnnGetVersion'], +} + +for lib_name, symbols in symbol_checks.items(): + try: + handle = ctypes.CDLL(f'/usr/lib/{lib_name}') + except OSError: + print(f' {lib_name}: skipped (cannot load without driver)') + continue + for sym in symbols: + try: + getattr(handle, sym) + except AttributeError: + print(f'FAIL: symbol {sym} not found in {lib_name}') + exit(1) + print(f' {lib_name}: {len(symbols)} symbols verified') + +print('Symbol verification: OK') +" + +echo "Calling CUDA library functions to verify runtime versions..." +docker run --rm "$IMAGE" -c " +import ctypes + +# These version-query functions work WITHOUT a GPU or driver. +# They read version info compiled into the library itself. + +# --- CUDA Runtime version --- +libcudart = ctypes.CDLL('/usr/lib/libcudart.so') +version = ctypes.c_int() +err = libcudart.cudaRuntimeGetVersion(ctypes.byref(version)) +assert err == 0, f'cudaRuntimeGetVersion failed with error {err}' +major = version.value // 1000 +minor = (version.value % 1000) // 10 +print(f' CUDA Runtime: {major}.{minor} (raw: {version.value})') +assert major == 12, f'Expected CUDA 12.x, got {major}.{minor}' + +# --- cuDNN version --- +libcudnn = ctypes.CDLL('/usr/lib/libcudnn.so') +libcudnn.cudnnGetVersion.restype = ctypes.c_size_t +ver = libcudnn.cudnnGetVersion() +cudnn_major = ver // 10000 +cudnn_minor = (ver % 10000) // 100 +cudnn_patch = ver % 100 +print(f' cuDNN: {cudnn_major}.{cudnn_minor}.{cudnn_patch} (raw: {ver})') +assert cudnn_major == 9, f'Expected cuDNN 9.x, got {cudnn_major}' + +# --- cuBLAS version --- +libcublas = ctypes.CDLL('/usr/lib/libcublas.so') +handle = ctypes.c_void_p() +err = libcublas.cublasCreate_v2(ctypes.byref(handle)) +if err == 0: + blas_ver = ctypes.c_int() + libcublas.cublasGetVersion_v2(handle, ctypes.byref(blas_ver)) + print(f' cuBLAS: version {blas_ver.value}') + libcublas.cublasDestroy_v2(handle) +else: + # cublasCreate may need driver init on some builds + print(f' cuBLAS: skipped (create returned {err}, expected without driver)') + +print('CUDA runtime version checks: OK') +" + +echo "Verifying NVIDIA env vars..." +docker run --rm "$IMAGE" -c " +import os +assert os.environ.get('NVIDIA_VISIBLE_DEVICES') == 'all', 'NVIDIA_VISIBLE_DEVICES not set' +assert os.environ.get('NVIDIA_DRIVER_CAPABILITIES') == 'compute,utility', 'NVIDIA_DRIVER_CAPABILITIES not set' +print('NVIDIA env vars: OK') +" + +echo "Verifying no shell..." +docker run --rm --entrypoint /bin/sh "$IMAGE" -c "echo fail" 2>/dev/null \ + && echo "::error::Shell found in image!" && exit 1 \ + || echo "No shell confirmed"