diff --git a/.changeset/no-docker.md b/.changeset/no-docker.md
new file mode 100644
index 000000000..3bb68cf6e
--- /dev/null
+++ b/.changeset/no-docker.md
@@ -0,0 +1,20 @@
+---
+'@pmndrs/docs': major
+---
+
+Drop Docker. `npx @pmndrs/docs build` does the same build, so the image, the `Dockerfile` and
+the publish steps that maintained them are gone. `ghcr.io/pmndrs/docs` stops being pushed —
+its existing tags stay in the registry, frozen.
+
+Major because of the git tag, not the inputs: releases force-move `vX`, so anything short of a
+major would slide every caller pinning `build.yml@v3` onto the Docker-free workflow. `@v3`
+keeps building through the image until its repository moves to `@v4`.
+
+`build.yml` itself keeps every input, every environment variable and the same Pages artifact.
+Only the build step changes, and `docker_tag` gives way to `version` — an npm version or range.
+
+The job keeps `id-token: write` — no longer to attest a Docker image, now to sign the npm
+publish with trusted publishing.
+
+`preview.sh` builds through the CLI too, and reads its options straight from the environment
+rather than forwarding each one into a container.
diff --git a/.changeset/npm-cli.md b/.changeset/npm-cli.md
index a18ee8e82..c2d690645 100644
--- a/.changeset/npm-cli.md
+++ b/.changeset/npm-cli.md
@@ -4,9 +4,9 @@
Publish the generator to npm, as `npx @pmndrs/docs build`.
-`--format website` statically exports the documentation site, as the Docker image does.
+`--format website` statically exports the documentation site.
`--format fragment` — the default — compiles MDX to plain HTML with no layout, stylesheet or
script, either from a folder or from stdin, and needs nothing but node.
-`bin/build.mjs` is gone: it predated the Docker image, was never published, and built a server
-bundle rather than a static export.
+`bin/build.mjs` is gone: it was never published, and built a server bundle rather than a
+static export.
diff --git a/.dockerignore b/.dockerignore
deleted file mode 100644
index e5915db4c..000000000
--- a/.dockerignore
+++ /dev/null
@@ -1,3 +0,0 @@
-node_modules
-out
-.next
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 99cbf4159..35e3d408b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -58,10 +58,10 @@ on:
theme_caution:
type: string
default: '#da3633'
- docker_tag:
+ version:
type: string
- default: '3'
- description: 'Docker image tag for ghcr.io/pmndrs/docs'
+ default: '4'
+ description: 'npm version or range of @pmndrs/docs to build with'
jobs:
build-job:
@@ -76,47 +76,16 @@ jobs:
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#example-of-setting-an-output-parameter
- id: set-base-path
run: echo "BASE_PATH=${{ inputs.base_path || steps.configurepages.outputs.base_path }}" >> "$GITHUB_OUTPUT"
- - run: |
- set -ex
-
- docker run --rm --init \
- -v "./$MDX":/app/docs \
- -e MDX \
- -e NEXT_PUBLIC_LIBNAME \
- -e NEXT_PUBLIC_LIBNAME_SHORT \
- -e NEXT_PUBLIC_LIBNAME_DOTSUFFIX_LABEL \
- -e NEXT_PUBLIC_LIBNAME_DOTSUFFIX_HREF \
- -e BASE_PATH \
- -e DIST_DIR \
- -e OUTPUT \
- -e HOME_REDIRECT \
- -e MDX_BASEURL \
- -e SOURCECODE_BASEURL \
- -e EDIT_BASEURL \
- -e NEXT_PUBLIC_URL \
- -e ICON \
- -e LOGO \
- -e GITHUB \
- -e DISCORD \
- -e THEME_PRIMARY \
- -e THEME_SCHEME \
- -e THEME_CONTRAST \
- -e THEME_NOTE \
- -e THEME_TIP \
- -e THEME_IMPORTANT \
- -e THEME_WARNING \
- -e THEME_CAUTION \
- -e CONTRIBUTORS_PAT \
- ghcr.io/pmndrs/docs:${{ inputs.docker_tag }} pnpm run build
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 24
+ - run: npx -y @pmndrs/docs@${{ inputs.version }} build "${{ inputs.mdx }}" out --format website
env:
- MDX: ${{ inputs.mdx }}
NEXT_PUBLIC_LIBNAME: ${{ inputs.libname }}
NEXT_PUBLIC_LIBNAME_SHORT: ${{ inputs.libname_short }}
NEXT_PUBLIC_LIBNAME_DOTSUFFIX_LABEL: ${{ inputs.libname_dotsuffix_label }}
NEXT_PUBLIC_LIBNAME_DOTSUFFIX_HREF: ${{ inputs.libname_dotsuffix_href }}
BASE_PATH: ${{ steps.set-base-path.outputs.BASE_PATH }}
- DIST_DIR: '${{ inputs.mdx }}/out${{ steps.set-base-path.outputs.BASE_PATH }}'
- OUTPUT: 'export'
HOME_REDIRECT: ${{ inputs.home_redirect }}
MDX_BASEURL: 'https://github.com/${{ github.repository }}/raw/${{ github.head_ref || github.ref_name }}/${{ inputs.mdx }}'
SOURCECODE_BASEURL: 'https://github.com/${{ github.repository }}/tree/${{ github.head_ref || github.ref_name }}'
@@ -138,4 +107,4 @@ jobs:
- uses: actions/upload-pages-artifact@v5
with:
- path: ${{ inputs.mdx }}/out${{ steps.set-base-path.outputs.BASE_PATH }}
+ path: out
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 54abb85ff..e11496a91 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,10 +10,6 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
-env:
- REGISTRY: ghcr.io
- IMAGE_NAME: ${{ github.repository }}
-
jobs:
main-job:
runs-on: ubuntu-latest
@@ -21,8 +17,7 @@ jobs:
permissions:
contents: write
pull-requests: write
- packages: write
- attestations: write
+ # npm trusted publishing signs the release with an OIDC token
id-token: write
# Update the Production/Preview environment with the Vercel deployed URL
@@ -135,48 +130,6 @@ jobs:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
- #
- # ██████ ██████ ██████ ██ ██ ███████ ██████
- # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
- # ██ ██ ██ ██ ██ █████ █████ ██████
- # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
- # ██████ ██████ ██████ ██ ██ ███████ ██ ██
- #
-
- - uses: docker/login-action@v3
- if: steps.check.outputs.changed == 'true'
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - uses: docker/metadata-action@v5
- if: steps.check.outputs.changed == 'true'
- id: meta
- with:
- images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- tags: |
- type=ref,event=branch
- type=raw,value=latest
- type=raw,value=${{ steps.get-version.outputs.major_version }}
- type=raw,value=${{ steps.get-version.outputs.version }}
-
- - uses: docker/build-push-action@v6
- id: push
- with:
- context: .
- # Push only if a new version was published
- push: ${{ steps.check.outputs.changed == 'true' }}
- tags: ${{ steps.meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
-
- - uses: actions/attest-build-provenance@v3
- if: steps.check.outputs.changed == 'true'
- with:
- subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- subject-digest: ${{ steps.push.outputs.digest }}
- push-to-registry: true
-
- name: git tags
if: steps.check.outputs.changed == 'true'
run: |
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 51da550e3..3d2008091 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -17,6 +17,10 @@ jobs:
id-token: write
uses: pmndrs/docs/.github/workflows/build.yml@main
with:
+ # Whatever is published, rather than a version number this repository would have to keep
+ # in step with itself. Our own site is then built by exactly what consumers install —
+ # a broken release shows up here before anyone downstream moves to it.
+ version: 'latest'
mdx: 'docs'
libname: 'Poimandres'
libname_short: 'pmndrs'
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index c9fb451f7..000000000
--- a/Dockerfile
+++ /dev/null
@@ -1,15 +0,0 @@
-FROM node:24-alpine
-
-RUN apk add --no-cache libc6-compat git && apk update
-
-WORKDIR /app
-
-# Copy package.json first so corepack can read the packageManager field
-COPY package.json pnpm-lock.yaml ./
-
-# Enable corepack and prepare pnpm (reads version from package.json)
-RUN corepack enable && corepack prepare --activate
-
-RUN pnpm install --frozen-lockfile
-
-COPY . .
\ No newline at end of file
diff --git a/README.md b/README.md
index 77d7591de..016990924 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ $ npx @pmndrs/docs build docs out # one .html per .mdx, asset
$ npx @pmndrs/docs build docs out --format website # the whole website, statically exported
```
-`--format fragment` (the default) needs nothing but node — no Docker, no `next build`. A
+`--format fragment` (the default) needs nothing but node — no `next build`, no bundler. A
fragment is the compiled MDX and nothing else: no layout, no stylesheet, no script. Mermaid
diagrams stay fenced blocks, and Sandpack shows its code without the editor.
@@ -22,18 +22,19 @@ one falls back to the environment variable it maps to, the same ones
[configuration](docs/getting-started/introduction.mdx#Configuration) documents.
-Docker
+Preview a folder of MDX, served
```sh
$ curl -sL https://raw.githubusercontent.com/pmndrs/docs/refs/heads/main/preview.sh | \
MDX="docs" \
ICON="🥑" \
- DOCKER_IMAGE="ghcr.io/pmndrs/docs:latest" \
sh
```
-- you can pass any option from [configuration](docs/getting-started/introduction.mdx#Configuration)
-- in `DOCKER_IMAGE`, you can specify any `:tag` value from [docs packages](https://github.com/pmndrs/docs/pkgs/container/docs) container registry
+Builds the website, then serves it — alongside the MDX folder itself, so relative assets
+resolve while you edit. Any option from
+[configuration](docs/getting-started/introduction.mdx#Configuration) goes in the same way, and
+`VERSION` picks a published version of the CLI.
@@ -41,7 +42,7 @@ $ curl -sL https://raw.githubusercontent.com/pmndrs/docs/refs/heads/main/preview
Every push to `main` redeploys [docs.pmnd.rs](https://docs.pmnd.rs) via [ci.yml](.github/workflows/ci.yml) — no [changeset](.changeset/) needed for that.
-Add one (`pnpm changeset`) only when downstream consumers pinning `pmndrs/docs/.github/workflows/build.yml@v3` or `ghcr.io/pmndrs/docs:v3` should pull the change. It bumps [`package.json`](package.json), tags `vX.Y.Z` + `vX`, and publishes a matching Docker image — so `@v3` resolves to the latest.
+Add one (`pnpm changeset`) only when downstream consumers pinning `pmndrs/docs/.github/workflows/build.yml@v4` or `@pmndrs/docs@4` should pull the change. It bumps [`package.json`](package.json), publishes to npm, and tags `vX.Y.Z` + `vX` — so `@v4` resolves to the latest.
TL;DR — site-only tweak: skip. Anything consumers see (workflow, build behavior, templates): add one.
diff --git a/docs/getting-started/introduction.mdx b/docs/getting-started/introduction.mdx
index 8a797ee31..a4725f7e9 100644
--- a/docs/getting-started/introduction.mdx
+++ b/docs/getting-started/introduction.mdx
@@ -204,11 +204,9 @@ $ (
http://localhost:3000
-### Docker
+### CLI
-```sh
-$ docker build -t pmndrs-docs .
-```
+No clone, no install — the published CLI does the same build:
```sh
$ cd ~/code/pmndrs/react-three-fiber
@@ -217,69 +215,20 @@ $ (
export _PORT=60141
- export MDX=docs
export NEXT_PUBLIC_LIBNAME="React Three Fiber"
export NEXT_PUBLIC_LIBNAME_SHORT="r3f"
- export NEXT_PUBLIC_LIBNAME_DOTSUFFIX_LABEL="docs"
- export NEXT_PUBLIC_LIBNAME_DOTSUFFIX_HREF="https://docs.pmnd.rs"
export BASE_PATH=/toto
- export DIST_DIR="$MDX/out$BASE_PATH"
- export OUTPUT=export
export HOME_REDIRECT=/getting-started/introduction
export MDX_BASEURL=http://localhost:$_PORT
- export SOURCECODE_BASEURL=
- export EDIT_BASEURL=
- export NEXT_PUBLIC_URL=
export ICON=🇨🇭
- export LOGO=/logo.png
export GITHUB=https://github.com/pmndrs/react-three-fiber
- export DISCORD=https://discord.com/channels/740090768164651008/740093168770613279
- export THEME_PRIMARY="#323e48"
- export THEME_SCHEME="tonalSpot"
- export THEME_CONTRAST="0"
- export THEME_NOTE="#1f6feb"
- export THEME_TIP="#238636"
- export THEME_IMPORTANT="#8957e5"
- export THEME_WARNING="#d29922"
- export THEME_CAUTION="#da3633"
- export CONTRIBUTORS_PAT=
- rm -rf "$MDX/out"
-
- docker run --rm --init -it \
- -v "./$MDX":/app/docs \
- -e MDX \
- -e NEXT_PUBLIC_LIBNAME \
- -e NEXT_PUBLIC_LIBNAME_SHORT \
- -e NEXT_PUBLIC_LIBNAME_DOTSUFFIX_LABEL \
- -e NEXT_PUBLIC_LIBNAME_DOTSUFFIX_HREF \
- -e BASE_PATH \
- -e DIST_DIR \
- -e OUTPUT \
- -e HOME_REDIRECT \
- -e MDX_BASEURL \
- -e SOURCECODE_BASEURL \
- -e EDIT_BASEURL \
- -e NEXT_PUBLIC_URL \
- -e ICON \
- -e LOGO \
- -e GITHUB \
- -e DISCORD \
- -e THEME_PRIMARY \
- -e THEME_SCHEME \
- -e THEME_CONTRAST \
- -e THEME_NOTE \
- -e THEME_TIP \
- -e THEME_IMPORTANT \
- -e THEME_WARNING \
- -e THEME_CAUTION \
- -e CONTRIBUTORS_PAT \
- pmndrs-docs pnpm run build
+ npx -y @pmndrs/docs build docs docs/out --format website
kill $(lsof -ti:"$_PORT")
- npx serve $MDX -p $_PORT --no-port-switching --no-clipboard &
+ npx serve docs -p $_PORT --no-port-switching --no-clipboard &
- npx -y serve "$MDX/out" &
+ npx -y serve docs/out &
wait
)
@@ -287,6 +236,8 @@ $ (
Then go to: http://localhost:3000
+Every option above has a flag too — `npx @pmndrs/docs build --help` lists them.
+
## Agents
`llms.txt` dumps and the pmndrs MCP server moved to their own page: [Agents](/agents/introduction).
diff --git a/preview.sh b/preview.sh
index fa46a4ec7..2c3796531 100755
--- a/preview.sh
+++ b/preview.sh
@@ -4,44 +4,17 @@ main() {
trap 'kill -9 0' SIGINT
export _PORT="${_PORT:-60141}"
- export DOCKER_IMAGE="${DOCKER_IMAGE:-ghcr.io/pmndrs/docs:latest}"
+ export VERSION="${VERSION:-latest}"
export MDX="${MDX:-docs}"
export NEXT_PUBLIC_LIBNAME="${NEXT_PUBLIC_LIBNAME:-Poimandres}"
+ # Relative assets resolve against the MDX folder, served below on the same port
+ export MDX_BASEURL="${MDX_BASEURL:-http://localhost:$_PORT}"
rm -rf "$MDX/out"
- # docker build -t $DOCKER_IMAGE .
-
- docker run --rm --init -t \
- -v "./$MDX":/app/docs \
- -e MDX \
- -e NEXT_PUBLIC_LIBNAME \
- -e NEXT_PUBLIC_LIBNAME_SHORT \
- -e NEXT_PUBLIC_LIBNAME_DOTSUFFIX_LABEL \
- -e NEXT_PUBLIC_LIBNAME_DOTSUFFIX_HREF \
- -e BASE_PATH \
- -e DIST_DIR="$MDX/out$BASE_PATH" \
- -e OUTPUT=export \
- -e HOME_REDIRECT \
- -e MDX_BASEURL=http://localhost:$_PORT \
- -e SOURCECODE_BASEURL \
- -e EDIT_BASEURL \
- -e NEXT_PUBLIC_URL \
- -e ICON \
- -e LOGO \
- -e GITHUB \
- -e DISCORD \
- -e THEME_PRIMARY \
- -e THEME_SCHEME \
- -e THEME_CONTRAST \
- -e THEME_NOTE \
- -e THEME_TIP \
- -e THEME_IMPORTANT \
- -e THEME_WARNING \
- -e THEME_CAUTION \
- -e CONTRIBUTORS_PAT \
- $DOCKER_IMAGE pnpm run build || exit 1
+ # Every other option is read straight from the environment — same process, nothing to forward
+ npx -y "@pmndrs/docs@$VERSION" build "$MDX" "$MDX/out" --format website || exit 1
kill $(lsof -ti:"$_PORT")
npx serve $MDX -p $_PORT --no-port-switching --no-clipboard &
@@ -51,4 +24,4 @@ main() {
wait
}
-main
\ No newline at end of file
+main
diff --git a/src/cli/main.ts b/src/cli/main.ts
index 852460ea1..955e222ef 100644
--- a/src/cli/main.ts
+++ b/src/cli/main.ts
@@ -11,8 +11,7 @@ import { buildWebsite } from './website'
*
* Each carries the environment variable the app reads it as, so that a flag and its variable
* are declared in one place, and `--help` says which is which. `Option.env()` then does the
- * fallback itself, which is what keeps the Docker image and the reusable workflow working
- * with no flags at all.
+ * fallback itself, which is what keeps the reusable workflow working with no flags at all.
*/
const websiteOptions = [
new Option('--libname ', 'Library name, e.g. "React Three Fiber"').env(