For developers
Publish a site you built with Astro, Vite, Hugo or Eleventy
We serve files. We never run a build — there is no build image, no install step and no command of yours that ever executes on our side. So the build runs where it already runs, on your machine or in your CI, and what you publish is the folder it wrote.
That is the entire difference between this and a host with a build pipeline, and everything below follows from it.
Which folder
Your build already writes one. These are the defaults:
| Tool | Output folder |
|---|---|
| Astro | dist |
| Vite | dist |
| Hugo | public |
| Eleventy | _site |
| Jekyll | _site |
| Next.js, static export | out |
| Create React App | build |
If yours is not here, it is whatever your own config says — the folder with index.html at its top level is the one.
Build for the root of a domain, not for a subfolder. Your site is served at the root of its own address, so a base path of / is what you want. A site built for /my-repo/ — which is what GitHub Pages often needs — will load its home page here and then fail to find its own stylesheets.
Route one: the command line
npx lemonade-host@latest deploy ./dist
That zips the folder, sends it, and prints the address. The package is lemonade-host, not lemonade — the bare name on npm belongs to somebody else.
Given no folder at all, the CLI looks for dist, build, out, public and _site, in that order, and takes the first one that exists and contains index.html. Failing all five it publishes the current directory, which is rarely what you meant, so name the folder in anything scripted.
You need a deploy token: portal → your site → Deploy tools. It reaches that one site, can publish and read its deploy history, and nothing else. See deploy tokens and the CLI reference for every flag.
--include-hidden publishes secrets. It is documented as a way to include dotfiles and node_modules, and it does that — but the same switch also lifts the guard on .env, .env.local, .npmrc, .netrc and SSH private keys. Reach for it knowing that.By default every dotfile, every dot-directory, node_modules, .git and the usual build detritus are skipped, so a stray .env in your output folder is not published by accident. Symlinks are skipped always, and are never followed.
Route two: GitHub, with the publish folder
Connect the repository once and every push to the branch you chose republishes. Your site → GitHub → install the app on that one repository, then set:
- the branch — the repository's default branch unless you say otherwise;
- the folder inside the repository to publish — empty means the repository root;
- automatic publishing — on by default.
The folder is a field you fill in. Nothing is detected: we take what is in the repository at that path and serve it. So this route works when the built output is committed. If your repository holds source that needs npm run build, either commit the build output to a folder and name it here, or build in CI and publish with the CLI instead.
Publish from GitHub has the rest, including what happens when a push does not publish.
Route three: build in CI, publish with the CLI
The common shape, and the one to use when you do not want built output in your history. Put the token in a repository secret called LEMONADE_TOKEN.
name: Publish
on:
push:
branches: [main]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run build
- run: npx lemonade-host@latest deploy ./dist --yes --json
env:
LEMONADE_TOKEN: ${{ secrets.LEMONADE_TOKEN }}
--yes because there is nobody to answer a prompt — though the CLI auto-confirms when its input is not a terminal, so it is belt and braces. --json so a failed step leaves your logs something parseable. The command exits non-zero when a publish is refused, which fails the job.
For Hugo or Eleventy, swap the build step and the folder: ./public and ./_site respectively.
What a publish does
Each publish replaces the whole site. It is not a sync — files you do not send stop being served. That is deliberate, and it is the only behaviour where deleting a page works the way you expect.
The limits, all of them checked before anything changes:
| The zip | 256 MB |
| Any single file | 25 MB |
| Number of files | 20,000 |
| The whole site, unpacked | 1 GB |
A refused publish changes nothing — the release that was live keeps serving. Every refusal and its fix. The previous release stays restorable for seven days: going back to an earlier version.
What does not come with it
No server-side code of any kind, no request-time environment variables, no rewrites or redirect rules read from your files. If your framework has a server mode, build it in its static mode. Your CI secrets stay in your CI — nothing we hold is ever read at request time, because nothing of yours runs at request time.
Dynamic behaviour that a static site normally cannot have is available as apps, one of which is a working contact form.
Not sure which route suits you? The decision table is shorter than this page. Coming from another host? Moving your site here.
A site is 99¢ a month billed yearly, or $2.99 billed monthly, and a staging copy is a second site at the same price — the full price list.