Managing models from the CLI and API

pull, list, ps, show, copy, rm and push, inspecting details and templates, and scripting model provisioning for a fleet of machines.

The everyday commands

# provision a machine with exactly the models an application needs
for model in \
  "llama3.2:3b-instruct-q4_K_M" \
  "qwen2.5:7b-instruct-q4_K_M" \
  "nomic-embed-text:v1.5"; do
  ollama pull "$model"
done

ollama list                       # what is on disk
ollama ps                         # what is in memory right now
ollama show llama3.2 --parameters # effective sampling defaults
ollama show llama3.2 --template   # the chat template
ollama show llama3.2 --system     # the baked-in system prompt, if any

ollama cp llama3.2:3b-instruct-q4_K_M baseline-3b    # a local alias
ollama rm old-model:latest                            # free disk space

# push a custom model to your registry namespace
ollama cp support-bot myuser/support-bot:v1
ollama push myuser/support-bot:v1
  • ollama ps is the command people forget. It tells you which models are resident, how long they will stay, and whether they are fully on the GPU.
  • Copying a model creates an alias, not a duplicate of the weights: the blobs are shared until one of them is deleted.
  • ollama rm deletes blobs that are no longer referenced by any model. Removing an alias can free a surprising amount of disk.
  • Record the exact tags your application depends on in a file that is versioned with the code, and pull from that file in the container build.

The management API

import requests

BASE = "http://localhost:11434"

def list_models():
    return requests.get(f"{BASE}/api/tags", timeout=10).json()["models"]

def running():
    return requests.get(f"{BASE}/api/ps", timeout=10).json()["models"]

def show(model):
    return requests.post(f"{BASE}/api/show", json={"model": model}, timeout=30).json()

def pull(model, stream=False):
    r = requests.post(f"{BASE}/api/pull",
                      json={"model": model, "stream": stream}, timeout=3600)
    r.raise_for_status()
    return r.json()

def delete(model):
    return requests.delete(f"{BASE}/api/delete",
                           json={"model": model}, timeout=30).status_code

for m in list_models():
    print(f"{m['name']:45s} {m['size'] / 1e9:6.2f} GB  {m['details']['quantization_level']}")

print([m["name"] for m in running()])
details = show("llama3.2:3b-instruct-q4_K_M")
print(details["details"]["parameter_size"], details["model_info"].get("llama.context_length"))
EndpointMethodPurpose
/api/tagsGETList local models with sizes and digests
/api/psGETList loaded models and their expiry
/api/showPOSTTemplates, parameters, model metadata
/api/pullPOSTDownload a model, optionally streaming progress
/api/pushPOSTUpload a custom model to the registry
/api/deleteDELETERemove a model and its unreferenced blobs
/api/createPOSTBuild a model from a Modelfile
⚠️
A provisioning script should verify the digest, not only the name. Two machines with llama3.2:3b can have different weights if one was pulled months earlier, and the difference shows up as an unexplained quality regression.

Provisioning and disk hygiene

import hashlib
import json
from pathlib import Path

LOCKFILE = Path("models.lock.json")

def lock(requirements, base="http://localhost:11434"):
    """Record the exact digest of every required model."""
    import requests
    available = {m["name"]: m for m in
                 requests.get(f"{base}/api/tags", timeout=10).json()["models"]}
    entries = {}
    for name in requirements:
        info = available.get(name)
        if info is None:
            raise SystemExit(f"missing model: {name} (run ollama pull {name})")
        entries[name] = {"digest": info["digest"], "size": info["size"],
                         "quantization": info["details"]["quantization_level"]}
    LOCKFILE.write_text(json.dumps(entries, indent=2), encoding="utf-8")
    return entries

def verify(requirements, base="http://localhost:11434"):
    import requests
    expected = json.loads(LOCKFILE.read_text(encoding="utf-8"))
    available = {m["name"]: m for m in
                 requests.get(f"{base}/api/tags", timeout=10).json()["models"]}
    differences = []
    for name in requirements:
        if name not in available:
            differences.append((name, "absent"))
        elif available[name]["digest"] != expected.get(name, {}).get("digest"):
            differences.append((name, "digest changed"))
    return differences

REQUIRED = ["llama3.2:3b-instruct-q4_K_M", "nomic-embed-text:v1.5"]
print(verify(REQUIRED))

# disk usage of the model store, on a default install layout
from subprocess import run
run(["du", "-sh", str(Path.home() / ".ollama" / "models")], check=False)
  • A lockfile with digests turns "which model is deployed?" into a diff instead of a conversation.
  • Prune unused models before a disk fills, not after. A full disk makes Ollama fail to pull and can leave a partially written blob behind.
  • Pull during the image build or the provisioning step, never at first request. A cold pull takes minutes and will time out the user who triggered it.
  • Keep the model store on its own volume when you can. It grows to tens of gigabytes and should not compete with logs for space.

FAQ

Where does Ollama store models?
~/.ollama/models by default, or /usr/share/ollama/.ollama/models for the system service. Set OLLAMA_MODELS to relocate it to a larger volume.
Can I copy models between machines?
Yes, copy the models directory while the service is stopped, then verify the digests with a lockfile. Copying is much faster than re-pulling a 40 GB model over a slow link.

Model tags, sizes and quantisation explained Serving Ollama in Docker and over a network

Last refreshed 2026-09-18.