Docker Containerization

Using Docker to Access Linux-Only ML Training Tools from Windows

Plenty of data science tooling assumes a Linux environment. Develop on Windows and that assumption becomes impactful, particularly with research code leaning on native compilers, system libraries or tightly controlled build chains. Docker bridges that gap cleanly and repeatably. By packaging a Linux environment into a lightweight container, it becomes possible to run the same tools locally that would otherwise require a dedicated Linux machine or server.

What follows covers why Docker suits this setting and how it differs from a virtual machine. The worked example prepares a docker image and container for the symbolic regression tool Brush, which currently does not have Windows support. Three interactive figures in the sections that follow build up this mental model before the walkthrough begins.

Virtual machines vs. Docker containers

Virtual machines and Docker containers both get Linux software running on a non-Linux system. How they get there could hardly be more different. A virtual machine virtualizes the hardware beneath it. A hypervisor presents virtual devices to the guest, which boots its own kernel on them and brings up system services and a full filesystem. This makes VMs flexible, but also heavy. Startup times are long, disk usage is large, and resource overhead can be significant.

Docker containers share the host kernel and isolate only what is needed at the application level. Instead of virtualizing hardware, Docker isolates the user space. This leads to faster startup, smaller images, and closer integration with local development workflows. For data science tasks that involve compiling code, running experiments, and iterating quickly, this difference matters. In practice, Docker feels closer to running native Linux tools, while a virtual machine (VM) feels like managing a second computer.

Figure 1 makes this contrast concrete. Toggle between the two models. A virtual machine stacks a complete guest operating system, kernel included, on top of a hypervisor. A container keeps only the application and its libraries, and shares the host kernel underneath. That shared kernel is the reason containers start faster and stay smaller, and on Windows it is supplied by WSL 2, which the next figure examines directly.

Figure 1

Two ways to run Linux software on one machine

Every virtual machine instance boots its own guest kernel and operating-system user space. A Linux container runs instead as a group of processes fenced off by a shared Linux kernel, carrying only its application and user-space dependencies. On a native Linux host that kernel belongs to the host. On Windows, Docker Desktop supplies it through WSL 2 (Figure 2). Use the toggle to compare the two stacks, and hover or focus any layer for a short explanation.

Layered comparison of a virtual machine stack and a container stack Both stacks sit on the same hardware. In the virtual machine view a host operating system and then a hypervisor sit above it, while the container view has neither: one shared Linux kernel sits directly above the hardware, with the Docker Engine above it only creating and managing containers, never running their code. In the virtual machine view, each of two instances is a tall box containing its own full guest operating system and kernel, then libraries, then the application, so the heavy guest operating system is duplicated per instance. In the container view, each container box holds only libraries and the application and relies on the single shared kernel below, so nothing heavy is duplicated. Hardware CPU · Memory · Disk · (optional) GPU Linux kernel shared by every container SHARED Linux kernel. Every container shares this one kernel. Namespaces isolate what each sees; cgroups limit resources. No guest kernel per container; on Windows it comes from WSL 2. Docker Engine + runtime creates and manages containers; the kernel enforces isolation Docker Engine. Creates and manages containers: builds images, starts them, sets up their config. The Linux kernel below actually runs their code and enforces the isolation. Container Application the ML training tool Libraries + dependencies only what the tool needs A single container. Only the app and the libraries it needs. No guest OS inside, so it is light. Many run at once, and they all share the one kernel below. Container Application another tool or service Libraries + dependencies isolated from the other container Multiple containers. Each container is isolated from the others, yet none carries its own OS. That is the core saving versus running a full VM per tool. Both containers share the one Linux kernel below (the engine only manages them) Container model. One kernel, shared by every container. Hover or focus the teal, blue and white blocks to see what each layer does. WHY IT IS LIGHTER No separate guest kernel per container, though each image may still hold Linux user-space files. Images are typically smaller and start faster than full VMs. Hardware CPU · Memory · Disk · (optional) GPU Host operating system the everyday system you work in Hypervisor creates virtual hardware for each guest Hypervisor. Emulates a full set of virtual hardware so a complete guest OS can boot inside each VM. Strong isolation, but heavier to run. Virtual machine Application Libraries + dependencies Guest operating system its own full kernel system services device drivers duplicated in every VM A virtual machine. Carries a complete guest OS with its own kernel, drivers and services. Every VM repeats all of this (the blue block below). Virtual machine Application Libraries + dependencies Guest operating system a second full kernel duplicated services duplicated drivers duplicated again, in full Duplicated weight. A second VM means a second full operating system in memory and on disk. Flexible and strongly isolated, but far heavier. Virtual machine model. Each VM boots a complete guest operating system of its own. Hover or focus a layer to see what it contributes. WHY IT IS HEAVIER Every virtual machine repeats a whole guest OS, so it uses more disk and memory and is slower to start. Switch to Container to see that weight disappear. Vertical comparison of the virtual machine and container stacks Both views sit on the same hardware. The virtual machine view adds a host operating system and a hypervisor; the container view instead has one shared Linux kernel directly above the hardware, with the Docker Engine only managing the containers. A virtual machine box adds a full guest operating system with its own kernel for each instance, which is heavy. A container box adds only libraries and the application and reuses one shared kernel, which is light. Container Applicationthe ML training tool Libraries + dependenciesonly what the tool needs Docker Engine + runtime creates and manages containers Linux kernel, shared by every container namespaces isolate what each sees; cgroups limit Hardware · CPU · Memory · Disk WHY IT IS LIGHTER No separate guest kernel per container; each image may still hold user-space files. Smaller than a VM. Virtual machine Application Libraries + dependencies Guest operating system its own full kernel system services · drivers heavy, copied for every VM Hypervisor creates virtual hardware per guest Host operating system Hardware · CPU · Memory · Disk WHY IT IS HEAVIER Each VM repeats a whole guest OS, so it needs more disk and memory.
Figure 1. Isolation boundary location. One virtual machine means one guest kernel and one full operating-system environment, every time. A container skips all of that. It carries an application and its user-space dependencies, and borrows a Linux kernel from whatever else runs on that host or backend. Hence small, hence quick to start. Docker Engine creates and manages containers. The Linux kernel enforces their isolation. Namespaces limit what each container sees, cgroups limit the resources it can use, and capabilities and seccomp tighten things further. That trust model differs from a virtual machine, which places a separate guest kernel behind a hardware-virtualization boundary, and a virtual machine remains the choice for highly untrusted workloads. On a native Linux host the shared kernel is the host’s. On Windows it belongs to Docker Desktop’s WSL 2 backend, which Figure 2 examines.

The reason tools are not available on Windows

Many advanced data science and machine learning tools are developed primarily for Linux. There are several reasons for this:

• Linux provides a stable and consistent toolchain for C, C++, and Fortran, which are common in high performance libraries.
• Package managers and build systems on Linux are easier to script and automate.
• Research code often targets server and cluster environments, which are overwhelmingly Linux-based.

While ports of such tools are sometimes possible, they are rarely the primary focus of development. Rather than fighting the platform mismatch, Docker allows the Linux environment to be brought directly into the local workflow.

Figure 2 shows how this works in practice on Windows, where the shared Linux kernel does not come from Windows at all. Hover or focus each layer to see its role, and use the switch to reveal what optional GPU access additionally requires.

Figure 2

How a Linux container runs on Windows

Docker does not run Linux tools on the Windows kernel directly. On Windows it uses WSL 2 by default, a lightweight virtual machine that provides a genuine Linux kernel, and every Linux container shares that one kernel. Hover or focus a layer to read what it does, and use the switch to see what optional GPU access requires.

The execution stack that runs a Linux container on Windows From the bottom up: physical hardware including CPU, memory, disk and an optional GPU, then Windows (the operating system you work in), then Docker Desktop (the Windows application that starts and manages the WSL 2 backend), then WSL 2 (a lightweight virtual machine that provides one full Linux kernel), then the Docker Engine running in Docker Desktop’s own docker-desktop WSL distribution, then the Linux containers on top, each holding the machine-learning tool and its libraries and sharing that single Linux kernel. A command you run reaches the tool inside the container. Optional GPU access threads up from the hardware GPU through the NVIDIA Windows driver, WSL 2 GPU paravirtualization and the Docker runtime into the container. It requires a supported NVIDIA GPU, current Windows and WSL 2 components, an NVIDIA Windows driver with WSL 2 support, Docker Desktop on the WSL 2 backend, a GPU-capable image and a run-time gpus request. The NVIDIA Container Toolkit is a separate step needed only when Docker Engine runs directly inside WSL rather than with Docker Desktop. Linux container ML training tool runs as if on native Linux Libraries + dependencies compilers, Python, native builds Linux container. The tool and its libraries, isolated with namespaces and cgroups. It shares the Linux kernel below, so no second OS is booted per tool. Docker Engine (dockerd) in its own docker-desktop WSL distro Docker Engine. The dockerd daemon runs inside Docker Desktop, in its own docker-desktop WSL distro. It builds images and starts containers. WSL 2 lightweight VM one full Linux kernel, shared by the Linux containers this is where Linux code executes Linuxkernel WSL 2: the key layer. A real but lightweight VM, created by the Windows virtualization platform. It provides one genuine Linux kernel that every container shares, so there is still exactly one kernel serving every tool. Docker Desktop Windows app, starts and manages the WSL 2 backend Docker Desktop. A Windows application. It sets up and manages the WSL 2 backend. It provides the docker command and the desktop dashboard. Windows the operating system you work in, with your files and editor Windows. The host operating system. Your project files, terminal and editor live here. Its own kernel cannot run Linux binaries, which is why WSL 2 supplies a Linux kernel above. Physical hardware CPU · Memory · Disk GPU (optional) Physical hardware. The physical processor, memory and disk. A GPU is optional. Everything above shares these resources through Windows and WSL 2. The Windows stack. Read it bottom to top. The blue WSL 2 band is the heart of it: one full Linux kernel, shared by every container. Hover or focus a layer. GPU driver GPU-PV runtime GPU path GPU ACCESS REQUIRES A supported NVIDIA GPU Current Windows + WSL 2 kernel NVIDIA driver with WSL 2 support Docker Desktop, WSL 2 backend A GPU image, run with –gpus The NVIDIA Container Toolkit is a separate step, needed only when you run Docker Engine directly inside WSL. The Windows to Linux container stack, vertical layout Bottom to top: hardware with an optional GPU, Windows, Docker Desktop, WSL 2 providing one full Linux kernel, the Docker Engine in Docker Desktop’s docker-desktop WSL distribution, and Linux containers on top that share that kernel. Optional GPU access threads through the NVIDIA Windows driver, WSL 2 GPU paravirtualization and the Docker runtime. It needs a supported NVIDIA GPU, current Windows and WSL 2, an NVIDIA driver with WSL 2 support, Docker Desktop on the WSL 2 backend, a GPU-capable image and a run-time gpus request. The NVIDIA Container Toolkit is separate, only for running Docker Engine directly inside WSL. Linux container ML training toolruns as if on native Linux Libraries + dependenciescompilers, Python, native builds Docker Engine (dockerd)in WSL 2 WSL 2 lightweight VM one full Linux kernel, shared Linux code executes here Linuxkernel Docker DesktopWindows app, manages the WSL 2 backend Windowsthe OS you work in, your files and editor Physical hardwareCPU · Memory · Disk GPU (optional) GPU path OPTIONAL GPU ACCESS A container can reach the GPU for faster training once a few requirements are met. Show the GPU path above to see them. GPU ACCESS (WSL 2 BACKEND) REQUIRES A supported NVIDIA GPU Current Windows and WSL 2 kernel NVIDIA driver with WSL 2 support Docker Desktop on the WSL 2 backend A GPU-capable image, run with –gpus NVIDIA Container Toolkit is separate: only when Docker Engine runs directly inside WSL, without Docker Desktop.
Figure 2. One Linux kernel, shared by every container. On Windows, Docker Desktop runs the Docker Engine inside its own docker-desktop WSL 2 distribution and uses the full Linux kernel that WSL 2 supplies. Your Linux containers share that one kernel, which keeps the approach lighter than giving every tool its own full virtual machine, even though virtualization is involved. Isolation is enforced by the kernel. Namespaces limit what each container can see, while cgroups limit the resources it can use. GPU acceleration runs through a defined path: from the GPU through the NVIDIA Windows driver, WSL 2 GPU paravirtualization and the Docker runtime. It needs a supported NVIDIA GPU, current Windows and WSL 2 components, an NVIDIA driver with WSL 2 support, Docker Desktop on the WSL 2 backend, a GPU-capable image and a run-time --gpus request. The NVIDIA Container Toolkit is installed separately in one case only, namely running Docker Engine directly inside WSL rather than through Docker Desktop. This figure shows Linux containers, the mode used throughout this article.

Docker works well for data science

Docker provides several concrete advantages beyond simple compatibility. Repeatability is one of the most important. In a Dockerfile, one can specify the exact operating system, system libraries, compiler versions, and Python packages required. When the image builds successfully, the environment is far more repeatable than an ad hoc local setup, though it is not frozen. As Figure 3 notes, a Dockerfile pins only what it is told to pin, and the example below leaves the base image tag, the Miniconda installer, the Brush clone and the conda packages free to move between builds. An exact rebuild would need a digest-pinned base image, a fixed Miniconda version, a pinned Brush commit, and locked package versions.

Isolation is another key benefit. Dependencies required for one project do not interfere with others, and system-level packages do not pollute the host machine. That matters most with research code that may require older compilers or specific library versions.

Finally, Docker images are portable. Run the same image locally, on a shared workstation, or on a cloud server. Architecture has to match. The Dockerfile below installs the Linux x86_64 build of Miniconda, so its image runs on x86_64 hosts. This reduces friction when moving from experimentation to larger-scale runs.

These benefits are easier to reason about once the difference between an image and a running container is clear. As shown in Figure 3, a Dockerfile is built once into an immutable image, which is then run to create a container with a thin writable layer on top. Docker throws that writable layer away when the container goes. To keep results, mount a folder from the Windows host, or use a named volume. This matters the moment the example below starts producing output.

Figure 3

From Dockerfile to a running container, and where your data lives

A Dockerfile is built once into an image, and that image is run to create a container. They are not the same thing, and by default anything written inside a container is lost when it is removed. Step through the four stages, or press Play, to see how a result is made and how it is kept.

The pipeline from Dockerfile to image to container, and the storage that keeps results On the left, a pipeline: a Dockerfile is turned by docker build into an image, which is an immutable stack of read-only layers. The docker run command then starts a container, which is the image’s read-only layers plus one thin writable layer on top. Below, three kinds of storage connect to the container: the container’s own writable layer, which is lost when the container is removed. A bind mount to your Windows project folder is kept on the host, and a named volume managed by Docker is also kept. On the right, a panel explains the currently selected stage. Dockerfile abridged recipe FROMubuntu:24.04 RUNapt-get … RUNconda env … RUNpip install … CMD[“/bin/bash”] docker build Image read-only layers layer: base OS layer: system deps layer: environment built once, reused docker run Container image + one writable layer thin writable layer R/W ONE IMAGE · read-only exactly one image per container Where a result is kept when the container is removed Writable layer Survives a stop and restart, but only inside this one container. LOST on container removal Bind mount Maps a chosen host or WSL folder into the container, writable. KEPT on your host filesystem Named volume Docker-managed storage that lasts until you explicitly delete it. KEPT by Docker across runs 1. The Dockerfile. A plain-text recipe. It names the base image and every step to build the environment: packages, Python, the tool. Nothing runs yet; it only describes what to build. IN SHORT A version-controlled build recipe. Repeatability also needs the build context and pinned inputs. 2. The image. docker build runs the recipe once and freezes it into an image: a stack of read-only layers. Immutable, and simple to reuse or copy elsewhere. REPEATABILITY NOTE Only as repeatable as the Dockerfile. Latest tags and unpinned installs can drift, so pin the versions that matter. 3. The container. docker run creates and starts a container from the image: read-only layers, plus one thin writable layer on top. IMAGE VS CONTAINER The image is the template on disk. A container is an instance of it, running or stopped, with its own writable layer. Many can run from one image at once. 4. Keeping results. Writes land on the writable layer, which is deleted with the container. To keep output, write it to a mount instead. TWO WAYS TO PERSIST A bind mount maps a host or WSL folder in, so output lands on your host. A named volume lets Docker keep it. Both survive the container’s removal. Dockerfile to image to container to storage, vertical layout A Dockerfile is built into an immutable image of read-only layers, which is run to create a container that adds one writable layer. The writable layer is lost when the container is removed. A bind mount to a Windows folder and a named volume both keep results. 1 Dockerfileabridged recipe FROM ubuntu:24.04 · RUN … · CMD [“/bin/bash”] names the base image and every build step build 2 Imageread-only layers, reusable layer: base OS layer: system deps + environment built once, reused everywhere run 3 Containerimage + one writable layer thin writable layer (read/write) the one image (read-only layers) exactly one image per container, running or stopped 4 Where results are kept on removal, the writable layer is deleted writable Container writable layer LOST when the container is removed Bind mount a host or WSL folder,mapped in (writable) KEPT on host Named volume Docker-managed, keptuntil you delete it KEPT by Docker REPEATABILITY NOTE ubuntu:24.04 is a mutable tag that can move. Latest installers and unpinned packages drift too.
Figure 3. Build once, create many containers, persist on purpose. docker build combines a Dockerfile and its build context into an image of read-only layers. docker run then creates and starts a container by adding one thin writable layer on top, so the image is the template and a container is an instance of it that may be running or stopped. That writable layer survives stop and restart. Removing the container deletes it. Results therefore go somewhere else, either to a bind mount on a host or WSL folder, or to a named volume that Docker keeps until it is deleted. A Dockerfile improves repeatability, and exact rebuilds need a digest-pinned base image, locked dependencies and verified downloads, since ubuntu:24.04 is a mutable tag that can point somewhere new tomorrow and the file also pulls Miniconda3-latest and unpinned packages.

Example: Running symbolic regression with Brush in Docker

Repeatable Builds. How a Dockerfile packages an Ubuntu environment to build and run the Brush symbolic regression tool on Windows, repeatably and in isolation. Key topics covered: Dockerfile build layers, Install system packages, Build and run, Miniconda and conda-forge, Create the brush_demo env, Clone and build Brush, Mount host folders, Validate the notebook.

Hover any card to explore

In this case, Docker is used to build a Linux image specifically for running the symbolic regression tool, Brush. More information about Brush can be found by accessing the link below: https://github.com/cavalab/brush

The Dockerfile below defines an Ubuntu-based image. It installs the system dependencies, sets up a Python environment, then compiles the native components Brush needs. A best practice when working with tools like Brush is to treat the container as a research instrument rather than a general-purpose environment. The Dockerfile should include only what is needed to build and run the model, and nothing more. This keeps build times reasonable and reduces the chance of hidden dependency issues.

Dockerfile
# Set Linux Version
FROM ubuntu:24.04

# Set environment variables for non-interactive installation
ENV DEBIAN_FRONTEND=noninteractive \
    CONDA_DIR=/opt/conda \
    PATH=/opt/conda/bin:$PATH

# Install system packages: git and minimal dependencies for Miniconda
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        git \
        wget \
        ca-certificates \
        bzip2 && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# Download and install latest Miniconda for Linux x86_64
RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && \
    /bin/bash /tmp/miniconda.sh -b -p $CONDA_DIR && \
    rm /tmp/miniconda.sh && \
    $CONDA_DIR/bin/conda clean -afy

# Configure conda to use only conda-forge channel
RUN $CONDA_DIR/bin/conda config --system --add channels conda-forge && \
    $CONDA_DIR/bin/conda config --system --set channel_priority strict && \
    $CONDA_DIR/bin/conda config --system --remove channels defaults || true

# Initialize conda for bash shells system-wide
RUN $CONDA_DIR/bin/conda init bash && \
    echo ". $CONDA_DIR/etc/profile.d/conda.sh" >> /etc/profile.d/conda.sh && \
    echo "conda activate brush_demo" >> /etc/profile.d/conda.sh

# Create the brush_demo conda environment
RUN $CONDA_DIR/bin/conda create -n brush_demo python=3.11 --override-channels -c conda-forge -y && \
    $CONDA_DIR/bin/conda clean -afy

# Clone the brush repository
RUN git clone https://github.com/cavalab/brush.git /opt/brush

# Install dependencies from environment.yml into the brush_demo environment
WORKDIR /opt/brush
RUN $CONDA_DIR/bin/conda env update -n brush_demo -f environment.yml && \
    $CONDA_DIR/bin/conda clean -afy

# Install matplotlib explicitly (conda-forge only)
RUN $CONDA_DIR/bin/conda run -n brush_demo \
    conda install matplotlib --override-channels -c conda-forge -y && \
    $CONDA_DIR/bin/conda clean -afy

# Install deap explicitly (conda-forge only)
RUN $CONDA_DIR/bin/conda run -n brush_demo \
    conda install deap --override-channels -c conda-forge -y && \
    $CONDA_DIR/bin/conda clean -afy

# Install brush in editable mode
RUN $CONDA_DIR/bin/conda run -n brush_demo pip install -e .

# Ensure conda is initialized in root's .bashrc
RUN echo ". $CONDA_DIR/etc/profile.d/conda.sh" >> /root/.bashrc && \
    echo "conda activate brush_demo" >> /root/.bashrc

# Set working directory
WORKDIR /opt/brush

# Default shell is bash
SHELL ["/bin/bash", "-c"]

# Default command: interactive bash shell
CMD ["/bin/bash"]

Before we can build the image, ensure that Docker Desktop is installed from: https://docs.docker.com/desktop.

After installing the software, ensure that Docker Desktop is running and open a PowerShell window and navigate to the directory containing the Dockerfile. We can now build the Docker image by running the command below:

Bash
docker build -t dockerbrush:latest --progress=plain .
  • docker build starts the image build process
  • -t dockerbrush:latest assigns a name and optional tag to the image
  • --progress=plain shows the full build progress
  • . specifies the build context, which is the current directory where the Dockerfile is located

After the image is built, we can create a container from it and mount local data directories into the container. The mount lets results travel back to the host filesystem, at the cost of opening the isolation boundary for that path. A bind mount is a deliberate hole in it, and container processes run as root by default, so files can land on the host owned by root. From the standpoint of the symbolic regression method, it is running on a standard Linux machine. From the perspective of the researcher, it integrates smoothly with a Windows-based workflow.

An example command to create and run a Docker container is provided below. The command as shown mounts nothing, so add a -v argument to share data directories between the host and the container, in the format /path/to/host/data:/path/to/container/data:

Bash
docker run -it --name brush_demo --restart unless-stopped dockerbrush
  • docker run starts a new container from a specified Docker image
  • -it allocates an interactive terminal, which gives direct shell interaction with the container
  • --name brush_demo assigns a human-readable name to the container for easier management
  • --restart unless-stopped configures the container to automatically restart if it exits or the system reboots, unless it is explicitly stopped by the user. With an interactive shell that means leaving the shell does not end the container. It restarts in the background under the same brush_demo name. To get back into it, run docker exec -it brush_demo bash. To retire it, run docker stop brush_demo followed by docker rm brush_demo, since stopping alone keeps the name reserved and re-running the command above would fail with a name conflict
  • dockerbrush specifies the Docker image used to create the container

We can now navigate to the Brush guide directory to access and run its demos from within the new Docker container.

Bash
cd /opt/brush/docs/guide

Using the jupyter nbconvert command, we can run Jupyter notebooks from the terminal, saving the output in a separate notebook. In this example, the archive.ipynb demo will be run from the terminal to verify that Brush was successfully installed.

Bash
jupyter nbconvert --to notebook --execute archive.ipynb
Completed 100% [====================][NbConvertApp] Writing 119498 bytes to archive.nbconvert.ipynb

The successful completion of the command confirms that the notebook executed from start to finish without errors, indicating that the Brush installation inside the Docker container is working as expected. The generated archive.nbconvert.ipynb file contains the fully executed notebook, including all outputs, and is a record of the run, though it is written inside the container and is lost when that container is removed. Copy it out with docker cp, or retire the container with docker stop brush_demo followed by docker rm brush_demo and start a fresh one with a -v mapping added to docker run and an –output-dir added to the nbconvert command. Bind-mounting the working directory itself would hide the cloned notebook and the run would fail. Reproducing the recorded run exactly would also require the image it ran in to be pinned to exact versions.

At this point, a running brush_demo container can be used both from the terminal and as a development environment. In particular, the running container can be attached directly in Visual Studio Code using the Docker or Dev Containers extension, which supports interactive exploration of the codebase, execution of additional notebooks, and iterative development with full IDE support. The result is a straightforward move from a command-line validation step to a more interactive workflow while keeping the environment isolated and repeatable.

Summary

Key Takeaways

Containers share the host kernel

A container is a set of processes isolated by kernel namespaces and cgroups. It packages an application and its user-space dependencies only, with no guest kernel, which is why it is typically smaller and quicker to start than a virtual machine.

On Windows the kernel comes from WSL 2

Docker Desktop does not run Linux binaries on the Windows kernel. It starts a lightweight WSL 2 virtual machine, the default backend, which supplies a genuine Linux kernel, and every Linux container shares that one kernel instead of booting a machine of its own.

Nothing persists unless it is mounted

The writable layer added at run time survives stop and restart, then disappears when the container is removed. Output outlives the container through a bind mount to a host or WSL folder, a named volume, or a docker cp taken before the container is removed, and the docker run command above adds no mount.

Repeatable is not the same as reproducible

This Dockerfile starts from the mutable tag ubuntu:24.04, pulls Miniconda3-latest and clones Brush unpinned, so the same file can build a different image later. Exact rebuilds need a digest-pinned base, locked dependencies and verified downloads.

GPU access has to be arranged

A container does not virtualize hardware, and the GPU is not handed to it by default. On Windows it requires a supported NVIDIA GPU, a driver with WSL 2 support, current WSL 2 components, a GPU-capable image and a –gpus request at run time.

Isolation is the practical payoff

Older compilers and specific library versions stay inside the image instead of polluting the host, and the same image runs on a laptop, a workstation or a cloud server of the same architecture. A running container can also be attached from VS Code with the Dev Containers extension.

Data & License

Data

No third-party dataset. The benchmarks and measurements shown are the author’s own, recorded on the hardware and software described in this article.

Article

© 2025 Philip Sarajlic. All rights reserved for the article’s original text and figures.

Code

Code examples in this article are licensed under the Common Public Attribution License Version 1.0 (CPAL-1.0), an OSI-approved copyleft license based on the Mozilla Public License 1.1. Initial Developer: Philip Sarajlic.

Attribution required by CPAL Exhibit B: © 2025 Philip Sarajlic · “Based on code by Philip Sarajlic” · philipsarajlic.com · no graphic image. This attribution must be displayed in Larger Works.

Modifications must be released in Source Code form under CPAL-1.0. Making the code usable by anyone other than you over a network is External Deployment under the license and is treated as distribution, so the Source Code must be made available to those users.

Full text: opensource.org/license/cpal-1-0 (SPDX identifier CPAL-1.0)

Scroll to Top

Free diagnostic

Would your model hold up to an external review?

Answer 39 questions in about nine minutes and get a clear picture of where your model stands.

You’ll receive a readiness score, a breakdown across 15 areas, your biggest evidence gaps, and the five questions an external reviewer would be most likely to ask first.

The assessment draws on guidance from:

  • TRIPOD+AI
  • PROBAST+AI
  • FDA GMLP
  • NIST AI RMF
  • SR 11-7

We’ll send you one email with your link, and your results when you finish. Your email address is carried through when you complete the assessment, so you won’t need to enter it again.

We don’t ask for your data, and there are no free-text fields in the assessment. See how we use your email address.