A folder or site
Put a folder on a public HTTPS address with one command. Publish it again to update it.
Publish
In the folder, run:
$ cd ~/my-site $ secretbase publish . Publishing my-site… Getting your files… Going online… Live at <address>. Anyone with the link can open it; add a password with `secretbase password on`. On this computer: <local address>
The app is named after the folder; --name <name> gives it another name. You can also publish a single page (page.html) or a .zip. On the dashboard (secretbase open), drop it on Projects: "Drop a folder, an HTML page or a .zip here".
The first time, it asks you to sign in to Tailscale: it opens the page, and prints Continue in your browser: <link> in case it didn't. Your app's address comes from that account.
What runs
- A website: plain files, or one built with Vite, Astro or Create React App.
- An app with its own server: Node, Python or Go.
- A folder with its own
Dockerfile, or a Docker Compose file.
Each app runs in its own sandbox. A finished app may have up to 20,000 files and 2 GB, installed packages included.
Packages are installed from your lockfile with npm, pnpm or Yarn: the version in packageManager; with none, pnpm is the one that wrote your pnpm-lock.yaml (pnpm 10 for lockfile 9.0, 8 for 6.x, 7 for 5.4, 6 for 5.3), and Yarn the newest. With pnpm 10 or newer, a package's own install script runs only if your project allows it (pnpm approve-builds); the others are skipped with a warning in the log, and the install goes on. Older pnpm (an older lockfile) runs every package's install script, as it always did.
Cache and other headers per path
Set Cache-Control and a few other headers for some paths in secretbase.json at the folder's root, in the same shape as headers in a vercel.json:
{
"headers": [
{"source": "/assets/**", "headers": [{"key": "Cache-Control", "value": "public, max-age=31536000, immutable"}]},
{"source": "/**/*.html", "headers": [{"key": "Cache-Control", "value": "no-cache"}, {"key": "X-Frame-Options", "value": "DENY"}]},
{"source": "/**/", "headers": [{"key": "Cache-Control", "value": "no-cache"}, {"key": "X-Frame-Options", "value": "DENY"}]}
]
}
sourceis a path or a glob:*matches within one part of the path,**any number of parts, so/assets/**is everything under/assets. A source has at most one**, and at most one*in each part. Patterns such as/assets/(.*)aren't read: write them as globs.- A rule matches the path in the address, not the file sent:
/and/docs/are paths of their own, and/*.htmlmatches neither (nor/docs/a.html, one part deeper)./**/matches every path ending in/, the home page included, and/**/*.htmlevery.htmlpath;/docsand/docs/are different paths, and/docs/**matches both. - Every rule whose
sourcematches applies, in order, so a later rule's value wins. The file's value replaces the one your app sends. - The headers you can set:
Cache-Control,CDN-Cache-Control,Content-Security-Policy,Content-Security-Policy-Report-Only,Cross-Origin-Embedder-Policy,Cross-Origin-Opener-Policy,Cross-Origin-Resource-Policy,Permissions-Policy,Referrer-Policy,X-Content-Type-Options,X-Frame-OptionsandX-Robots-Tag. - Up to 50 rules, 10 headers each. They apply to websites and apps alike, from the next publish on, and never to the password page.
Single-page apps: rewrites
A single-page app (React Router, Vue Router and the like) has one index.html for all its pages. So that a reload or a shared link to /about opens the app instead of "404 page not found", list rewrites in secretbase.json at the folder's root, in the same shape as in a vercel.json:
{
"rewrites": [
{"source": "/**", "destination": "/index.html"}
]
}
- A path with a file of its own always gets that file, so
/assets/app.jsstays the script. Only a path with no file takes the first rewrite whosesourcematches, and gets itsdestinationfile. sourceis written as for headers above: a path or a glob, such as/app/**.destinationis one file of your website, such as/index.html(a path ending in/is that folder'sindex.html), written as the file's name is, spaces included, never with%20. It can't be another address. Publish stops if the file isn't there.- Up to 50 rewrites. They apply to websites, from the next publish on; an app with its own server answers its own paths.
Update, undo, take offline
Change your files and run secretbase publish . again: the app keeps its address. Then, in the app's folder:
secretbase undo: puts the previous release back online.secretbase logs: the build and app output.secretbase offlineandsecretbase online: take the app off its address and put it back.secretbase password on: visitors need a password.secretbase delete: removes the app and its data here. Your folder is kept.
$ cd ~/my-app $ secretbase offline my-app is offline. Its address stays the same; `secretbase online my-app` puts it back. $ secretbase online Going online… Live at <address>. Anyone with the link can open it; add a password with `secretbase password on`. On this computer: <local address> $ secretbase password on Password on. Visitors need: <password>
From another folder, name the app: secretbase logs my-app.
If something goes wrong
- "There's no earlier release to go back to. (HB-MACHINE-005)":
undoneeds two publishes. - "Nothing was made public: my-app runs on this computer only … until you accept the notice": accept it once with
secretbase noticein a terminal. - "The finished app is too big to publish": remove files or packages it doesn't need.
- An app is published but has no public address (HB-FRONTDOOR-017): this computer's three Tailscale addresses are in use. Delete an app, add a Tailscale key (
secretbase tailscale-key), or use your own domain.