The Moment of Creation: Writing the Dockerfile for Curio's CUDA Proving Stack
A Single Line That Encapsulates an Hour of Research
In the course of a marathon coding session to build a production-ready Docker container for the Filecoin Curio proving stack, there is a message that appears almost anticlimactic in its brevity. At message index 562, the assistant writes:
Now let me write the Dockerfile: [write] /tmp/czk/Dockerfile.cuzk Wrote file successfully.
That is the entirety of the message. Three lines. A tool call and its confirmation. On its surface, it looks like a trivial checkpoint — the assistant simply announces an intention and executes it. But this message is the culmination of an extraordinary density of research, architectural reasoning, and systems-level understanding. It represents the moment when more than a dozen threads of investigation — spanning Docker build patterns, CUDA toolkit compatibility, Rust and Go toolchain versions, system package names for Ubuntu 24.04, submodule initialization order, and the intricate dance of FFI compilation flags — all converge into a single file on disk. Understanding why this message matters requires unpacking everything that led to it and everything it set in motion.
The Context: Why This Dockerfile Was Needed
The broader session had been building toward containerization for hours. The assistant had already implemented Pre-Compiled Constraint Evaluator (PCE) extraction for all four Filecoin proof types (PoRep, WinningPoSt, WindowPoSt, SnapDeals), fixed a crash caused by is_extensible() mismatch between RecordingCS and WitnessCS constraint systems, threaded a gpu_index parameter through the entire proving stack from C++ CUDA kernels up through Rust FFI into Go, and deployed and verified multi-GPU load balancing on a remote test host. The software stack was working. The next logical step was packaging it for deployment.
The user's instruction was clear: "Build a Docker container with curio (Go binary), cuzk (Rust/CUDA binary), all dependencies, and an entrypoint that fetches 32G PoRep and Snap proving params on first start." The container needed CUDA 13, mainnet build tags, and runtime parameter fetching rather than baking multi-gigabyte proving parameters into the image itself.
This was not a simple task. The Curio project is a heterogeneous engineering marvel — a Go binary that wraps a Rust cryptographic library (filecoin-ffi) via CGO, which in turn links against a C++ CUDA library (supraseal) that itself depends on BLST (BLS signatures), SPDK (storage performance), and sppark (GPU acceleration). The Rust/CUDA daemon (cuzk) adds another layer of complexity with its own build toolchain. Containerizing this stack requires getting every layer right: the base image, the build toolchain, the system dependencies, the build order, and the runtime environment.
The Research Phase: Building the Blueprint
The assistant did not simply start typing a Dockerfile. Messages 549 through 561 represent an exhaustive research phase. The assistant read the existing OpenCL-based Dockerfile at /tmp/czk/Dockerfile to understand the project's established container patterns. It read the build makefiles (00-vars.mk, 10-deps.mk, 30-build.mk) to understand how FFI_USE_CUDA=1, FFI_USE_CUDA_SUPRASEAL=1, and FFI_BUILD_FROM_SOURCE=1 control the build. It read the GitHub Actions dependency list (install-deps/action.yml) to compile a comprehensive list of apt packages needed. It read the supraseal build.sh to understand GCC version requirements and CUDA architecture targets. It read build-blst.sh to understand how BLST is cloned and compiled.
Then came the verification steps. The assistant searched Docker Hub to confirm that nvidia/cuda:13.0.2-devel-ubuntu24.04 and nvidia/cuda:13.0.2-runtime-ubuntu24.04 images exist. It verified that libssl3t64 is the correct OpenSSL package name for Ubuntu 24.04 Noble. It confirmed libhwloc15 is the right hwloc runtime library. It checked that the Go module is github.com/filecoin-project/curio with Go 1.24.7 and that the Rust toolchain file specifies channel 1.86.0. It examined the existing docker/curio/ directory to understand the project's Docker conventions.
This research phase embodies a crucial principle of effective systems engineering: verify before you write. Every package name, every version number, every build flag was checked against reality before being committed to the Dockerfile. The assistant was building a blueprint from primary sources, not from memory or guesswork.
The Architectural Decisions Encoded in the Dockerfile
When the assistant finally writes the Dockerfile in message 562, that single write tool call encodes several significant architectural decisions:
Multi-stage build: The Dockerfile uses a two-stage pattern. Stage 1 (builder) uses the CUDA 13 devel image as its base and installs Go 1.24, Rust 1.86.0, gcc-13, and all build dependencies. Stage 2 (runtime) uses the slimmer CUDA 13 runtime image and copies only the compiled binaries and runtime libraries. This is standard Docker best practice — keep build toolchain out of the final image to minimize size and attack surface.
Base image choice: Using nvidia/cuda:13.0.2-devel-ubuntu24.04 as the builder is a deliberate decision. The devel image includes nvcc, the CUDA compiler, and all headers needed to compile CUDA kernels. The runtime image (nvidia/cuda:13.0.2-runtime-ubuntu24.04) includes only the CUDA driver libraries needed to execute CUDA programs. Ubuntu 24.04 Noble was chosen for its modern package set and compatibility with the required toolchains.
Build order: The Dockerfile sequences the build carefully. First, system dependencies are installed via apt. Then git submodule update --init --recursive fetches the filecoin-ffi and sppark submodules. Then make deps builds BLST, filecoin-ffi, and supraseal. Then make build compiles the curio Go binary with CUDA support. Finally, cargo build --release --bin cuzk-daemon compiles the Rust/CUDA daemon. This order respects the dependency chain — supraseal must be built before the Go binary can link against it.
Runtime parameter fetching: Rather than baking the ~100GB of proving parameters into the image, the assistant designed an entrypoint script (written in message 558) that checks for parameters on first start and runs curio fetch-params 32GiB if they are missing. This keeps the image size manageable and allows the same image to be used with different parameter sets.
Input Knowledge Required
To write this Dockerfile, the assistant needed a remarkably broad knowledge base:
- Docker multi-stage build patterns: Understanding how to structure a builder stage and a runtime stage, how to use
COPY --from=builderto transfer artifacts, and how to select appropriate base images. - CUDA Docker ecosystem: Knowing that NVIDIA publishes devel, runtime, and base variants of CUDA images, and understanding which variant is appropriate for building vs. running.
- Ubuntu package management: Knowing the specific apt package names for libhwloc, libssl, libgmp, libnuma, and other dependencies on Ubuntu 24.04.
- Go build system: Understanding CGO, build tags, and how
CGO_LDFLAGS_ALLOWworks to permit linker flags that Go would otherwise reject. - Rust/Cargo build system: Understanding how to build a specific binary from a workspace, how
rust-toolchain.tomlcontrols the Rust version, and how to set environment variables for nvcc. - Filecoin FFI build process: Understanding the interplay of
FFI_USE_CUDA,FFI_USE_CUDA_SUPRASEAL, andFFI_BUILD_FROM_SOURCEflags, and knowing that the FFI must be built from source (not using precompiled binaries) when CUDA support is needed. - supraseal build requirements: Knowing that GCC 12 or 13 is required for C++17 compatibility, that CUDA architecture targets range from sm_75 to sm_90, and that BLST must be built with
-march=native.
Output Knowledge Created
The Dockerfile.cuzk file (visible when read back in message 567) is a permanent artifact that encodes all of the above knowledge into a reproducible build process. It transforms the implicit understanding of how Curio and cuzk are built into an explicit, automated, containerized build. This is knowledge that can be shared, versioned, and executed by anyone with Docker and an NVIDIA GPU.
The file also creates a template for future containerization efforts. If the project adds support for new GPU architectures, new proof types, or new sector sizes, the Dockerfile provides a clear pattern for extending the build. The entrypoint script similarly provides a pattern for runtime initialization that can be adapted for different parameter sets or startup behaviors.
Assumptions and Potential Issues
The Dockerfile makes several assumptions that could prove incorrect:
- CUDA 13 image availability: The assistant confirmed that
nvidia/cuda:13.0.2-devel-ubuntu24.04exists on Docker Hub, but this assumes continued availability. If NVIDIA restructures its Docker tags, the base image reference would need updating. - Network access during build: The Dockerfile clones git repositories (BLST, SPDK) during the build. This assumes the build environment has unrestricted network access. In air-gapped environments, these dependencies would need to be vendored or mirrored.
- Sufficient build resources: Compiling CUDA code and Rust code simultaneously requires significant memory and disk space. The Docker build might fail on resource-constrained systems.
- Python environment quirks: As the subsequent messages in the session reveal, the build encountered a
pip uninstallerror during SPDK setup due to a missing RECORD file in the base image's Python environment. This was an unforeseen interaction between the CUDA base image's pre-installed Python packages and the SPDK build script's cleanup logic. - GCC version compatibility: The Dockerfile installs gcc-13 specifically, but the supraseal build script checks for GCC 12 or 13. If a future version of the base image ships a different default GCC, the build could break.
The Significance of This Moment
Message 562 is a study in the power of preparation. The assistant could have started writing the Dockerfile immediately, guessing at package names and build flags, iterating through trial and error. Instead, it invested in research — reading every relevant file, verifying every assumption, checking every package name against reality. When the moment came to write, the Dockerfile emerged fully formed.
This approach is characteristic of expert systems engineering. The actual writing of code is often the smallest part of the work. The real effort goes into understanding the system, verifying assumptions, and building a mental model of how all the pieces fit together. The Dockerfile is not created in the writing — it is created in the research. The writing is merely the act of recording what has already been discovered.
In the broader context of the session, this message marks the transition from research to execution. The next messages (563 onwards) will test the Dockerfile against reality, discovering the missing jq dependency, the libcuda.so.1 symlink issue, the PEP 668 pip restriction, and the SPDK pip uninstall error. These are not failures of the Dockerfile's design — they are the inevitable friction of translating a theoretical build process into a real containerized environment. The Dockerfile written in message 562 was the correct first draft; the subsequent iterations would refine it based on empirical feedback from the build system.
This is the essence of building complex systems: research deeply, write deliberately, then iterate relentlessly.