A site ships either as static files from pmx export or as a live server from pmx serve. Both build the same pages through the same access rules.
Choose the target using How PathMX runs, then follow the
matching workflow below. Run commands from the Quickstart
project; bun run pmx uses its installed CLI without a global launcher.
-
Export with the public origin the site will live at.
bun run pmx export --origin https://example.comYou should see
Exported N routes to …/dist. Use--out <path>for a different directory and--base-path /guideto mount below a prefix. -
Look at the output.
dist/ ├── index.html, index.md each page and its authored Markdown ├── llms.txt, robots.txt, sitemap.xml ├── 404.html missing-page document ├── .pmx/ hashed styles, scripts, and build marker └── .pathmx/manifest.json canonical route contract for hostsUpload
dist/to any static host. Pages keep client-side navigation and refresh themselves when a new deployment lands. -
Read the diagnostics. Export includes only anonymously readable pages. Server-only behavior that cannot run in the exported site is reported with its Source. Review these diagnostics before publishing. Browser-local Actions need a supported browser host; static export does not make arbitrary server operations available offline.
Preview the files over HTTP before uploading. If Python 3 is installed:
python3 -m http.server 8080 --directory dist
Open http://localhost:8080/ and follow the workshop lesson link. Reload the
lesson's deep URL, view its .md alternate, and check the stylesheet and Player.
This previews files only; it does not run server Actions. Stop it with Ctrl+C.
Your production host must serve the exported routes; see the route manifest when
configuring redirects or rewrites.
Follow the Cloudflare guide to publish with the optional adapter.
PathMX shows a themed recovery page when a browser opens a missing page. The
requested URL stays in the address bar and the response carries HTTP status
404. To replace its content, add paths/404.page.md:
# This trail ends here
The page may have moved. Let's find another way.
[Back to home](/)
The override uses normal Markdown, theme settings, and layout selection. It must
be publicly readable; a private override is ignored. Open /404.page to preview
it as an ordinary page. Missing-page responses use browser navigation rather
than the Source runtime, so recovery links also work without JavaScript. Reload
a missing URL to see edits to its recovery content.
Requests that prefer text/markdown receive the override's Markdown, or the
default recovery text when no override exists. Raw Source reads and API errors
retain their normal error responses. Recovery responses are marked noindex
and are not cached.
The live server and Cloudflare adapter select the recovery page automatically.
Directory exports include 404.html; configure other hosts to serve it for
missing URLs with status 404, preserving the requested URL. With --base-path /guide, that file is dist/guide/404.html. A file-only preview server may show
its own error page until configured to use this file.
The automatic convention currently applies to 404.page.md. Files such as
500.page.md or 503.page.md remain ordinary pages; they do not enable exception
handling or maintenance mode.
-
Set the production environment in
<project>/.env.PATHMX_ORIGIN=https://example.com PATHMX_HOST=127.0.0.1 PATHMX_PORT=3003Command-line
--hostand--portwin, then these variables, thenPORT. The default is0.0.0.0:3000. -
Run the server from the project directory under a process supervisor.
bun run startIt stays in the foreground, watches Markdown and CSS, and shuts down cleanly on
SIGINTorSIGTERM. -
Probe health.
curl -I http://127.0.0.1:3003/api/runtime/healthYou should see
204while the Source is valid and503after a rejected edit until it is corrected.
Keep .env and .pathmx/ out of Git and in your backup policy. Source under
paths/ stays in version control.
For Deploy's macOS adapter, prepare one ordinary server checkout from the same
network Git repository as your authoring checkout. Install Bun and Python 3 for
the service user, clone the project, run bun install --frozen-lockfile, and create
a separate live branch with git switch -c live. The committed dependencies
must already include the compatible @pathmx/deploy package. Configure .env
and verify bun run start plus the health probe above before supervising it.
Stop the foreground process before loading the service.
This complete example assumes a macOS user named site, a checkout at
/Users/site/sources/course, and Bun at /Users/site/.bun/bin/bun. Replace those
paths and the service label with your actual values; plist strings do not expand
~ or shell variables. Create
~/Library/LaunchAgents/net.example.course.plist for that service user:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>net.example.course</string>
<key>ProgramArguments</key>
<array>
<string>/Users/site/sources/course/node_modules/.bin/pmx</string>
<string>serve</string>
</array>
<key>WorkingDirectory</key><string>/Users/site/sources/course</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key><string>/Users/site/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>PATHMX_HOST</key><string>127.0.0.1</string>
<key>PATHMX_PORT</key><string>3003</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>/Users/site/Library/Logs/course.log</string>
<key>StandardErrorPath</key><string>/Users/site/Library/Logs/course-error.log</string>
</dict>
</plist>
Run these on the server as the service user, with its GUI login session available:
mkdir -p ~/Library/Logs
plutil -lint ~/Library/LaunchAgents/net.example.course.plist
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/net.example.course.plist
launchctl enable "gui/$(id -u)/net.example.course"
launchctl kickstart "gui/$(id -u)/net.example.course"
launchctl print "gui/$(id -u)/net.example.course"
curl -I http://127.0.0.1:3003/api/runtime/health
The plist check returns OK; the service report should show a running process,
and health returns 204. Inspect the two log files if it stops or health fails.
A user LaunchAgent depends on that user's GUI domain; an SSH-only session does not
create it. Do not bootstrap a second copy over an already-loaded service. Configure
TLS/proxy delivery separately before exposing the public origin.
These are server-changing bootstrap commands. Deploy setup subsequently discovers this existing service; it does not create it.
For systemd, containers, or managed hosts, configure the equivalent executable and working directory. The process runs in the foreground and closes its live streams and plugin resources on shutdown. The supervisor owns restart policy; the host owns TLS, proxies, logs, and backups.
The project manifest anchors environment, plugins, Actions, and db/.
Selecting another Source root does not relocate those resources. Use a loopback
binding behind a colocated proxy or tunnel. Keep secrets and durable runtime state
in the backup policy, with appropriate filesystem access.
For an existing macOS launchd service, Server deployment coordinates shared-Git updates, backups, validation, restart, and recovery reports. Use its setup and dry-run before the first managed update.
A live site that accepts Source mutations needs one stable checkout. Checkpoint
and push domain Source changes, back up .env and durable db/, then
fetch and merge or fast-forward the new revision. Install locked dependencies,
run bun run check, restart the supervised process, and check health and representative
application behavior.
Rollback with a corrective or revert commit merged into the live history so later domain changes survive. Immutable release-directory switching fits read-only Sources; it does not preserve a writable repository's continuity by itself.