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 deploy script shipped by the app-builder template (scripts/deploy.mjs). It walks dist/, uploads only changed files, commits a release, and prints the URL:

Terminal window
npm run build
MUHKOO_DEPLOY_KEY=mk_live_sk_… MUHKOO_APP_ID=<appId> node scripts/deploy.mjs
# or just: npm run deploy

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.

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

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.