Posted on
Artificial Intelligence

Artificial Intelligence DevOps Case Studies

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Artificial Intelligence DevOps Case Studies: A Bash-First Playbook for Linux

If your model works on your laptop but pages you at 2 a.m. in production, you don’t have a model problem—you have a DevOps problem. In AI, shipping value requires repeatable pipelines, portable runtimes, scalable inference, and observable systems. This article walks through practical, Bash-friendly case studies that show how real teams moved from flaky experiments to reliable AI services on Linux.

What you’ll get:

  • Why AI DevOps is a must-have, not a nice-to-have

  • 3 real-world case studies with command-line steps

  • Install commands for apt, dnf, and zypper

  • Copy-paste code snippets to start today

Why AI DevOps matters on Linux

  • Reproducibility or bust: Without data and environment versioning, you can’t debug or trust results.

  • Shipping beats perfect: Containers and CI/CD make “works here” also work everywhere else.

  • Scale or stall: A nightly batch job might be fine until a customer signs a bigger contract.

  • Visibility saves sleep: Metrics and logs turn “what happened?” into “we saw it coming.”

Below are three case studies you can reproduce with Bash on Ubuntu/Debian (apt), Fedora/RHEL (dnf), and openSUSE (zypper).


Case Study 1: Reproducible training with DVC + MLflow (goodbye “works on my machine”)

Goal: Version your data and experiments so you can rewind, compare, and re-run exactly.

1) Install prerequisites

# Ubuntu/Debian (apt)
sudo apt update
sudo apt install -y git git-lfs python3 python3-venv python3-pip make curl jq

# Fedora/RHEL (dnf)
sudo dnf -y install git git-lfs python3 python3-pip make curl jq

# openSUSE (zypper)
sudo zypper refresh
sudo zypper install -y git git-lfs python3 python3-pip make curl jq

2) Initialize a project with DVC and MLflow

git init ai-repro && cd ai-repro
git lfs install

python3 -m venv .venv
. .venv/bin/activate
pip install --upgrade pip
pip install dvc mlflow scikit-learn pandas joblib
dvc init

3) Track data, define a training step, and log metrics

mkdir -p raw
curl -L -o raw/iris.csv https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv
dvc add raw/iris.csv
git add .gitignore raw/iris.csv.dvc .dvc
git commit -m "Track raw data with DVC"

cat > train.py <<'PY'
import os, json, mlflow, pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import f1_score
import joblib

mlflow.set_tracking_uri("mlruns")
mlflow.set_experiment("iris")

df = pd.read_csv("raw/iris.csv")
X = df.drop(columns=["species"])
y = df["species"]
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.2, random_state=int(os.getenv("SEED","42")))

with mlflow.start_run():
    n_estimators=int(os.getenv("N_ESTIMATORS","100"))
    clf=RandomForestClassifier(n_estimators=n_estimators, random_state=0)
    clf.fit(Xtr, ytr)
    preds=clf.predict(Xte)
    f1=f1_score(yte, preds, average="macro")
    mlflow.log_param("n_estimators", n_estimators)
    mlflow.log_metric("f1_macro", f1)
    os.makedirs("model", exist_ok=True)
    joblib.dump(clf, "model/model.pkl")
    mlflow.log_artifact("model/model.pkl")
    print(json.dumps({"f1_macro":f1}))
PY

# Add a DVC stage to make the pipeline reproducible
dvc stage add -n train -d raw/iris.csv -d train.py -o model \
  -p N_ESTIMATORS,SEED ". .venv/bin/activate && python train.py"

git add train.py dvc.yaml dvc.lock .gitignore
git commit -m "Baseline training pipeline with MLflow + DVC"

4) Run, compare, and view the UI

# Baseline run
N_ESTIMATORS=100 SEED=42 dvc repro

# Tweak a hyperparam and re-run
N_ESTIMATORS=200 dvc repro

# Launch MLflow UI
. .venv/bin/activate
mlflow ui --backend-store-uri mlruns -h 0.0.0.0 -p 5000
# Visit http://localhost:5000 to compare runs

What teams learn:

  • Git tracks code; DVC tracks data; MLflow tracks runs and metrics.

  • You get a time machine for your experiments and a clean audit trail.


Case Study 2: Containerized model serving with Podman/Docker (+ optional GPU)

Goal: Package and serve a model behind a simple REST API, with optional NVIDIA GPU acceleration.

1) Install a container runtime

Podman (rootless by default; great for dev and prod):

# Ubuntu/Debian (apt)
sudo apt update
sudo apt install -y podman

# Fedora/RHEL (dnf)
sudo dnf -y install podman

# openSUSE (zypper)
sudo zypper refresh
sudo zypper install -y podman

Docker (if you already use it):

# Ubuntu/Debian (apt)
sudo apt update
sudo apt install -y docker.io
sudo systemctl enable --now docker

# Fedora/RHEL (dnf) - Moby Engine
sudo dnf -y install moby-engine
sudo systemctl enable --now docker

# openSUSE (zypper)
sudo zypper refresh
sudo zypper install -y docker
sudo systemctl enable --now docker

Optional: NVIDIA Container Toolkit for GPU access

Ubuntu/Debian (apt):

distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
  sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -fsSL https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
  sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#' | \
  sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt update
sudo apt install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=auto
sudo systemctl restart docker || true
sudo systemctl restart podman || true

Fedora/RHEL (dnf):

distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
  sudo gpg --dearmor -o /etc/pki/rpm-gpg/NVIDIA-Container-Toolkit-Keyring.gpg
curl -fsSL https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.repo | \
  sudo tee /etc/yum.repos.d/nvidia-container-toolkit.repo
sudo dnf -y install nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=auto
sudo systemctl restart docker || true
sudo systemctl restart podman || true

openSUSE (zypper):

distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
  sudo gpg --dearmor -o /etc/pki/rpm-gpg/NVIDIA-Container-Toolkit-Keyring.gpg
curl -fsSL https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.repo | \
  sudo tee /etc/zypp/repos.d/nvidia-container-toolkit.repo
sudo zypper refresh
sudo zypper install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=auto
sudo systemctl restart docker || true
sudo systemctl restart podman || true

2) Write a minimal FastAPI server and containerize it

mkdir -p ai-serve && cd ai-serve

# Example model artifact
python3 - <<'PY'
import joblib
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
X, y = load_iris(return_X_y=True)
clf=RandomForestClassifier().fit(X,y)
import os; os.makedirs("model", exist_ok=True)
joblib.dump(clf, "model/model.pkl")
print("Wrote model/model.pkl")
PY

cat > app.py <<'PY'
from fastapi import FastAPI
import joblib, os
from pydantic import BaseModel
import numpy as np

app=FastAPI()
model_path=os.getenv("MODEL_PATH","/model/model.pkl")
model=joblib.load(model_path)

class Item(BaseModel):
    features: list

@app.get("/healthz")
def health():
    return {"ok":True}

@app.post("/predict")
def predict(item: Item):
    X = np.array(item.features).reshape(1,-1)
    yhat = model.predict(X)[0]
    return {"prediction": str(yhat)}
PY

cat > requirements.txt <<'REQ'
fastapi uvicorn joblib scikit-learn numpy
REQ

cat > Dockerfile <<'DF'
FROM python:3.11-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
COPY model/ /model/
EXPOSE 8000
CMD ["uvicorn","app:app","--host","0.0.0.0","--port","8000"]
DF

Build and run (CPU):

# Podman
podman build -t ai-serve:0.1 .
podman run --rm -p 8000:8000 ai-serve:0.1

# Docker
docker build -t ai-serve:0.1 .
docker run --rm -p 8000:8000 ai-serve:0.1

Run with GPU (requires NVIDIA toolkit set up):

# Docker
docker run --rm --gpus all -p 8000:8000 ai-serve:0.1

# Podman (using OCI hooks; environment variables recommended)
podman run --rm -e NVIDIA_VISIBLE_DEVICES=all -e NVIDIA_DRIVER_CAPABILITIES=compute,utility -p 8000:8000 ai-serve:0.1

Smoke test:

curl -s http://localhost:8000/healthz
curl -s -X POST http://localhost:8000/predict \
  -H 'Content-Type: application/json' \
  -d '{"features":[5.1,3.5,1.4,0.2]}'

Zero-downtime tip:

  • Run blue and green containers on different ports (e.g., 8000 and 8001), front them with Nginx/HAProxy, then switch the upstream. Health-check /healthz before the cutover. This pattern avoids killing the old container until the new one is proven healthy.

What teams learn:

  • Container images make your inference server portable and testable.

  • Adding /healthz and versioned tags enables safe rollouts.


Case Study 3: Scalable batch inference with Ray (no Kubernetes required)

Goal: Spread batch scoring across multiple nodes with minimal orchestration.

1) Install Python, pip, tmux

# Ubuntu/Debian (apt)
sudo apt update
sudo apt install -y python3 python3-pip tmux

# Fedora/RHEL (dnf)
sudo dnf -y install python3 python3-pip tmux

# openSUSE (zypper)
sudo zypper refresh
sudo zypper install -y python3 python3-pip tmux

2) Create a virtualenv and install Ray

python3 -m venv ~/rayenv
. ~/rayenv/bin/activate
pip install --upgrade pip
pip install "ray[default]" pandas scikit-learn

3) Start a small Ray cluster and run jobs

On the head node:

ray start --head --port=6379

On each worker node (replace HEAD_IP):

ray start --address='HEAD_IP:6379'

Submit a simple batch job:

cat > batch_infer.py <<'PY'
import ray, time
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier

ray.init(address="auto")

@ray.remote
def score(batch):
    X, y = load_iris(return_X_y=True)
    clf=RandomForestClassifier().fit(X,y)  # pretend we loaded it; demo only
    return clf.predict(batch).tolist()

if __name__ == "__main__":
    X, _ = load_iris(return_X_y=True)
    batches=[X[i:i+10] for i in range(0, len(X), 10)]
    start=time.time()
    results = ray.get([score.remote(b) for b in batches])
    print("Predictions:", sum(len(r) for r in results))
    print("Elapsed:", round(time.time()-start,3), "s")
PY

python3 batch_infer.py

Tear down:

ray stop

What teams learn:

  • Horizontal scale doesn’t have to start with Kubernetes.

  • Ray abstracts scheduling so you can parallelize Python without rewriting your pipeline.


Lessons from the trenches

  • Pin the stack: Lock Python deps in requirements.txt and container base images by digest.

  • Promote via artifacts: Train once, promote the exact model file through staging to prod.

  • Bake health and version endpoints: Automate readiness and rollbacks.

  • Keep GPUs optional: CPU-first deployments are simpler; turn on GPUs only when profiling proves the need.

  • Logs and metrics or it didn’t happen: Even a simple /metrics endpoint or request logger can halve your MTTR.

Call to action

  • Pick one case study above and replicate it in a scratch repo today.

  • Wrap it in a Makefile so new teammates can “make up” and “make run”.

  • Add CI to build the container and launch a smoke test on every commit.

  • When you’re ready, extend with proper observability (Prometheus/Grafana) and CD.

Got stuck or want this as a starter template? Tell me your distro and constraints (GPU? air-gapped? rootless?), and I’ll tailor the Bash playbook.