L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Déploiement et opérations · SKILL.md

mlops-automation

mlops-automation

Automate an MLOps project with mise tasks, lefthook hooks, Docker images, GitHub Actions, and MLflow tracking on a SQL backend. Use when adding a task runner, git hooks, CI/CD, or experiment tracking to a working package.

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: mlops-automation description: Automate an MLOps project with mise tasks, lefthook hooks, Docker images, GitHub Actions, and MLflow tracking on a SQL backend. Use when adding a task runner, git hooks, CI/CD, or experiment tracking to a working package. license: MIT metadata: author: Médéric HURIER (Fmind) source: github.com/MLOps-Courses/mlops-coding-skills/tree/main/mlops-automation created: 2026-01-25 updated: 2026-10-12

MLOps Automation

Goal

To elevate the codebase to production standards by adding Task Automation (mise), Git Hooks (lefthook), Containerization (docker), CI/CD (github-actions), and Experiment Tracking (mlflow).

Prerequisites

  • Language: Python 3.14
  • Manager: uv
  • Context: Preparing for scale and deployment.

Instructions

Preserve existing work and repository visibility. Prepare local changes first; commit, push, change remote settings, publish, or deploy only with user authorization.

1. Task Automation

Expose a single, shared task vocabulary with mise (replaces just/make).

  1. Tool: mise — pins the toolchain and defines tasks in mise.toml.
  2. Vocabulary: install, format, check, test, build, watch. Run everything via mise run <task> so hooks and CI reuse the same entrypoints.
  3. Core Tasks:
    • format: Format code and config (ruff format, dprint fmt).
    • check: Static checks in parallel (ruff check, ty, pip-audit, gitleaks, trivy, actionlint + zizmor).
    • test: Run pytest.
    • build: Build the wheel (uv build).
  4. The Gate: define all = ["mise run format", "mise run check", "mise run test", "mise run build"]. This is the one command a developer, a hook, an agent, or CI runs; nothing else is allowed to define "ready".
  5. Pinned Tools: declare every non-Python tool under [tools], run mise lock, and commit mise.lock — it records the exact version, URL, and checksum per platform, so uv.lock pins the libraries and mise.lock pins the binaries.
  6. No Silent Installs: set [settings.task] run_auto_install = false so a task fails loudly on a missing tool instead of downloading one mid-run.

2. Git Hooks

Catch issues locally with lefthook (replaces pre-commit).

  1. Framework: lefthook with thin hooks — every command delegates to a mise run task so hooks and CI stay identical.

  2. Explicit Priorities: lefthook orders commands alphabetically, so state the order yourself — formatters at 10, the staged secret scan at 20, the whole-tree checks at 30. Without priorities, check can read files that the formatter has not rewritten yet.

  3. Staged Files: pass {staged_files} to the formatters and set stage_fixed: true, so a reformat is restaged into the commit being made rather than left dirty in the working tree.

  4. Secret Gate: run mise run check:leaks --staged before the checks. A history scan does not look at the commit you are about to create; --staged does.

  5. pre-push: Run mise run test.

  6. Example:

    # lefthook.yml
    pre-commit:
      parallel: false
      commands:
        format:dprint:
          priority: 10
          glob: "*.{json,md,toml,yaml,yml}"
          run: mise run format:dprint {staged_files}
          stage_fixed: true
        format:python:
          priority: 10
          glob: "*.py"
          run: mise run format:python {staged_files}
          stage_fixed: true
        check:leaks:
          priority: 20
          run: mise run check:leaks --staged
        check:
          priority: 30
          run: mise run check
    
    pre-push:
      commands:
        test:
          run: mise run test
    
  7. Commits: Enforce Conventional Commits (e.g., feat: add new model) so git-cliff can generate the changelog.

3. Containerization

Reproducibility anywhere.

  1. Tool: docker.
  2. Base Image: Use python:3.14-slim for a minimal footprint; copy a pinned uv binary into the build stage (ghcr.io/astral-sh/uv:0.13.0, never :latest — a floating tag is invisible to Dependabot).
  3. Optimization:
    • Layer Caching: Install dependencies with uv sync --frozen --no-install-project --no-dev before copying src/; then install the package with uv sync --frozen --no-dev --no-editable.
    • Multi-stage: Copy the non-editable .venv to an identical Python runtime image, or install the wheel and its locked runtime dependencies in the final stage.
  4. Non-root: Run as a fixed numeric user (USER 10001:10001) and copy with --chown=10001:10001, so ownership is stable across rebuilds and readable by a host that does not share the image's /etc/passwd.
  5. Lint It: add check:dockerfile = "hadolint Dockerfile" to check; the Dockerfile is code and deserves the same gate.
  6. Registry: ask for the company artifact registry, or use ghcr.io for GitHub.

4. CI/CD Workflows

Automate verification and release with GitHub Actions.

  1. Platform: ask for the company CI/CD platform, or use github-actions for GitHub.

  2. One Step: CI runs mise run all and nothing else. A workflow that lists format, then check, then test as separate steps will drift from the local gate; a workflow with one step cannot.

  3. Hardening: pin the runner (ubuntu-24.04), set timeout-minutes, keep permissions: contents: read, and check out with persist-credentials: false so no step inherits a push token it does not need.

  4. Prove Cleanliness: end with test -z "$(git status --porcelain)". Because mise run all starts by formatting, a dirty tree at the end means a contributor committed unformatted files.

  5. Example:

    # .github/workflows/ci.yml
    name: CI
    on:
      push:
        branches: [main]
      pull_request:
    permissions:
      contents: read
    concurrency:
      group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
      cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
    jobs:
      check:
        runs-on: ubuntu-24.04
        timeout-minutes: 20
        steps:
          - name: Checkout repository
            uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
            with:
              persist-credentials: false # no step pushes back to the repository
          - name: Install mise system
            uses: jdx/mise-action@08ae2a091c038e1e05ec6f5115dc5c69d5a2a6b8 # v5.1.2
            with:
              cache: true # reuse the tools pinned by mise.lock
          - name: Run canonical gate
            run: mise run all
          - name: Verify no changes
            run: test -z "$(git status --porcelain)"
    
  6. Audit the Workflows: add check:actions = ["actionlint", "zizmor --offline .github/workflows/"] to check. actionlint catches invalid syntax and shell bugs; zizmor catches workflow security issues (script injection, over-broad permissions, credential persistence). Pin actions to full commit SHAs with readable version comments; the default pinning rule can then enforce the policy without a relaxation file.

  7. Scheduled Security Pass: push CI only scans recent commits, so add .github/workflows/security.yml on a weekly schedule (plus workflow_dispatch) that checks out with fetch-depth: 0 and runs the full-history gitleaks git --redact=100 and mise run check:scan using the committed Trivy policy. Keep fetch-depth: 0 out of ci.yml: a full clone on every push costs time and buys nothing the scheduled job does not already cover.

  8. Release: cd.yml on Release builds the image and publishes docs via the official GitHub Pages Actions (configure-pages, upload-pages-artifact, deploy-pages).

  9. Dependencies: add .github/dependabot.yml for every ecosystem in use (uv, github-actions, docker), grouping minor and patch updates into one pull request per ecosystem so majors are still reviewed alone.

5. AI/ML Experiments & Registry

Manage the ML lifecycle with MLflow 3.

  1. Platform: MLflow (3.16.1+).
  2. Backend: use a SQL store, locally as well as in production. sqlite:///mlflow.db is a real SQLAlchemy backend that supports the model registry and shares its shape with a production Postgres, so the client API can stay consistent. A shared deployment also needs artifact access, authentication, migration, and backup ownership. The legacy file store is in maintenance mode and disabled by default. MLFLOW_ALLOW_FILE_STORE=true is a migration escape hatch; use SQL for current features and a shared production service.
  3. Local Server: mlflow server --backend-store-uri=sqlite:///mlflow.db --artifacts-destination=./mlartifacts. Add mlflow.db to .gitignore — it is local state, and committing it puts a binary database in the history.
  4. Tracking: Use mlflow.autolog(); log metrics, params, and artifacts.
  5. Models: Log models with the keyword name= (e.g., mlflow.pyfunc.log_model(name=...)).
  6. Validation: Gate promotion with mlflow.validate_evaluation_results against explicit metric thresholds.
  7. Registry:
    • Register top models manually or via CI.
    • Aliases: Use an alias such as @Champion for a deployment pointer, matching its case exactly. Evaluate and promote an immutable candidate version first; aliases can move. Never rely on moving versions (e.g., v1 -> v2).

6. Design Patterns

Write flexible code.

  1. Strategy: For swappable algorithms (e.g., different model types).
  2. Factory: For creating objects from config (e.g., ModelFactory).
  3. Adapter: For standardizing mismatched interfaces.

7. Write It Down

Automation only helps if the next contributor finds it.

  1. AGENTS.md: the commands, the definition of done, the conventions, and the layout — for AI assistants.
  2. README.md: the same ground truth for humans, without the agent-facing rules.
  3. Rule: when a task changes, both files change in the same commit. A stale command in AGENTS.md is worse than no command at all.

Self-Correction Checklist

  • Task Vocabulary: Does mise run all (and format/check/test/build) work?
  • Pinning: Are mise.lock and uv.lock committed?
  • Hooks: Do the hooks use explicit priorities, {staged_files}, and check:leaks --staged?
  • Image: Is the Dockerfile multi-stage on python:3.14-slim, non-root, with a pinned uv, and does hadolint pass?
  • CI/CD: Is ci.yml a single mise run all step with persist-credentials: false and the porcelain assertion?
  • Workflow Audit: Do actionlint and zizmor pass, and does security.yml rescan the full history weekly?
  • Tracking: Do runs land in a SQL-backed MLflow store, with validated thresholds before promotion?
Skills enregistrées