From c8501636f1f56b249d120cf0b00d79f9644b4d83 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Thu, 19 Dec 2024 11:46:41 +0100 Subject: [PATCH] Update Dockerfile and add workflow to register. [ci skip] --- .github/workflows/Container.yml | 105 ++++++++++++++++++++++++++++++++ Dockerfile | 69 ++++++++++++++++++--- README.md | 9 +++ 3 files changed, 173 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/Container.yml diff --git a/.github/workflows/Container.yml b/.github/workflows/Container.yml new file mode 100644 index 0000000000..0b2ce03676 --- /dev/null +++ b/.github/workflows/Container.yml @@ -0,0 +1,105 @@ +name: Publish Docker image + +on: + workflow_dispatch: + inputs: + tag: + description: 'Tag to build instead' + required: false + default: '' + mark_as_latest: + description: 'Mark as latest' + type: boolean + required: false + default: false + push: + tags: + - 'v*' + branches: + - master + +jobs: + push_to_registry: + name: Build container - Julia ${{ matrix.julia }} - CUDA ${{ matrix.cuda }} + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + strategy: + matrix: + julia: ["1.10", "1.11"] + cuda: ["11.8", "12.6"] + include: + - julia: "1.11" + cuda: "12.6" + default: true + + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Check out the package + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref || github.ref_name }} + path: package + + - name: Get package spec + id: pkg + run: | + if [[ -n "${{ inputs.tag }}" ]]; then + echo "ref=${{ inputs.tag }}" >> $GITHUB_OUTPUT + echo "name=${{ inputs.tag }}" >> $GITHUB_OUTPUT + elif [[ "${{ github.ref_type }}" == "tag" ]]; then + echo "ref=${{ github.ref_name }}" >> $GITHUB_OUTPUT + echo "name=${{ github.ref_name }}" >> $GITHUB_OUTPUT + else + echo "ref=${{ github.sha }}" >> $GITHUB_OUTPUT + echo "name=dev" >> $GITHUB_OUTPUT + fi + + VERSION=$(grep "^version = " package/Project.toml | cut -d'"' -f2) + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Get CUDA major version + id: cuda + run: | + CUDA_MAJOR=$(echo ${{ matrix.cuda }} | cut -d'.' -f1) + echo "major=${CUDA_MAJOR}" >> $GITHUB_OUTPUT + + - name: Log in to registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=raw,value=${{ steps.pkg.outputs.name }}-julia${{ matrix.julia }}-cuda${{ steps.cuda.outputs.major }} + type=raw,value=${{ steps.pkg.outputs.name }},enable=${{ matrix.default == true && (github.ref_type == 'tag' || inputs.tag != '') }} + type=raw,value=latest,enable=${{ matrix.default == true && (github.ref_type == 'tag' || (inputs.tag != '' && inputs.mark_as_latest)) }} + type=raw,value=dev,enable=${{ matrix.default == true && github.ref_type == 'branch' && inputs.tag == '' }} + labels: | + org.opencontainers.image.version=${{ steps.pkg.outputs.version }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: . + push: true + provenance: false # the build fetches the repo again, so provenance tracking is not useful + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + JULIA_VERSION=${{ matrix.julia }} + CUDA_VERSION=${{ matrix.cuda }} + PACKAGE_SPEC=CUDA#${{ steps.pkg.outputs.ref }} diff --git a/Dockerfile b/Dockerfile index 2a943e3e7c..093990a6e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,59 @@ # example of a Docker container for CUDA.jl with a specific toolkit embedded at run time. +# +# supports selecting a Julia and CUDA toolkit version, as well as baking in specific +# versions of the CUDA.jl package (identified by a spec recorgnized by the Pkg REPL). +# +# CUDA.jl and other packages are shipped in a system depot, with the user depot mounted +# at `/depot`. persistency is possible by mounting a volume at this location. +# running with reduced privileges (by using `--user`) is also supported. -FROM julia:1.8-bullseye +ARG JULIA_VERSION=1 +FROM julia:${JULIA_VERSION} + +ARG CUDA_VERSION=12.6 + +ARG PACKAGE_SPEC=CUDA + +LABEL org.opencontainers.image.authors="Tim Besard " \ + org.opencontainers.image.description="A CUDA.jl container with CUDA ${CUDA_VERSION} and Julia ${JULIA_VERSION}" \ + org.opencontainers.image.title="CUDA.jl" \ + org.opencontainers.image.url="https://juliagpu.org/cuda/" \ + org.opencontainers.image.source="https://github.com/JuliaGPU/CUDA.jl" \ + org.opencontainers.image.licenses="MIT" # system-wide packages +# no trailing ':' as to ensure we don't touch anything outside this directory. without it, +# Julia touches the compilecache timestamps in its shipped depot (for some reason; a bug?) ENV JULIA_DEPOT_PATH=/usr/local/share/julia -RUN julia -e 'using Pkg; Pkg.add("CUDA")' +# pre-install the CUDA toolkit from an artifact. we do this separately from CUDA.jl so that +# this layer can be cached independently. it also avoids double precompilation of CUDA.jl in +# order to call `CUDA.set_runtime_version!`. +RUN julia -e '#= configure the preference =# \ + env = "/usr/local/share/julia/environments/v$(VERSION.major).$(VERSION.minor)"; \ + mkpath(env); \ + write("$env/LocalPreferences.toml", \ + "[CUDA_Runtime_jll]\nversion = \"'${CUDA_VERSION}'\""); \ + \ + #= install the JLL =# \ + using Pkg; \ + Pkg.add("CUDA_Runtime_jll")' && \ + #= demote the JLL to an [extras] dep =# \ + find /usr/local/share/julia/environments -name Project.toml -exec sed -i 's/deps/extras/' {} + && \ + #= remove nondeterminisms =# \ + cd /usr/local/share/julia && \ + rm -rf compiled registries scratchspaces logs && \ + find -exec touch -h -d "@0" {} + && \ + touch -h -d "@0" /usr/local/share -# hard-code a CUDA toolkit version -RUN julia -e 'using CUDA; CUDA.set_runtime_version!(v"12.2")' -# re-importing CUDA.jl below will trigger a download of the relevant artifacts - -# generate the device runtime library for all known and supported devices. -# this is to avoid having to do this over and over at run time. -RUN julia -e 'using CUDA; CUDA.precompile_runtime()' +# install CUDA.jl itself +RUN julia -e 'using Pkg; pkg"add '${PACKAGE_SPEC}'"; \ + using CUDA; CUDA.precompile_runtime()' && \ + #= remove useless stuff =# \ + cd /usr/local/share/julia && \ + rm -rf registries scratchspaces logs # user environment @@ -25,6 +63,17 @@ RUN julia -e 'using CUDA; CUDA.precompile_runtime()' # case there might not be a (writable) home directory. RUN mkdir -m 0777 /depot -ENV JULIA_DEPOT_PATH=/depot:/usr/local/share/julia + +# we add the user environment from a start-up script +# so that the user can mount `/depot` for persistency +ENV JULIA_DEPOT_PATH=/usr/local/share/julia: +COPY <