Skip to content
Docs · Build & deploy

Deployments

Deploy from GitHub, a folder upload or Harakumo Repos. The pipeline recognizes static sites, edge programs and server apps and serves them at https://<slug>.harakumo.app.

View as Markdown
All topics
On this page

Three ways in, one pipeline: a GitHub repository (when you press Deploy or call the API), an upload (dashboard, CLI, SDK or files sent by an AI assistant), or a commit in Harakumo Repos. A failed deploy never replaces a working one.

Git repoUpload (folder / tar.gz)classifystatic outputbuild scriptNext.js / Node serverHarakumo edgebuild machinenpm ci + buildapp containernpm startsite liveapp live<slug>.harakumo.applive
queued→building→deploying→ready
GitHub optional — both transports feed the same pipeline

How your code is recognized, in order

  1. A worker.js (or _worker.js) at the root becomes an edge program with your production environment variables.
  2. A Next.js app that is not a static export becomes a server app.
  3. An index.html in the root or in dist/, build/, out/, _site/, public/, site/ or docs/ becomes a static site. (From Git, a repository with a build script only serves dist/, build/, out/ or _site/.)
  4. A package.json build script (Vite, Astro, Create React App …) builds on a build machine; the output becomes a static site.
  5. A package.json start script (Express and friends) becomes a server app.

A server app must not carry a static index.html in the places step 3 looks, or it is served as a static site. Keep its pages in another folder. Server apps (steps 2 and 5) are on Pro and up; static sites, edge programs and functions deploy on every plan.

Static sites

  • Served from the edge cache (the workspace's CDN setting), then object storage. They scale with traffic.
  • Up to 500 files per site.
  • Dotfiles (.env, .git …) and key files (.pem, .key, id_rsa …) are never served; /.well-known/ is.

Server apps

  • On Pro and up. On Hobby a deploy that would run as a server app answers 402 plan_limit (with required_plan) before anything is built. Server apps that were already live on a free workspace when server apps moved to Pro keep running and can be redeployed. A server app deployed on a paid plan is not one of those: if its workspace moves to Hobby, new deployments of it are refused.
  • Run with npm start (or next start) on Node 22 in one container: about a quarter of a CPU and 1 GB of memory, one copy.
  • Listen on process.env.PORT. Production environment variables are injected at deploy.
  • After 15 idle minutes the app sleeps; the next visitor sees a short starting page while it wakes (usually seconds, longer on a cold start).
  • The disk is wiped on every sleep and deploy: keep uploads and data in Storage and Databases.
  • If the app crashes three times, the project shows App crashed with the app's own output, and visitors get a plain 503 page.
  • At most 40 server apps are awake across the platform today; sleeping apps do not count.

From GitHub

  • Set the repository once (at project creation or PATCH /api/projects/:id { repoUrl, repoBranch }). Only github.com repositories are supported.
  • Deploys run when you press Deploy or call the API. A push to GitHub does not deploy on its own yet.
  • Only the production branch deploys. A different branch is refused unless repoUrl is set in the same call, which makes that branch production.
  • Private repository: add a GITHUB_TOKEN environment variable to the project (a fine-grained token with read access to that repository). It is used only for that project.

From an upload

  • Up to 25 MB compressed, 40 MB unpacked, 10 MB per file and 5,000 files.
  • Leave out node_modules and build output: the build machine installs and builds. The dashboard leaves node_modules, .next, .git, .vercel, .turbo, .cache and .env files behind for you.
  • Put secrets in environment variables, never in an uploaded .env file.
  • A key limited to the project can deploy it; a read-only key can read deployments and logs.

Redeploy and roll back

POST /api/deployments/:id/redeploy deploys the same source again as a new deployment: the kept files of an upload (the serving deployment's files are kept) or a GitHub deployment's commit. Redeploying an older GitHub deployment is a rollback. This is also how environment variable changes take effect.

Build minutes

The time each deploy takes, failed ones included, counts toward the build minutes the plan includes each calendar month (UTC): 100 minutes hobby, 1,000 minutes pro, 10,000 minutes enterprise, on Hobby across all your free workspaces. On Hobby, new deploys are refused once the plan's build minutes allowance is used up (402 usage_limit). On Pro and Enterprise, use past it is charged daily from the Balance at $0.01 per minute, and new deploys pause (402 usage_paused) only while a charge cannot be paid. Billing → Usage shows the month so far.

Status and logs

A deployment moves queued → building → deploying → ready, or failed with the reason in its logs. Server apps also report runtime status (running, crashed or failed) and recent output in runtime_status and runtime_logs.

Each deployment records who started it. triggered_by is the person: whoever was signed in, the owner of a connected AI assistant, or for an API key (the CLI, the SDK, an agent) whoever created the key. triggered_via names the API key when one was used, for example "API key: ci", and is empty otherwise.

Deploy a folder and wait for the URL (SDK)
const { deployment } = await hk.deployments.uploadDir(projectId, './dist');
let d = deployment;
while (!['ready', 'failed'].includes(d.status)) {
  await new Promise(r => setTimeout(r, 3000));
  ({ deployment: d } = await hk.deployments.get(deployment.id));
}
console.log(d.status, d.preview_url);

Reference

SDK
await hk.projects.update(project.id, { repoUrl: 'https://github.com/you/repo', repoBranch: 'main' });
const { deployment } = await hk.deployments.create(project.id);            // latest commit on the production branch
const { deployment: d } = await hk.deployments.get(deployment.id);        // status, logs, preview_url

const { deployment: up } = await hk.deployments.uploadDir(project.id, './my-app');   // Node: a folder
// await hk.deployments.uploadArchive(project.id, tarball);                // a gzip tar (Buffer, Uint8Array or Blob)