# Deployments

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.

## 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.

## 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)**

```js
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**

```js
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)
```

**CLI**

```bash
harakumo deploy 12               # the project's GitHub repository
harakumo deploy 12 ./my-app      # upload a folder
harakumo deploys 12              # recent deployments with status and URL
```

**REST**

```http
POST  /api/projects/:id/deployments   { "repoUrl"?, "branch"? }     # branch must be the production branch
GET   /api/deployments/:id            # status, logs, preview_url, runtime_status, runtime_logs
POST  /api/deployments/:id/redeploy   # same files or commit again (rollback for GitHub)

# Deploy without a repository — a gzip tar (tar -czf app.tgz -C my-app .):
curl -X POST https://harakumo.com/api/projects/:id/deployments/upload \
  -H "Authorization: Bearer hk_live_…" -H "content-type: application/gzip" --data-binary @app.tgz

# … or files as JSON (binary files with "base64": true):
curl -X POST https://harakumo.com/api/projects/:id/deployments/upload \
  -H "Authorization: Bearer hk_live_…" -H "content-type: application/json" \
  -d '{ "files": [{ "path": "index.html", "content": "<h1>hi</h1>" }] }'
```
