MLOpsMLOps Foundations & Engineering Essentials

Why should ML containers run as non-root users?

Running an ML container as a non-root user means its training script or inference API runs under an ordinary Linux user ID rather than the privileged root user (UID 0).

This follows the principle of least privilege: a prediction service that only reads an approved model and answers requests usually does not need permission to modify operating-system files or administer the container.

A beginner-friendly example

Imagine a food-delivery company deploying an API that predicts delivery times.

Suppose a bug in its web application allows an attacker to execute a command inside the inference container.

  • If the application runs as root: The command may have broad permissions inside the container, including changing files owned by root. Depending on container configuration, mounts, and other vulnerabilities, the consequences can be more serious.
  • If it runs as an ordinary user: The command normally has only that user's file and process permissions. It cannot freely overwrite root-owned system files.

Important: Non-root execution reduces potential damage; it does not make container escape or data theft impossible. A compromised process may still access the model, credentials, networks, and mounted files that its user can access.

A practical Dockerfile

Assume app/main.py defines a FastAPI application named app, and requirements.txt contains the tested inference dependencies.

DOCKERFILE
FROM python:3.12-slim WORKDIR /app # Install packages while building the image. COPY requirements.txt ./ RUN python -m pip install --no-cache-dir -r requirements.txt # Create an unprivileged user with a fixed numeric ID. RUN groupadd --gid 10001 mlgroup \ && useradd --uid 10001 --gid 10001 \ --create-home --no-log-init mluser # Copy only application files with suitable ownership. COPY --chown=10001:10001 app/ ./app/ ENV HOME=/home/mluser \ PYTHONUNBUFFERED=1 # The deployed application does not execute as root. USER 10001:10001 EXPOSE 8000 CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

What matters here?

  • `RUN groupadd` and `useradd`: Create a named group and user with predictable numeric IDs. These commands run while building the image, before the final user switch.
  • `COPY --chown`: Gives the copied application directory suitable ownership instead of relying on accidental permissions.
  • `USER 10001:10001`: Makes later commands and the running API use this user and group unless the deployment overrides it.
  • Port 8000: Allows the API to listen on an ordinary unprivileged port rather than relying on privileged port binding.

A fixed UID/GID can make volume permission configuration easier, although a matching numeric ID on another machine is not an authentication boundary.

Build and check the runtime identity:

BASH
docker build -t delivery-time-api:1.0 . docker run --rm --entrypoint id delivery-time-api:1.0

The second command should report UID 10001 and GID 10001. Then test the real prediction endpoint: changing the user must not prevent the application from loading its model or reaching approved services.

What changes for an ML workload?

A non-root user needs carefully planned access to:

  1. Model artifacts: If the model file is copied into the image, it must be readable by the serving user. For a model downloaded at startup, its destination directory must be writable.
  2. Caches and temporary files: ML frameworks and model libraries may try to create caches or temporary files. Set a writable location such as an approved directory in the user's home or a mounted /tmp, and check its permissions.
  3. Datasets and checkpoints: Training containers need permission to read datasets and write checkpoints. Grant access only to the required directories and storage locations.
  4. GPU devices: GPU workloads also need appropriate device access, drivers, and runtime configuration. Use supported device plugins and permissions rather than running everything with --privileged as a shortcut.

A common production failure: An API worked as root but fails after switching to UID 10001 because a mounted model directory is readable only by root. The right fix is usually to adjust the mount, ownership, file permissions, or storage access—not to restore root access.

How do you enforce non-root execution in Kubernetes?

A Kubernetes workload can include a container securityContext like this:

YAML
securityContext: runAsNonRoot: true runAsUser: 10001 runAsGroup: 10001 allowPrivilegeEscalation: false capabilities: drop: ["ALL"] seccompProfile: type: RuntimeDefault

This is a securityContext fragment, not a complete Pod manifest.

  • `runAsNonRoot`: Instructs Kubernetes to reject starting the container as root.
  • `runAsUser` / `runAsGroup`: Select the process identity.
  • `allowPrivilegeEscalation: false`: Prevents the process from gaining additional privileges through mechanisms such as setuid executables, subject to documented platform behavior.
  • `capabilities.drop: ["ALL"]`: Removes additional Linux capabilities unless a documented workload requirement needs an exception.
  • `seccompProfile: RuntimeDefault`: Applies the container runtime's default system-call filtering profile.

A read-only root filesystem can provide another layer of protection, but test it with the application first: ML frameworks may need explicitly mounted writable cache or temporary directories.

Is a non-root application the same as Docker rootless mode?

No.

  • Dockerfile `USER`: Controls the user executing the application *inside the container*.
  • Rootless Docker: Configures the Docker daemon and containers to run inside user namespaces without root privileges on the host.

They address different layers and may complement each other.

Also, the container image's USER setting can be overridden by some deployment configurations. Enforce runtime policies where needed instead of assuming the Dockerfile alone guarantees the final process identity.

Common mistakes

  • Running as root merely to avoid fixing model-directory or cache permissions.
  • Granting the application access to the Docker socket, broad host paths, or unnecessary secrets.
  • Assuming non-root execution alone eliminates the need for dependency scanning, patching, network controls, or secure model loading.
  • Ignoring GPU and mounted-volume permissions until a production deployment fails.

Questions 41 and 42 covered Dockerfile ingredients and multistage image size/security. This concept focuses specifically on limiting the authority of the running ML process.

In short: Run ML containers with a dedicated non-root identity whenever practical, grant only required file and device access, and enforce the setting at deployment time. It is one important layer of defense—not a substitute for broader container security.

#non-root ML containers#Dockerfile USER machine learning inference#Kubernetes runAsNonRoot ML workload#ML container least privilege#ML model directory permissions Docker#rootless Docker vs USER#mlops#container-security#non-root#docker

Learning Sequence

MLOps Curriculum

37% Completed
Up NextMLOps

How should environment variables and configuration files be used in ML containers?

Explore Related Questions

Deepen your understanding across related MLOps topics.

Machinoai Logo
Machinoai

Your edge in the AI era. Deep dives into machine learning concepts, paper explainers, verified industry jobs, and developer tools.

© 2026 Machinoai. All rights reserved.