diff --git a/.github/workflows/deploy-redirects.yaml b/.github/workflows/deploy-redirects.yaml
new file mode 100644
index 00000000000..350125f7bd2
--- /dev/null
+++ b/.github/workflows/deploy-redirects.yaml
@@ -0,0 +1,59 @@
+name: Deploy docs.rapids.ai compatibility site
+
+on:
+ schedule:
+ - cron: "0 9 * * *"
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+
+concurrency:
+ group: deploy-docs-rapids-ai-redirects
+ cancel-in-progress: false
+
+defaults:
+ run:
+ shell: bash
+
+permissions:
+ contents: read
+ id-token: write
+
+jobs:
+ deploy:
+ name: Assemble and deploy compatibility site
+ runs-on: ubuntu-latest
+ env:
+ RAPIDS_DOCS_BASE_URL: https://docs.rapids.ai/
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ - name: Set up uv
+ uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
+ with:
+ enable-cache: true
+ - name: Install dependencies
+ run: uv sync --locked
+ - name: Build and validate portal
+ run: make check
+ - name: Configure source AWS credentials
+ uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
+ with:
+ role-to-assume: ${{ vars.AWS_ROLE_ARN }}
+ aws-region: ${{ vars.AWS_REGION }}
+ role-duration-seconds: 7200
+ - name: Assemble and validate API documentation
+ run: make assemble
+ - name: Generate compatibility redirects
+ run: |
+ uv run python scripts/generate_redirect_site.py \
+ --output _site/_redirects
+ - name: Deploy compatibility site to docs.rapids.ai
+ env:
+ NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_API_TOKEN }}
+ NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DOCS_SITE_ID }}
+ run: | # zizmor: ignore[adhoc-packages]
+ npm install --global --force @aschmidt8/netlify-cli
+ netlify deploy --prod --debug --dir=_site
diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml
index 049bb7c4571..75e45060b1b 100644
--- a/.github/workflows/deploy.yaml
+++ b/.github/workflows/deploy.yaml
@@ -13,12 +13,13 @@ defaults:
run:
shell: bash
permissions:
- id-token: write
contents: read
jobs:
build:
- name: Build (and deploy)
+ name: Build and publish NVIDIA docs portal
runs-on: ubuntu-latest
+ env:
+ RAPIDS_DOCS_BASE_URL: https://docs.nvidia.com/datascience/
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
@@ -32,26 +33,74 @@ jobs:
run: uv sync --locked
- name: Build and validate portal
run: make check
- - uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 #v5.1.1
+ - name: Configure NVIDIA docs AWS credentials
+ uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0
with:
- role-to-assume: ${{ vars.AWS_ROLE_ARN }}
- aws-region: ${{ vars.AWS_REGION }}
- role-duration-seconds: 7200 # 2h
- - name: Assemble complete documentation site
- run: make assemble
- - name: Deploy site
+ aws-access-key-id: ${{ secrets.NVIDIA_DOCS_AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: ${{ secrets.NVIDIA_DOCS_AWS_SECRET_ACCESS_KEY }}
+ aws-region: ${{ secrets.NVIDIA_DOCS_AWS_REGION }}
+ unset-current-credentials: true
+ - name: Publish portal to NVIDIA docs S3
env:
- NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_API_TOKEN }}
- NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DOCS_SITE_ID }}
- # TODO: use official netlify-cli pkg after https://github.com/netlify/cli/issues/1809
- # is resolved and deployed.
- run: | # zizmor: ignore[adhoc-packages]
- npm install --global --force @aschmidt8/netlify-cli
-
- ARGS=""
- if [ "$GITHUB_REF_NAME" = "main" ]; then
- ARGS="--prod"
- fi
- netlify deploy "$ARGS" \
- --debug \
- --dir=_site
+ TARGET_BUCKET: ${{ secrets.NVIDIA_DOCS_S3_BUCKET }}
+ run: |
+ # The deployment repository owns this subtree. Excluded objects are also
+ # protected from --delete by the AWS CLI.
+ aws s3 sync --delete \
+ --exclude 'deployment/*' \
+ _site "s3://${TARGET_BUCKET}/developer/docs/datascience"
+ - name: Check out Akamai request template
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ repository: rapidsai/shared-actions
+ ref: main
+ path: shared-actions
+ persist-credentials: false
+ sparse-checkout: publish-docs
+ - name: Install Akamai request dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install --yes --no-install-recommends xsltproc
+ - name: Prepare Akamai cache flush request
+ env:
+ AKAMAI_ACCESS_TOKEN: ${{ secrets.NVIDIA_DOCS_AKAMAI_ACCESS_TOKEN }}
+ AKAMAI_CLIENT_SECRET: ${{ secrets.NVIDIA_DOCS_AKAMAI_CLIENT_SECRET }}
+ AKAMAI_CLIENT_TOKEN: ${{ secrets.NVIDIA_DOCS_AKAMAI_CLIENT_TOKEN }}
+ AKAMAI_EMAILS_TO_NOTIFY: ${{ secrets.NVIDIA_DOCS_AKAMAI_EMAILS_TO_NOTIFY }}
+ AKAMAI_HOST: ${{ secrets.NVIDIA_DOCS_AKAMAI_HOST }}
+ run: |
+ umask 077
+ {
+ echo '[default]'
+ echo "client_secret = ${AKAMAI_CLIENT_SECRET}"
+ echo "host = ${AKAMAI_HOST}"
+ echo "access_token = ${AKAMAI_ACCESS_TOKEN}"
+ echo "client_token = ${AKAMAI_CLIENT_TOKEN}"
+ } > ~/.edgerc
+ xsltproc \
+ --stringparam target-path datascience \
+ shared-actions/publish-docs/akamai-eccu-flush.xslt \
+ shared-actions/publish-docs/akamai-eccu-flush.xslt \
+ | sed 's/xmlns:match="x" //' > /tmp/flush.xml
+ jq -Rnc \
+ --arg emails "${AKAMAI_EMAILS_TO_NOTIFY}" \
+ '$emails | split(",") | map(select(length > 0))' \
+ > /tmp/email-addresses.json
+ - name: Submit Akamai cache flush request
+ env:
+ REQUEST_NAME: rapidsai-docs-${{ github.run_id }}
+ run: |
+ uv tool run --from httpie-edgegrid http \
+ --ignore-stdin \
+ --auth-type edgegrid \
+ -a default: \
+ :/eccu-api/v1/requests \
+ metadata=@/tmp/flush.xml \
+ propertyName=docs.nvidia.com \
+ propertyNameExactMatch=true \
+ propertyType=HOST_HEADER \
+ requestName="${REQUEST_NAME}" \
+ statusUpdateEmails:=@/tmp/email-addresses.json
+ - name: Delete Akamai credentials
+ if: always()
+ run: rm -f ~/.edgerc
diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml
index d35a3931dd1..d778f4639e1 100644
--- a/.github/workflows/pr.yaml
+++ b/.github/workflows/pr.yaml
@@ -42,6 +42,8 @@ jobs:
permissions:
contents: read
id-token: write
+ env:
+ RAPIDS_DOCS_BASE_URL: https://docs.rapids.ai/
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -55,11 +57,15 @@ jobs:
run: uv sync --locked
- name: Build and validate portal
run: make check
- - name: Configure AWS credentials
+ - name: Configure source AWS credentials
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: ${{ vars.AWS_REGION }}
role-duration-seconds: 7200
- - name: Assemble and validate complete documentation site
+ - name: Assemble and validate API documentation
run: make assemble
+ - name: Generate compatibility redirects
+ run: |
+ uv run python scripts/generate_redirect_site.py \
+ --output _site/_redirects
diff --git a/404.md b/404.md
index 236231eb33b..4e7bd8bd838 100644
--- a/404.md
+++ b/404.md
@@ -6,12 +6,13 @@ orphan: true
We could not find the page you were looking for.
diff --git a/README.md b/README.md
index 29cd6271ad2..12d4e488b8c 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# NVIDIA RAPIDS Documentation
This repository contains the source for the
-[NVIDIA RAPIDS documentation site](https://docs.rapids.ai/). The site is built
+[NVIDIA RAPIDS documentation site](https://docs.nvidia.com/datascience/). The site is built
with Sphinx and the NVIDIA Sphinx theme.
## Build the site
@@ -16,17 +16,28 @@ make serve
The rendered site is written to `_site`. The server uses port 8000 by default;
override it with `PORT` (for example, `make serve PORT=8080`).
-## Build the full site
+## Build the docs.rapids.ai compatibility site
-The complete docs site imports versioned API documentation and the deployment
-documentation from the private `rapidsai-docs` S3 bucket. Configure a read-only
-AWS profile named `rapids-docs`, then run:
+The compatibility site imports versioned API documentation and deployment
+documentation from the private `rapidsai-docs` S3 bucket. It continues to serve
+real API content from `docs.rapids.ai/api/` until each library migrates
+to `docs.nvidia.com`. Configure a read-only AWS profile named `rapids-docs`, then
+run:
```shell
AWS_PROFILE=rapids-docs make full
```
This applies the RAPIDS library/version selectors to the imported documentation.
+Generate the Netlify redirect file after assembly:
+
+```shell
+uv run python scripts/generate_redirect_site.py --output _site/_redirects
+```
+
+The generated rules redirect portal pages and only those library versions whose
+migration metadata points at `docs.nvidia.com`. Unmatched API routes and shared
+assets remain real files in the assembled site.
## Validation
@@ -37,10 +48,17 @@ make check
Run checks including linting, tests, and a local build.
Pull requests opened against `rapidsai/docs` are copied to a
-`pull-request/` branch by the RAPIDS copy-PR bot. That branch runs the
-same validation and dry-runs assembly of the complete S3-backed documentation
-tree without deploying it. Netlify's repository integration separately creates
-a site preview. Merges to `main` deploy the production site.
+`pull-request/` branch by the RAPIDS copy-PR bot. That branch builds the
+full `docs.rapids.ai` compatibility site and validates its generated redirects.
+Netlify's repository integration separately creates a site preview.
+
+Merges to `main` and the daily scheduled workflow publish the portal to
+`docs.nvidia.com/datascience/`. The independently published
+`docs.nvidia.com/datascience/deployment/` subtree is explicitly preserved.
+The companion compatibility workflow assembles and publishes the remaining API
+documentation to `docs.rapids.ai`, with redirects for migrated API versions and
+portal routes. Both automated and manually triggered compatibility deployments
+use permanent HTTP 301 redirects.
## Repository layout
diff --git a/extensions/rapids_docs/api.py b/extensions/rapids_docs/api.py
index 5e113c0c01a..3a66fa42e2f 100644
--- a/extensions/rapids_docs/api.py
+++ b/extensions/rapids_docs/api.py
@@ -3,22 +3,7 @@
"""Render the API documentation listings."""
-
-def _version_label(project: dict, version_name: str, releases: dict) -> str:
- override = project.get("version-overrides", {}).get(version_name)
- if override:
- return str(override)
- version_key = "ucxx_version" if "ucxx" in project["path"].lower() else "version"
- return str(releases[version_name][version_key])
-
-
-def _documentation_url(project: dict, version_name: str, version: str) -> str:
- first_docs_nvidia_com_release = project["first_docs_nvidia_com_release"]
- if first_docs_nvidia_com_release and tuple(map(int, version.split("."))) >= tuple(
- map(int, first_docs_nvidia_com_release.split("."))
- ):
- return f"https://docs.nvidia.com/{project['path']}/{version}/"
- return f"https://docs.rapids.ai/api/{project['path']}/{version_name}/"
+from .routes import _documentation_url, _version_label
def _api_docs(data: dict, section: str) -> str:
diff --git a/extensions/rapids_docs/lifecycle.py b/extensions/rapids_docs/lifecycle.py
index 4f594e01b74..1aebd166270 100644
--- a/extensions/rapids_docs/lifecycle.py
+++ b/extensions/rapids_docs/lifecycle.py
@@ -27,6 +27,8 @@ def _jinja_environment(app) -> Environment:
def _context(app, docname: str = "index") -> dict:
data = app.rapids_portal_data
+ config = getattr(app, "config", None)
+ site_baseurl = getattr(config, "html_baseurl", "https://docs.nvidia.com/datascience/")
return {
**data,
"api_docs": lambda section: _api_docs(data, section),
@@ -36,6 +38,7 @@ def _context(app, docname: str = "index") -> dict:
),
"platform_support_content": lambda: _platform_support(data),
"previous_schedules": lambda: _previous_schedules(data),
+ "site_baseurl": site_baseurl.rstrip("/") + "/",
}
diff --git a/extensions/rapids_docs/notices.py b/extensions/rapids_docs/notices.py
index 79038357c88..6df14ef48df 100644
--- a/extensions/rapids_docs/notices.py
+++ b/extensions/rapids_docs/notices.py
@@ -13,6 +13,7 @@
from bs4 import BeautifulSoup
from .dates import _date, _long_date
+from .routes import _site_url
_NOTICE_STATUS_COLORS = {"blue", "green", "purple", "red", "yellow"}
@@ -103,11 +104,12 @@ def _build_rss(app, exception) -> None:
ElementTree.SubElement(
channel, "description"
).text = "Notices communicate and document changes in RAPIDS for contributors, developers, users, and the community."
- ElementTree.SubElement(channel, "link").text = "https://docs.rapids.ai/notices/"
+ base_url = app.config.html_baseurl
+ ElementTree.SubElement(channel, "link").text = _site_url(base_url, "/notices/")
ElementTree.SubElement(
channel,
"{http://www.w3.org/2005/Atom}link",
- href="https://docs.rapids.ai/notices/feed.xml",
+ href=_site_url(base_url, "/notices/feed.xml"),
rel="self",
type="application/rss+xml",
)
@@ -129,7 +131,7 @@ def _build_rss(app, exception) -> None:
ElementTree.SubElement(item, "description").text = html.unescape(description)
published = notice.get("notice_updated") or notice["notice_created"]
ElementTree.SubElement(item, "pubDate").text = _rss_date(published)
- url = f"https://docs.rapids.ai/notices/{Path(notice['docname']).name}/"
+ url = _site_url(base_url, f"/notices/{Path(notice['docname']).name}/")
ElementTree.SubElement(item, "link").text = url
ElementTree.SubElement(item, "guid", isPermaLink="true").text = url
for category in [*notice.get("tags", []), *notice.get("categories", [])]:
diff --git a/extensions/rapids_docs/routes.py b/extensions/rapids_docs/routes.py
new file mode 100644
index 00000000000..548fe3c9b85
--- /dev/null
+++ b/extensions/rapids_docs/routes.py
@@ -0,0 +1,83 @@
+# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+
+"""Resolve portal and API documentation URLs for each hosting target."""
+
+from urllib.parse import urljoin, urlsplit, urlunsplit
+
+
+def _version_label(project: dict, version_name: str, releases: dict) -> str:
+ override = project.get("version-overrides", {}).get(version_name)
+ if override:
+ return str(override)
+ version_key = "ucxx_version" if "ucxx" in project["path"].lower() else "version"
+ return str(releases[version_name][version_key])
+
+
+def _version_tuple(version: str) -> tuple[int, ...]:
+ return tuple(map(int, version.split(".")))
+
+
+def _with_suffix(base_url: str, suffix: str) -> str:
+ return urljoin(base_url.rstrip("/") + "/", suffix.lstrip("/"))
+
+
+def _documentation_url(
+ project: dict,
+ version_name: str,
+ version: str,
+ suffix: str = "",
+) -> str:
+ if external_docs_url := project.get("external_docs_url"):
+ return _with_suffix(external_docs_url, suffix)
+
+ first_nvidia_release = project["first_docs_nvidia_com_release"]
+ if first_nvidia_release and _version_tuple(version) >= _version_tuple(first_nvidia_release):
+ base_url = f"https://docs.nvidia.com/{project['path']}/{version}/"
+ else:
+ base_url = f"https://docs.rapids.ai/api/{project['path']}/{version_name}/"
+ return _with_suffix(base_url, suffix)
+
+
+def _project_for_path(data: dict, project_path: str) -> dict | None:
+ for section in ("apis", "libs", "inactive-projects"):
+ for project in data["docs"][section].values():
+ if project["path"].lower() == project_path.lower():
+ return project
+ return None
+
+
+def _api_documentation_url(url: str, data: dict | None) -> str | None:
+ if data is None:
+ return None
+
+ parsed = urlsplit(url)
+ parts = parsed.path.strip("/").split("/")
+ if len(parts) < 3 or parts[0] != "api" or parts[2] not in {"legacy", "stable", "nightly"}:
+ return None
+
+ project = _project_for_path(data, parts[1])
+ if project is None or not project["versions"].get(parts[2]):
+ return None
+
+ version_name = parts[2]
+ version = _version_label(project, version_name, data["releases"])
+ suffix = "/".join(parts[3:])
+ if suffix and parsed.path.endswith("/"):
+ suffix += "/"
+ destination = urlsplit(_documentation_url(project, version_name, version, suffix))
+ return urlunsplit(
+ (
+ destination.scheme,
+ destination.netloc,
+ destination.path,
+ parsed.query,
+ parsed.fragment,
+ )
+ )
+
+
+def _site_url(base_url: str, url: str, data: dict | None = None) -> str:
+ if not url.startswith("/") or url.startswith("//"):
+ return url
+ return _api_documentation_url(url, data) or _with_suffix(base_url, url)
diff --git a/extensions/rapids_docs/urls.py b/extensions/rapids_docs/urls.py
index 49d5943996a..85d7cf23ec8 100644
--- a/extensions/rapids_docs/urls.py
+++ b/extensions/rapids_docs/urls.py
@@ -1,13 +1,14 @@
-# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES.
+# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Rewrite root-relative portal URLs for the configured site base URL."""
import re
-from urllib.parse import urljoin
from docutils import nodes
+from .routes import _site_url
+
_HTML_URL_RE = re.compile(r"(?P\b(?:href|src)=['\"])(?P/(?!/)[^'\"]*)")
_TOCTREE_RE = re.compile(r"^```\{toctree\}\n.*?^```$", re.MULTILINE | re.DOTALL)
_TOCTREE_ENTRY_RE = re.compile(
@@ -15,19 +16,13 @@
)
-def _absolute_url(base_url: str, url: str) -> str:
- return urljoin(base_url.rstrip("/") + "/", url.lstrip("/"))
-
+def _rewrite_url(url: str, base_url: str, data: dict | None = None) -> str:
+ return _site_url(base_url, url, data)
-def _rewrite_url(url: str, base_url: str) -> str:
- if url.startswith("/") and not url.startswith("//"):
- return _absolute_url(base_url, url)
- return url
-
-def _rewrite_html_urls(text: str, base_url: str) -> str:
+def _rewrite_html_urls(text: str, base_url: str, data: dict | None = None) -> str:
return _HTML_URL_RE.sub(
- lambda match: match["attribute"] + _rewrite_url(match["url"], base_url), text
+ lambda match: match["attribute"] + _rewrite_url(match["url"], base_url, data), text
)
@@ -35,10 +30,13 @@ def _rewrite_toctree_urls(app, docname: str, source: list[str]) -> None:
base_url = app.config.html_baseurl
if not base_url:
return
+ data = getattr(app, "rapids_portal_data", None)
def rewrite_toctree(match: re.Match) -> str:
return _TOCTREE_ENTRY_RE.sub(
- lambda entry: entry["prefix"] + _absolute_url(base_url, entry["url"]) + entry["suffix"],
+ lambda entry: (
+ entry["prefix"] + _rewrite_url(entry["url"], base_url, data) + entry["suffix"]
+ ),
match[0],
)
@@ -49,16 +47,17 @@ def _rewrite_absolute_urls(app, doctree, docname: str) -> None:
base_url = app.config.html_baseurl
if not base_url:
return
+ data = getattr(app, "rapids_portal_data", None)
for node in doctree.findall(nodes.reference):
uri = node.get("refuri", "")
if uri.startswith("/") and not uri.startswith("//"):
- node["refuri"] = _absolute_url(base_url, uri)
+ node["refuri"] = _rewrite_url(uri, base_url, data)
for node in doctree.findall(nodes.raw):
if node.get("format") != "html":
continue
- text = _rewrite_html_urls(node.astext(), base_url)
+ text = _rewrite_html_urls(node.astext(), base_url, data)
if text != node.astext():
node.rawsource = text
node.clear()
@@ -69,6 +68,7 @@ def _rewrite_theme_urls(app, pagename: str, templatename: str, context: dict, do
base_url = app.config.html_baseurl
if not base_url:
return
+ data = getattr(app, "rapids_portal_data", None)
pathto = context["pathto"]
css_tag = context["css_tag"]
@@ -76,12 +76,14 @@ def _rewrite_theme_urls(app, pagename: str, templatename: str, context: dict, do
toctree = context["toctree"]
def rewrite_path(*args, **kwargs) -> str:
- return _rewrite_url(pathto(*args, **kwargs), base_url)
+ return _rewrite_url(pathto(*args, **kwargs), base_url, data)
context["pathto"] = rewrite_path
- context["css_tag"] = lambda css: _rewrite_html_urls(css_tag(css), base_url)
- context["js_tag"] = lambda js: _rewrite_html_urls(js_tag(js), base_url)
- context["toctree"] = lambda **kwargs: _rewrite_html_urls(toctree(**kwargs) or "", base_url)
+ context["css_tag"] = lambda css: _rewrite_html_urls(css_tag(css), base_url, data)
+ context["js_tag"] = lambda js: _rewrite_html_urls(js_tag(js), base_url, data)
+ context["toctree"] = lambda **kwargs: _rewrite_html_urls(
+ toctree(**kwargs) or "", base_url, data
+ )
for key in ("favicon_url", "logo_url"):
if key in context:
- context[key] = _rewrite_url(context[key], base_url)
+ context[key] = _rewrite_url(context[key], base_url, data)
diff --git a/install/index.md b/install/index.md
index 8231d4625f8..2b7c2b9114f 100644
--- a/install/index.md
+++ b/install/index.md
@@ -109,7 +109,7 @@ See the WSL2 setup [troubleshooting section](#wsl2-troubleshooting).
All provisioned systems need to be RAPIDS capable. Below is a list of requirements for the current release. For requirements of historical RAPIDS versions, see [Platform Support](/platform-support/).
**GPU:** NVIDIA Volta™ or higher with [compute capability](https://developer.nvidia.com/cuda-gpus) 7.0+
-- Pascal™ GPU support was [removed in 24.02](https://docs.rapids.ai/notices/rsn0034/). Compute capability 7.0+ is required for RAPIDS 24.02 and later.
+- Pascal™ GPU support was [removed in 24.02](/notices/rsn0034/). Compute capability 7.0+ is required for RAPIDS 24.02 and later.
**OS:**
- Linux distributions with `glibc>=2.28` (released in August 2018), which include the following:
@@ -150,7 +150,7 @@ Aside from the system requirements, other considerations for best performance in
### Cloud Instance GPUs
-If you do not have access to GPU hardware, there are several cloud service providers (CSP) that are RAPIDS enabled. Learn how to deploy RAPIDS on AWS, Azure, GCP, and IBM cloud on our [Cloud Deployment Page](https://docs.rapids.ai/deployment/stable/cloud/index.html).
+If you do not have access to GPU hardware, there are several cloud service providers (CSP) that are RAPIDS enabled. Learn how to deploy RAPIDS on AWS, Azure, GCP, and IBM cloud on our [Cloud Deployment Page](/deployment/stable/cloud/).
Several services also offer **free and limited** trials with GPU resources:
- [Amazon SageMaker Studio Lab](https://studiolab.sagemaker.aws/)
diff --git a/notices/rsn0054.md b/notices/rsn0054.md
index b1472a90fb2..b1b94e70eec 100644
--- a/notices/rsn0054.md
+++ b/notices/rsn0054.md
@@ -29,7 +29,7 @@ notice_updated: 2025-12-12
RAPIDS is raising the minimum required CUDA version for the entire software suite from 12.0 to 12.2 starting with the v25.12 release.
All of RAPIDS will require a minimum of CUDA 12.2 including containers, all published packages (wheels and conda), and compilation from source in Release `v25.12`, scheduled for December 11, 2025.
`v25.10` will be the last RAPIDS release to support CUDA 12.0 and 12.1 runtimes in any format.
-We are continuing support for CUDA 12 and 13 containers, with CUDA major version tags. See [RSN 53](https://docs.rapids.ai/notices/rsn0053/) for more information.
+We are continuing support for CUDA 12 and 13 containers, with CUDA major version tags. See [RSN 53](/notices/rsn0053/) for more information.
## Impact
diff --git a/resources/burn-down-guide.md b/resources/burn-down-guide.md
index cb2b7f27abc..90a652b4488 100644
--- a/resources/burn-down-guide.md
+++ b/resources/burn-down-guide.md
@@ -77,7 +77,7 @@ Suggested template:
:warning: cuDF/cuML/cuGraph/RMM/cuStrings/dask-cuda v0.9 have moved to the burn down stage - `branch-0.10` is available but *not the default branch yet*
*Burn down ends Tuesday, August 13*
-See https://docs.rapids.ai/maintainers for full v0.9 schedule
+See the [maintainer documentation](/maintainers/) for the full v0.9 schedule.
Please keep the following in mind:
- *Stop adding issues/PRs for v0.9*; unless deemed critical by the PICs
@@ -86,7 +86,7 @@ Please keep the following in mind:
- *Move open issues/PRs* to the new v0.9 boards and branch
- Forward-mergers are in place to merge updates from v0.9 to v0.10 - https://gpuci.gpuopenanalytics.com/view/gpuCI%20-%20forward-mergers/
-See https://docs.rapids.ai/releases/process/#burn-down for more details on the burn down and development process.
+See the [release process](/releases/process/#burn-down) for more details on the burn down and development process.
*v0.9 boards:*
- cuDF - https://github.com/rapidsai/cudf/projects/15
diff --git a/scripts/generate_redirect_site.py b/scripts/generate_redirect_site.py
new file mode 100644
index 00000000000..d93f0012348
--- /dev/null
+++ b/scripts/generate_redirect_site.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python3
+# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+
+"""Generate redirects for the hybrid docs.rapids.ai compatibility site."""
+
+from __future__ import annotations
+
+import argparse
+import json
+import sys
+from pathlib import Path
+
+import yaml
+
+ROOT = Path(__file__).resolve().parents[1]
+sys.path.insert(0, str(ROOT))
+
+from extensions.rapids_docs.routes import _documentation_url, _version_label # noqa: E402
+
+DOCS_CONFIG = ROOT / "_data" / "docs.yml"
+RELEASES_CONFIG = ROOT / "_data" / "releases.json"
+MANUAL_REDIRECTS = ROOT / "_redirects"
+SECTIONS = ("apis", "libs", "inactive-projects")
+VERSION_NAMES = ("legacy", "stable", "nightly")
+PORTAL_PREFIXES = (
+ "_static",
+ "contributing",
+ "install",
+ "licenses",
+ "maintainers",
+ "notices",
+ "platform-support",
+ "releases",
+ "resources",
+ "user-guide",
+ "visualization",
+)
+PORTAL_FILES = ("404", "404.html", "LICENSE", "SECURITY.md", "genindex", "search")
+REDIRECT_STATUS = 301
+
+
+def _rule(source: str, destination: str) -> str:
+ return f"{source} {destination} {REDIRECT_STATUS}!"
+
+
+def _project_rules(project: dict, releases: dict) -> list[str]:
+ rules = []
+ emitted_sources = set()
+ project_path = project["path"]
+
+ for version_name in VERSION_NAMES:
+ if not project["versions"].get(version_name):
+ continue
+ version = _version_label(project, version_name, releases)
+ destination = _documentation_url(project, version_name, version)
+ if not project.get("external_docs_url") and destination.startswith(
+ "https://docs.rapids.ai/"
+ ):
+ continue
+
+ for source_version in (version_name, version):
+ source = f"/api/{project_path}/{source_version}"
+ if source in emitted_sources:
+ continue
+ emitted_sources.add(source)
+ rules.extend(
+ [
+ _rule(source, destination),
+ _rule(
+ f"{source}/*",
+ _documentation_url(project, version_name, version, ":splat"),
+ ),
+ ]
+ )
+
+ return rules
+
+
+def generate_redirects() -> str:
+ docs = yaml.safe_load(DOCS_CONFIG.read_text())
+ releases = json.loads(RELEASES_CONFIG.read_text())
+ rules = [
+ "# Generated redirects for the hybrid docs.rapids.ai compatibility site.",
+ "# Unmatched API routes continue to serve assembled documentation files.",
+ "# Manual compatibility redirects run first and may intentionally chain.",
+ MANUAL_REDIRECTS.read_text().rstrip(),
+ "",
+ "# API documentation aliases and numeric versions.",
+ ]
+ for section in SECTIONS:
+ for project in docs[section].values():
+ rules.extend(_project_rules(project, releases))
+
+ rules.extend(
+ [
+ "",
+ "# Deployment docs are independently published below the datascience prefix.",
+ _rule(
+ "/deployment/*",
+ "https://docs.nvidia.com/datascience/deployment/:splat",
+ ),
+ "",
+ "# Portal routes move beneath docs.nvidia.com/datascience.",
+ _rule("/", "https://docs.nvidia.com/datascience/"),
+ _rule("/api", "https://docs.nvidia.com/datascience/api/"),
+ _rule("/api/", "https://docs.nvidia.com/datascience/api/"),
+ ]
+ )
+ for prefix in PORTAL_PREFIXES:
+ rules.extend(
+ [
+ _rule(
+ f"/{prefix}",
+ f"https://docs.nvidia.com/datascience/{prefix}/",
+ ),
+ _rule(
+ f"/{prefix}/*",
+ f"https://docs.nvidia.com/datascience/{prefix}/:splat",
+ ),
+ ]
+ )
+ for filename in PORTAL_FILES:
+ rules.append(
+ _rule(
+ f"/{filename}",
+ f"https://docs.nvidia.com/datascience/{filename}",
+ )
+ )
+ rules.extend(
+ [
+ "",
+ "# Do not add a /* fallback: /api and /assets contain real site content.",
+ "",
+ ]
+ )
+ return "\n".join(rules)
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--output", type=Path, required=True)
+ args = parser.parse_args()
+
+ output = generate_redirects()
+ args.output.parent.mkdir(parents=True, exist_ok=True)
+ args.output.write_text(output)
+ print(f"Wrote {args.output}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/validate_site.py b/scripts/validate_site.py
index f349710f7ba..ba6aedff6cc 100644
--- a/scripts/validate_site.py
+++ b/scripts/validate_site.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES.
+# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Validate the rendered RAPIDS portal and optional assembled documentation tree."""
@@ -7,6 +7,7 @@
from __future__ import annotations
import argparse
+import os
import re
from pathlib import Path
from urllib.parse import urlsplit
@@ -66,6 +67,10 @@ def main() -> None:
missing.append(f"{len(missing_search_notices)} individual notices are absent from search")
home = (args.site / "index.html").read_text(errors="ignore")
+ expected_baseurl = os.environ.get("RAPIDS_DOCS_BASE_URL", "https://docs.rapids.ai/")
+ expected_baseurl = expected_baseurl.rstrip("/") + "/"
+ if f' None:
if markdown_links:
missing.append("same-site Markdown links remain:\n " + "\n ".join(markdown_links))
+ if expected_baseurl == "https://docs.nvidia.com/datascience/":
+ misplaced_api_links = []
+ legacy_portal_links = []
+ for path in html_files:
+ text = path.read_text(errors="ignore")
+ if re.search(
+ r"https://docs\.nvidia\.com/datascience/api/[^\"']+/(legacy|stable|nightly)/",
+ text,
+ ):
+ misplaced_api_links.append(str(path.relative_to(args.site)))
+ if re.search(r"https://docs\.rapids\.ai/(?!api/)", text):
+ legacy_portal_links.append(str(path.relative_to(args.site)))
+ if misplaced_api_links:
+ missing.append(
+ "migrated API links incorrectly point below /datascience/api in: "
+ + ", ".join(misplaced_api_links)
+ )
+ if legacy_portal_links:
+ missing.append(
+ "portal links still point to docs.rapids.ai in: " + ", ".join(legacy_portal_links)
+ )
+
if args.full:
full_paths = [
"api/cudf/stable",
"api/cudf/latest",
"api/cudf/nightly",
+ "api/dask-cudf/legacy",
+ "api/dask-cudf/stable",
+ "api/dask-cudf/nightly",
"deployment/stable/index.html",
"deployment/nightly/index.html",
]
diff --git a/sphinx/conf.py b/sphinx/conf.py
index 5f68a3ef968..8a7b50232c1 100644
--- a/sphinx/conf.py
+++ b/sphinx/conf.py
@@ -1,4 +1,4 @@
-# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES.
+# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
@@ -56,7 +56,11 @@
html_theme = "nvidia_sphinx_theme"
html_static_path = ["_static"]
html_extra_path = ["../_redirects"]
-html_baseurl = "https://docs.rapids.ai/"
+html_baseurl = (
+ os.environ.get("RAPIDS_DOCS_BASE_URL")
+ or os.environ.get("DEPLOY_PRIME_URL")
+ or "https://docs.rapids.ai/"
+).rstrip("/") + "/"
html_scaled_image_link = False
html_theme_options = {
diff --git a/tests/test_redirects.py b/tests/test_redirects.py
new file mode 100644
index 00000000000..2e8948b7d71
--- /dev/null
+++ b/tests/test_redirects.py
@@ -0,0 +1,39 @@
+# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+
+import json
+
+from scripts import generate_redirect_site
+
+
+def test_redirects_route_migrated_docs_and_portal() -> None:
+ redirects = generate_redirect_site.generate_redirects()
+ releases = json.loads(generate_redirect_site.RELEASES_CONFIG.read_text())
+ stable = releases["stable"]["version"]
+
+ assert f"/api/cudf/stable/* https://docs.nvidia.com/cudf/{stable}/:splat 301!" in redirects
+ assert f"/api/cudf/{stable} https://docs.nvidia.com/cudf/{stable}/ 301!" in redirects
+ assert "/deployment/* https://docs.nvidia.com/datascience/deployment/:splat 301!" in redirects
+ assert "/notices/* https://docs.nvidia.com/datascience/notices/:splat 301!" in redirects
+ assert "/ https://docs.nvidia.com/datascience/ 301!" in redirects
+ assert " 302!" not in redirects
+ assert "\n/* " not in redirects
+
+
+def test_redirects_route_external_unversioned_docs() -> None:
+ redirects = generate_redirect_site.generate_redirects()
+
+ assert "/api/cuvs/stable/* https://docs.nvidia.com/cuvs/:splat 301!" in redirects
+
+
+def test_redirects_leave_unmigrated_api_docs_and_shared_assets_local() -> None:
+ redirects = generate_redirect_site.generate_redirects()
+ releases = json.loads(generate_redirect_site.RELEASES_CONFIG.read_text())
+ legacy = releases["legacy"]["version"]
+
+ assert "/api/dask-cudf/stable " not in redirects
+ assert f"/api/dask-cudf/{legacy} " not in redirects
+ assert "/api/cudf/legacy " not in redirects
+ assert f"/api/cudf/{legacy} " not in redirects
+ assert "/api/* " not in redirects
+ assert "/assets/* " not in redirects
diff --git a/tests/test_rendering.py b/tests/test_rendering.py
index 1416c4a1288..fa2f4a371a0 100644
--- a/tests/test_rendering.py
+++ b/tests/test_rendering.py
@@ -94,6 +94,12 @@ def test_standard_jinja_syntax_and_raw_blocks() -> None:
assert rendered == stable_version + "\n${{ matrix.PY_VER }}\n"
+def test_context_defaults_to_nvidia_portal() -> None:
+ app = SimpleNamespace(rapids_portal_data={})
+
+ assert lifecycle._context(app)["site_baseurl"] == "https://docs.nvidia.com/datascience/"
+
+
def test_toctree_url_rewriting() -> None:
app = SimpleNamespace(
config=SimpleNamespace(html_baseurl="https://docs.example.com/datascience/")
@@ -171,6 +177,32 @@ def test_absolute_url_rewriting() -> None:
)
+def test_api_documentation_url_rewriting() -> None:
+ app = SimpleNamespace(
+ config=SimpleNamespace(html_baseurl="https://docs.nvidia.com/datascience/"),
+ rapids_portal_data=portal_data._load_data(APP),
+ )
+ migrated = nodes.reference(
+ "",
+ "cuDF guide",
+ refuri="/api/cudf/stable/user_guide/10min/?source=portal#intro",
+ )
+ unmigrated = nodes.reference(
+ "",
+ "Dask-cuDF guide",
+ refuri="/api/dask-cudf/stable/user_guide/",
+ )
+ doctree = nodes.container("", migrated, unmigrated)
+
+ urls._rewrite_absolute_urls(app, doctree, "user-guide/index")
+
+ stable_version = app.rapids_portal_data["releases"]["stable"]["version"]
+ assert migrated["refuri"] == (
+ f"https://docs.nvidia.com/cudf/{stable_version}/user_guide/10min/?source=portal#intro"
+ )
+ assert unmigrated["refuri"] == ("https://docs.rapids.ai/api/dask-cudf/stable/user_guide/")
+
+
def test_theme_url_rewriting() -> None:
app = SimpleNamespace(
config=SimpleNamespace(html_baseurl="https://docs.example.com/datascience/")