Docker Practices for Large Language Model Deployment
Large language models (LLMs) are everywhere — powering chatbots, copilots, and AI-driven apps across industries. But if you’ve ever tried to run one outside of a managed service, you know the pain: gigabytes of model weights, conflicting Python dependencies, fragile CUDA versions, and a GPU setup that only seems to work on your machine.
This is where Docker shines. By packaging the entire environment — code, libraries, and drivers — into a container, you can run an LLM anywhere, whether it’s your laptop, a cloud GPU node, or a Kubernetes cluster. Containers give you reproducibility, portability, and isolation: exactly what’s needed for the messy world of LLMOps.
In this article, we’ll explore how to run LLM workloads inside Docker. We’ll build a working container that serves predictions from a Hugging Face model, enable GPU support with NVIDIA’s container toolkit, and show how the same image can scale in Kubernetes. Along the way, we’ll cover common pitfalls like CUDA drift, bloated images, and cold starts — and share best practices to avoid them.
The goal is simple: by the end, you’ll see that Docker isn’t just for microservices — it’s quickly becoming an essential building block for deploying and scaling AI.
Why LLMOps Needs Docker
Running an LLM isn’t as simple as pip install transformers. These models often require dozens of dependencies, specific CUDA drivers, and sometimes gigabytes of model weights. Without containers, developers usually end up in “dependency hell,” where code runs on one machine but fails on another.
Docker solves this problem by providing a consistent runtime environment. Here’s why it’s particularly valuable for LLMOps:
- Reproducibility. Package PyTorch, TensorFlow, CUDA, and Hugging Face libraries in a Docker image → guaranteed consistency.
- Isolation. With NVIDIA Container Toolkit, containers can safely share GPU hardware without driver/library conflicts.
- Portability. The identical container may execute locally, on-prem, or in-cloud services such as AWS Sagemaker, GCP Vertex AI, or Azure ML.
- Scalability. Orchestrators such as Kubernetes can automatically utilize the full power of container replications to scale inference workloads of LLM.
- Security and compliance. Containers can be scanned, signed, and enforced with runtime policies.
Requests flow through a Dockerized app, model runtime, NVIDIA toolkit, GPU drivers, and finally the hardware. Each layer builds on the previous one to make large language models portable, scalable, and reliable across environments.
Creating an LLM-Ready Docker Image
Let’s begin by constructing a Docker image that can serve predictions from a Hugging Face model via FastAPI.
This image will come from the NVIDIA CUDA images, which support GPU-ready environments. We will top it with Python, PyTorch, FastAPI, and Hugging Face Transformers. This will make it a portable inference container.
Here’s a sample Dockerfile:
|
|
Explanation:
- We will use NVIDIA CUDA as the base image. This ensures GPU compatibility out of the box.
- Next, install Python. Add PyTorch, Hugging Face Transformers, FastAPI, and Uvicorn to complete our serving stack.
- Lastly, replicate a basic
app.pywhich loads a model and serves predictions on a REST endpoint.
Sample app.py:
|
|
Build the image with:
|
|
And run it locally with:
|
|
You now have a Dockerized LLM. It works on text prompts and generates completions. It is also reproducible and portable, running the same way everywhere.
Running LLMs With GPU Support
LLMs shine on GPUs, but giving containers access to GPU hardware requires some extra setup. By default, Docker can’t talk directly to your GPU drivers — that’s where the NVIDIA Container Toolkit comes in. This toolkit bridges your host GPU with the container runtime so Docker images can execute CUDA operations.
Once you have installed the toolkit on your host, you can run a GPU-enabled container with this simple command:
|
|
Explanation:
--gpus alltells Docker to expose all available GPUs to the container. You can also limit it to one GPU (--gpus "device=0") if you’re running multiple workloads.-p 8080:8080maps the FastAPI app inside the container to port 8080 on your machine.my-llm-containeris the image we built in the previous section.
The container loads CUDA drivers from the host. It exposes them to PyTorch inside the container when it starts. In case of a mismatch of the drivers, errors like “invalid device function” may occur. To avoid this, you need to check the NVIDIA CUDA compatibility matrix. With that, you can ensure that your host drivers support the CUDA version in your image.
To confirm that the GPU is actually available, you can run:
|
|
This runs nvidia-smi inside the container, showing GPU utilization, driver version, and CUDA compatibility. If you see your GPU listed, you’re good to go.
Using this implementation, you have a containerized LLM that has the ability to make use of hardware acceleration. Whether it’s on your laptop’s RTX card, a cloud GPU, or inside Kubernetes, your model is now ready to run fast and efficiently.
Scaling LLM Workloads in Kubernetes — With GPUs
When your LLM works in Docker, it is simple to scale it to Kubernetes. After the deployment of pods to the GPU nodes, the number of replicas can be increased or decreased.
Prerequisites (One-Time)
- Install the NVIDIA Device Plugin DaemonSet. This exposes GPUs in Kubernetes under
nvidia.com/gpuresources. - Ensure that you have at least one GPU node pool in your cluster, be it GKE, AKS, EKS, or on-prem, and label it (e.g.,
accelerator=nvidia).
Minimal Deployment + Service
|
|
Optional: Spread, Scale, and Burst
Topology spread (avoid all pods on one node):
|
|
Autoscale by QPS/CPU (HPA example):
|
|
GPU-aware pod anti-affinity (keep replicas on different GPU nodes):
|
|
Notes and Tips
- Cold starts: Use a writable cache (emptyDir, CSI, or object store pre-warm) so models aren’t re-downloaded every pod start.
- Driver/Runtime match: Ensure node driver versions are compatible with your CUDA base image.
- Requests vs. limits: Set realistic CPU/memory, so GPUs stay fully utilized. Under-requesting can throttle performance.
- Ingress and scaling: Put an Ingress, service mesh, or gateway in front. Scale replicas horizontally rather than just boosting single-pod batch sizes.
Challenges and Trade-Offs
Docker helps bring order to the chaos of LLM deployment, but it’s not magic. Running large AI models in containers brings its own problems that teams need to think about.
- Image bloat: LLM containers can get huge — often tens of gigabytes once you add CUDA libraries, PyTorch, and model weights. Big images slow everything down: builds, pushes, pulls, and even cluster rollouts.
- Dependency hell (still around, just in a box): CUDA, cuDNN, and PyTorch versions must still match the GPU driver on the host. If they don’t, you’ll hit runtime errors no matter how “contained” things look.
- Cold starts: Spinning up a new container often means pulling or loading gigabytes of model weights. This delays scaling on demand, which is painful for latency-sensitive apps.
- GPU scheduling headaches: In shared clusters, deciding who gets which GPU is not simple. Kubernetes GPU scheduling takes careful resource limits and sometimes extra operators to keep things fair.
- Hardware lock-in: Docker hides the OS but not the hardware. If your container expects AVX2 or specific GPU features, it won’t run on weaker nodes.
- Security and compliance risks: GPU-ready images often come from public registries and pull in lots of dependencies. You still need to scan, sign, and lock them down to avoid shipping vulnerabilities into production.
The Trade-Off Docker provides portability, reproducibility, and scalability of LLMs. It also results in bigger images, colder, slow warming up, and needs to be closely orchestrated. The trick is to treat these trade-offs as design limits, not deal-breakers — and use best practices like model caching, multi-stage builds, and proactive driver checks.
Best Practices and Recommendations
Most of the impediments to running LLMs in Docker can be managed with the right practices. The following are some approaches that ensure that your containers are lean, secure, and reliable.
- Use multi-stage builds: By separating the build env — compilers and dev tools — from the runtime env — your apps and libraries, you can keep the images lightweight. This isolation eliminates build artifacts, reduces image size, and accelerates deployment.
- Cache model weights: Pre-download Hugging Face or PyTorch models in your image, or mount them as volumes. This avoids long cold starts and saves bandwidth from re-downloading models every time.
- Pin dependencies: Lock down versions of CUDA, cuDNN, PyTorch, and Transformers in your Dockerfile. Don’t use the “latest” tag. This will ensure every build is reproducible and predictable
- Align drivers and runtimes: Ensure that the CUDA version within your container is compatible with the host-based version of the driver of the GPU. See the compatibility list at NVIDIA to prevent run-time failures.
- Scan and sign images: Use tools like Trivy or Grype to scan for vulnerabilities. Sign your images before pushing them so you don’t accidentally pull something unsafe into production.
- Monitor GPU utilization: Track GPU usage with nvidia-smi, Prometheus, or DCGM exporters. This helps with cost efficiency and ensures GPUs don’t sit idle (or overloaded).
- Plan for cold starts: For latency-sensitive apps, preload models into memory or keep warm standby pods. If you don’t need a giant model, consider smaller distilled or quantized models to save time and resources.
Takeaway Treat LLM containers like production-grade services, not one-off experiments. By caching, pinning, scanning, and monitoring, you can turn fragile AI stacks into reliable, portable, and secure deployments.
Conclusion
Large language models are powerful but notoriously hard to run outside of managed services. Their heavy dependencies, hardware quirks, and scaling needs often turn simple experiments into production headaches.
Docker changes the game. Libraries, codes, and drivers can be bundled into portable containers. It provides reproducibility, isolation, and scalability that traditional setups lack. The container that works on a laptop can also work on a GPU-backed Kubernetes cluster — assuming you take care of the trade-offs like image bloat, cold starts, and driver mismatches.
The bottom line: Docker isn’t just for microservices anymore. It’s becoming a critical foundation for deploying and scaling AI workloads reliably. Teams that embrace containers for LLMOps will find it easier to move from prototype to production — without losing sleep over dependency hell or “works on my machine” failures.