diff --git a/tutorial_binary_cache.rst b/tutorial_binary_cache.rst index 7a8d2b26d..5e2730c6a 100644 --- a/tutorial_binary_cache.rst +++ b/tutorial_binary_cache.rst @@ -10,274 +10,252 @@ Binary Caches Tutorial ================================== -In this section of the tutorial, you will learn how to share Spack-built binaries across machines and users using build caches. +This section covers how to share Spack-built binaries across machines and users using **build caches**. -We will explore a few concepts that apply to all types of build caches, but the focus is primarily on **OCI container registries** (like Docker Hub or Github Packages) as a storage backend for binary caches. -Spack supports a range of storage backends, such as an ordinary filesystem, Amazon S3, and Google Cloud Storage, but OCI build caches have a few interesting properties that make them worth exploring more in-depth. +Spack supports a range of storage backends for build caches: an ordinary filesystem, Amazon S3, Google Cloud Storage, and any OCI-compatible container registry (Docker Hub, GitHub Packages, a local ``docker registry``, and so on). +We begin with the filesystem mirror that the tutorial container is already configured to use, and then move on to OCI registries, which carry the additional property that the same artifacts can be used as runnable container images. -Before we configure a build cache, let's install the ``julia`` package, which is an interesting example because it has some non-trivial dependencies like ``llvm``, and features an interactive REPL that we can use to verify that the installation works. +``julia`` is the running example throughout. +It has a non-trivial dependency in ``llvm`` and an interactive REPL that makes it easy to confirm an installation works. -.. code-block:: spec +------------------------------------------- +Installing julia from the build cache +------------------------------------------- + +The tutorial container ships a pre-populated filesystem build cache, registered in the :ref:`basics section ` as the signed mirror named ``tutorial``. +Because that mirror is already active, ``julia`` and its dependencies are available as binaries without any further configuration. + +Create an environment with a view and add ``julia`` to it: + +.. code-block:: console $ mkdir ~/myenv && cd ~/myenv $ spack env create --with-view view . $ spack -e . add julia - $ spack -e . install - -Let's run the ``julia`` REPL -.. code-block:: console +.. note:: - $ ./view/bin/julia - julia> 1 + 1 - 2 + The terms *mirror* and *build cache* are used almost interchangeably, since every build cache is a binary mirror. + Source mirrors also exist but are not covered in this tutorial. -Now we'd like to share these executables with other users. -First we will focus on sharing the binaries with other *Spack* users, and later we will see how users completely unfamiliar with Spack can easily use the applications too. +Install the environment: ------------------------------------------------- -Setting up an OCI build cache on GitHub Packages ------------------------------------------------- +.. code-block:: console -For this tutorial we will be using GitHub Packages as an OCI registry, since most people have a GitHub account and it's easy to use. + $ spack -e . install + ... + [+] dkvv7m3 julia@1.12.6 /home/spack/spack/opt/spack/linux-x86_64_v3/julia-1.12.6-dkvv7m3wqj5xn2lphsc6tay8fbiruoze (3s) -First, go to ``_ to generate a Personal Access Token (classic) with ``write:packages`` permissions. -Copy this token. +Both ``julia`` and every transitive dependency, including ``llvm``, are fetched and relocated from the ``tutorial`` mirror; nothing is built from source. +Given a build cache, the concretizer prefers concrete specs for which binaries already exist. -Next, we will add this token to the mirror configuration section for the Spack environment. -Replace `` with your GitHub username and `` with your GitHub username or an organization where you have permission to create packages. -The build cache name, `buildcache-${USER}-${HOSTNAME}`, is a suggestion; you can choose your own. +Confirm that the executable works through the environment's view: .. code-block:: console - $ export MY_OCI_TOKEN= - $ spack -e . mirror add \ - --oci-username \ - --oci-password-variable MY_OCI_TOKEN \ - --unsigned \ - my-mirror \ - oci://ghcr.io//buildcache-${USER}-${HOSTNAME} - + $ ./view/bin/julia -e 'println(1 + 1)' + 2 .. note:: - We talk about mirrors and build caches almost interchangeably, because every build cache is a binary mirror. - Source mirrors exist too, which we will not cover in this tutorial. + Build caches can be used across different Linux distributions. + The concretizer reuses specs that have a host-compatible ``libc`` (e.g. ``glibc`` or ``musl``), and binaries built with ``gcc`` carry their compiler runtime libraries as a separate dependency, so users do not need to install a compiler first. +------------------------------------------------- +Setting up a local OCI build cache +------------------------------------------------- -Your ``spack.yaml`` file should now contain the following: +The previous section consumed binaries from a build cache. +This section covers publishing binaries to one, using an **OCI container registry** as the backend. -.. code-block:: yaml - - spack: - specs: - - julia - mirrors: - my-mirror: - url: oci://ghcr.io//buildcache-- - access_pair: - id: - secret_variable: MY_OCI_TOKEN - signed: false - -Let's push ``julia`` and its dependencies to the build cache +OCI registries are useful in this role because the same artifacts can serve both as a Spack build cache and as runnable container images. +OCI registries in common use include Docker Hub, GitHub Container Registry (GHCR), and Amazon ECR. +For this tutorial we run a registry locally, which avoids authentication: .. code-block:: console - $ spack -e . buildcache push my-mirror - -which outputs - -.. code-block:: text - - ==> Selected 66 specs to push to oci://ghcr.io//buildcache-- - ==> Checking for existing specs in the buildcache - ==> 66 specs need to be pushed to ghcr.io//buildcache-- - ==> Uploaded sha256:d8d9a5f1fa443e27deea66e0994c7c53e2a4a618372b01a43499008ff6b5badb (0.83s, 0.11 MB/s) - ... - ==> Uploading manifests - ==> Uploaded sha256:cdd443ede8f2ae2a8025f5c46a4da85c4ff003b82e68cbfc4536492fc01de053 (0.64s, 0.02 MB/s) - ... - ==> Pushed zstd@1.5.6/ew3aaos to ghcr.io//buildcache--:zstd-1.5.6-ew3aaosbmf3ts2ylqgi4c6enfmf3m5dr.spack - ... - ==> Pushed julia@1.9.3/dfzhutf to ghcr.io//buildcache--:julia-1.9.3-dfzhutfh3s2ekaltdmujjn575eip5uhl.spack - -The location of the pushed package, when referred to as an OCI image, will be: - -.. code-block:: text + $ docker run -d --rm -p 5000:5000 --name registry registry - ghcr.io//buildcache--:julia-1.9.3-dfzhutfh3s2ekaltdmujjn575eip5uhl.spack +This is the official `registry image `_ from Docker Hub. +It serves an empty OCI registry on ``http://localhost:5000``. -look very similar to a container image --- we will get to that in a bit. +Add it as a second mirror: -.. note:: +.. code-block:: console - Binaries pushed to GitHub packages are ``private`` by default, which means you need a token to download them. - You can change the visibility to ``public`` by going to GitHub Packages from your GitHub account, selecting the ``buildcache`` package, go to ``package settings``, and change the visibility to ``public`` in the ``Danger Zone`` section. - This page can also be directly accessed by going to + $ spack -e . mirror add --unsigned my-registry oci+http://localhost:5000/buildcache - .. code-block:: text +The URL has three parts: - https://github.com/users//packages/container/buildcache/settings +* ``oci+http://``: an OCI registry over plain HTTP, without TLS. + A remote registry would normally use ``oci://`` (HTTPS). +* ``localhost:5000``: the registry's host and port. +* ``/buildcache``: the image name under which Spack publishes all artifacts. +The environment's ``spack.yaml`` now contains the mirror: -------------------------------- -Installing from the build cache -------------------------------- +.. code-block:: yaml -We will now verify that the build cache works by reinstalling ``julia``. + mirrors: + my-registry: + url: oci+http://localhost:5000/buildcache + signed: false -Let's make sure that we *only* use the build cache that we just created, and not the builtin one that is configured for the tutorial. -The easiest way to do this is to override the ``mirrors`` config section in the environment by using a double colon in the ``spack.yaml`` file: +The same configuration works against a hosted registry such as GHCR or Docker Hub by switching to an ``oci://`` URL and supplying credentials with ``--oci-username`` and ``--oci-password-variable``. -.. code-block:: yaml +------------------------------------- +Pushing to the OCI build cache +------------------------------------- - spack: - specs: - - julia - mirrors:: # <- note the double colon - my-mirror: - url: oci://ghcr.io//buildcache-- - access_pair: - id: - secret_variable: MY_OCI_TOKEN - signed: false +Push the environment to the local registry: -An "overwrite install" should be enough to show that the build cache is used (output will vary based on your specific configuration): - -.. code-block:: spec +.. code-block:: console - $ spack -e . install --overwrite julia - ==> Fetching https://ghcr.io/v2//buildcache--/blobs/sha256:34f4aa98d0a2c370c30fbea169a92dd36978fc124ef76b0a6575d190330fda51 - ==> Fetching https://ghcr.io/v2//buildcache--/blobs/sha256:3c6809073fcea76083838f603509f10bd006c4d20f49f9644c66e3e9e730da7a - ==> Extracting julia-1.9.3-dfzhutfh3s2ekaltdmujjn575eip5uhl from binary cache - [+] /home/spack/spack/opt/spack/linux-ubuntu22.04-x86_64_v3/gcc-11.4.0/julia-1.9.3-dfzhutfh3s2ekaltdmujjn575eip5uhl + $ spack -e . buildcache push --without-build-dependencies my-registry + ==> Selected 62 specs to push to oci+http://localhost:5000/buildcache + ==> Checking for existing specs in the buildcache + ==> [1/62] Pushed gcc-runtime@11.5.0/rcxunjn: sha256:c9362f6244e3... (0.08s, 125.76 MB/s) + ... + ==> [62/62] Pushed julia@1.12.6/dkvv7m3: sha256:f8863558da07... (0.21s, 92.40 MB/s) + ==> Uploading manifests + ==> [1/62] Tagged gcc-runtime@11.5.0/rcxunjn as localhost:5000/buildcache:gcc-runtime-11.5.0-rcxunjnw4voqkt5zieeerei767e4s7py.spack + ... + ==> [62/62] Tagged julia@1.12.6/dkvv7m3 as localhost:5000/buildcache:julia-1.12.6-dkvv7m3wqj5xn2lphsc6tay8fbiruoze.spack -Two blobs are fetched for each spec: a metadata file and the actual binary package. -If you've used ``docker pull`` or other container runtimes before, these types of hashes may look familiar. -OCI registries are content addressed, which means that we see hashes like these instead of human-readable file names. +Two things about this invocation are worth noting. ------------------------------------- -Reuse of binaries from a build cache ------------------------------------- +The ``--without-build-dependencies`` flag is passed because ``julia`` was installed from a binary cache, so build-only dependencies like ``cmake`` are not present on this machine. +Without the flag, Spack would report that those packages are not installed. -Spack's concretizer optimizes for **reuse**. -This means that it will avoid source builds if it can use specs for which binaries are readily available. +All artifacts live under the single image name ``localhost:5000/buildcache``. +Spack auto-generates one tag per spec, of the form ``--.spack``, including the package hash so that each spec resolves to a distinct tag. -In the previous example we managed to install packages from our build cache, but we did not concretize our environment again. -Users on other machines with different distributions will have to concretize, and therefore we should make sure that the build cache is indexed so that the concretizer can take it into account. -This can be done by running +Re-running the push detects that nothing needs to be uploaded: .. code-block:: console - $ spack -e . buildcache update-index my-mirror + $ spack -e . buildcache push --without-build-dependencies my-registry + ==> Selected 62 specs to push to oci+http://localhost:5000/buildcache + ==> Checking for existing specs in the buildcache + ==> All specs are already in the buildcache. Use --force to overwrite them. -This operation can take a while for large build caches, since it fetches all metadata of available packages. -For convenience you can also run ``spack buildcache push --update-index ...`` to avoid a separate step. +------------------------------------------- +Reinstalling from the OCI build cache +------------------------------------------- +So far ``julia`` could have come from either mirror. +To confirm that the OCI registry works as a build cache on its own, disable the filesystem ``tutorial`` mirror by changing ``mirrors:`` to ``mirrors::`` in ``spack.yaml``. +The trailing ``::`` replaces, rather than extends, the mirrors inherited from Spack's global configuration: -.. note:: +.. code-block:: yaml - As of Spack 0.22, build caches can be used across different Linux distros. - The concretizer will reuse specs that have a host-compatible ``libc`` dependency (e.g. ``glibc`` or ``musl``). - For packages compiled with ``gcc`` (and a few other compilers), users do not have to install compilers first, as the build cache contains the compiler runtime libraries as a separate package dependency. + mirrors:: + my-registry: + url: oci+http://localhost:5000/buildcache + signed: false -After an index is created, it's possible to list the available packages in the build cache: +Reinstall ``julia`` with ``--overwrite``. +Only ``julia`` is reinstalled; its dependencies remain installed and are not refetched. +The default installer output does not report where a package comes from, so pass ``-v`` to surface the fetch: .. code-block:: console - $ spack -e . buildcache list --allarch + $ spack -e . install -v --overwrite -y julia + [ ] dkvv7m3 julia@1.12.6 fetching from build cache (0s) + ==> Fetching http://localhost:5000/v2/buildcache/blobs/sha256:f8863558da07... + ==> Fetching http://localhost:5000/v2/buildcache/blobs/sha256:4af76428dc77... + [ ] dkvv7m3 julia@1.12.6 relocating (1s) + [+] dkvv7m3 julia@1.12.6 /home/spack/spack/opt/spack/linux-x86_64_v3/julia-1.12.6-dkvv7m3wqj5xn2lphsc6tay8fbiruoze (3s) +Two blobs are fetched per spec: a JSON manifest and the binary tarball. +OCI registries are content-addressed, hence the ``sha256:...`` identifiers rather than human-readable filenames. ---------------------------------- Creating runnable container images ---------------------------------- -The build cache we have created uses an OCI registry, which is the same technology that is used to store container images. -So far we have used this build cache as any other build cache: the concretizer can use it to avoid source builds, and ``spack install`` will fetch binaries from it. +So far the OCI registry has been used only as a Spack build cache. +Since the artifacts are also valid OCI images, they can be pulled directly with ``docker``. -However, we can also use this build cache to share binaries directly as runnable container images. - -We can already attempt to run the image associated with the ``julia`` package that we have pushed earlier: +Consider what happens when running an image without a base image: .. code-block:: console - $ docker run ghcr.io//buildcache--:julia-1.9.3-dfzhutfh3s2ekaltdmujjn575eip5uhl.spack julia - exec /home/spack/spack/opt/spack/linux-ubuntu22.04-x86_64_v3/gcc-11.4.0/julia-1.9.3-dfzhutfh3s2ekaltdmujjn575eip5uhl/bin/julia: no such file or directory + $ docker run --rm localhost:5000/buildcache:julia-1.12.6-dkvv7m3wqj5xn2lphsc6tay8fbiruoze.spack julia -e 'println(1 + 1)' + exec /home/spack/spack/opt/spack/linux-x86_64_v3/julia-1.12.6-dkvv7m3.../bin/julia: no such file or directory -but immediately we see it fails. -The reason is that one crucial part is missing, and that is ``glibc``, which Spack always treats as an external package. +The run fails because the layers we pushed contain the Spack-built artifacts but not the host's ``glibc``, which Spack always treats as an external package. +Without a base image the container has no ``/lib`` directory at all, which produces the error above. -To fix this, we force push to the registry again, but this time we specify a base image with a recent version of ``glibc``, for example from ``ubuntu:24.04``: +The resolution is to push again with ``--base-image`` pointing at a minimal distribution that provides a compatible ``glibc``: .. code-block:: console - $ spack -e . buildcache push --force --base-image ubuntu:24.04 my-mirror - ... - ==> Pushed julia@1.9.3/dfzhutf to ghcr.io//buildcache:julia-1.9.3-dfzhutfh3s2ekaltdmujjn575eip5uhl.spack + $ spack -e . buildcache push --force --without-build-dependencies \ + --base-image ubuntu:24.04 my-registry -Now let's pull this image again and run it: +The base image's ``libc`` must be at least as new as the one used at build time, otherwise the binaries fail at runtime with errors of the form ``version `GLIBC_2.38' not found``. +The distribution itself need not match. +``archlinux:latest``, for example, ships a sufficiently recent ``glibc`` while providing a different userland (``pacman`` instead of ``apt``, and so on). + +The image now runs: .. code-block:: console - $ docker pull ghcr.io//buildcache--:julia-1.9.3-dfzhutfh3s2ekaltdmujjn575eip5uhl.spack - $ docker run -it --rm ghcr.io//buildcache--:julia-1.9.3-dfzhutfh3s2ekaltdmujjn575eip5uhl.spack - root@f53920f8695a:/# julia - julia> 1 + 1 + $ docker run --rm localhost:5000/buildcache:julia-1.12.6-dkvv7m3wqj5xn2lphsc6tay8fbiruoze.spack julia -e 'println(1 + 1)' 2 -This time it works! -The minimal ``ubuntu:24.04`` image provides us not only with ``glibc``, but also other utilities like a shell. - -Notice that you can use any base image of choice, like ``fedora`` or ``rockylinux``. -The only constraint is that it has a ``libc`` compatible with the external ``libc`` Spack used to build the binaries. -Spack does not validate this. +In addition to ``glibc``, the base image provides a shell and the standard utilities. -------------------------------------- Spack environments as container images -------------------------------------- -The previous container image is a good start, but it would be nice to add some more utilities to the image. -If you've paid attention to the output of some of the commands we have run so far, you may have noticed that Spack generates exactly one image tag for each package it pushes to the registry. -Every Spack package corresponds to a single layer in each image, and the layers are shared across the different image tags. +The preceding section produced one image per package. +For most uses a single image containing the full environment is more convenient. -Because Spack installs every package into a unique prefix, it is incredibly easy to compose multiple packages into a container image. -In contrast to Docker images built from commands in a ``Dockerfile`` where each command is run in sequence, Spack package layers are independent, and can in principle be combined in any order. - -Let's add a simple text editor like ``vim`` to our previous environment next to ``julia``, so that we could both edit and run Julia code. - -.. note:: +Add a text editor to the environment, so that the image can both edit and run Julia code: - You may want to change ``mirrors::`` to ``mirrors:`` in the ``spack.yaml`` file to avoid a source build of ``vim`` --- but a source build should be quick. - -.. code-block:: spec +.. code-block:: console $ spack -e . install --add vim -This time, when we push to the OCI registry, we also pass ``--tag julia-and-vim`` to instruct Spack to create an additional image tag for the environment as a whole, with a more human-readable name: +.. note:: + + With the ``tutorial`` mirror disabled, ``vim`` is built from source. + Change ``mirrors::`` back to ``mirrors:`` first to install it from the ``tutorial`` cache; either way the build is quick. +Pass ``--tag`` to assign the environment image a human-readable name: .. code-block:: console - $ spack -e . buildcache push --base-image ubuntu:24.04 --tag julia-and-vim my-mirror - ==> Tagged ghcr.io//buildcache:julia-and-vim + $ spack -e . buildcache push --without-build-dependencies \ + --base-image ubuntu:24.04 \ + --tag julia-and-vim \ + my-registry + ... + ==> Tagged localhost:5000/buildcache:julia-and-vim + +Spack publishes each package as its own image layer. +Layers are shared between image tags, so the combined image takes almost no extra storage. +Unlike Docker, where each ``RUN`` line creates a layer that depends on the previous one, Spack package layers are independent and can be combined in any order. -Now let's run a container from this image: +Run the combined image: .. code-block:: console - $ docker run -it --rm ghcr.io//buildcache--:julia-and-vim - root@f53920f8695a:/# vim ~/example.jl # create a new file with some Julia code - root@f53920f8695a:/# julia ~/example.jl # and run it + $ docker run -it --rm localhost:5000/buildcache:julia-and-vim + root@f53920f8695a:/# vim example.jl # write some Julia code + root@f53920f8695a:/# julia example.jl # and run it ------------------------------------- -Do I need ``docker`` or ``buildah``? ------------------------------------- +Both ``julia`` and ``vim`` are immediately usable because Spack writes ``PATH`` into the image's environment, pointing at each package's install prefix. +Spack does not materialize the environment's view inside the image; the packages live at their original Spack prefixes, identical to those on the host. -In older versions of Spack it was common practice to generate a ``Dockerfile`` from a Spack environment using the ``spack containerize`` command, and then use ``docker build`` or other runtimes to create a container image. +---------------------------------------- +Relation to ``docker build`` workflows +---------------------------------------- -This would trigger a multi-stage build, where the first stage would install Spack itself, compilers and the environment, and the second stage would copy the installed environment into a smaller image. -For those familiar with ``Dockerfile`` syntax, it would structurally look like this: +In earlier versions of Spack the common practice was to generate a ``Dockerfile`` from a Spack environment using ``spack containerize`` and then build the image with ``docker build``: .. code-block:: Dockerfile @@ -288,32 +266,30 @@ For those familiar with ``Dockerfile`` syntax, it would structurally look like t FROM COPY --from=build /opt/spack/opt /opt/spack/opt -This approach is still valid, and the ``spack containerize`` command continues to exist, but it has a few downsides: +This approach still works and ``spack containerize`` is still available, but it has several drawbacks: -* When ``RUN spack -e /root/env install`` fails, ``docker`` will not cache the layer, meaning that all dependencies that did install successfully are lost. - Troubleshooting the build typically means starting from scratch either within a ``docker run`` session or on the host system. -* In certain CI environments, it is not possible to use ``docker build`` directly. - For example, the CI script itself may already run in a Docker container, and running ``docker build`` *safely* inside a container (Docker-in-Docker) is tricky. +* If ``RUN spack -e /root/env install`` fails, Docker discards the whole layer, including any successfully built dependencies. + Troubleshooting typically requires starting from scratch in a ``docker run`` session. +* Some CI environments cannot run ``docker build`` safely — for example, when the CI script itself runs inside a container ("Docker-in-Docker"). -The takeaway is that Spack decouples the steps that ``docker build`` combines: build isolation, running the build, and creating an image. -You can run ``spack install`` on your host machine or in a container, and run ``spack buildcache push`` separately to create an image. +The OCI build cache approach decouples the three responsibilities that ``docker build`` combines: build isolation, running the build, and producing an image. +``spack install`` can be run in any environment (host, sandbox, container), and ``spack buildcache push`` then turns the result into images. ---------- Relocation ---------- -Spack is different from many package managers in that it lets users choose where to install packages. -This makes Spack very flexible, as users can install packages in their home directory and do not need root privileges. -The downside is that sharing binaries is more complicated, as binaries may contain hard-coded, absolute paths to machine specific locations, which have to be adjusted when these binaries are installed on a different machine or in a different path. +Spack installs packages under an arbitrary prefix, typically ``~/spack/opt/spack/...``. +This is more flexible than most package managers, but it also means that binaries contain absolute paths to machine-specific locations, which must be rewritten when the binary is reinstalled elsewhere. -Fortunately Spack handles this automatically upon install from a binary cache. -But when you build binaries that are intended to be shared, there is one thing you have to keep in mind: Spack can relocate hard-coded paths in binaries *provided that the target prefix is shorter than the prefix used during the build*. +Spack does this rewriting automatically when installing from a binary cache. +When producing binaries that are meant to be redistributed, one constraint applies: Spack can only relocate paths in a binary if the target prefix is no longer than the prefix used at build time. -The reason is that binaries typically embed these absolute paths in string tables, which is a list of null-terminated strings, to which the program stores offsets. -That means we can only modify strings in-place, and if the new path is longer than the old one, we would overwrite the next string in the table. +The reason is that absolute paths typically reside in the binary's string table — a list of null-terminated strings referenced by offset. +Strings can be edited in place, but they cannot grow without overwriting their neighbors. -To maximize the chances of successful relocation, you should build your binaries in a relatively long path. -Fortunately Spack can automatically pad paths to make them longer, using the following command: +To maximize the likelihood of successful relocation, build in a relatively long path. +Spack can pad install prefixes automatically: .. code-block:: console @@ -323,29 +299,30 @@ Fortunately Spack can automatically pad paths to make them longer, using the fol Using build caches in CI ------------------------ -Build caches are a great way to speed up CI pipelines. -Both GitHub Actions and GitLab CI support container registries, and this tutorial should give you a good starting point to leverage them. +Build caches also speed up CI pipelines. +Both GitHub Actions and GitLab CI support container registries, so the workflow described above applies directly in CI. -Spack also provides a basic GitHub Action that already provides you with a binary cache: +Spack provides a GitHub Action that configures a shared build cache: .. code-block:: yaml jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - name: Set up Spack uses: spack/setup-spack@v2 - run: spack install python # uses a shared build cache -and the `setup-spack readme `_ shows you how to cache further binaries that are not in the shared build cache. +See the `setup-spack readme `_ for instructions on caching additional binaries that are not in the shared build cache. ------- Summary ------- -In this tutorial we have created a build cache on top of an OCI registry, which can be used +This tutorial covered: -* to run ``spack install julia vim`` on machines and have Spack fetch pre-built binaries instead of building from source. -* to automatically create container images for individual packages when pushing to the cache. -* to create container images for entire Spack environments (multiple packages) at once. +* installing ``julia`` from the pre-configured ``tutorial`` build cache without any source builds; +* setting up a local OCI registry as a second build cache and pushing the environment to it; +* reinstalling from the OCI cache to confirm that it functions as a regular Spack mirror; +* using the same OCI artifacts as runnable container images, both per-package and as a combined environment with ``--tag``.