Skip to content

Build Pipeline

TMA.sh builds from your linked GitHub repository and deploys immutable outputs. This page describes the exact flow used by the platform worker and background build worker.

Build settings come from your project’s buildConfig:

{
"installCommand": "npm install",
"buildCommand": "npm run build",
"outputDir": "dist"
}

For newly created dashboard projects, these are the default values unless you change them in project settings.

The deployment pipeline is:

1. Trigger deployment (GitHub webhook, dashboard, or CLI)
2. Create deployment record (status: queued)
3. Enqueue build job
4. Build worker claims deployment (status: building)
5. Build container:
a. Clone repo at branch/commit
b. Run install command at repo root
c. Run build command at repo root
d. Validate output (`index.html` + size limits)
e. Upload static files to R2
f. Bundle optional API routes / bot handlers
6. Background deploy steps:
a. Persist build metadata and build log
b. Provision/update KV and optional D1
c. Deploy optional API/bot workers
d. Activate production or preview routing
7. Deployment status becomes ready

If any step fails, deployment status becomes failed and existing production routing is unchanged.

  • Install/build command timeout: 5 minutes per command inside the build container
  • Container request timeout: 10 minutes end-to-end for the background builder call

Deployment size limits (enforced at build time)

Section titled “Deployment size limits (enforced at build time)”

Before uploading files to R2, TMA.sh validates your build output:

ArtifactLimit
Static output (total)800 MB
Code-like static file (.html, .css, .js, .mjs, .cjs, .wasm, .map, .webmanifest)10 MB per file
Other static file (images, fonts, media, docs, etc.)25 MB per file
API route bundle (server/api/index.ts or .js)10 MB
Bot handler bundle (bot/index.ts or .js)10 MB

If any limit is exceeded, the deployment fails during validation with a clear error in build logs.

A deployment moves through a fixed set of statuses:

queued → building → deploying → ready
failed
ready/failed (older retained window) → cleaned → purged
  • queued — Deployment record exists, waiting for a build container.
  • building — Build container is running: cloning/installing/building.
  • deploying — Build succeeded. Assets are being uploaded and routing is being updated.
  • ready — Deployment is live and serving traffic.
  • failed — Something went wrong. Build logs contain the error details.
  • cleaned — Marked as cleaned by retention or preview cleanup logic.
  • purged — Final cleanup state after cron processes cleaned deployments.

TMA.sh is primarily a static hosting platform, but it supports lightweight server-side API routes for cases where your Mini App needs backend logic.

If your project contains a server/api/index.ts or server/api/index.js file, TMA.sh detects it during the build step, bundles it with esbuild, and deploys it to Cloudflare Workers for Platforms. Your API routes are then accessible at:

https://{project}.tma.sh/api/* (same-origin proxy)
https://pr{number}--{project}.tma.sh/api/* (same-origin preview proxy)
https://{project}--api.tma.sh/* (dedicated API host)
https://pr{number}--{project}--api.tma.sh/* (dedicated preview API host)

Server routes have access to KV storage, environment variables defined in your project’s secrets, and the managed D1 database (if provisioned).

The only requirement is that your entry file exports a default object with a standard fetch handler. Any framework that produces this shape works — Hono, itty-router, or plain Web API:

// server/api/index.ts -- using Hono (recommended)
import { Hono } from "hono"
const app = new Hono()
app.get("/api/health", (c) => {
return c.json({ status: "ok" })
})
export default app
// server/api/index.ts -- plain fetch handler, no framework
export default {
fetch(request: Request, env: Record<string, unknown>) {
return Response.json({ status: "ok" })
},
}

When the build output includes database migration SQL statements, TMA.sh provisions/migrates a D1 database during the deploy step:

  1. First deploy with migrations — A new D1 database is created for the project and migrations are applied in order.
  2. Subsequent deploys — Only new (pending) migrations are applied. Already-applied migrations are tracked and skipped.
  3. No migration payload in build output — The migration step is skipped and no database is provisioned.

Migration application happens after the build succeeds and before routing tables are updated. If a migration fails, the deployment is marked as failed and the previous production deployment continues serving traffic. Storage quota is checked against your plan’s limit before migrations are applied.

A managed database requires a Pro or Team plan. See the Managed Database guide for setup instructions.

If your project contains a bot/index.ts or bot/index.js file, TMA.sh bundles it separately and deploys it as a bot webhook handler. This allows your Telegram bot to respond to commands and messages alongside serving the Mini App.

Build logs are streamed in near real time through KV while a build is running, then persisted to R2 for long-term retrieval after completion. Logs are accessible from the dashboard and CLI (tma logs). Typical log content includes:

  • Dependency installation output
  • Build command output (stdout and stderr)
  • Asset upload summary (file count, total size)
  • Route activation/update messages

Builds run in isolated Cloudflare Containers with:

  • Runtime: Bun (latest stable)
  • Memory: Sufficient for typical frontend builds
  • Timeout: Builds that exceed the time limit are terminated and marked as failed
  • Network: Outbound access for installing dependencies from npm registries
  • Isolation: Each build runs in its own container with no access to other projects