diff --git a/site/src/content/docs/how-to/installation.mdx b/site/src/content/docs/how-to/installation.mdx
index 05429e1e..93763cef 100644
--- a/site/src/content/docs/how-to/installation.mdx
+++ b/site/src/content/docs/how-to/installation.mdx
@@ -211,6 +211,10 @@ Content-Security-Policy:
Skins
+
+Self-host the player
+
+
----
That's it! You now have a fully functional Video.js player. Go forth and play.
diff --git a/site/src/content/docs/how-to/self-host-the-player.mdx b/site/src/content/docs/how-to/self-host-the-player.mdx
new file mode 100644
index 00000000..e21414b9
--- /dev/null
+++ b/site/src/content/docs/how-to/self-host-the-player.mdx
@@ -0,0 +1,64 @@
+---
+title: Self-host the player
+description: Serve the Video.js HTML player from your own origin for offline, air-gapped, or restricted-network deployments
+---
+
+import Aside from '@/components/Aside.astro';
+import DocsLink from '@/components/docs/DocsLink.astro';
+
+By default, the installation flow loads the player from a public CDN. For offline, air-gapped, or locked-down deployments, you can serve everything from your own origin instead. Pick one of the options below.
+
+## Bundle a single file (recommended)
+
+Install the package, put the player's imports into an entry file, then let any bundler produce one self-contained module you can host anywhere. For the default video player, that's:
+
+```js title="player.js"
+import '@videojs/html/video/player';
+import '@videojs/html/video/skin';
+```
+
+```bash
+npm install @videojs/html
+npx esbuild player.js --bundle --format=esm --minify --sourcemap --outfile=public/player.js
+```
+
+Load the output with `type="module"`:
+
+```html
+
+```
+
+You get one file (plus a sourcemap), no runtime requests to a CDN, and only the features you import. Any bundler works (Vite, Rollup, webpack) as long as the output stays ESM.
+
+## Mirror the prebuilt CDN files
+
+To skip the bundler, copy the prebuilt CDN bundle to your server as-is.
+
+
+
+```bash
+npm install @videojs/html
+mkdir -p public
+cp -r node_modules/@videojs/html/cdn public/videojs
+```
+
+Then point the script tag at your copy instead of the CDN:
+
+```html
+
+```
+
+The file name matches your player: `video.js`, `audio.js`, `background.js`, and so on. Every option is present in the `cdn/` folder you copied.
+
+Hashed chunk names change between releases, so re-copy the directory whenever you upgrade `@videojs/html`.
+
+## HLS and other media types
+
+HLS and other non-default media need their own module on top of the player. Match it in your self-hosted build:
+
+- **Bundle:** add the media import to your entry file (`import '@videojs/html/media/hls-video';`) and use the matching `` element with your `.m3u8` source.
+- **Mirror:** serve the media file too and add its script tag (``).
+
+Without the media module the player UI renders but the video never loads, with no console error.
diff --git a/site/src/docs.config.ts b/site/src/docs.config.ts
index 333e2f24..c299efaf 100644
--- a/site/src/docs.config.ts
+++ b/site/src/docs.config.ts
@@ -46,7 +46,11 @@ export const sidebar: Sidebar = [
sidebarLabel: 'How to',
llmsDescription:
'Task-oriented guides with step-by-step instructions to achieve a specific outcome by applying one or more concepts. Each guide may assume you already understand the relevant concepts.',
- contents: [{ slug: 'how-to/customize-skins' }, { slug: 'how-to/build-your-own-component' }],
+ contents: [
+ { slug: 'how-to/customize-skins' },
+ { slug: 'how-to/build-your-own-component' },
+ { slug: 'how-to/self-host-the-player', frameworks: ['html'] },
+ ],
},
{
sidebarLabel: 'Components',