Posted on
Artificial Intelligence

Artificial Intelligence Platform Engineering

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

Artificial Intelligence Platform Engineering on Linux: From Notebook to Production, the Bash Way

You have a promising model in a notebook. But as soon as you try to turn it into a product, the build breaks, environments drift, training isn’t reproducible, and costs spiral. Sound familiar? That’s the gap AI Platform Engineering closes: a disciplined way to build, ship, run, and observe AI systems across your team and infrastructure.

This article shows you, from a Linux Bash perspective, how to stand up a practical, minimal AI platform pattern you can use today: containerized runtimes, experiment tracking, CI/CD, and observability—using tools you can install with your distro’s package manager and run from your terminal.

Why AI Platform Engineering matters

  • Reproducibility: The same code, data, and runtime must produce the same results everywhere.

  • Speed with safety: Fast iteration through automation, but with guardrails (tests, reviews, policies).

  • Cost control: Right-size compute and release engineering so you’re paying for outcomes, not waste.

  • Compliance and security: Standardized images, dependency scanning, audit trails.

  • Operability: Clear health signals and metrics so you can trust model behavior in production.

Prerequisites: install core tools

We’ll use Podman for rootless containers (works similarly to Docker), Git for versioning, Python/pip for local ML tasks, and curl for quick tests.

Debian/Ubuntu (apt):

sudo apt update
sudo apt install -y podman git python3 python3-pip curl

Fedora/RHEL/CentOS Stream (dnf):

sudo dnf install -y podman git python3 python3-pip curl

openSUSE Leap/Tumbleweed (zypper):

sudo zypper refresh
sudo zypper install -y podman git python3 python3-pip curl

Verify:

podman --version
git --version
python3 --version
pip3 --version

Tip: If you prefer Docker, you can substitute docker for podman in the commands below.


1) Standardize runtimes with containers

We’ll create a tiny model service with FastAPI, trained on the Iris dataset. First, train a minimal model and save it as an artifact:

Create train.py:

cat > train.py << 'EOF'
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
import joblib

X, y = load_iris(return_X_y=True)
clf = LogisticRegression(max_iter=200).fit(X, y)
joblib.dump(clf, "model.joblib")
print("Saved model.joblib")
EOF

Train the model in an isolated container:

podman run --rm -v "$PWD":/work -w /work python:3.11-slim \
  bash -lc "pip install --no-cache-dir scikit-learn joblib && python train.py"

Create a FastAPI app that loads the saved model, exposes health, prediction, and Prometheus metrics:

Create app.py:

cat > app.py << 'EOF'
from fastapi import FastAPI, Response
from pydantic import BaseModel
import joblib, numpy as np, time
from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST

api = FastAPI()
REQUESTS = Counter('inference_requests_total', 'Total inference requests')
LATENCY = Histogram('inference_latency_seconds', 'Latency per inference seconds')

class Item(BaseModel):
    features: list[float]

model = joblib.load("model.joblib")

@api.get("/healthz")
def healthz():
    return {"status": "ok"}

@api.post("/predict")
def predict(item: Item):
    REQUESTS.inc()
    start = time.time()
    x = np.array(item.features).reshape(1, -1)
    y = model.predict(x).tolist()
    LATENCY.observe(time.time() - start)
    return {"prediction": y}

@api.get("/metrics")
def metrics():
    return Response(content=generate_latest(), media_type=CONTENT_TYPE_LATEST)
EOF

Define a container image:

Create Dockerfile:

cat > Dockerfile << 'EOF'
FROM python:3.11-slim

# Minimal system deps for science stacks can improve reliability
RUN pip install --no-cache-dir fastapi uvicorn[standard] scikit-learn joblib prometheus-client pydantic

WORKDIR /app
COPY model.joblib /app/model.joblib
COPY app.py /app/app.py

EXPOSE 8080
CMD ["uvicorn", "app:api", "--host", "0.0.0.0", "--port", "8080"]
EOF

Build and run:

podman build -t ai-platform/api:0.1 .
podman run --rm -p 8080:8080 ai-platform/api:0.1

In a second terminal, test the service:

curl -s http://localhost:8080/healthz
curl -s -X POST http://localhost:8080/predict \
  -H 'content-type: application/json' \
  -d '{"features":[5.1,3.5,1.4,0.2]}'
curl -s http://localhost:8080/metrics | head

You now have a reproducible, portable inference service with built-in metrics.


2) Track experiments and artifacts with MLflow

Centralize metrics, params, and models so you can compare runs and reproduce results.

Install MLflow locally (user site to avoid system Python issues):

python3 -m pip install --user mlflow==2.14.1 scikit-learn pandas

Start an MLflow tracking server using SQLite for metadata and the local filesystem for artifacts:

mkdir -p mlruns
mlflow server \
  --backend-store-uri sqlite:///mlruns/mlflow.db \
  --default-artifact-root file://$PWD/mlruns \
  --host 0.0.0.0 --port 5000

In a new terminal, run a training script that logs to MLflow:

Create train_mlflow.py:

cat > train_mlflow.py << 'EOF'
import mlflow, mlflow.sklearn
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
import pandas as pd

mlflow.set_experiment("iris-demo")
mlflow.sklearn.autolog(log_model=True)

X, y = load_iris(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

with mlflow.start_run():
    model = LogisticRegression(max_iter=300)
    model.fit(X_train, y_train)
    preds = model.predict(X_test)
    acc = accuracy_score(y_test, preds)
    mlflow.log_metric("accuracy", acc)
    print("Logged run with accuracy:", acc)
EOF

Point the client to the tracking server and run:

export MLFLOW_TRACKING_URI=http://127.0.0.1:5000
python3 train_mlflow.py

Open your browser to:

http://127.0.0.1:5000

You’ll see runs, metrics, parameters, and models logged for future reuse and comparison.


3) Ship images with CI/CD

Turn your container build into a repeatable pipeline that runs on every commit. Here’s a minimal GitHub Actions workflow that builds and pushes your image to a registry.

Create .github/workflows/build.yaml:

cat > .github/workflows/build.yaml << 'EOF'
name: build-and-push
on:
  push:
    branches: [ "main" ]
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
      - name: Set up Buildx
        uses: docker/setup-buildx-action@v3
      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}/ai-platform-api:0.1
EOF

If you’re using a different registry (e.g., Docker Hub, Quay), adjust the login and tags accordingly.

Manual push from Linux with Podman:

podman login ghcr.io
podman tag ai-platform/api:0.1 ghcr.io/youruser/ai-platform-api:0.1
podman push ghcr.io/youruser/ai-platform-api:0.1

Now every change to main produces a fresh, immutable image you can deploy to Kubernetes, Nomad, or a VM.


4) Observe what your models do

You’ve already exposed Prometheus metrics at /metrics. You can scrape them with Prometheus and visualize in Grafana.

Example Prometheus scrape job (add to prometheus.yml):

scrape_configs:
  - job_name: 'ai-api'
    static_configs:
      - targets: ['host.docker.internal:8080']  # on macOS/Windows with Docker
      - targets: ['localhost:8080']            # on Linux host if port-forwarded

To quickly verify metrics without Prometheus:

curl -s http://localhost:8080/metrics | grep inference_requests_total
curl -s http://localhost:8080/metrics | grep inference_latency_seconds_bucket | head

Structured logging tip: write JSON logs for easier parsing downstream. For FastAPI/Uvicorn, you can enable JSON logging by passing --log-config or configuring Python’s logging module; centralize logs via your platform’s log collector.


Putting it together: a minimal, repeatable AI platform loop

  • Develop: Code + notebooks → extract training/eval into scripts.

  • Repro: Build container images to standardize runtimes.

  • Track: Use MLflow for experiments, metrics, and models.

  • Ship: CI/CD builds and pushes images on every commit.

  • Run and Observe: Deploy containers, scrape metrics, inspect logs, iterate.

This approach scales from your laptop to a cluster without changing commands or code structure.

Conclusion and next steps

You just built the core of an AI platform from your Linux terminal: a reproducible model service, experiment tracking, CI/CD, and observability. The value is fewer surprises, faster iteration, and lower risk when moving from prototype to production.

Your next steps:

  • Wrap your team’s existing notebooks into train/eval scripts and containerize them.

  • Stand up a shared MLflow server and agree on experiment naming and model lifecycle.

  • Add tests and automated checks to your CI (data schema checks, linting, unit tests).

  • Deploy to your target runtime (Kubernetes, VMs) and hook up Prometheus/Grafana.

If you want a follow-up, ask for a Kubernetes deployment walkthrough (kustomize/helm), secrets management patterns, or GPU scheduling guidance—all runnable from Bash on your favorite Linux distro.