diff --git a/contributing/DOCS.md b/contributing/DOCS.md index c47bb269c..aa07d9673 100644 --- a/contributing/DOCS.md +++ b/contributing/DOCS.md @@ -72,8 +72,8 @@ just website-dev # Vite dev server on http://127.0.0.1:5173 Docs/blog links on the landing resolve same-origin (`/docs`, `/blog`), which 404 in standalone dev. Point them at a live site while iterating: `just website-dev https://dstack.ai`. -The Sky product page is served at `/products/sky/`. Google Analytics and the social/OG image -reuse the same property and MkDocs-generated card as the rest of the site. +Keep page metadata in the HTML entries and `website/src/App/index.tsx` in sync. +When adding or moving product pages, update the [sitemap template](../mkdocs/overrides/sitemap.xml). ## Building the whole site @@ -196,7 +196,7 @@ mkdocs/ # docs_dir for the mkdocs site website/ # React (Vite) landing page — served at "/" ├── index.html # Entry; title, OG/meta, Google Analytics -├── src/ # App, pages (Home, Sky), components, routes +├── src/ # App, pages (Home, Sky, Factory), components, routes └── public/static/ # Landing assets (namespaced to avoid clashing with /assets) scripts/docs/ diff --git a/mkdocs/assets/stylesheets/cloudscape-docs.css b/mkdocs/assets/stylesheets/cloudscape-docs.css index 2a9caf228..3f4105ad1 100644 --- a/mkdocs/assets/stylesheets/cloudscape-docs.css +++ b/mkdocs/assets/stylesheets/cloudscape-docs.css @@ -292,9 +292,8 @@ margin-top: 0; } -/* The switcher rail — copied from the landing (website/src/styles.css, .gs-rail block): grouped - rows under solid bars, with a selected card. The docs and the landing are separate - builds, so changes to the rail styles must be mirrored here (and vice versa). */ +/* Shared rail visuals match website/src/styles.css. Docs have no current product, + so rows are highlighted only on hover or keyboard focus. */ .gs-rail { display: flex; flex-direction: column; @@ -329,10 +328,11 @@ border-bottom-left-radius: 0; border-bottom-right-radius: 0; } -.gs-opt.gs-opt--on { +.gs-opt:hover, +.gs-opt:focus-visible { border-radius: 8px; background: var(--cs-bg); - box-shadow: inset 0 0 0 1px var(--cs-text); + box-shadow: inset 0 0 0 0.5px var(--cs-text); } .gs-opt--feat { padding: 13px 14px; diff --git a/mkdocs/docs/concepts/presets.md b/mkdocs/docs/concepts/presets.md index 6dd875714..5a0ef995d 100644 --- a/mkdocs/docs/concepts/presets.md +++ b/mkdocs/docs/concepts/presets.md @@ -272,7 +272,7 @@ Presets are pushed to and pulled from the registry hosted at [dstack Sky](https: Presets are private by default. Set **Presets** to **Public** in the project settings to let anyone browse and pull its presets without signing in, including users of a self-hosted `dstack` server. Only project admins and global admins can change this setting; enabling it does not let anyone join the project. -A self-hosted registry is part of [dstack Factory](https://calendly.com/dstackai/discovery-call){ target="_blank" }. +A self-hosted registry is part of [dstack Factory](/products/factory/){ target="_blank" }. ## Export a preset diff --git a/mkdocs/index.md b/mkdocs/index.md index 50475eafc..0fcf89bff 100644 --- a/mkdocs/index.md +++ b/mkdocs/index.md @@ -1,8 +1,7 @@ --- # The landing page ("/") is now the React app in website/. When the docs are served on -# their own (e.g. `mkdocs serve`), this page redirects to /docs/. The title/description -# below are only used to render the social card that the React landing reuses as its OG -# image (assets/images/social/index.png). +# their own (e.g. `mkdocs serve`), this page redirects to /docs/. The React landing has +# its own metadata and social preview, separate from this redirect page. template: redirect.html title: The orchestration stack for AI infrastructure description: dstack is a unified control plane for GPU provisioning and orchestration that works with any GPU cloud, Kubernetes, or on-prem clusters. diff --git a/mkdocs/overrides/header-2.html b/mkdocs/overrides/header-2.html index cdea2fe8a..86a97c4b1 100644 --- a/mkdocs/overrides/header-2.html +++ b/mkdocs/overrides/header-2.html @@ -83,7 +83,7 @@ - } + visual={} title="Bring your own compute" imageFirst > @@ -118,7 +55,10 @@ function BringComputeBlock() { ); } -export function KeyConceptsBlock({ children }: { children?: ReactNode }) { +export function KeyConceptsBlock({ children, imageFirst = false }: { + children?: ReactNode; + imageFirst?: boolean; +}) { return ( } title="AI-native orchestration" + imageFirst={imageFirst} > {children ?? <> Managing AI infrastructure requires first-class primitives for compute management, training, inference, and observability that support heterogeneous AI compute. diff --git a/website/src/pages/Home/FaqSection.tsx b/website/src/pages/Home/FaqSection.tsx index 7172498f6..a51dc60c3 100644 --- a/website/src/pages/Home/FaqSection.tsx +++ b/website/src/pages/Home/FaqSection.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { ReactNode, useState } from 'react'; import Button from '@cloudscape-design/components/button'; import ExpandableSection from '@cloudscape-design/components/expandable-section'; import SpaceBetween from '@cloudscape-design/components/space-between'; @@ -6,7 +6,12 @@ import { mainButtonStyle } from '../../cloudscape-theme'; import { AlternatingDocBlock } from '../../components/AlternatingDocBlock'; import { highlightTerms } from '../../components/highlightTerms'; -const faqItems = [ +type FaqItem = { + q: string; + a: string | string[]; +}; + +const faqItems: FaqItem[] = [ { q: 'How does dstack differ from Slurm?', a: 'Slurm is a battle-tested workload manager with decades of production use in HPC environments. dstack is a unified orchestration layer built for containerized AI workloads and heterogeneous AI compute. While both support batch jobs and distributed training, dstack also provides first-class primitives for compute management, inference, and observability, including native cloud provisioning.', @@ -26,15 +31,19 @@ const faqItems = [ ]; // FAQ block: a single-open accordion of questions beside contact actions. -export function FaqSection({ items = faqItems, imageFirst = false }: { - items?: typeof faqItems; +export function FaqSection({ items = faqItems, showContact = true, imageFirst = false, action, children }: { + items?: FaqItem[]; + showContact?: boolean; imageFirst?: boolean; + action?: ReactNode; + children?: ReactNode; }) { const [openQuestion, setOpenQuestion] = useState(null); return (
{items.map(item => ( @@ -45,25 +54,28 @@ export function FaqSection({ items = faqItems, imageFirst = false }: { expanded={openQuestion === item.q} onChange={({ detail }) => setOpenQuestion(detail.expanded ? item.q : null)} > - {highlightTerms(item.a)} + {(Array.isArray(item.a) ? item.a : [item.a]).map((paragraph, index) => ( +

{highlightTerms(paragraph)}

+ ))} ))} } title="FAQ" - imageFirst={imageFirst} - action={ + action={action ?? ( - + {showContact && ( + + )} - } + )} > - Have questions, or need help? Reach out to us on Discord or directly. + {children ?? <>Have questions, or need help? Reach out to us on Discord{showContact && ' or directly'}.}
); diff --git a/website/src/pages/Home/GetStartedSection.tsx b/website/src/pages/Home/GetStartedSection.tsx index edd2c3560..a07fb08e7 100644 --- a/website/src/pages/Home/GetStartedSection.tsx +++ b/website/src/pages/Home/GetStartedSection.tsx @@ -7,7 +7,7 @@ import { mainButtonStyle } from '../../cloudscape-theme'; import { installMethods, maxInstallLines, padYamlToLines } from '../../data/snippets'; import { DOCS_URL, ROUTES, docsUrl } from '../../routes'; -const BoxGlyph = () => ( +export const BoxGlyph = () => ( @@ -17,7 +17,7 @@ export const CloudGlyph = () => ( ); -const LayersGlyph = () => ( +export const LayersGlyph = () => ( @@ -103,33 +103,31 @@ export function GetStartedSection() {
Self-hosted
-
+ dstack The open-source control plane for AI-native orchestration. -
+ dstack Factory - A complete software stack for AI labs, inference providers, and data centers. + A heterogeneous orchestration stack for AI token factories.
Hosted by us
- + dstack Sky - An AI cloud with AI-native orchestration. Rent GPUs on demand or bring your own compute. + One account across GPU clouds. Better prices and unified billing.
diff --git a/website/src/pages/Home/HomePage.tsx b/website/src/pages/Home/HomePage.tsx index 2b9a8788a..7bc27e100 100644 --- a/website/src/pages/Home/HomePage.tsx +++ b/website/src/pages/Home/HomePage.tsx @@ -27,9 +27,9 @@ export function HomePage() {

{highlightTerms( - 'dstack is an open-source orchestration layer for heterogeneous AI compute. ' + - 'It standardizes how workloads run across GPU clouds, Kubernetes, and on-prem clusters, ' + - 'across NVIDIA, AMD, Tenstorrent, and TPU accelerators.', + 'dstack is an open-source orchestration layer for AI workloads on heterogeneous accelerators. ' + + 'It standardizes how you manage compute and run training and inference on GPU clouds, ' + + 'Kubernetes, VMs, or bare-metal clusters.', )}

diff --git a/website/src/pages/Sky/MarketplaceTabs.tsx b/website/src/pages/Sky/MarketplaceTabs.tsx new file mode 100644 index 000000000..852d431ba --- /dev/null +++ b/website/src/pages/Sky/MarketplaceTabs.tsx @@ -0,0 +1,92 @@ +import Button from '@cloudscape-design/components/button'; +import { mainButtonStyle } from '../../cloudscape-theme'; +import { ContentTabs } from '../../components/ContentTabs'; +import { ChipGlyph, CloudGlyph } from '../Home/GetStartedSection'; +import { gpuPrices } from './pricing'; + +// Default marketplace clouds from dstack Sky's /api/backends/list_base_types. +const partnerGroups = [ + ['AWS', 'GCP'], + ['Lambda', 'Runpod'], + ['Verda', 'Nebius'], +]; + +export function MarketplaceTabs() { + return ( + + + + GPU + On-demand + Spot + + + + {gpuPrices.map(gpu => ( + + + + + {gpu.name}{' '}{gpu.memory} + + + {gpu.onDemand} + {gpu.spot ?? '—'} + + ))} + + + ), + footer: ( + + One account and unified billing across GPU clouds + + ), + }, + { + id: 'partners', + label: 'Partners', + content: ( +
+ {partnerGroups.map(group => ( +
    + {group.map(partner => ( +
  • + + {partner} +
  • + ))} +
+ ))} +
+ ), + footer: ( + <> + + Offer your GPU capacity through dstack Sky + + + + ), + }, + ]} + /> + ); +} diff --git a/website/src/pages/Sky/SkyPage.tsx b/website/src/pages/Sky/SkyPage.tsx index a11ec22af..0248322df 100644 --- a/website/src/pages/Sky/SkyPage.tsx +++ b/website/src/pages/Sky/SkyPage.tsx @@ -1,35 +1,17 @@ -import { KeyboardEvent, useRef, useState } from 'react'; import Button from '@cloudscape-design/components/button'; import { useLayoutContext } from '../../App'; -import { heroButtonStyle, mainButtonStyle } from '../../cloudscape-theme'; +import { heroButtonStyle } from '../../cloudscape-theme'; import { AlternatingDocBlock } from '../../components/AlternatingDocBlock'; +import { ComputeSourcesTabs } from '../../components/ComputeSourcesTabs'; import { highlightTerms } from '../../components/highlightTerms'; import { KeyConceptsBlock } from '../Home/ExploreSection'; import { FaqSection } from '../Home/FaqSection'; -import { CapList, ChipGlyph, CloudGlyph, KubernetesGlyph, ServerGlyph } from '../Home/GetStartedSection'; import { TrustedBySection } from '../Home/TrustedBySection'; import { HeroSquircle } from './HeroSquircle'; -import { gpuPrices } from './pricing'; - -const computeTabs = ['On-demand', 'Reserved clusters', 'BYOC']; +import { MarketplaceTabs } from './MarketplaceTabs'; export function SkyPage() { const { theme } = useLayoutContext(); - const [computeTab, setComputeTab] = useState(0); - const tabRefs = useRef<(HTMLButtonElement | null)[]>([]); - - const onTabKeyDown = (event: KeyboardEvent, index: number) => { - const nextIndex = { - ArrowRight: (index + 1) % computeTabs.length, - ArrowLeft: (index + computeTabs.length - 1) % computeTabs.length, - Home: 0, - End: computeTabs.length - 1, - }[event.key]; - if (nextIndex === undefined) return; - event.preventDefault(); - setComputeTab(nextIndex); - tabRefs.current[nextIndex]?.focus(); - }; return (
@@ -40,12 +22,12 @@ export function SkyPage() {
-

The AI cloud with AI-native orchestration

+

One account across GPU clouds

{highlightTerms( - 'dstack Sky is a heterogeneous AI cloud with NVIDIA Blackwell, Hopper, and AMD Instinct GPUs. ' + - 'Built on dstack, it combines compute management, training, inference, ' + - 'and observability in one interface.', + 'dstack Sky combines GPU capacity from multiple cloud partners with competitive pricing, ' + + 'unified billing, and AI-native orchestration. We host and manage the dstack server for you, ' + + 'whether you use the marketplace or bring your own cloud and on-prem compute.', )}

@@ -68,122 +50,53 @@ export function SkyPage() {
-
- {computeTabs.map((label, index) => ( - - ))} -
-
- {computeTab === 0 && ( - - - - - - - - - - {gpuPrices.map(gpu => ( - - - - - - ))} - -
GPUOn-demandSpot
- - - {gpu.name}{' '}{gpu.memory} - - {gpu.onDemand}{gpu.spot ?? '—'}
- )} - {computeTab === 1 && ( - , title: 'GPU clusters', sub: 'Reserve for a fixed period at a price agreed in advance' }, - ]} /> - )} - {computeTab === 2 && ( - , title: 'GPU clouds', sub: 'AWS, GCP, Azure, Nebius, Runpod, and more' }, - { icon: , title: 'SSH fleets', sub: 'Pre-provisioned VMs or bare-metal' }, - { icon: , title: 'Kubernetes', sub: 'Existing Kubernetes clusters' }, - ]} /> - )} -
-
- {computeTab !== 1 && ( - - {computeTab === 0 ? 'Prices in USD per GPU-hour' : 'Bring your own compute'} - - )} - {computeTab === 1 && ( - - )} -
-
- } - title="On-demand GPUs for AI workloads" + visual={} + title="Better prices, unified billing" imageFirst > - Access affordable on-demand and spot GPUs with pay-as-you-go pricing. + The dstack Sky marketplace offers on-demand and spot GPUs from a wide set of partners. + You get competitive pricing and capacity across regions, without depending on a + single cloud. +
+
+ One dstack Sky account gives you access to all marketplace partners. Billing is unified, + with no separate cloud accounts to manage. + + + } + title="Bring your own compute" + > + dstack Sky provisions and manages compute in your own GPU cloud accounts using your + credentials. Compute is billed directly to those accounts.

- You can also bring compute from your own cloud accounts or on-prem infrastructure - and use it alongside Sky GPUs. + You can also bring on-prem compute, including Kubernetes clusters, pre-provisioned + VMs, and bare-metal clusters. We host and maintain the dstack server for you.
- + dstack Sky provides first-class primitives for compute management, training, - inference, and observability across heterogeneous AI compute. Use one interface - to efficiently utilize cloud GPUs, manage your own clusters, or run your own - AI token factory at scale. + inference, and observability across heterogeneous AI compute.

- Sky is built on dstack and uses the same CLI and YAML configurations as a - self-hosted dstack server. + dstack Sky includes all the features of open-source dstack and uses the same CLI and + YAML configurations. It adds a built-in gateway with an HTTPS domain, advanced + observability, and usage metering.
- diff --git a/website/src/router.tsx b/website/src/router.tsx index fb46c14dd..6c9e810c8 100644 --- a/website/src/router.tsx +++ b/website/src/router.tsx @@ -1,5 +1,6 @@ import { Navigate, createBrowserRouter } from 'react-router-dom'; import { App } from './App'; +import { FactoryPage } from './pages/Factory/FactoryPage'; import { HomePage } from './pages/Home'; import { SkyPage } from './pages/Sky/SkyPage'; import { ROUTES } from './routes'; @@ -12,6 +13,7 @@ export const router = createBrowserRouter([ children: [ { index: true, element: }, { path: ROUTES.SKY, element: }, + { path: ROUTES.FACTORY, element: }, { path: '*', element: }, ], }, diff --git a/website/src/routes.ts b/website/src/routes.ts index 4d759478c..8a1309ac4 100644 --- a/website/src/routes.ts +++ b/website/src/routes.ts @@ -1,6 +1,6 @@ // Central route table + cross-links to the MkDocs-served parts of the site. // -// This app owns `/` and `/products/sky`. Docs and blog are served by MkDocs on the +// This app owns `/` and `/products/*`. Docs and blog are served by MkDocs on the // SAME origin in production (`/docs`, `/blog`). For standalone landing development you // can point those links at the live site by setting VITE_DOCS_BASE, e.g. // VITE_DOCS_BASE=https://dstack.ai npm run dev @@ -9,6 +9,7 @@ const SITE_BASE = (import.meta.env.VITE_DOCS_BASE ?? '').replace(/\/+$/, ''); export const ROUTES = { HOME: '/', SKY: '/products/sky', + FACTORY: '/products/factory', } as const; export type Route = (typeof ROUTES)[keyof typeof ROUTES]; diff --git a/website/src/styles.css b/website/src/styles.css index 13d8fa919..fd7e718a8 100644 --- a/website/src/styles.css +++ b/website/src/styles.css @@ -382,6 +382,10 @@ p { font-size: 17px; } +.home-hero .highlight { + font-weight: 600; +} + /* Hero CTAs sit side by side, each sized to its own label (not forced to equal width). */ .home-hero__actions { margin-top: 28px; @@ -582,6 +586,7 @@ p { Higher specificity than `.docs-article a` so the reset wins regardless of source order. */ .docs-article .doc-action a, .docs-article .figma-card a, +.docs-article .gs-boxfoot a, .docs-article .gs-deploy a { text-decoration: none !important; } @@ -992,6 +997,14 @@ p { color: var(--cs-text) !important; } +.faq-list p { + font-size: inherit; +} + +.faq-list p + p { + margin-top: 1em; +} + .testimonial-person { display: flex; @@ -1062,10 +1075,12 @@ p { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } -.gs-opt.gs-opt--on { +.gs-rail:not(:has(a.gs-opt:hover, a.gs-opt:focus-visible)) .gs-opt.gs-opt--on, +.gs-rail a.gs-opt:hover, +.gs-rail a.gs-opt:focus-visible { border-radius: 8px; background: var(--cs-bg); - box-shadow: inset 0 0 0 1px var(--cs-text); + box-shadow: inset 0 0 0 0.5px var(--cs-text); } /* Row names mirror the tab typography: 16px / 700. */ @@ -1184,15 +1199,15 @@ p { border-radius: 12px; overflow: hidden; } -.gs-box--compute { - height: auto; - min-height: 360px; -} -.gs-box--compute.gs-box--offers { +.gs-box.gs-box--compute, +.gs-box.gs-box--compute-sources { height: 360px; } -.gs-box--offers .gs-skybody { +.gs-box--compute .gs-skybody, +.gs-box--compute-sources .gs-skybody { overflow-y: auto; +} +.gs-box--offers .gs-skybody { padding-top: 0; } .sky-gpu-prices { @@ -1236,10 +1251,6 @@ p { .sky-gpu-prices td { padding-left: 8px; } - .gs-box--compute .gs-tabs, - .gs-box--compute .gs-tab { - padding-inline: 4px; - } } .gs-boxfoot { display: flex; @@ -1261,6 +1272,15 @@ p { flex: 0 0 auto; } +@media (max-width: 480px) { + .gs-box--compute .gs-boxfoot { + flex-wrap: wrap; + } + .gs-box--compute .gs-foot__note { + flex-basis: 100%; + } +} + /* Shared tabs for installation methods and Sky compute options. */ .gs-tabs { display: flex; @@ -1896,6 +1916,10 @@ p { grid-template-columns: 1fr 1fr; width: 100%; } + + .home-hero__actions--single { + grid-template-columns: max-content; + } /* Each button fills its half-column; center the label and trim the desktop 34px side padding so both fit. !important: Cloudscape sets text-align:start via a boosted rule. */ .home-hero__actions [class*='awsui_button'] { diff --git a/website/vite.config.ts b/website/vite.config.ts index 18790fe84..ff84e085a 100644 --- a/website/vite.config.ts +++ b/website/vite.config.ts @@ -8,7 +8,7 @@ import react from '@vitejs/plugin-react'; // `assetsDir` is namespaced to `website-assets/` (not Vite's default `assets/`) so the // build can be overlaid onto the MkDocs `site/` output without colliding with MkDocs's // own `/assets/...` tree. Public files live under `public/static/` for the same reason — -// the HTML entries provide the landing and Sky product routes on static hosting. +// the HTML entries provide the landing and product routes on static hosting. export default defineConfig({ base: process.env.BASE_PATH || '/', plugins: [react()], @@ -18,6 +18,7 @@ export default defineConfig({ input: { home: fileURLToPath(new URL('./index.html', import.meta.url)), sky: fileURLToPath(new URL('./products/sky/index.html', import.meta.url)), + factory: fileURLToPath(new URL('./products/factory/index.html', import.meta.url)), }, }, },