- Posted on
- • Artificial Intelligence
Artificial Intelligence Homelab Case Studies
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Artificial Intelligence Homelab Case Studies (A Bash‑First Guide)
Ever wished you could run private, low‑latency AI at home—without sending your data to the cloud or paying per‑token fees? Good news: modern open models and lightweight tooling make this not only possible, but practical on modest hardware. In this article, you’ll get three real homelab case studies—with copy/paste‑ready Bash and minimal moving parts—that deliver immediate value: a local AI chat server, an “ask my docs” retrieval system, and experiment tracking with MLflow.
What you’ll get:
Clear problem → value framing for each case study
Why a homelab for AI makes sense (privacy, control, cost)
3 actionable builds with commands for apt, dnf, and zypper
Reusable snippets you can adapt to your own setup
Why AI in a homelab?
Privacy and control: Keep sensitive documents, camera feeds, and prompts on hardware you own.
Latency and reliability: Local models answer instantly—even offline.
Cost predictability: One‑time hardware + open models beats recurring API bills.
Skill building: Infra, automation, containers, Python, and MLOps—all hands‑on.
Design principles used here:
Prefer rootless containers (Podman) and system packages.
Keep data on disk in simple, inspectable formats (text, JSON, SQLite).
Start CPU‑first; add GPU acceleration later if you need it.
Case Study 1: Local LLM Chat (Ollama + Open WebUI)
Goal: A private, web‑based chat UI backed by a local LLM.
What you’ll build:
Ollama model server on the host
Open WebUI in a container connecting to Ollama
1.1 Install prerequisites
- apt (Debian/Ubuntu):
sudo apt update
sudo apt install -y podman curl
- dnf (Fedora/RHEL/CentOS Stream):
sudo dnf install -y podman curl
- zypper (openSUSE):
sudo zypper install -y podman curl
1.2 Install and run Ollama
curl -fsSL https://ollama.com/install.sh | sh
# Start the service (if the installer didn’t already):
sudo systemctl enable --now ollama || ollama serve &
Pull a small, capable model (adjust to your RAM/CPU):
ollama pull llama3.2:3b
Quick sanity test:
curl -s http://localhost:11434/api/generate \
-d '{"model":"llama3.2:3b","prompt":"Say hello in one short sentence."}'
1.3 Launch Open WebUI via Podman
Use host networking so the container can reach Ollama on 11434:
podman run -d --name openwebui --network=host \
-e OLLAMA_BASE_URL=http://127.0.0.1:11434 \
ghcr.io/open-webui/open-webui:latest
Open http://localhost:8080 in your browser. Select the model (e.g., llama3.2:3b) and chat.
Pro tip:
- CPU‑only works fine for smaller models. Add a GPU later if desired (check Ollama docs for CUDA/ROCm requirements).
Case Study 2: Ask‑My‑Docs (RAG) with Local Embeddings + Ollama
Goal: Ask natural‑language questions about your PDFs/TXT/MD—fully offline.
Approach:
Convert documents to plain text
Embed and index chunks locally (NumPy + sentence-transformers)
Retrieve top matches and ask Ollama with that context
2.1 Install prerequisites
- apt:
sudo apt update
sudo apt install -y python3 python3-venv python3-pip git poppler-utils
- dnf:
sudo dnf install -y python3 python3-venv python3-pip git poppler-utils
- zypper:
sudo zypper install -y python3 python3-pip git poppler-tools
2.2 Project layout and environment
mkdir -p ~/homelab-rag/{docs,corpus,index}
cd ~/homelab-rag
python3 -m venv .venv
. .venv/bin/activate
pip install -U pip
pip install sentence-transformers numpy tqdm httpx
2.3 Convert documents to text (Bash)
Place PDFs/TXT/MD into ~/homelab-rag/docs, then run:
cat > to_text.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
in_dir="${1:-docs}"
out_dir="${2:-corpus}"
mkdir -p "$out_dir"
shopt -s nullglob
for f in "$in_dir"/*; do
base="$(basename "$f")"
name="${base%.*}"
ext="${base##*.}"
case "$ext" in
pdf)
pdftotext -layout "$f" "$out_dir/$name.txt"
;;
md|txt)
cp "$f" "$out_dir/$name.txt"
;;
*)
echo "Skipping unsupported file: $f" >&2
;;
esac
done
echo "Text corpus in: $out_dir"
EOF
chmod +x to_text.sh
./to_text.sh docs corpus
2.4 Embed corpus and build a lightweight index (Python)
cat > embed.py << 'PY'
import os, json, glob
import numpy as np
from tqdm import tqdm
from sentence_transformers import SentenceTransformer
MODEL = "sentence-transformers/all-MiniLM-L6-v2"
CHUNK = 1000
OVERLAP = 150
def chunk_text(t, size=CHUNK, overlap=OVERLAP):
out, i = [], 0
while i < len(t):
out.append(t[i:i+size])
i += size - overlap
return [c for c in out if c.strip()]
def main(corpus_dir="corpus", index_dir="index"):
os.makedirs(index_dir, exist_ok=True)
model = SentenceTransformer(MODEL)
texts, meta = [], []
for path in tqdm(sorted(glob.glob(os.path.join(corpus_dir, "*.txt")))):
with open(path, "r", encoding="utf-8", errors="ignore") as f:
text = f.read()
for j, chunk in enumerate(chunk_text(text)):
texts.append(chunk)
meta.append({"file": os.path.basename(path), "chunk_id": j, "preview": chunk[:180].replace("\n"," ")})
if not texts:
print("No texts found. Run the to_text.sh step first.")
return
print(f"Embedding {len(texts)} chunks...")
emb = model.encode(texts, normalize_embeddings=True, convert_to_numpy=True)
np.save(os.path.join(index_dir, "embeddings.npy"), emb)
with open(os.path.join(index_dir, "meta.jsonl"), "w", encoding="utf-8") as out:
for m in meta:
out.write(json.dumps(m, ensure_ascii=False) + "\n")
with open(os.path.join(index_dir, "texts.jsonl"), "w", encoding="utf-8") as out:
for t in texts:
out.write(json.dumps({"text": t}, ensure_ascii=False) + "\n")
print(f"Index written to: {index_dir}")
if __name__ == "__main__":
main()
PY
python embed.py
2.5 Query with local retrieval + Ollama (Python)
cat > ask.py << 'PY'
import os, sys, json, numpy as np, httpx
from sentence_transformers import SentenceTransformer
OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://127.0.0.1:11434")
MODEL = os.environ.get("MODEL", "llama3.2:3b")
TOPK = int(os.environ.get("TOPK", "5"))
def load_index(index_dir="index"):
emb = np.load(os.path.join(index_dir, "embeddings.npy"))
meta = [json.loads(l) for l in open(os.path.join(index_dir, "meta.jsonl"), encoding="utf-8")]
texts = [json.loads(l)["text"] for l in open(os.path.join(index_dir, "texts.jsonl"), encoding="utf-8")]
return emb, meta, texts
def main():
if len(sys.argv) < 2:
print("Usage: python ask.py \"your question here\"")
sys.exit(1)
question = sys.argv[1]
emb, meta, texts = load_index()
enc = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
q = enc.encode([question], normalize_embeddings=True, convert_to_numpy=True)[0]
sims = emb @ q
top_idx = sims.argsort()[-TOPK:][::-1]
ctx = "\n\n---\n\n".join([texts[i] for i in top_idx])
prompt = f"Use the context to answer concisely.\n\nContext:\n{ctx}\n\nQuestion: {question}\nAnswer:"
payload = {"model": MODEL, "prompt": prompt, "stream": False}
try:
r = httpx.post(f"{OLLAMA_URL}/api/generate", json=payload, timeout=120)
r.raise_for_status()
print(json.loads(r.text)["response"].strip())
except Exception as e:
print(f"[WARN] Ollama call failed: {e}")
print("Top contexts:\n", ctx[:2000])
if __name__ == "__main__":
main()
PY
python ask.py "Summarize the key points across my documents."
You now have an offline, private Q&A tool over your docs. Tune CHUNK/OVERLAP/TOPK for your content.
Case Study 3: Track Experiments with MLflow (Local UI + SQLite)
Goal: Keep your training runs organized, compare metrics, and version artifacts—no cloud needed.
3.1 Install prerequisites
If you completed Case Study 2, you already have Python and venv. Otherwise:
- apt:
sudo apt update
sudo apt install -y python3 python3-venv python3-pip
- dnf:
sudo dnf install -y python3 python3-venv python3-pip
- zypper:
sudo zypper install -y python3 python3-pip
3.2 Create an MLflow workspace
mkdir -p ~/ai-mlflow
cd ~/ai-mlflow
python3 -m venv .venv
. .venv/bin/activate
pip install -U pip
pip install mlflow scikit-learn pandas numpy
Run the server (SQLite backend, local artifacts):
mlflow server \
--backend-store-uri sqlite:///mlflow.db \
--default-artifact-root "$(pwd)/artifacts" \
--host 0.0.0.0 --port 5000
Open http://localhost:5000
3.3 Log a simple training run
cat > train.py << 'PY'
import mlflow, mlflow.sklearn
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
X, y = load_iris(return_X_y=True, as_frame=True)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.2, random_state=42)
n_estimators, depth = 200, 5
with mlflow.start_run():
mlflow.log_param("n_estimators", n_estimators)
mlflow.log_param("max_depth", depth)
clf = RandomForestClassifier(n_estimators=n_estimators, max_depth=depth, random_state=42)
clf.fit(Xtr, ytr)
acc = accuracy_score(yte, clf.predict(Xte))
mlflow.log_metric("accuracy", acc)
mlflow.sklearn.log_model(clf, "model")
print("Accuracy:", acc)
PY
python train.py
Refresh the MLflow UI to compare runs.
3.4 Optional: run MLflow as a user service (systemd)
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/mlflow.service << 'EOF'
[Unit]
Description=MLflow Tracking Server (user)
After=network-online.target
[Service]
Type=simple
WorkingDirectory=%h/ai-mlflow
ExecStart=%h/ai-mlflow/.venv/bin/mlflow server --backend-store-uri sqlite:///%h/ai-mlflow/mlflow.db --default-artifact-root %h/ai-mlflow/artifacts --host 0.0.0.0 --port 5000
Restart=on-failure
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now mlflow
Homelab Tips That Pay Off
Backups: Snapshot your ~/homelab-rag and ~/ai-mlflow folders. Your embeddings, indexes, and experiment history are assets.
Security: Bind to localhost or firewall ports if you open UIs (Open WebUI 8080, MLflow 5000).
Resources: Start with small models (e.g., 3B parameter LLMs). Scale only when you need better quality.
Automation: Wrap commands into Makefiles or systemd units for repeatable starts.
Conclusion and Next Steps
You just stood up:
A private local LLM with a friendly web UI
An offline “ask my docs” RAG pipeline
A proper MLOps tracking UI with MLflow
Next steps:
Add a GPU and switch Ollama to a larger model for richer answers.
Point the RAG pipeline at wikis, logs, and code to supercharge your ops.
Containerize MLflow or reverse‑proxy behind Caddy/NGINX for multi‑user access.
Tie it all together with Ansible so you can rebuild in minutes.
Have a favorite homelab AI use‑case you want to see? Reply with your hardware and goals—I’ll tailor the next guide with Bash you can paste and run.