flypod [path]
Deploy a directory or single file to a public flypod URL; owned and permanent when logged in, otherwise anonymous with a 14-day TTL.
The default command deploys a directory or single file. Logged in, the deploy is owned and permanent. Anonymous, it lives 14 days; running flypod login later automatically claims every anonymous site this CLI remembers, so you don't need to track tokens to make a deploy permanent.
Synopsis
flypod [path] [flags]
flypod deploy [path] [flags]flypod deploy is the same command spelled out. The path can be a folder or a single file; for several files, put them in a folder and deploy the folder.
The first flypod in a folder creates a site. Every later flypod in that folder updates the same site, exactly as flypod update does: same URL, a new version, and the flypod update receipt. To make a separate site from a folder that already has one, pass --new. If the folder's site is gone (deleted, or expired), flypod makes a new one, as if the folder had never deployed.
With no path, flypod auto-detects the first of these directories that contains an index.html:
dist/
build/
out/
.output/public
.vercel/output/static
public/
_site/A candidate with no index.html of its own counts when exactly one folder inside it has one (Angular's dist/<app>/browser). Failing all of them, a folder of plain pages (an index.html and no package.json, _config.yml or other project file) deploys as it is. A project root is never deployed by guesswork: when nothing is found, the no_build_dir error's fix names the build command and the folder it writes, such as Run `npm run build`, then `flypod ./dist`.
The screen goes to stderr, and by default it is short: a confirmation, the URL, the command that ships a change, and when the site ends. Warnings always show. --verbose prints the full receipt. When stdout is not a terminal (a pipe, URL=$(flypod)), the URL alone is also printed there; in a terminal it is not repeated. In a non-TTY context, animation is stripped automatically. On success, flypod saves a folder-to-site link so flypod update knows where to ship next.
The deploy output shows no token. The site's manage_token is saved to projects.json (mode 0600) keyed by the folder you deployed from, which is how update, rollback, and versions work with no arguments. flypod token reveals it if you ever need the plaintext.
Flags
| Flag | Description |
|---|---|
--json | Emit a single JSON object (see below). |
--verbose | More of the same receipt. In a terminal: the rollback and history commands, and the site id, version id and source folder. With --json: also the site's tokens, the source folder, file and byte counts, timing, the expiry as days and as an ISO date, and a next_note. |
-q, --quiet | Print the URL on stdout. Nothing else. |
--timeline | Turn on History: a chip on the site's pages that opens earlier versions, and a page that compares two. Off by default, because it makes replaced versions public. See the version timeline. --history is the same flag. |
--no-timeline | Turn History off. --no-history is the same flag. If both are given, off wins. |
--new | Make a separate new site, even though this folder already has one. The earlier site stays live, and this folder then updates the new one. |
--comments | Turn comments on for the site, in anonymous mode. Needs a signed-in deploy: without one, the deploy still succeeds and the receipt says comments_skipped. |
--comments --mode <mode> | Same, choosing the mode: anonymous, passcode, or approved. Any other value fails with usage before anything is uploaded. |
-h, --help | Print usage and exit. |
-v, --version | Print the CLI version and exit. |
Examples
Deploy the auto-detected build directory:
flypodhttps://a3f1c2d4e5b60789.flypod.pageDeploy an explicit directory:
flypod ./distMachine-readable output:
flypod ./dist --json{
"ok": true,
"action": "created",
"url": "https://a3f1c2d4e5b60789.flypod.page",
"site_id": "a3f1c2d4e5b60789",
"version_id": "8f1a0c2e44b7d913",
"expires_at": 1782929906088,
"healed": ["spa-fallback"],
"warnings": [],
"link_saved": true,
"next": {
"update": "flypod update",
"versions": "flypod versions",
"rollback": "flypod rollback",
"keep": "flypod login --json"
}
}action is "created" on a folder's first deploy. A flypod in a folder that already has a site answers with the flypod update receipt, whose action is "updated".
On a deploy made while signed in, expires_at is null and next has no keep entry. While the site expires, next.keep is flypod login --json: see signing in from an agent.
Some keys appear only when they apply:
| Key | When |
|---|---|
skipped | Something in the folder was left out (secrets, version control, dependencies, .flypodignore matches, links pointing outside the folder). Each entry has a path and a reason. |
replaced_link | --new ran in a folder that was already linked to another site. { site_id, url } of the previous site, which is still live and still expiring. |
comments_enabled, comments_mode | --comments was passed. When it could not be applied because the deploy was not signed in, also comments_skipped: "not_signed_in" and comments_fix. |
timeline, history_url | --timeline or --no-timeline was passed. timeline is the state the site is actually in (a refused switch does not fail the deploy); history_url is present while it is on. |
manage_token | Only when link_saved is false (see below). |
The full receipt:
flypod ./dist --json --verbose{
"ok": true,
"action": "created",
"url": "https://a3f1c2d4e5b60789.flypod.page",
"site_id": "a3f1c2d4e5b60789",
"version_id": "8f1a0c2e44b7d913",
"expires_at": 1782929906088,
"manage_token": "…",
"claim_token": "…",
"source": "dist",
"files": 12,
"bytes": 48213,
"elapsed_ms": 742,
"healed": [],
"warnings": [],
"link_saved": true,
"expires_in_days": 14,
"expires_at_iso": "2026-10-19T12:00:00.000Z",
"next": { "update": "flypod update", "versions": "flypod versions", "rollback": "flypod rollback", "keep": "flypod login --json" },
"next_note": "This folder is linked to a3f1c2d4e5b60789. Run `flypod update` (or plain `flypod` again: in a linked folder it updates) to publish changes to this same URL…"
}Notes
warningslists problems that survived the deploy. Each has acodeand afix_recipenaming the change to make. A deploy can succeed and still be broken for visitors, so checkwarnings.lengthbefore you treat the URL as good.healedlists what flypod fixed for you —spa-fallbackmeans it recognised a single-page app and unknown paths now serveindex.html. Both arrays are always present, so no guard is needed.- The default receipt carries no tokens. They are credentials that cannot be rotated, the CLI has saved what it needs, and a receipt is what gets pasted into chats and logs. The one exception: when
link_savedisfalsethe receipt includesmanage_token, because nothing else is holding it. nextholds the literal commands for what comes after. Usenext.updateto ship a change; in a linked folder plainflypoddoes the same. When the link could not be saved, everynextcommand carries--site <id>.flypod --newin a folder that is already linked makes a separate site and points the folder at it. It says so before uploading (one line on stderr under-q), and the--jsonreceipt carriesreplaced_linkplus anew-site-in-linked-folderwarning whosefix_recipesays the earlier site stays live,flypodagain ships to the new one, andflypod update --site <old id>ships to the earlier one.- When
flypodruns as an update and the update is refused, the error'sfixends by namingflypod --newas the way to make a separate site from this folder instead. - Keys that don't apply are omitted, not set to
null: under--verbosean owned deploy has noclaim_tokenkey, an anonymous one has noowner_account_idkey. expires_atis epoch milliseconds on anonymous deploys, andnullwhen the site is owned.- Use
--quietin scripts to capture the URL cleanly. It prints the URL on stdout and nothing else — no token. - What is uploaded: everything in the folder except
.git,.hg,.svn,node_modules,.DS_Store,Thumbs.db,.envand.env.*, at any depth. Add gitignore-style patterns to a.flypodignorein the folder to leave out more. What was left out is listed after the deploy (skippedin--json). - Against flypod.dev, the CLI checks the size limits (200 files, 10 MB) before uploading and fails with
too_large, naming the limit, instead of uploading everything to be refused. A self-hosted server sets its own limits and the CLI leaves the check to it. - A path that does not exist fails with
no_build_dir(orunknown_commandwhen it is a bare word close to a verb), before anything is uploaded. - To make an anonymous deploy permanent later, just run
flypod login— it claims every anonymous site this CLI remembers, in one step. - See Tokens and ownership for how account sessions,
manage_token, andclaim_tokendiffer.
Authenticate first with flypod login to get permanent, owned deploys from the start.
If flypod cannot write the folder link — a read-only or ephemeral FLYPOD_CONFIG_DIR, the classic CI container — it says so loudly and prints the manage_token, because at that point you are the only possible holder of it. Under -q that same case emits the token on stderr. This is the one moment the token belongs on screen.