Skip to content

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.

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.

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:

Terminal window
npm run build
muhkoo 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 you

Deploy on every push to main with the bundled .github/workflows/deploy.yml. Set these repository secrets (Settings → Secrets and variables → Actions):

SecretWhat
MUHKOO_DEPLOY_KEYapp secret key (mk_live_sk_…) — authorizes the deploy
MUHKOO_APP_IDthe app id
VITE_MUHKOO_KEYapp publishable key (mk_live_pk_…) — baked into the bundle

If you’re rolling your own deploy, the API is under /api/apps/:appId/hosting, authorized by Authorization: Bearer <app sk | dev session>:

MethodPathBodyPurpose
PUT/hosting/blob/:sharaw file bytesUpload 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/hostingSite status: url, current release, bytes, history.
POST/hosting/rollback{ releaseId }Re-point to a prior release.
DELETE/hosting/releases/:releaseIdDelete a non-current release from history.
DELETE/hostingUnpublish.

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.

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-origin
Cross-Origin-Embedder-Policy: credentialless
Cross-Origin-Resource-Policy: same-origin

credentialless 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": true to your app.json (top level, or under a "hosting" object) and run muhkoo provision.
  • Portal — App Detail → Hosting → toggle Cross-origin isolation.
  • APIPATCH /api/apps/:appId (see below).
MethodPathBodyPurpose
PATCH/api/apps/:appId{ "crossOriginIsolation": true }Turn isolation on/off. Authorized by Authorization: Bearer <dev session>.

The change takes effect immediately — no redeploy needed.

Serve your app on your own domainapp.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.

  1. In the portal, open your app → Custom domains → enter your hostname (e.g. app.yourcompany.com) → Add domain.

  2. The portal shows two DNS records. Add them at your DNS provider:

    TypeNameValueWhy
    CNAMEapp.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.
  3. 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.

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:

  1. Add www.yourcompany.com (not the bare yourcompany.com) as the custom domain, and add the two records above for the www host.

  2. At your registrar, turn on domain forwarding for the root: yourcompany.comhttps://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.dev with your publishable key from <slug>.apps.muhkoo.dev; the default CORS allowedOrigins: "*" 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.