Building a CI/CD Pipeline for Small AI/Computer Vision Projects

Mar 23, 2023 6 min read

Introduction

Many AI/Computer Vision projects initially start with a Jupyter notebook or a Python script that runs locally on a personal machine. That is fine in the experimental phase. But to put it into a portfolio or a small production environment, the project needs more than just a main.py file.

A better system needs to answer:

Is the code reproducible?
Can the model service be built?
Are there tests for the API?
Can a Docker image be created?
Can it be redeployed upon a code push?
If the model changes, are basic errors detected?

This post documents a minimal CI/CD pipeline for a small AI/Computer Vision project, such as a defect detection, object detection, or image classification API.

1. Pipeline goals

For a small project, I don’t need Kubernetes or a heavy MLOps system from the start. I need a clear pipeline:

Push / Pull Request

Lint & Unit Test

Model/API Smoke Test

Build Docker Image

Push Image / Deploy

Basic Monitoring

This pipeline helps the project transition from a “demo running on my machine” to a “service that can be built, tested, and redeployed”.

2. Proposed project structure

cv-defect-api/
  app/
    main.py
    inference.py
    schemas.py
  models/
    model.onnx
  tests/
    test_api.py
    test_inference.py
  sample_data/
    normal.jpg
    defect.jpg
  Dockerfile
  requirements.txt
  .github/
    workflows/
      ci.yml
  README.md

If the model is large, it shouldn’t be committed directly to Git. It can be stored in release artifacts, S3, an internal Google Drive, or a model registry. But for a small portfolio project, a lightweight model or a mock model can be used to demo the pipeline.

3. Minimal API service

Example using FastAPI:

from fastapi import FastAPI, UploadFile

app = FastAPI()

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

@app.post("/predict")
async def predict(file: UploadFile):
    image_bytes = await file.read()
    # result = run_inference(image_bytes)
    result = {"label": "normal", "confidence": 0.98}
    return result

The /health endpoint is extremely important. It helps CI/CD and deployment platforms know if the service is alive or dead.

4. What to test for Computer Vision?

Computer Vision projects don’t just test code. But the first version should still have simple tests.

Unit test

Test the preprocessing function:

def test_preprocess_returns_expected_shape():
    image = load_sample_image("sample_data/normal.jpg")
    tensor = preprocess(image)
    assert tensor.shape == (1, 3, 224, 224)

API smoke test

Test that the endpoint runs:

from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_health():
    response = client.get("/health")
    assert response.status_code == 200
    assert response.json()["status"] == "ok"

Inference sanity test

No need to expect a perfect model. But you should check if the output format is correct:

def test_prediction_schema():
    result = run_inference("sample_data/normal.jpg")
    assert "label" in result
    assert "confidence" in result
    assert 0 <= result["confidence"] <= 1

5. Minimal Dockerfile

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Docker helps make the runtime environment more consistent. If the service runs in a container, deploying to Render, Railway, AWS, or a private server will be easier.

6. GitHub Actions pipeline

A simple workflow:

name: CI

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  test-and-build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

      - name: Run tests
        run: pytest

      - name: Build Docker image
        run: docker build -t cv-defect-api:latest .

This version doesn’t push the image yet. But it ensures: every pull request must install dependencies, run tests, and successfully build the Docker image.

7. Build and push Docker image

When more stable, you can push the image to a registry. GitHub has an official guide for publishing Docker images using GitHub Actions.

High-level example:

- name: Login to Docker Hub
  uses: docker/login-action@v3
  with:
    username: ${{ secrets.DOCKERHUB_USERNAME }}
    password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push
  uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    tags: username/cv-defect-api:latest

Don’t hard-code tokens in the repo. Use GitHub Secrets.

8. How to deploy a small project?

For a portfolio project, I’d choose based on simplicity:

Vercel → frontend/demo page
Render/Railway → backend FastAPI
Supabase/PostgreSQL → database if needed
Docker Hub/GHCR → image registry

If using AWS, you could use EC2 or ECS, but don’t overcomplicate AWS if the goal is just a clear technical demo.

9. Minimal monitoring

Without monitoring, you won’t know about errors after deployment.

At a minimum, log:

  • request path
  • latency
  • status code
  • prediction label
  • confidence
  • error traceback if inference fails

Example simple log:

import time

start = time.time()
result = run_inference(image)
latency_ms = int((time.time() - start) * 1000)

logger.info({
    "event": "prediction_completed",
    "latency_ms": latency_ms,
    "label": result["label"],
    "confidence": result["confidence"],
})

For Computer Vision, also monitor:

inference latency
failed prediction rate
image size distribution
confidence distribution
number of predictions per day

10. Common pitfalls

  • Tests only cover the API, not the inference format.
  • Docker image builds successfully but model file is missing.
  • Model is too heavy, making CI extremely slow.
  • Secrets accidentally committed.
  • No /health endpoint.
  • No logs after deployment.
  • README doesn’t clearly explain how to run locally.

11. What should be in the README?

A good portfolio project should have a clear README:

Problem
Architecture
Tech Stack
How to run locally
API endpoints
CI/CD pipeline
Demo video
Limitations
Next steps

Conclusion

CI/CD for a small AI/Computer Vision project doesn’t need to start with an enterprise system. You just need:

pytest
Dockerfile
GitHub Actions
health check
basic inference test
deployment target
a good README

My main lessons:

  • AI projects also need software engineering discipline.
  • The model is only one part of the system.
  • Docker helps reduce “it works on my machine” issues.
  • CI/CD helps detect bugs before deployment.
  • Minimal monitoring makes the project feel more like production.

References

New posts, shipping stories, and nerdy links straight to your inbox.

2x per month, pure signal, zero fluff.


Share this post on: