Hosting
Muhkoo can host your app’s frontend too. Every app gets a DNS subdomain —
https://<slug>.apps.muhkoo.dev (the <slug> is your app’s slug) — and you
deploy your built static site there. No separate hosting account, no DNS to wire
up. Deploys are content-addressed (only changed files upload), instant (an atomic
release flip), and rollback-able.
Deploy auth — the app secret key
Section titled “Deploy auth — the app secret key”Deploys are authorized by your app’s secret key (mk_live_sk_… /
mk_test_sk_…) as a bearer token (a developer session also works). The secret key
is server-side only — never ship it in the browser bundle (that’s the
publishable pk). In CI it’s a repository secret. Rotate or revoke it in the
portal like any key.
Deploy
Section titled “Deploy”The simplest path is the muhkoo CLI (npm i -g @muhkoo/cli). It
walks dist/, uploads only changed files, commits a release, and prints the URL:
npm run buildmuhkoo deploy --app <appId> --key mk_live_sk_…# or, from a provisioned app dir (.muhkoo-app.json present): muhkoo deploy# the app-builder templates wire `npm run deploy` to run this for youGitHub CI/CD
Section titled “GitHub CI/CD”Deploy on every push to main with the bundled .github/workflows/deploy.yml. Set
these repository secrets (Settings → Secrets and variables → Actions):
| Secret | What |
|---|---|
MUHKOO_DEPLOY_KEY | app secret key (mk_live_sk_…) — authorizes the deploy |
MUHKOO_APP_ID | the app id |
VITE_MUHKOO_KEY | app publishable key (mk_live_pk_…) — baked into the bundle |
The deploy API
Section titled “The deploy API”If you’re rolling your own deploy, the API is under /api/apps/:appId/hosting,
authorized by Authorization: Bearer <app sk | dev session>:
| Method | Path | Body | Purpose |
|---|---|---|---|
| PUT | /hosting/blob/:sha | raw file bytes | Upload one file; the server verifies sha256(body) === sha and dedups. |
| POST | /hosting/releases | { manifest: { "<path>": "<sha>" } } | Commit a release (validates blobs, enforces quota, flips live). Returns { releaseId, url, bytes, files }. |
| GET | /hosting | — | Site status: url, current release, bytes, history. |
| POST | /hosting/rollback | { releaseId } | Re-point to a prior release. |
| DELETE | /hosting/releases/:releaseId | — | Delete a non-current release from history. |
| DELETE | /hosting | — | Unpublish. |
The manifest maps each file’s path (relative to dist/, forward slashes,
index.html at the root) to its blob sha. index.html is required; unknown
deep-link paths fall back to it (single-page-app routing). Asset files are cached
immutably; index.html revalidates, so a deploy is live immediately.
The platform keeps the last 10 releases per app (the live one is always kept); older releases auto-prune on deploy, and you can delete any non-current release.
Cross-origin isolation
Section titled “Cross-origin isolation”Some browser features — SharedArrayBuffer, and therefore multi-threaded
WebAssembly (e.g. ffmpeg.wasm, some game engines, OpenCV) — only work on a
cross-origin-isolated page. That requires the document to be served with the
Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy response headers,
which you can’t set from static files.
Muhkoo makes this a per-app, opt-in hosting setting. Turn it on and the platform serves your app with:
Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: credentiallessCross-Origin-Resource-Policy: same-origincredentialless is used (rather than require-corp) so your cross-origin
resources still load — CDN scripts, images, fonts, and CORS API calls — without
every third party having to send Cross-Origin-Resource-Policy. In the browser,
self.crossOriginIsolated becomes true and SharedArrayBuffer is available.
Enable it in any of three ways:
- Provision spec — add
"crossOriginIsolation": trueto yourapp.json(top level, or under a"hosting"object) and runmuhkoo provision. - Portal — App Detail → Hosting → toggle Cross-origin isolation.
- API —
PATCH /api/apps/:appId(see below).
| Method | Path | Body | Purpose |
|---|---|---|---|
| PATCH | /api/apps/:appId | { "crossOriginIsolation": true } | Turn isolation on/off. Authorized by Authorization: Bearer <dev session>. |
The change takes effect immediately — no redeploy needed.
Custom domains
Section titled “Custom domains”Serve your app on your own domain — app.yourcompany.com instead of
<slug>.apps.muhkoo.dev. Muhkoo issues and auto-renews the TLS certificate;
you keep your domain at whatever DNS provider you already use, and Muhkoo never
touches your DNS.
-
In the portal, open your app → Custom domains → enter your hostname (e.g.
app.yourcompany.com) → Add domain. -
The portal shows two DNS records. Add them at your DNS provider:
Type Name Value Why CNAME app.yourcompany.comcname.muhkoo.ioRoutes traffic to your app. CNAME _acme-challenge.app.yourcompany.com(shown in portal) One-time; lets Muhkoo issue + auto-renew the cert. Must be DNS-only — see below. -
Muhkoo verifies ownership from those records, then the domain flips to Active and serves your app over HTTPS (usually a few minutes). It only goes live after verification — an unverified domain is never served. The cert renews itself from then on.
Root (apex) domains
Section titled “Root (apex) domains”The traffic record is a CNAME, and DNS doesn’t allow a CNAME on a root (apex)
domain — yourcompany.com with nothing in front. Most registrars, GoDaddy
included, reject it outright (“invalid record”). Use a subdomain and forward the
root to it:
-
Add
www.yourcompany.com(not the bareyourcompany.com) as the custom domain, and add the two records above for thewwwhost. -
At your registrar, turn on domain forwarding for the root:
yourcompany.com→https://www.yourcompany.com, 301 / permanent. On GoDaddy this is Domain → Forwarding (not a DNS record) — GoDaddy manages the root’s A records for you.
Now www.yourcompany.com serves your app directly and the bare yourcompany.com
redirects to it. Nothing leaves your registrar and no DNS transfer is needed.
Manage domains over the API too, under /api/apps/:appId/hosting/domains
(Authorization: Bearer <app sk | dev session>): POST { hostname } to attach,
GET to list with live status + the records to add, DELETE /:hostname to detach.
- One site per app (the subdomain is the app’s slug, reserved to that app — two apps can never collide on a host).
- The hosted page calls
api.muhkoo.devwith your publishable key from<slug>.apps.muhkoo.dev; the default CORSallowedOrigins: "*"permits it. If you tighten CORS, add the hosting origin. - Rollback is a pointer flip — instant. The last 10 releases stay available to roll back to (or delete); the live release is never pruned.