Ollama: Production Deployment

Production deployment is local AI's final leap from lab to battlefield — highly available, scalable, and disaster-resilient.

💡 Tip: Nginx reverse proxy + load balancing is the standard architecture for production deployment — Nginx handles SSL termination, API Key authentication, rate limiting, and traffic distribution, while Ollama nodes only need to bind to 127.0.0.1. For multi-node clusters, the least_conn (least connections) strategy is recommended because inference request durations vary significantly.

📋 Prerequisites: You should first master the following

1. What You Will Learn


2. A Real Story from a SaaS Entrepreneur

💡 Tip: In production environments, Nginx load balancing should use the least_conn (least connections) strategy rather than the default round_robin. Ollama inference request durations vary greatly (short Q&A takes 1 second, long text takes 30 seconds), and least_conn distributes load more evenly.

ℹ️ Info: Ollama does not natively support cluster mode (no master-slave sync, no distributed inference). Model consistency and session affinity for multi-node clusters need to be implemented at the upper layer (Nginx/FastAPI). This also applies to Kubernetes deployments.

(1) The Pain Point: Single Point of Failure Causes Total Service Outage

Alice's SupportBot runs on a single Ollama server. Server maintenance or GPU OOM causes service interruption, taking down the customer service system for 2 hours and affecting 500+ customers.

(2) The Solution: Multi-Node High-Availability Cluster

Deploy a 3-node Ollama cluster + Nginx load balancer, with automatic failover if any node goes down:

100%
flowchart TD
    A[Nginx Load Balancer] --> B[Ollama Node 1<br/>GPU 1]
    A --> C[Ollama Node 2<br/>GPU 2]
    A --> D[Ollama Node 3<br/>GPU 3]

3. Production Architecture Design

⚠️ Warning: In a multi-node cluster, each Ollama node independently manages its models and state — there is no master-slave replication like in databases. A model pulled on Node A will not automatically sync to Node B. You must ensure model consistency across all nodes through a unified initialization script or shared storage.

(1) Single Node vs Multi-Node

Dimension Single Node Multi-Node Cluster
Availability Single point of failure N-1 fault tolerance
Throughput Limited Linear scaling
Cost Low High (3x+)
Operations complexity Low Medium
Use Case Dev/small scale Production

(2) Production Architecture Overview

100%
flowchart TD
    A[Internet] --> B[WAF / CDN]
    B --> C[Nginx<br/>SSL + Auth + LB]
    C --> D[Ollama Node 1<br/>GPU + Model]
    C --> E[Ollama Node 2<br/>GPU + Model]
    C --> F[Ollama Node 3<br/>GPU + Model]
    D --> G[NFS / S3<br/>Shared Model Storage]
    E --> G
    F --> G
    D --> H[Chroma Cluster]
    E --> H
    F --> H
    I[Prometheus] --> D
    I --> E
    I --> F
    I --> J[Grafana Dashboard]

(3) Component List

Component Count Specifications Purpose
Nginx LB 1 2 vCPU, 4GB RAM Load balancing + SSL
Ollama Node 3 8 vCPU, 16GB RAM, 1x RTX 4090 Model inference
Chroma 1 4 vCPU, 8GB RAM, SSD Vector storage
NFS/S3 1 500GB+ Model shared storage
Prometheus 1 2 vCPU, 4GB RAM Monitoring

4. High Availability

⚠️ Note: SSL certificate configuration is mandatory for production — HTTPS not only encrypts transmission but is also a prerequisite for API Key authentication (sending API Keys in plaintext over HTTP means no security at all). Use certbot for free Let's Encrypt certificates, or Caddy for automatic HTTPS.

(1) Health Checks and Failover

Mechanism Check Method Interval Timeout
Nginx passive check Mark unavailable on request failure Every request 5s
Active health check Periodic GET /api/tags 10s 3s
Application-layer check Custom inference test 30s 10s

▶ Example 1: Nginx Load Balancer Configuration

NGINX
# /etc/nginx/conf.d/ollama-lb.conf

upstream ollama_cluster {
    least_conn;  # Route to least busy node

    server ollama-node1:11434 max_fails=3 fail_timeout=30s;
    server ollama-node2:11434 max_fails=3 fail_timeout=30s;
    server ollama-node3:11434 max_fails=3 fail_timeout=30s;
}

server {
    listen 443 ssl;
    server_name ai.example.com;

    ssl_certificate     /etc/ssl/certs/ai.example.com.crt;
    ssl_certificate_key /etc/ssl/private/ai.example.com.key;

    location /v1/ {
        # API Key authentication
        if ($http_x_api_key != "your-secret-key") {
            return 401 '{"error": "Unauthorized"}';
        }

        proxy_pass http://ollama_cluster;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_connect_timeout 5s;
        proxy_read_timeout 120s;

        # Rate limiting
        limit_req zone=api burst=20 nodelay;
    }

    # Health check endpoint
    location /health {
        proxy_pass http://ollama_cluster/api/tags;
    }
}

Output:

TEXT
// Execution successful

▶ Example 2: Automatic Failover Script

BASH
#!/bin/bash
# Ollama cluster health monitor

NODES=("ollama-node1:11434" "ollama-node2:11434" "ollama-node3:11434")
HEALTH_URL="/api/tags"
ALERT_EMAIL="ops@example.com"

check_node() {
    local node=$1
    if curl -sf --connect-timeout 3 "http://$node$HEALTH_URL" > /dev/null; then
        echo "UP"
    else
        echo "DOWN"
    fi
}

while true; do
    for node in "${NODES[@]}"; do
        status=$(check_node "$node")
        if [ "$status" = "DOWN" ]; then
            echo "ALERT: $node is DOWN at $(date)" >> /var/log/ollama_health.log
            # Send alert
            echo "Ollama node $node is DOWN" | mail -s "OLLAMA ALERT" "$ALERT_EMAIL" 2>/dev/null
        fi
    done
    sleep 10
done

Output:

TEXT
NAME                    ID              SIZE    
llama3.2:latest        a80...          2.0 GB  
mistral:latest         61...           4.1 GB

5. Scaling Strategies

(1) Scaling Trigger Conditions

Metric Scale-Up Threshold Scale-Down Threshold Wait Time
Request latency P95 > 5s < 2s 5 minutes
Concurrent connections > 80% capacity < 30% capacity 5 minutes
GPU utilization > 85% < 40% 10 minutes
Queue length > 10 = 0 3 minutes

(2) Scaling Method Comparison

Method Speed Cost Complexity
Manual scaling Slow (hours) Controllable Low
Automatic scripts Medium (minutes) Controllable Medium
K8s HPA Fast (seconds) Automatic High

▶ Example 3: Latency-Based Auto-Scaling

PYTHON
import subprocess
import time
from typing import Optional

class AutoScaler:
    def __init__(self, scale_up_threshold: float = 5.0,
                 scale_down_threshold: float = 2.0,
                 cooldown: int = 300):
        self.scale_up_threshold = scale_up_threshold
        self.scale_down_threshold = scale_down_threshold
        self.cooldown = cooldown
        self.last_scale_time = 0

    def get_avg_latency(self) -> Optional[float]:
        try:
            result = subprocess.run(
                ["curl", "-sf", "http://localhost:11434/api/chat", "-d",
                 '{"model":"qwen2.5","messages":[{"role":"user","content":"hi"}],"stream":false}'],
                capture_output=True, text=True, timeout=10
            )
            if result.returncode == 0:
                import json
                data = json.loads(result.stdout)
                duration_ns = data.get("total_duration", 0)
                return duration_ns / 1e9
        except Exception:
            pass
        return None

    def check_and_scale(self):
        latency = self.get_avg_latency()
        if latency is None:
            return

        now = time.time()
        if now - self.last_scale_time < self.cooldown:
            return

        if latency > self.scale_up_threshold:
            print(f"High latency ({latency:.1f}s), scaling up...")
            self._scale_up()
            self.last_scale_time = now
        elif latency < self.scale_down_threshold:
            print(f"Low latency ({latency:.1f}s), scaling down...")
            self._scale_down()
            self.last_scale_time = now

    def _scale_up(self):
        # Add new Ollama node (e.g., via Docker or cloud API)
        print("Adding Ollama node...")

    def _scale_down(self):
        # Remove idle Ollama node
        print("Removing idle Ollama node...")

# Usage
# scaler = AutoScaler()
# while True:
#     scaler.check_and_scale()
#     time.sleep(60)

Output:

TEXT
Adding Ollama node...
Removing idle Ollama node...

6. Kubernetes Deployment

(1) K8s Deployment Architecture

100%
flowchart TD
    A[Ingress<br/>nginx-ingress] --> B[Service<br/>ollama-svc]
    B --> C[Deployment<br/>ollama-deploy<br/>3 replicas]
    C --> D[Pod 1<br/>GPU + Model]
    C --> E[Pod 2<br/>GPU + Model]
    C --> F[Pod 3<br/>GPU + Model]
    G[PVC<br/>model-storage] --> D
    G --> E
    G --> F

(2) GPU Scheduling Key Configuration

Resource Configuration Description
nvidia.com/gpu (requests) Resource request 1 GPU per Pod
nvidia.com/gpu (limits) Resource limit Maximum 1 GPU
nodeSelector GPU node selection Specify GPU nodes
tolerations GPU taint toleration Allow scheduling to GPU nodes

▶ Example 4: Ollama K8s Deployment

YAML
# ollama-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ollama
  labels:
    app: ollama
spec:
  replicas: 3
  selector:
    matchLabels:
      app: ollama
  template:
    metadata:
      labels:
        app: ollama
    spec:
      nodeSelector:
        gpu: "true"
      tolerations:
        - key: nvidia.com/gpu
          operator: Exists
          effect: NoSchedule
      containers:
        - name: ollama
          image: ollama/ollama
          ports:
            - containerPort: 11434
          resources:
            requests:
              nvidia.com/gpu: 1
              memory: "8Gi"
            limits:
              nvidia.com/gpu: 1
              memory: "16Gi"
          env:
            - name: OLLAMA_HOST
              value: "0.0.0.0:11434"
            - name: OLLAMA_NUM_PARALLEL
              value: "4"
            - name: OLLAMA_KEEP_ALIVE
              value: "30m"
          volumeMounts:
            - name: model-storage
              mountPath: /root/.ollama
          livenessProbe:
            httpGet:
              path: /api/tags
              port: 11434
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /api/tags
              port: 11434
            initialDelaySeconds: 10
            periodSeconds: 5
      volumes:
        - name: model-storage
          persistentVolumeClaim:
            claimName: ollama-models-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: ollama-svc
spec:
  selector:
    app: ollama
  ports:
    - port: 11434
      targetPort: 11434
  type: ClusterIP

Output:

TEXT
K8s Deployment created successfully, Pods running normally

Output:

TEXT
HPA auto-scaling configured successfully, cluster resource monitoring enabled

▶ Example 5: K8s HPA Auto-Scaling

YAML
# ollama-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: ollama-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: ollama
  minReplicas: 2
  maxReplicas: 6
  metrics:
    - type: Resource
      resource:
        name: nvidia.com/gpu
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Pods
          value: 1
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Pods
          value: 1
          periodSeconds: 300

Output:

TEXT
# HPA created successfully
kubectl get hpa ollama-hpa
# NAME          REFERENCE            TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
# ollama-hpa   Deployment/ollama    45%/80%         2         6         3          5m

7. Disaster Recovery and Rollback

(1) Disaster Recovery Strategies

Strategy RTO RPO Cost
Active-passive 5-15 minutes 0 2x hardware
Active-active 0 (automatic failover) 0 3x hardware
Cold standby 1-4 hours Lossy 1x + storage

(2) Configuration-as-Code

Resource Version Management Method
Modelfile Git repository
Docker Compose Git repository
K8s Manifests Git + ArgoCD
Nginx configuration Git repository
Model versions Ollama tags + documentation

8. Comprehensive Example: Production Deployment Checklist

PYTHON
# ============================================
# Comprehensive: Production deployment checklist
# Full verification before going live
# ============================================

PRODUCTION_CHECKLIST = {
    "infrastructure": [
        ("Ollama nodes: 3+ replicas running", "CRITICAL"),
        ("GPU drivers: installed and verified", "CRITICAL"),
        ("Model storage: shared NFS/S3 mounted", "CRITICAL"),
        ("Network: internal VLAN for Ollama traffic", "HIGH"),
        ("DNS: ai.example.com resolves to LB", "HIGH"),
    ],
    "security": [
        ("Ollama bound to 127.0.0.1 on each node", "CRITICAL"),
        ("Nginx SSL certificate valid", "CRITICAL"),
        ("API Key authentication enabled", "CRITICAL"),
        ("Rate limiting configured (60 req/min)", "HIGH"),
        ("WAF rules in place", "MEDIUM"),
    ],
    "high_availability": [
        ("Health checks configured (Nginx + app layer)", "CRITICAL"),
        ("Failover tested: kill 1 node, service continues", "CRITICAL"),
        ("Load balancer: least_conn algorithm", "HIGH"),
        ("Session affinity: disabled (stateless)", "HIGH"),
    ],
    "monitoring": [
        ("Prometheus scraping all nodes", "HIGH"),
        ("Grafana dashboards created", "HIGH"),
        ("Alert rules: GPU OOM, high latency, service down", "CRITICAL"),
        ("Log aggregation: Loki or ELK", "MEDIUM"),
    ],
    "disaster_recovery": [
        ("Config in Git (Modelfile, Compose, K8s)", "HIGH"),
        ("Model backup: GGUF files on S3/NFS", "HIGH"),
        ("Chroma data backup: daily snapshot", "HIGH"),
        ("Recovery drill: tested within last 30 days", "MEDIUM"),
    ],
    "performance": [
        ("Baseline benchmark recorded", "HIGH"),
        ("OLLAMA_NUM_PARALLEL configured", "HIGH"),
        ("OLLAMA_KEEP_ALIVE=30m set", "MEDIUM"),
        ("num_ctx per endpoint optimized", "MEDIUM"),
    ]
}

def run_checklist() -> str:
    report = ["# Production Deployment Checklist\n"]
    total = 0
    checked = 0

    for category, items in PRODUCTION_CHECKLIST.items():
        report.append(f"\n## {category.replace('_', ' ').title()}")
        for item, priority in items:
            total += 1
            report.append(f"- [ ] [{priority}] {item}")

    report.append(f"\n---\nTotal items: {total}")
    return "\n".join(report)

print(run_checklist())

❓ FAQ

Q What is the minimum number of GPUs for a 3-node cluster?
A Minimum 3 (1 per node). If budget is limited, a 2-node setup (1 primary + 1 backup) works, but throughput is halved during failover.
Q Does every machine need to pull model files?
A If using shared storage (NFS), pull only once. If using local storage, each machine needs to pull. NFS shared + local cache is recommended.
Q Should I choose Kubernetes or Docker Compose?
A Docker Compose for < 5 nodes (simpler). Kubernetes for > 5 nodes or when auto-scaling is needed. Docker Compose is sufficient for most scenarios.
Q How do I test failover?
A Manually stop an Ollama node (docker stop or systemctl stop), verify that Nginx automatically routes traffic to other nodes with no user impact.
Q Does Ollama have a built-in cluster mode?
A No. Ollama is a single-instance service; clusters require an external load balancer combination. Each Ollama instance runs independently; Nginx handles request distribution.
Q How do I manage model versions?
A Use Modelfile + Git for configuration management. Model files use tags (qwen2.5:v1.0) for version identification. In production, use fixed tags, not :latest.

📖 Summary


📝 Exercises

  1. Basic (⭐): Design a single-machine production deployment plan — Nginx + single Ollama + Chroma. Draw an architecture diagram and write a Docker Compose configuration.
  2. Intermediate (⭐⭐): Set up a 2-node Ollama cluster + Nginx load balancer, and test manual failover.
  3. Advanced (⭐⭐⭐): Write a complete production deployment document — architecture diagram, Docker Compose/K8s configuration, security hardening, monitoring and alerting, disaster recovery plan — and complete a failover drill.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏