- Posted on
- • Artificial Intelligence
Artificial Intelligence CI/CD Projects
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Ship AI Like Software: Practical CI/CD for Machine Learning on Linux (with Bash)
Modern ML teams lose time hand-running notebooks, babysitting training jobs, and “fixing it in prod.” The fix is not more heroics—it’s CI/CD built for AI. With a few Linux-friendly tools and some Bash glue, you can turn your model code, data, and containers into a reliable pipeline that ships value continuously.
This article explains why CI/CD is essential for AI projects, then walks you through a practical, Linux-first setup you can copy. You’ll get actionable steps, sample scripts, and distro-specific install commands (apt, dnf, zypper).
Why CI/CD matters for AI (and how it’s different)
Reproducibility is fragile: Tiny environment changes or unchecked randomness derail experiments.
Data is part of the product: Code alone isn’t enough—version the data and model artifacts too.
Hardware constraints: You can’t run full GPU training on every push—split fast checks from heavy jobs.
Governance/compliance: You’ll need audit trails for models, dependencies, and images.
Good CI/CD bakes in reproducibility, fast feedback, artifact tracking, and safe delivery—without slowing you down.
Prerequisites: Linux packages you’ll actually use
Install Git, Python, venv/pip, Make, and Docker (or Podman). Pick your distro:
Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y git python3 python3-venv python3-pip make docker.io podman pipx
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"
newgrp docker
pipx ensurepath
Fedora/RHEL (dnf):
sudo dnf install -y git python3 python3-pip python3-virtualenv make docker podman pipx
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"
newgrp docker
pipx ensurepath
openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y git python3 python3-pip python3-virtualenv make docker podman pipx
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"
newgrp docker
pipx ensurepath
Optional user-space tools (keeps your system clean):
pipx install pre-commit
pipx install pip-audit
pipx install "dvc[s3]"
Project venv for Python deps:
python3 -m venv .venv
. .venv/bin/activate
pip install -U pip wheel
pip install -U pytest fastapi uvicorn[standard] scikit-learn
A simple, reproducible project layout
ai-cicd-demo/
├─ src/
│ ├─ train.py
│ └─ serve.py
├─ tests/
│ └─ test_infer.py
├─ data/ # large/raw data (DVC manages, not Git)
├─ models/ # trained artifacts (DVC manages)
├─ dvc.yaml
├─ requirements.txt
├─ Makefile
├─ Dockerfile
└─ .github/workflows/ci.yml
requirements.txt:
fastapi
uvicorn[standard]
scikit-learn
pytest
joblib
Minimal training (src/train.py):
#!/usr/bin/env python3
import json, os, joblib
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import numpy as np
RANDOM_SEED = int(os.environ.get("SEED", 42))
np.random.seed(RANDOM_SEED)
def main():
X, y = load_iris(return_X_y=True, as_frame=False)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.2, random_state=RANDOM_SEED)
clf = RandomForestClassifier(n_estimators=50, random_state=RANDOM_SEED)
clf.fit(Xtr, ytr)
preds = clf.predict(Xte)
acc = accuracy_score(yte, preds)
print(f"accuracy={acc:.4f}")
os.makedirs("models", exist_ok=True)
joblib.dump(clf, "models/model.joblib")
with open("models/metrics.json", "w") as f:
json.dump({"accuracy": acc}, f)
# Contract: ensure minimal perf
assert acc > 0.85, "Accuracy regression detected"
if __name__ == "__main__":
main()
Tiny service (src/serve.py):
from fastapi import FastAPI
from pydantic import BaseModel, conlist
import joblib
app = FastAPI()
model = joblib.load("models/model.joblib")
class Item(BaseModel):
features: conlist(float, min_items=4, max_items=4)
@app.post("/predict")
def predict(item: Item):
y = model.predict([item.features])[0].item()
return {"prediction": int(y)}
Unit test (tests/test_infer.py):
import joblib
import numpy as np
def test_model_contract():
clf = joblib.load("models/model.joblib")
x = np.array([[5.1, 3.5, 1.4, 0.2]])
y = clf.predict(x)
assert y.shape == (1,)
Makefile (developer ergonomics + CI parity):
SHELL := /bin/bash
.ONESHELL:
.SHELLFLAGS := -eu -o pipefail -c
PY := . .venv/bin/activate
venv:
python3 -m venv .venv
$(PY); pip install -U pip wheel
$(PY); pip install -r requirements.txt
train:
$(PY); python src/train.py
test:
$(PY); pytest -q
serve:
$(PY); uvicorn src.serve:app --host 0.0.0.0 --port 8000
build:
docker build -t ai-cicd-demo:latest .
run:
docker run --rm -p 8000:8000 ai-cicd-demo:latest
Dockerfile (reproducible runtime):
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
RUN --mount=type=cache,target=/var/cache/apt \
apt-get update && \
apt-get install -y --no-install-recommends ca-certificates && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY src/ src/
COPY models/ models/ # CI will ensure model exists before build (or build trains)
EXPOSE 8000
CMD ["uvicorn", "src.serve:app", "--host", "0.0.0.0", "--port", "8000"]
DVC pipeline (dvc.yaml) to track artifacts:
stages:
train:
cmd: python src/train.py
deps:
- src/train.py
outs:
- models/model.joblib
metrics:
- models/metrics.json:
cache: false
Initialize DVC and Git (once):
git init
dvc init
git add .
git commit -m "Initial AI CI/CD demo"
5 actionable steps to production-grade AI CI/CD
1) Standardize environments with containers and lockfiles
Why: Reproducibility; “works on my machine” goes away.
How: Use requirements.txt (or pip-tools/uv/poetry) plus a Dockerfile you can run anywhere.
Action:
make venv
pip freeze > requirements.txt
make build
Tip: Prefer Docker for broad compatibility; Podman is drop-in on many distros:
podman build -t ai-cicd-demo:latest .
podman run --rm -p 8000:8000 ai-cicd-demo:latest
2) Test the right things (fast)
Why: Full training is slow; you need fast feedback every push.
How: Unit tests, deterministic seeds, smoke training on tiny data, and a performance gate.
Action:
SEED=123 make train
make test
Add a nightly job for full training; keep PR checks under a few minutes.
3) Version data and models like code
Why: You must know exactly which data and model produced a result.
How: Use DVC so artifacts and metrics are traceable to commits.
Action:
dvc add models/model.joblib
git add models/model.joblib.dvc .gitignore
git commit -m "Track model with DVC"
Configure a remote (S3, SSH, etc.) to store large files:
dvc remote add -d storage s3://your-bucket/path
dvc push
4) Automate CI for build, test, and security
Why: Consistent, repeatable checks protect you from regressions and supply chain risk.
How: GitHub Actions (or GitLab CI, Jenkins) runs tests, checks deps, builds images, and scans.
GitHub Actions example (.github/workflows/ci.yml):
name: CI
on:
push:
branches: [ main ]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install deps
run: |
python -m venv .venv
. .venv/bin/activate
pip install -U pip wheel
pip install -r requirements.txt pytest joblib
- name: Train (smoke)
run: |
. .venv/bin/activate
SEED=42 python src/train.py
- name: Run tests
run: |
. .venv/bin/activate
pytest -q
- name: Pip audit
run: |
pipx install pip-audit
pip-audit -r requirements.txt
build_and_scan:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t ghcr.io/${{ github.repository }}:latest .
- name: Trivy scan (no local install)
run: |
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image --exit-code 1 --severity HIGH,CRITICAL \
ghcr.io/${{ github.repository }}:latest
- GitLab CI quick-start (.gitlab-ci.yml):
stages: [test, build]
test:
image: python:3.11-slim
stage: test
script:
- python -m venv .venv
- . .venv/bin/activate
- pip install -r requirements.txt pytest joblib
- SEED=42 python src/train.py
- pytest -q
artifacts:
paths:
- models/
build:
image: docker:24
stage: build
services: [docker:24-dind]
script:
- docker build -t registry.gitlab.com/$CI_PROJECT_PATH:latest .
- docker push registry.gitlab.com/$CI_PROJECT_PATH:latest
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
5) Deliver safely with a staging gate
Why: Catch runtime issues before users do.
How: Build and push image on main, deploy to staging, run smoke tests, then promote.
Action (local/staging smoke):
docker run -d --rm -p 8000:8000 --name ai-demo ghcr.io/OWNER/REPO:latest
sleep 2
curl -s -X POST http://localhost:8000/predict \
-H 'Content-Type: application/json' \
-d '{"features":[5.1,3.5,1.4,0.2]}'
docker stop ai-demo
For production, use a GitOps flow (e.g., update a Helm chart or Compose file in a deploy repo after CI passes).
Real-world tips
Separate CPU-only PR checks from scheduled GPU jobs. Run full training nightly or on-demand.
Pin random seeds and document hardware/software in CI logs.
Cache dependencies in CI to speed up runs.
Store metrics as JSON and fail CI if they regress past a threshold.
Use pre-commit hooks to auto-format and lint before code lands:
pre-commit install
What you get by adopting AI CI/CD
Confidence: Every change is tested, scanned, and reproducible.
Speed: Developers run the same Make targets locally that CI runs in the cloud.
Traceability: You can answer “which code and data produced this model?” anytime.
Safety: Performance and security gates stop regressions before production.
Call to action
Initialize the layout above in your repo.
Get CI green locally:
make venv
SEED=42 make train
make test
make build
Turn on the CI workflow and ship your first staging image.
Add one improvement per week: data remotes, nightly GPU training, canary tests, or infra-as-code for deployment.
Ship models like software—reliably, repeatedly, and with less stress.