Julia in WSL: Project Environments, Manifests, and Precompilation
Run Julia projects natively in WSL with explicit environments, committed manifests, controlled precompilation, and verified Linux package artifacts.
Julia is well suited to a Linux development environment under WSL when its executable, package depot, project files, and native dependencies are all installed on the Linux side. This setup lets developers exercise the same Linux binary and filesystem assumptions used by Linux CI without turning WSL into a production compute cluster. The important distinction is between a project’s declared Julia package environment and the broader machine state required to build and run it.
Julia’s package manager provides project environments and a manifest that records resolved package versions and sources. Those files help reproduce the Julia dependency graph, but they do not pin the WSL kernel, system shared libraries, source datasets, hardware, or every external service. Keep those boundaries explicit in the project README and CI configuration.
Select one Linux Julia installation
Install Julia using the project’s chosen supported method and record the version, architecture, and executable path. Do not reuse a Windows Julia depot or copy package artifacts between Windows and Linux. Native packages can include compiled code and platform-specific artifacts; a package that has already been built for Windows is not a valid Linux cache entry merely because its Julia source is identical.
Keep the checkout and Julia depot on the Linux filesystem. Microsoft recommends the distro filesystem for Linux command-line workloads. WSL paths mounted from Windows are useful for intentional interoperability, but they introduce another filesystem and path-translation boundary. Use a controlled fixture under /mnt/c only when testing that boundary, not as the baseline for package precompilation or file-heavy workloads.
Verify which Julia is being executed before changing packages:
command -v julia
julia --version
julia -e 'println(Sys.BINDIR); println(Sys.MACHINE); println(DEPOT_PATH)'
If a Windows installation is also on PATH, make the WSL executable explicit while diagnosing a project. Editors can launch a different runtime from the one visible in an interactive terminal. Confirm the process environment and working directory in the exact launcher used by tests or notebooks.
Define and activate a project environment
Julia environments are selected by an active project. A project file declares direct dependencies and compatibility constraints; a manifest records the resolved graph. Use the package manager from the repository root and make the environment explicit in commands so a globally active environment cannot contaminate the result.
cd "$HOME/src/julia-lab"
julia --project=. -e 'using Pkg; Pkg.instantiate()'
julia --project=. -e 'using Pkg; Pkg.status()'
Pkg.instantiate() materializes dependencies for the active project using the repository’s project and manifest metadata. Pkg.status() provides a useful review of the environment, but it does not prove all packages can load or that an application test passes. Add direct dependencies intentionally through the package manager rather than editing the manifest by hand. If a project supports multiple Julia versions, validate the compatibility range separately rather than assuming one manifest proves every supported version.
Commit Project.toml and, for applications or reproducibility-sensitive work, normally commit the corresponding manifest according to the project’s policy. A library package may use compatibility bounds to let downstream users resolve their own graphs; an application may prefer a fully resolved manifest. Make the intended policy explicit. A stale manifest can continue to run on one machine while a clean checkout resolves or loads differently.
Understand package loading and precompilation
Julia’s code-loading system compiles methods as they are needed and caches precompiled package images. First-run latency can therefore differ substantially from a warmed session. Precompilation is associated with Julia version, platform, dependencies, and package state; it should not be treated as a portable binary cache to copy between Windows and WSL. Keep generated depots out of version control unless the project has a narrow, documented reason to preserve an artifact.
For a controlled test, instantiate in a clean project and run the same script twice. Record first-run and warm-run behavior separately. If a package fails during precompilation, capture the full error and identify whether the problem is Julia compatibility, an artifact download, a native library, or package code. Deleting the entire depot can force a large re-download and erase useful cache evidence, so test with a separate temporary depot before clearing the main one.
Use project activation deliberately in source files and scripts. A package module can declare its dependencies in its project while a top-level application uses a different environment. Avoid launching a script from an arbitrary current directory if it depends on a local project file. In automation, pass --project=/absolute/or/repository/path or set the working directory predictably, then log the active project and Julia version.
Native artifacts and filesystem boundaries
Some Julia packages download binary artifacts or call external tools. These artifacts must match the Linux architecture and platform in WSL. If a package reports an unavailable artifact, confirm the Julia platform triplet and support matrix instead of overriding the download URL with a Windows binary. For packages that depend on system libraries, install the distribution’s documented runtime or development package and confirm the dynamic linker can resolve it.
Keep data and output paths stable. Relative paths are resolved against the process working directory, not necessarily the project file’s directory. In scripts, derive paths from a known project root or accept them as explicit arguments. Test file names with spaces, non-ASCII characters, and case differences if input may come from Windows. Do not assume the Linux environment sees a Windows drive path in the same form as a native Windows Julia process.
Julia’s parallel and distributed APIs are useful for local experiments, but WSL does not make one distribution a multi-host cluster. Process launch, worker environment, thread count, and CPU limits still matter. Record thread and worker settings for benchmarks, and distinguish compilation time from steady-state computation. A local speedup is not a deployment capacity estimate.
Test packages independently from startup speed
For packages that define tests, invoke the package manager’s test workflow in the active project and keep external services disposable. A package loading successfully is only an import check; it does not verify numerical invariants, file handling, or extension behavior. Split fast unit tests from longer integration tests, and note which one a local command ran. If a test depends on a GPU, database, or network endpoint, report that capability explicitly rather than calling the entire environment “reproducible.”
When comparing a cold environment with a warmed depot, preserve separate timings for dependency instantiation, precompilation, test execution, and application work. Package precompile caches can improve iteration speed, while runtime compilation may still occur on first use of methods. A long first run is not necessarily a deadlock; inspect CPU, disk, and Julia logs before terminating a process. Conversely, a warm session can hide a missing dependency that a new WSL user would have to fetch.
If teams share a project, agree on a Julia compatibility range and when the manifest is updated. Review dependency updates for transitive package changes and rerun tests after refreshing the manifest. Do not commit a personal depot or absolute WSL path to make another developer’s environment “work.”
Troubleshoot the actual layer
If packages resolve but cannot load, inspect Project.toml, Manifest.toml, compatibility bounds, Julia version, and artifact logs. If a native library fails to load, inspect the Linux path and package-manager state rather than changing Windows PATH. If precompilation is repeatedly invalidated, compare project and Julia versions, file ownership, and the location of DEPOT_PATH. If builds are slow, separate source download, artifact fetch, precompilation, compilation, and data I/O.
For a clean reproducibility check, clone or copy the project into a disposable Linux directory, keep the manifest, use a new depot, instantiate, and run a bounded test. Do not delete the active depot or modify lock metadata until the failure is understood. If the test uses a remote service or data source, capture that dependency separately; a deterministic Julia environment cannot freeze an external endpoint.
Acceptance criteria
Accept the WSL Julia environment when the Linux executable and version are recorded, commands activate the intended project, package and manifest policy is documented, a clean depot can instantiate the environment, and representative tests pass from the Linux filesystem. Confirm that platform artifacts are Linux-native, data paths are explicit, and first-run compilation is not confused with steady-state performance.
Julia in WSL offers a productive Linux language environment. It is not a guarantee that a package supports every architecture, that project files lock system libraries, or that local parallel tests predict cluster behavior.
Related:
- Nix in WSL: Reproducible Development Shells Inside a Linux Distro
- Rust in WSL: rustup Toolchains, Linux Linkers, and Target Artifacts
Sources: