Skip to content

Host

host

Defines the launcher-owned Modal app that hosts a full Mirumoji instance

mirumoji modal deploy lets users run the entire stack (the FastAPI server and the built React frontend) privately on their Modal account

The shared deploy lifecycle functions lives in mirumoji.modal

Compute
  • The deployed mirumoji-host app keeps exactly one container warm for its entire lifecycle, so background tasks are never cut off by a scaledown, the in-process job queue keeps running, and the single-writer SQLite database only ever has one instance

  • By default that container is CPU-Only and runs the modal backend, offloading every GPU task to a second app (mirumoji-offload, the same one the local modal backend uses, reusing the same mirumoji config variables) that always scales to zero, so a GPU is paid for only while a job runs

  • Setting MIRUMOJI_HOST_ON_GPU instead runs that one container on a GPU with the local whisper backend in-process, so no offload app is used (see build_host_app for the two modes)

Persistence
  • A named modal.Volume is mounted at DATA_MOUNT, a path separate from the server's data path. The server reads and writes user data on the container's local disk, and a background task (see launcher.modal.app) mirrors it to the volume, keeping media and database I/O off the slow volume FUSE layer while Modal's volume commits still persist it durably

  • mirumoji modal deploy creates that volume in the user's Modal workspace when it doesn't exist, and mirumoji modal stop -v allows the user to delete it at any time

Image
  • When deployed as a full mirumoji instance to Modal, a single FastAPI app serves both the frontend and the server (unlike when running locally with docker compose, where the frontend is served by Nginx instead)

  • The image is composed at runtime with Modal's Python SDK from the published backend image (CPU, or GPU for a GPU host) with the published frontend build copied in, so mirumoji modal deploy always uses the latest published releases (see _host_image)

HF_CACHE_DIR = '/root/.cache/huggingface' module-attribute

Where the faster-whisper model cache lives in the backend images

The GPU host grafts this directory from MODAL_GPU_IMAGE (which pre-caches the model) onto BACKEND_GPU_IMAGE, so the first transcription loads it from disk instead of downloading it

build_host_app(env, host_config, *, version, on_gpu, nonpreemptible)

Builds the deployable Modal host app with the user's config injected

Host Modes
  • By default the web container is CPU-Only and runs the modal backend, offloading transcription and conversion to the on-demand mirumoji-offload GPU worker, so a GPU is paid for only while a job runs

  • When MIRUMOJI_HOST_ON_GPU is 1 the container runs on the GPU named by MIRUMOJI_MODAL_GPU with the local whisper backend in-process, so there is a single app and no offload worker, at the cost of an always-warm GPU. The transcribe backend is injected here so the CLI and the GUI never have to decide it

Injected Environment
  • env carries the user's relevant resolved configuration (Modal Tokens + LLM Keys + Web Password + Modal Config) as an inline Secret, so the container behaves like a local server with the frontend served alongside it

  • The secret is passed inline and bundled with the deploy so that the user never has to manage a persistent Modal secret

Resources
  • host_config sizes the web container itself, so cpu and memory are reserved rather than left to Modal's fractional default, which would throttle the server enough to fail its health check and get the container recycled

  • max_inputs sets how many requests the one container serves at once

Function Registration
  • web stays a plain module-level function so include_source can re-import it cleanly by name in the container

  • The endpoint decorators (asgi_app wrapped in concurrent) and the deploy-time config (image, volume, secret, scaling) are applied here at registration, so nothing decorates web at module scope (a module-level web endpoint is re-imported as a bare PartialFunction the runtime rejects, unlike the offload worker's Cls path)

Single Container
  • min_containers=1 keeps one warm container so a background transcription is never cut off and the in-process job queue runs

  • max_containers=1 pins the count to one, since the server holds in-process state and writes a single-writer SQLite file on local disk (mirrored to the volume), so it can't run as multiple replicas

  • The pinned count also makes a scaledown window unnecessary, so none is set

Non-Preemptible
  • MIRUMOJI_HOST_NONPREEMPTIBLE=1 runs the CPU host on guaranteed capacity (a 3x price) so a spot reclaim never restarts it mid-job

  • Modal forbids non-preemptible GPU functions, so this is rejected when combined with the GPU host

Parameters:

Name Type Description Default
env dict[str, str]

The environment injected into the container as an inline secret

required
host_config dict[str, str]

The resolved host reservations (CPU cores, memory in MiB, and max concurrent requests), each already defaulted by the caller, that size the web container

required
version str

The published image version to compose from

required
on_gpu bool

Run the host on a GPU (a GPU host) with the in-process backend, instead of a CPU host that offloads to the worker

required
nonpreemptible bool

Run the CPU host on non-preemptible capacity

required

Returns:

Type Description
App

The configured mirumoji-host app with web registered

Raises:

Type Description
ModalError

If a host reservation is not a number, or if a non-preemptible GPU host is requested (an unsupported combination)

ensure_host_deployed(env, host_config, *, version, on_gpu, nonpreemptible, force=False)

Creates the modal.Volume if it doesn't exist and deploys the Modal host app unless one with the same version and host mode is already live

Idempotent and identity-tracked through mirumoji.modal: it never duplicates the app, rolls it forward when the resolved version changes, and redeploys when the host mode (GPU / non-preemptible) or a reservation changes at the same version (folded into the deploy tags, see below)

Parameters:

Name Type Description Default
env dict[str, str]

The environment to inject into the container

required
host_config dict[str, str]

The resolved host reservations sizing the web container (see build_host_app)

required
version str

The published image version to compose from and track

required
on_gpu bool

Run the host on a GPU, instead of a CPU host that offloads to the worker (see build_host_app)

required
nonpreemptible bool

Run the CPU host on non-preemptible capacity

required
force bool

Redeploy even when the same version is already live, to roll out a code or image change without a version bump

False

Raises:

Type Description
ModalError

If credentials are missing or the deploy fails

web()

Builds and returns the Modal host FastAPI application

Container-Side
  • This runs in the Modal container, so it imports the app factory lazily and points it at the frontend copied into the image

  • The endpoint decorators (asgi_app, concurrent) are applied at registration in build_host_app, not here, so web stays plainly re-importable

Returns:

Type Description
FastAPI

The host FastAPI application