Pipeline: ingest, CI, deploy
This document maps the Learn ingest pipeline end to end: every
step ingest.py performs, the source repositories it pulls
from, the CI workflow that triggers it, and how the result
gets to learn.netdata.cloud.
The orchestrator: ingest/ingest.py
Live entrypoint: ${NETDATA_REPOS_DIR}/learn/ingest/ingest.py.
The legacy ingest.js and ingest.md at the learn-repo root
are NOT the orchestrator -- ignore them.
Argument parsing (ingest.py:2513-2762)
| Flag | Purpose |
|---|---|
--repos OWNER/REPO:BRANCH ... |
Override the default repo list. |
--local-repo NAME:/path ... |
Use a local copy via shutil.copytree instead of cloning. |
--dry-run |
Skip output writes. |
--debug |
Verbose logging. |
--docs-prefix DOCS |
Default docs; the target directory in learn for published files. |
--fail-links |
Exit 1 if broken internal links are detected at the end of the run. |
--fail-links-{netdata,helmchart,onprem,asd,grafana,github} |
Per-source-repo fail-on-broken-links. |
--gh-token |
GitHub token for clones. |
--use_plain_https |
Force HTTPS clone URLs (no SSH). |
--ignore-on-prem-repo |
Skip netdata-cloud-onprem clone and ignore broken-link references to it. |
The 16-step flow (ingest.py:2513-3087, __main__)
Argument parsing.
Cleanup (
ingest.py:2766-2768):unsafe_cleanup_folders('ingest-temp-folder')-- wipes the work dir.safe_cleanup_learn_folders('docs')-- walks${NETDATA_REPOS_DIR}/learn/docs/and removes every.md/.mdx/.jsonthat does NOT carrypart_of_learn: True. The flag is the opt-in for hand-authored files that should survive between runs. Currently onlydocs/ask-nedi.mdx.
Clone all repos (
ingest.py:2776-2800) intoingest-temp-folder/<repo>/with--depth 1. Each repo tracks the branch declared indefault_repos(ingest.py:74-105).--ignore-on-prem-reposkips the on-prem clone and ignores broken-link references to it.--local-repo X:/pathand--repos /path/to/repouseshutil.copytreefor developer ergonomics.Move + validate map (
ingest.py:2802-2825):ingest-temp-folder/netdata/docs/.map/map.yaml->./map.yaml.- Validate against
ingest-temp-folder/netdata/docs/.map/map.schema.json. - Build
MAP_SIDEBAR_ORDERandMAP_DOC_SCOPEfor sidebar position assignment. - Schema failure -> exit code 2.
Enumerate markdowns (
ingest.py:2828-2829):glob('ingest-temp-folder/**/*.md*')plus dot-directories (fetch_markdown_from_repo,ingest.py:1207-1211).Populate integrations (
ingest.py:2832): scan all markdowns forINTEGRATION_MARKER, parse hidden metadata, bucket by category, replace eachintegration_placeholderrow in the map with sorted (bylearn_rel_path,sidebar_label) integration rows. Writes the resulting tabular map toingest/generated_map.yaml.Sidebar position assignment (
ingest.py:2838-2840):automate_sidebar_positionwalks the (now expanded) map dataframe and assigns sibling-relative positions in steps of 10 within each parent scope, ordered by map traversal.Inject metadata into source files (
ingest.py:2842-2889): for each markdown, look up its row bycustom_edit_urland write the hidden metadata block at the top of the file. Iflearn_status == Published, compute the destination viacreate_mdx_path_from_metadata, store the destination + ingestedRepo into_publish, also injectslug:andlearn_link:into the file. Otherwise file is dropped.Path collision check (
ingest.py:2891-2908): warn if any twoto_publishentries collide case-insensitively.Publish each file (
ingest.py:2916-2919):local_to_absolute_links(md_file, to_publish)-- convert relative/abs-from-repo-root markdown links into GitHub view URLs (rewritten in step 12).copy_doc(md_file, to_publish[md_file]['learnPath'])-- copy temp file todocs/<...>.mdx(creating dirs).sanitize_page(learnPath)-- runs MDX-escape transforms (seemdx-rules.md).
Build cross-reference dictionary (
ingest.py:2925-2927):add_new_learn_path_key_to_dictproduces, for every published file, a map: GitHub-view-link AND GitHub-edit-link -> final/docs/...URL. Handles the duplicate-segment slug trim and explicitslug:overrides.Rewrite GitHub links to Learn URLs (
ingest.py:2929-2930):convert_github_linkswalks every published file. Anyhttps://github.com/netdata/<repo>/blob/.../*.mdin the body that maps to a file into_publishis rewritten to its final Learn URL. Header anchors are validated; broken anchors are accumulated. Links to integration md files that aren't in the map fall back to the parent README's URL (ingest.py:2153-2209). Links to GitHub files that exist in the repos but aren't in the map stay as GitHub links (intentional --file_exists_in_reposatingest.py:711-719). Truly missing targets becomeUNCORRELATED_LINK_COUNTERincrements.Generate redirects (
ingest.py:2932): seeredirects.md.Broken-link reporting (
ingest.py:2940-3022): print broken URLs grouped by repo, broken anchors grouped by repo, decide whether to exit 1 based on--fail-linksflags.Post-processing (
ingest.py:3030-3065):- Save current
(custom_edit_url -> new_learn_path)mapping toingest/one_commit_back_file-dict.yamlfor next run's redirect diff. - Cleanup
ingest-temp-folderandmap.yaml. get_dir_make_file_and_recurse('./docs')-- auto-create grid pages for any integration directory that lacks an overview.ensure_category_json_for_dirs('docs')-- write_category_.jsonfor any directory still without an overview page.normalize_sidebar_positions_by_parent('docs')-- assign sibling positions deterministically per parent scope.
- Save current
No git push from the script. The CI workflow handles
git add/ commit / PR.
How content gets fetched
ingest.py does NOT use the GitHub REST API for content. It
clones full repos with gitpython (ingest.py:1101-1137), so
there's no API rate-limit consideration in the live pipeline.
(Legacy ingest.js did use the API; ignore it.)
The 6 source repositories
Default at ingest/ingest.py:74-105:
| Logical repo | Owner | Branch | What it feeds |
|---|---|---|---|
netdata |
netdata |
master |
The bulk of Learn -- all docs declared in <repo>/docs/.map/map.yaml, plus integration metadata generated by the agent build (collectors, exporters, secretstore, alerts, functions). Hosts the canonical map files. |
netdata-cloud-onprem |
netdata |
master |
On-prem documentation (Netdata Cloud On-Prem category). Skippable via --ignore-on-prem-repo. |
.github |
netdata |
main |
CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md -- only those whose edit URLs appear in map.yaml. |
agent-service-discovery |
netdata |
master |
Service-discovery docs for the agent. |
netdata-grafana-datasource-plugin |
netdata |
master |
Grafana datasource integration docs. |
helmchart |
netdata |
master |
Kubernetes Helm chart docs. |
Override at runtime: --repos OWNER/REPO:BRANCH ... or
--local-repo NAME:/path ... (ingest.py:2607-2613).
The legacy ingest.js listed only 4 repos (netdata, .github,
go.d.plugin, agent-service-discovery). go.d.plugin is no
longer pulled (its docs were absorbed into the netdata
monorepo); netdata-cloud-onprem,
netdata-grafana-datasource-plugin, helmchart are new in the
Python pipeline.
CI: ${NETDATA_REPOS_DIR}/learn/.github/workflows/ingest.yml
Triggers (ingest.yml:2-18)
workflow_dispatch-- manual.schedule: cron "10 8-23/3 * * *"-- every 3 hours from 08:10 to 23:10 UTC (6 runs/day).pushtomasterwhenplugins/**,src/**,static/**,sidebar.js,package.json,yarn.lock,tailwind.config.js, or any**/*.md/**/*.mdxchanges in the LEARN repo (not in source repos -- those trigger ingest indirectly via the cron).
Steps (ingest.yml:24-101)
- Checkout (full history,
fetch-depth: 0) usingsecrets.GITHUB_TOKEN. - SSH agent for
secrets.NETDATABOT_SSH_PRIVATE_KEY(used to clone private repos like.github). - Python 3.10, install
${NETDATA_REPOS_DIR}/learn/.learn_environment/ingest-requirements.txt(pip,requests==2.33.0,Pillow,PyGithub,gitpython,mergedeep,pandas,numpy,retrypy,pyyaml,jsonschema). - Run
python ingest/ingest.py --fail-links 2>&1-- captures output for issue creation, propagates exit code 1 (broken links) into a workflow output but does NOT fail the workflow on broken links (only on real errors,ingest.yml:65-77). - Update kickstart checksum -- fetches
https://raw.githubusercontent.com/netdata/netdata/master/packaging/installer/kickstart.sh, MD5s it, replaces literal@KICKSTART_CHECKSUM@placeholder indocs/Netdata Agent/Installation/Linux/Linux.mdx. peter-evans/create-pull-request@v6.0.1-- opens or updates a branch namedingestwith title "Ingest New Documentation", labelsingest, automation. The PR is reviewed and merged manually by the team.- If broken links were found, create or update a single open
GitHub issue with label
broken-links.
CI: ${NETDATA_REPOS_DIR}/learn/.github/workflows/daily-learn-link-check.yml
Daily cron 00 04 * * * -- runs
python ingest/check_learn_links.py, which extracts every
learn_link: URL from every .md / .mdx in docs/,
HEAD/GET checks them, exits 1 (failing the workflow) when any
404 is observed.
Deploy: Netlify
There is no GitHub-side build/deploy workflow. Build & deploy are handled by Netlify:
- README confirms Netlify deploys from
masterautomatically. - Site is
netdata-docusauruson Netlify. - Branches
master,staging,staging1each get their own deploy (preview URLs). - Build env pinned by
static.toml:3-4/netlify.toml [build]:NPM_VERSION=10.9.2,NODE_VERSION=22.14.0,NETLIFY_USE_YARN=true,NODE_OPTIONS=--max_old_space_size=4096. - Build command:
yarn build(Docusaurus default). - Output dir:
build/. - Propagation time: whatever Netlify takes after a master
commit; redirects ship in
netlify.tomlso they apply at the edge immediately upon deploy.
End-to-end timing
- Maintainer pushes to
netdata/netdatamaster (via merged PR). - Up to 3 hours later,
${NETDATA_REPOS_DIR}/learn/.github/workflows/ingest.ymlcron fires. - Ingest opens a PR in the learn repo titled "Ingest New Documentation".
- A maintainer reviews and merges the PR.
- Netlify auto-deploys
master. Within minutes, the new content is live onlearn.netdata.cloud.
So end-to-end: 0-3 hours of cron lag + manual review/merge +
minutes of Netlify deploy. Plan accordingly when something
must be live by a deadline -- you can also trigger the ingest
manually via workflow_dispatch to skip the cron lag.
Local testing
From within the learn repo:
# Install deps once.
python3 -m venv venv && . venv/bin/activate
pip install -r .learn_environment/ingest-requirements.txt
# Test against your local netdata clone.
python3 ingest/ingest.py --local-repo netdata:<repo> --ignore-on-prem-repo --fail-links-netdata
# Then build the site locally.
yarn install
yarn start # dev server
# OR
yarn build # full build to ./build