Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference
# Minor and patch updates arrive grouped, one pull request per ecosystem; majors stay
# separate so a breaking bump is always reviewed on its own. There is no `docker`
# ecosystem here: this repository builds a static site, not an image.
version: 2
updates:
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
commit-message:
prefix: "chore(deps)"
groups:
python:
patterns: ["*"]
update-types: ["minor", "patch"]
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
commit-message:
prefix: "chore(deps)"
groups:
actions:
patterns: ["*"]
update-types: ["minor", "patch"]
21 changes: 14 additions & 7 deletions .github/workflows/assign.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
name: Auto Assign
# Issues only, deliberately. A `pull_request: [opened]` trigger runs with a read-only
# GITHUB_TOKEN when the pull request comes from a fork, so `issues: write` is never
# granted and the assignment fails — a red check on every pull request from exactly the
# external contributors the README invites. Removing the trigger fixes that at the root
# instead of hiding a guaranteed failure behind `continue-on-error`.
on:
issues:
types: [opened]
pull_request:
types: [opened]
permissions:
contents: read
jobs:
run:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
issues: write
pull-requests: write
issues: write # the only write this workflow needs: assign the new issue
steps:
- name: "Auto-assign issue"
uses: pozil/auto-assign-issue@v2
- name: Auto-assign issue
# v3 moved the action to ES modules and v4 to the Node 24 runtime; neither renamed
# an input, so `repo-token` / `assignees` / `numOfAssignee` carry over unchanged.
uses: pozil/auto-assign-issue@v4
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
assignees: fmind
Expand Down
15 changes: 9 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ concurrency:
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
check:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
persist-credentials: false # no step pushes back to the repository
- name: Install mise toolchain
uses: jdx/mise-action@v4
- name: Format sources
run: mise run format
- name: Run checks
run: mise run check
with:
cache: true
- name: Run canonical gate
run: mise run all
- name: Verify no changes
run: git diff --exit-code
run: test -z "$(git status --porcelain)"
14 changes: 11 additions & 3 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,26 @@ concurrency:
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 20
# A job-level block replaces the workflow-level one rather than adding to it, so
# `contents: read` must be repeated here or the checkout would run with no read scope.
permissions:
pages: write
id-token: write
contents: read # checkout reads the repository
pages: write # upload and deploy the site artifact
id-token: write # OIDC token the Pages deployment is verified with
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
persist-credentials: false # the deploy uses the OIDC token, not the git credential
- name: Install mise toolchain
uses: jdx/mise-action@v4
with:
cache: true
- name: Build site
run: mise run build
- name: Configure Pages
Expand Down
30 changes: 30 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Security
# Push CI scans only the latest commits, so a secret committed earlier and later
# removed would never be seen again. This is the full-history counterpart: same
# pinned scanners, whole history, no reports to parse — a finding fails the job.
on:
schedule:
- cron: "17 3 * * 1"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: security
cancel-in-progress: true
jobs:
scan:
name: Full-history scan
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout complete history
uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false # scanners only read the checkout
- name: Install mise system
uses: jdx/mise-action@v4
- name: Scan complete Git history
run: gitleaks git --redact=100 --verbose
- name: Scan full checkout
run: trivy fs .
10 changes: 10 additions & 0 deletions .github/zizmor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copy to .github/zizmor.yml.
# Actions are deliberately pinned to major-version tags (see the upgrade-tools
# skill): tags track security patches within a major, at the cost of trusting
# the tag. zizmor's default policy demands hash-pins; relax it to ref-pins so
# the audit enforces the actual policy instead of fighting it.
rules:
unpinned-uses:
config:
policies:
"*": ref-pin
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# https://git-scm.com/docs/gitignore

# Caches
.mypy_cache/
.cache/

# Environs
.env
/.venv/

# Websites
Expand Down
30 changes: 24 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,42 @@ Context and rules for AI agents working in this repository. Humans should start

## Setup & core commands

All work goes through `mise` (see `mise.toml`); git hooks (`lefthook.yml`) and CI call the same tasks.
All work goes through `mise` (see `mise.toml`); git hooks (`lefthook.yml`) and CI call the same tasks. Run `mise tasks` for the full list.

- Install: `mise run install` — sync dependencies (`uv`) and install git hooks.
- Gate: `mise run all` — `format`, then `check`, then `build`. This is exactly what CI runs; a change is not done until it passes.
- Install: `mise run install` — sync dependencies (`uv`) and install git hooks (`lefthook`).
- Format: `mise run format` — `dprint` for config/markup only; prose under `docs/` is intentionally not reformatted.
- Check: `mise run check` — `dprint check`, `mkdocs build --strict` (catches broken links/config), `gitleaks`.
- Check: `mise run check` — runs `check:actions` (`actionlint` + `zizmor`), `check:build` (`mkdocs build --strict`, which catches broken links and bad config), `check:format` (`validate-pyproject`, `dprint check`, `uv lock --check`), `check:leaks` (`gitleaks`), `check:scan` (`trivy fs`), and `check:vuln` (`pip-audit`).
- Build: `mise run build` — `mkdocs build`; Serve locally: `mise run serve` (live reload).
- There is no `test` task: the repository is prose, and its only executable assertion is the strict site build that `check:build` already runs. Do not add a `test` alias that re-runs it.

## Conventions

- Content lives in `docs/`, one numbered folder per chapter; the sidebar order comes from the numeric filename prefixes (no explicit `nav:`), so keep prefixes consistent when adding pages.
- Each page follows a consistent What / Why / How question-and-answer structure.
- Numbered lists use `1.` for every item so rendering stays dynamic; `docs/**` is excluded from `dprint` so prose formatting is preserved.
- Teach the current canonical stack: `uv`, Ruff, `ty`, `pytest`, `mise`, `lefthook`, `dprint`, `git-cliff`, MLflow 3, Docker, Python 3.14. Keep tool versions and examples in sync with the [cookiecutter-mlops-package](https://github.com/fmind/cookiecutter-mlops-package) and [mlops-python-package](https://github.com/fmind/mlops-python-package).
- Tool versions are pinned in `mise.toml` and locked in `mise.lock`; Python dependencies are locked in `uv.lock`. Both lockfiles are committed and `check:format` fails if `uv.lock` drifts from `pyproject.toml`.
- Commits: Conventional Commits; no attribution. Releases use `git-cliff`.

## Repository layout

- `docs/` — course chapters (`0. Overview` → `7. Observability`), `assets/`, and `CNAME` (custom domain, copied into the built site).
- `mkdocs.yml` — site configuration (Material theme, `strict: true`); `pyproject.toml` — mkdocs dependencies (`uv`, `package = false`).
- `mise.toml` — tasks and pinned tools; `lefthook.yml` — git hooks; `dprint.jsonc` — config/markup formatter; `cliff.toml` — changelog.
- `.github/workflows/` — `ci.yml` (strict build check on PR/push) and `pages.yml` (build + deploy to GitHub Pages on push to `main`).
- `mkdocs.yml` — site configuration (Material theme, `strict: true`); `pyproject.toml` — mkdocs dependencies and the `dev` dependency group (`uv`, `package = false`).
- `mise.toml` / `mise.lock` — tasks and pinned tools; `lefthook.yml` — git hooks; `dprint.jsonc` — config/markup formatter; `trivy.yaml` — scanner policy; `cliff.toml` — changelog.

## GitHub configuration

Every file under `.github/` and what it does:

- `workflows/ci.yml` — runs `mise run all` on pushes to `main` and on every pull request, then asserts the working tree is unchanged.
- `workflows/pages.yml` — builds the site and deploys it to GitHub Pages on pushes to `main`.
- `workflows/security.yml` — weekly (and manual) full-history `gitleaks` and full-checkout `trivy` scan; the push-triggered checks only see recent commits.
- `workflows/assign.yml` — assigns newly opened issues to `fmind`. Issues only: a fork pull request gets a read-only token, so a `pull_request` trigger would fail on every external contribution.
- `dependabot.yml` — weekly grouped minor/patch updates for the `uv` and `github-actions` ecosystems, prefixed `chore(deps)`.
- `zizmor.yml` — relaxes `unpinned-uses` to `ref-pin`, because actions are deliberately pinned to major-version tags.
- `FUNDING.yml` — GitHub Sponsors link.

## Definition of done

A change is complete only when it answers the original request, fixes root causes rather than masking symptoms, comments the reasoning at any non-obvious choice, keeps `AGENTS.md` and `README.md` in sync with what the repository actually does, and passes `mise run all` warning-free. Never weaken a check to make the gate green. Do not commit, push, or tag unless the user asks.
41 changes: 27 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,45 @@ Whether you are a beginner eager to explore or an experienced professional seeki
## Course Content

1. **Initializing**: Set up your development environment, manage Python versions, and handle external dependencies.
2. **Prototyping**: Use Jupyter notebooks for ML prototyping, explore dataset manipulation, and perform initial model assessments.
3. **Productionizing**: Transition from notebooks to clean Python packages, learn about modular coding, and understand different programming paradigms.
4. **Validating**: Focus on code quality with typing, linting, testing, and debugging to ensure your ML projects are robust and maintainable.
5. **Refining**: Dive into advanced MLOps techniques including CI/CD workflows, software containers, and model registries to streamline your operations.
6. **Sharing**: Learn how to effectively organize and document your MLOps projects to ensure they are accessible and collaborative.
7. **Observability**: Gain comprehensive insights into the behavior and performance of your deployed models and infrastructure.
1. **Prototyping**: Use Jupyter notebooks for ML prototyping, explore dataset manipulation, and perform initial model assessments.
1. **Productionizing**: Transition from notebooks to clean Python packages, learn about modular coding, and understand different programming paradigms.
1. **Validating**: Focus on code quality with typing, linting, testing, and debugging to ensure your ML projects are robust and maintainable.
1. **Refining**: Dive into advanced MLOps techniques including CI/CD workflows, software containers, and model registries to streamline your operations.
1. **Sharing**: Learn how to effectively organize and document your MLOps projects to ensure they are accessible and collaborative.
1. **Observability**: Gain comprehensive insights into the behavior and performance of your deployed models and infrastructure.

## Installation

To start contributing , you will need to set up your development environment:
To start contributing, you will need to set up your development environment:

1. Clone the repository.
2. In the cloned repository directory, install dependencies and git hooks with [mise](https://mise.jdx.dev/) (which drives [uv](https://docs.astral.sh/uv/)):
1. In the cloned repository directory, install dependencies and git hooks with [mise](https://mise.jdx.dev/) (which drives [uv](https://docs.astral.sh/uv/)):

```bash
mise run install
```
```bash
mise run install
```

1. Serve the documentation locally (from that directory) to see course material in your browser:

```bash
mise run serve
```
```bash
mise run serve
```

You can then access the course at this URL from your computer: <http://localhost:8000/>

## Development

Every task goes through [mise](https://mise.jdx.dev/); run `mise tasks` for the full list.

| Command | Purpose |
| ------------------ | --------------------------------------------------------------------------------------------------------- |
| `mise run install` | Sync Python dependencies (`uv`) and install the git hooks (`lefthook`). |
| `mise run format` | Format JSON, Markdown, TOML, and YAML with `dprint`. |
| `mise run check` | Run every static check: workflows, strict site build, formatting, secrets, scanning, and vulnerabilities. |
| `mise run build` | Build the static site into `site/`. |
| `mise run serve` | Serve the documentation locally with live reload. |
| `mise run all` | The canonical gate — format, check, build — exactly what CI runs. |

## Contributions

This course is [open source under the CC-BY 4.0 license](https://github.com/MLOps-Courses/mlops-coding-course/blob/main/LICENSE.txt), and we welcome contributions! Whether it's improving the documentation, adding new examples, or fixing bugs, your input is valuable. Check out the `docs` among other project files to see where you can contribute.
Expand Down
2 changes: 1 addition & 1 deletion dprint.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
"plugins": [
"https://plugins.dprint.dev/g-plane/pretty_yaml-v0.6.0.wasm",
"https://plugins.dprint.dev/json-0.22.0.wasm",
"https://plugins.dprint.dev/json-0.23.0.wasm",
"https://plugins.dprint.dev/markdown-0.22.1.wasm",
"https://plugins.dprint.dev/toml-0.7.0.wasm"
]
Expand Down
18 changes: 10 additions & 8 deletions lefthook.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
# https://lefthook.dev
# Thin hooks: every command delegates to a `mise run` task so hooks and CI stay identical.
# Lefthook orders commands alphabetically, so priorities are explicit: formatters (10)
# restage before the staged secret scan (20) and the whole-tree checks (30) read from disk.

pre-commit:
parallel: false
commands:
format:dprint:
priority: 1
priority: 10
glob: "*.{json,md,toml,yaml,yml}"
run: mise run format {staged_files}
run: mise run format:dprint {staged_files}
stage_fixed: true
check:leaks:
priority: 2
priority: 20
run: mise run check:leaks --staged
check:
priority: 3
priority: 30
run: mise run check

pre-push:
commands:
build:
run: mise run check:build
# No pre-push hook. The canonical one runs the test suite, and this repository has none:
# its only executable assertion is `mkdocs build --strict`, which pre-commit's `check`
# already runs on the whole tree. A pre-push stage would re-run it against an unchanged
# tree and buy nothing but latency; CI (`mise run all`) is the gate that follows a push.
Loading