Introduction to Local LLM Deployment with Ollama and Docker
Large Language Models (LLMs) have transformed how developers build intelligent applications, but relying on cloud APIs introduces latency, cost, and privacy concerns. Running LLMs locally solves these problems, and Ollama has emerged as one of the most popular tools for simplifying local LLM deployment. When combined with Docker, you get a reproducible, portable, and production-ready environment for serving language models on your own infrastructure.
In this tutorial, you will learn how to deploy local LLMs using Ollama and Docker, from a basic single-container setup to a multi-container architecture with a custom model configuration. By the end, you will have a working local LLM service accessible via a REST API.
What Is Ollama?
Ollama is an open-source tool that lets you run large language models locally on your machine. It abstracts away the complexity of model quantization, weight downloading, and inference engine configuration. With a single command, you can pull and run models like Llama 3, Mistral, Phi-3, Gemma, and many others.
Ollama provides:
- A CLI tool for pulling, running, and managing models
- A REST API compatible with OpenAI's API format
- Support for custom model configurations via Modelfiles
- Automatic quantization for efficient memory usage
- Cross-platform support for macOS, Linux, and Windows
Why It Matters
Deploying LLMs locally with Ollama and Docker matters for several key reasons:
Privacy and Data Sovereignty
When you send prompts to a cloud-based LLM API, your data leaves your infrastructure. For industries handling sensitive information — healthcare, finance, legal, or internal enterprise data — local deployment ensures that no prompt or response ever leaves your network.
Cost Control
Cloud LLM APIs charge per token. For high-volume applications, costs can spiral quickly. A local deployment has a fixed hardware cost and zero per-query fees, making it economically viable for bulk processing tasks like document summarization, log analysis, or code review.
Latency Reduction
Local inference eliminates network round trips. For interactive applications where response time is critical, running the model on-premises can dramatically reduce latency, especially when the model is warm and loaded in memory.
Reproducibility with Docker
Docker containers encapsulate the entire runtime environment. This means your Ollama setup behaves identically whether it runs on a developer laptop, a CI server, or a production GPU machine. Docker also makes it easy to scale horizontally and integrate Ollama into existing containerized architectures.
Prerequisites
Before you begin, ensure you have the following:
- Docker installed (version 20.10 or later)
- Docker Compose installed (v2 or later)
- At least 8 GB of RAM (16 GB or more recommended for larger models)
- Sufficient disk space (models range from 2 GB to 40 GB depending on size)
- Optional: An NVIDIA GPU with CUDA support for faster inference
Verify your Docker installation:
docker --version
docker compose version
Running Ollama in Docker: The Quick Start
The fastest way to get Ollama running is with a single Docker command. Ollama publishes an official image on Docker Hub.
docker run -d \
--name ollama \
-p 11434:11434 \
-v ollama_data:/root/.ollama \
ollama/ollama
Let's break down this command:
-druns the container in detached mode--name ollamaassigns a recognizable name to the container-p 11434:11434maps the container's API port to your host-v ollama_data:/root/.ollamapersists downloaded models in a named volume
Check that the container is running:
docker ps
You should see the Ollama container listed with a status of "Up." Test the API endpoint:
curl http://localhost:11434
If everything is working, you will receive the response Ollama is running.
Pulling and Running Your First Model
Once Ollama is running, you can pull a model using the Ollama CLI inside the container. Let's start with Llama 3.2, a lightweight and capable model:
docker exec -it ollama ollama pull llama3.2
This downloads the quantized model weights. The download size depends on the model — Llama 3.2 (3B parameters) is approximately 2 GB. Once the download completes, run the model interactively:
docker exec -it ollama ollama run llama3.2
You can now type prompts directly in the terminal. To exit the interactive session, type /bye or press Ctrl+D.
Using the REST API
Ollama exposes a REST API on port 11434. Here is how to generate a completion using curl:
curl http://localhost:11434/api/generate -d '{
"model": "llama3.2",
"prompt": "Explain what a Docker container is in one sentence.",
"stream": false
}'
For chat-style interactions with message history, use the /api/chat endpoint:
curl http://localhost:11434/api/chat -d '{
"model": "llama3.2",
"messages": [
{"role": "system", "content": "You are a helpful coding assistant."},
{"role": "user", "content": "Write a Python function to reverse a string."}
],
"stream": false
}'
Setting "stream": false returns the complete response as a single JSON object. If you omit this parameter or set it to true, Ollama streams the response token by token via Server-Sent Events, which is useful for real-time UI updates.
Using Docker Compose for a Managed Setup
For any real-world deployment, Docker Compose provides better manageability than raw docker run commands. Create a docker-compose.yml file:
version: "3.9"
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama
restart: unless-stopped
# Uncomment the following lines for GPU support
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
volumes:
ollama_data:
Start the service:
docker compose up -d
View logs to confirm a healthy startup:
docker compose logs -f ollama
Stop the service when you are done:
docker compose down
The named volume ollama_data persists across container restarts, so your downloaded models remain available even after you recreate the container.
GPU Acceleration with Docker
CPU inference works but is slow for larger models. If you have an NVIDIA GPU, you can enable GPU acceleration. First, install the NVIDIA Container Toolkit on your host:
# Ubuntu/Debian
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \
| sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \
| sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \
| sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
Then use the GPU-enabled Compose configuration by uncommenting the deploy section shown earlier. Verify GPU access inside the container:
docker exec -it ollama nvidia-smi
If the NVIDIA driver and toolkit are correctly configured, you will see the GPU information table. GPU inference can be 10x to 50x faster than CPU inference depending on the model size.
Creating a Custom Model with a Modelfile
Ollama supports custom model configurations through a Modelfile, which is similar to a Dockerfile. You can define a system prompt, adjust parameters like temperature and context window, and even import custom weights.
Create a file named Modelfile:
FROM llama3.2
# Set a system prompt that defines the model's behavior
SYSTEM """
You are an expert software engineer. You provide concise, accurate answers
with working code examples. Always explain your reasoning briefly before
showing code.
"""
# Adjust generation parameters
PARAMETER temperature 0.3
PARAMETER top_p 0.9
PARAMETER num_ctx 4096
PARAMETER stop "</answer>"
# Add a custom template for structured output
TEMPLATE """
{{ if .System }}<|system|>
{{ .System }}<|end|>
{{ end }}{{ if .Prompt }}<|user|>
{{ .Prompt }}<|end|>
{{ end }}<|assistant|>
{{ .Response }}<|end|>
"""
Build the custom model inside the running Ollama container. First, copy the Modelfile into the container:
docker cp Modelfile ollama:/tmp/Modelfile
docker exec -it ollama ollama create my-coder -f /tmp/Modelfile
Now you can run your custom model:
curl http://localhost:11434/api/chat -d '{
"model": "my-coder",
"messages": [
{"role": "user", "content": "Write a Dockerfile for a Node.js app."}
],
"stream": false
}'
The custom model inherits the base model's capabilities but applies your system prompt and parameter tuning to every interaction.
Integrating Ollama with Other Containers
In a typical architecture, Ollama serves as a backend inference engine for a frontend application or API gateway. Here is a Compose file that runs Ollama alongside a simple Python API wrapper:
version: "3.9"
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
volumes:
- ollama_data:/root/.ollama
restart: unless-stopped
ports:
- "11434:11434"
api:
build: ./api
container_name: llm-api
environment:
- OLLAMA_URL=http://ollama:11434
- MODEL_NAME=llama3.2
ports:
- "8000:8000"
depends_on:
- ollama
restart: unless-stopped
volumes:
ollama_data:
Create the API service in ./api/main.py using FastAPI:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import httpx
import os
app = FastAPI()
OLLAMA_URL = os.getenv("OLLAMA_URL", "http://ollama:11434")
MODEL_NAME = os.getenv("MODEL_NAME", "llama3.2")
class ChatRequest(BaseModel):
message: str
class ChatResponse(BaseModel):
response: str
@app.post("/chat", response_model=ChatResponse)
async def chat(req: ChatRequest):
try:
async with httpx.AsyncClient(timeout=120.0) as client:
result = await client.post(
f"{OLLAMA_URL}/api/chat",
json={
"model": MODEL_NAME,
"messages": [{"role": "user", "content": req.message}],
"stream": False,
},
)
result.raise_for_status()
data = result.json()
return ChatResponse(response=data["message"]["content"])
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/health")
async def health():
return {"status": "ok"}
Create ./api/requirements.txt:
fastapi==0.115.0
uvicorn==0.30.6
httpx==0.27.2
Create ./api/Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Start the full stack:
docker compose up -d --build
Before using the API, pull the model into the Ollama container:
docker exec -it ollama ollama pull llama3.2
Test the wrapper API:
curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"message": "What are the benefits of containerization?"}'
The API container communicates with Ollama over the internal Docker network using the service name ollama as the hostname. This is a clean, decoupled architecture where the inference engine and application logic are independently scalable.
Preloading Models on Startup
In production, you want models to be available immediately when the container starts, without manual ollama pull commands. You can create a custom Ollama image that preloads models:
FROM ollama/ollama:latest
# Copy a startup script
COPY start.sh /start.sh
RUN chmod +x /start.sh
CMD ["/start.sh"]
Create start.sh:
#!/bin/bash
set -e
# Start Ollama in the background
ollama serve &
OLLAMA_PID=$!
# Wait for the API to be ready
echo "Waiting for Ollama to start..."
until curl -s http://localhost:11434 >/dev/null 2>&1; do
sleep 1
done
echo "Ollama is ready. Pulling models..."
# Preload models
ollama pull llama3.2
ollama pull nomic-embed-text
echo "Models loaded successfully."
# Wait for the background process
wait $OLLAMA_PID
Build and run:
docker build -t ollama-preloaded .
docker run -d --name ollama -p 11434:11434 -v ollama_data:/root/.ollama ollama-preloaded
This approach ensures that every time the container starts, the required models are available without manual intervention.
Best Practices
Use Persistent Volumes
Always mount a volume at /root/.ollama. Without persistence, every container recreation triggers a full model re-download, which wastes bandwidth and time. Named volumes are preferred over bind mounts for model storage because Docker manages them and they avoid permission issues.
Set Resource Limits
LLM inference is resource-intensive. In a shared environment, set memory and CPU limits to prevent Ollama from starving other services:
services:
ollama:
image: ollama/ollama:latest
deploy:
resources:
limits:
memory: 16G
cpus: "4"
Secure the API Endpoint
By default, Ollama listens on all interfaces with no authentication. In production, either bind it to localhost only (127.0.0.1:11434) or place it behind a reverse proxy with authentication. Here is a simple nginx configuration snippet:
server {
listen 8080;
location / {
proxy_pass http://ollama:11434;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Optional: add basic auth
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
}
}
Choose the Right Model Size
Selecting an appropriately sized model is critical. A model that is too large will cause out-of-memory errors or extreme latency. A model that is too small may produce low-quality output. As a general guideline:
- 3B parameters (e.g., Llama 3.2 3B): Needs ~2 GB RAM, good for simple tasks and edge devices
- 7B–8B parameters (e.g., Llama 3.1 8B, Mistral 7B): Needs ~5 GB RAM, balanced quality and performance
- 13B–14B parameters: Needs ~8 GB RAM, higher quality reasoning
- 70B parameters: Needs ~40 GB RAM, near-GPT-4 quality, requires GPU
Monitor Container Health
Add a health check to your Compose file so Docker can detect and restart unhealthy containers:
services:
ollama:
image: ollama/ollama:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:11434"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
restart: unless-stopped
Use Streaming for Better UX
For user-facing applications, enable streaming responses. Ollama streams tokens as they are generated, which gives users immediate feedback instead of waiting for the entire response. Most HTTP clients support streaming via Server-Sent Events or chunked transfer encoding.
Keep Images Updated
Ollama releases updates frequently with performance improvements and new model support. Periodically pull the latest image and recreate your containers:
docker compose pull
docker compose up -d
Always test updates in a staging environment first, as new versions may change API behavior or model compatibility.
Conclusion
Deploying local LLMs with Ollama and Docker gives you a powerful, private, and cost-effective alternative to cloud-based AI APIs. Ollama handles the complexity of model management and inference, while Docker provides the portability, reproducibility, and orchestration capabilities needed for production deployments. By following the patterns in this tutorial — persistent volumes, Docker Compose orchestration, custom Modelfiles, GPU acceleration, and proper security practices — you can build a robust local LLM infrastructure that scales from a developer laptop to a production server. Whether you are building a coding assistant, a document analysis pipeline, or a chatbot for internal use, this stack provides a solid foundation that keeps your data private, your costs predictable, and your architecture clean.