# When Splitting Serverless Apps Earns Its Keep

My Weird Prompts — an AI-generated podcast. Daniel Rosehill sends in a prompt; AI hosts Corn and Herman discuss it. Verify important claims independently.

- Episode: 6295
- Published: 2026-10-27
- Duration: 21:55
- Audio: https://episodes.myweirdprompts.com/audio/serverless-app-boundaries-deployment-cadence-41957.m4a
- Tags: cloud-computing, containers-and-virtualization, devops, open-source, ai-inference
- Web page: https://myweirdprompts.com/episode/serverless-app-boundaries-deployment-cadence-41957/
- JSON: https://myweirdprompts.com/api/episodes/serverless-app-boundaries-deployment-cadence-41957.json

## Summary

Architectural boundaries often reflect deployment schedules rather than execution limits. On platforms like Modal, grouping web endpoints, background cron jobs, and heavy GPU workers into a single app provides atomic deployments without sharing containers, scaling budgets, or container images. Splitting these workloads into separate apps isolates blast radiuses and decouples release cadences, but it also trades type safety for cross-app lookup risks and version coordination. True decoupling depends on whether components actually ship on independent schedules.

## The prompt

> On serverless platforms like Modal, Cloud Run or AWS Lambda, where should you draw the line between one app and many? My podcast's whole production backend is a single Modal app: the web endpoints, the episode orchestration, the GPU text-to-speech workers, cron jobs and so on. My first instinct was that this is bad design, but each function already scales on its own, in its own containers. So what does splitting into separate apps actually buy you? Platforms stack several levels: workspace or project (mostly a billing and permissions boundary), then app or service, then function. I'm less interested in the billing level than the technical one. What are the real criteria for a boundary at the app level: how often each part deploys, how far a bad deploy can spread, a heavy GPU image versus a light web image, secrets, versioning the contract between parts? And at what point does splitting turn into a distributed monolith that costs more than it saves?

## Show notes

Organizing backend infrastructure forces teams to decide between monolithic cohesion and isolated microservices. In Modal, an app defines an atomic deployment boundary and shared namespace, not a shared physical machine or scaling ceiling. Individual functions within the same app still retain independent container scaling, image specifications, and function-level secret attachments. Consequently, splitting workloads like GPU speech workers from web endpoints should be driven by differing deployment cadences or blast-radius isolation rather than mere hardware differences or concerns about idle resource costs. However, carving services into separate apps introduces real trade-offs: cross-app function lookups forfeit compile-time type safety, require deployed targets rather than ephemeral development instances, and demand disciplined API design to avoid creating a distributed monolith.

## Sources

- [Modal docs](https://modal.com/docs/guide/apps) — Apps, Functions, and entrypoints
- [Modal docs](https://modal.com/docs/guide/project-structure) — Project structure (component Apps, `App.include`, module mode)
- [Modal docs](https://modal.com/docs/guide/trigger-deployed-functions) — Invoking deployed Functions (lookups, version pinning, limitations)
- [Modal docs](https://modal.com/docs/guide/environments) — Environments
- [Modal docs](https://modal.com/docs/guide/secrets) — Secrets
- [AWS Compute Blog](https://aws.amazon.com/blogs/compute/best-practices-for-organizing-larger-serverless-applications/) — Best practices for organizing larger serverless applications
- [Google Cloud](https://cloud.google.com/run/docs/deploying) — Deploying (Cloud Run revisions, image limits, service naming)
- [Google Cloud](https://docs.cloud.google.com/run/docs/overview/what-is-cloud-run) — What is Cloud Run (service / job / worker pool / instance), (search snippet only)
- [serverlessfirst.com](https://serverlessfirst.com/setting-boundaries/) — Setting boundaries in your serverless application
- [catio.tech](https://www.catio.tech/blog/monolith-to-microservices) — Monolith to Microservices: When to Migrate (and When Not) (2026 retrospective; Prime Video case), (search snippet only)
- [László Nemes](https://laszlonemes.com/blog/micromonoliths) — Two kinds of micromonolith, (search snippet only)
- [The Neural Base](https://theneuralbase.com/modal/learn/advanced/app-name-vs-workspace/) — app_name vs workspace | Modal Advanced Course, (third-party; its secrets claim conflicts with source 5, treat as outdated)

## Transcript

Corn: ...putting everything in one box only worries me if opening it changes everything inside. Daniel's looking at our production backend and having that reaction. Web endpoints, episode orchestration, GPU speech workers, scheduled jobs, all deployed as one Modal app whenever the pipeline merges. His instinct says bad design. His observation says each function already has its own containers and scales independently. He wants to know what another app boundary actually buys.
Herman: The useful word there is deployed. Modal defines an app as something that groups functions for atomic deployment and provides a shared namespace. That describes what changes together, not what must run together.
Corn: Our entire production department shares a deploy button.
Herman: It does, although sharing that button doesn't mean sharing a machine, an image, or a scaling limit. That's why importing the usual monolith anxiety can lead you astray here.
Corn: I also don't want to dismiss the anxiety just because the GPU scales properly. A web correction shouldn't casually replace the machinery making our voices. Those are different concerns, Herman.
Herman: They are. The platform's resource model separates them more cleanly than the word app suggests.

Herman: At the outer level you've got the workspace, or the project on other platforms. Billing, access, environments, resource ownership. Those boundaries have technical consequences, but Daniel's concern sits below them. The app is the deployment namespace. Inside it, each function is the independent execution and scaling unit.
Corn: Meaning a quiet cron function doesn't inherit the speech worker's enthusiasm for expensive equipment.
Herman: Correct. Well... correct by default. Minimum container settings can deliberately retain capacity. Otherwise, with no live inputs, a function can have no running containers and no compute charge even while its app remains deployed. A deployed app isn't a permanently occupied server.
Corn: Finally, an institution that understands being available without doing anything.
Herman: Your ideal work arrangement already has documentation.
Corn: Mine requires fewer decorators.
Herman: Modal's exact phrase, “atomic deployment,” matters. Redeploying the named app updates that deployment in place. You're submitting the app's functions together. Their independent scaling survives that operation.
Corn: I can have one source tree, several images, independent workers, and one release. Those are four separate choices, not a package deal.
Herman: Yes, and the labels change elsewhere. A Cloud Run service has its own endpoint and immutable revisions. A Lambda function is an execution unit, while AWS generally recommends organizing deployment repositories around groups of functions and resources forming a microservice. You can't substitute service for app across those platforms and preserve the meaning.
Corn: Particularly when someone presents a diagram with six rectangles and calls that evidence.
Herman: Six rectangles can mean six release schedules, six permissions domains, or six pieces of a single transaction. I'd ask what each line between them requires before counting them.
Herman: Would you split our speech worker immediately, Corn, knowing only that it's GPU work?
Corn: No. I'd want to know whether it's actually holding up web changes. Different hardware is suggestive. Different release behavior is evidence. If both parts change together for every episode feature, moving the GPU behind another app name might simply create another thing that must agree.
Herman: That distinction is where the practical decision starts.

Corn: Modal almost invites both answers. It recommends component apps for faster targeted builds and fewer conflicts with concurrent work, then gives you App.include to assemble those components into one deployable app again.
Herman: You can organize the speech and web components separately in Python, include both in a full app, and deploy the composition. That preserves source organization without promising separate releases. The composed result still has one function namespace, so names must remain unique. Otherwise one can shadow another.
Corn: Two functions enter. One keeps its name.
Herman: Source modularity, deployment modularity, and runtime independence don't have to match. The platform supports picking those separately. That isn't indecision in the documentation. They're different tools.
Corn: Deploy cadence seems the strongest ordinary reason. Suppose the web endpoints change daily and the speech image monthly. The web developer keeps walking through the speech build machinery for work that doesn't touch speech.
Herman: That can make targeted deployments much faster. One correction, though: it doesn't necessarily rebuild every heavy image layer from scratch. Caching and unchanged image definitions matter. A shared deploy makes that image part of the deployment process; it doesn't guarantee a full fresh GPU build on every merge.
Corn: The cache has been acquitted. The release meeting continues.
Herman: I'd measure elapsed deployment time and unnecessary work before splitting. If the heavy component barely affects the pipeline, the theoretical benefit could be tiny. If image construction or validation dominates it, a separate lifecycle becomes attractive.
Corn: You were about to separate every interesting piece of hardware.
Herman: I was about to separate the part I wouldn't want your web patch redeploying.
Corn: You've installed enough interesting tools to know those aren't equivalent.
Herman: Fair objection. The release pattern gets a vote before my affection for GPUs.

Herman: Blast radius is another reason. A named app gets updated in place, so a faulty release can affect all the functions shipped in it. Separate apps limit what that deployment directly replaces.
Corn: Directly is carrying weight. A perfectly healthy speech app still fails the episode if the caller sends it nonsense.
Herman: Deployment isolation doesn't guarantee behavioral isolation. Also, I don't have a Modal incident with measured losses to put against this. The blast-radius argument follows from its documented deployment semantics, not a published postmortem establishing how often this happens.
Corn: Would you accept the shared release if we could reliably return to the previous one?
Herman: More readily. Cloud Run makes revisions immutable. Even if you deploy using an image tag, the revision resolves that tag to a digest and keeps serving that exact image. That gives rollback and traffic management a concrete object to target.
Corn: A label that cannot quietly point at different machinery. I approve.
Herman: I predicted that before I finished saying immutable, Corn.
Corn: I prefer software that stays put while I'm asleep.
Herman: Image size is related but distinct. Modal functions can use different images within the same app. Splitting doesn't magically make the web container lighter if it was already using a light image.
Corn: It may make the release lighter without changing the container.
Herman: Right distinction. Other platforms impose harder image constraints. Cloud Run rejects individual image layers larger than nine point nine gigabytes when deploying from Docker Hub or an Artifact Registry remote repository. That's a layer limit under those deployment conditions, not a universal maximum total image size.
Corn: One enormous layer can ruin your architectural philosophy.
Herman: Secrets are a boundary people often draw unnecessarily. On Modal, secrets are attached per function. The GPU worker doesn't need the web endpoint's database password merely because they're in the same app.
Corn: Separate credential attachment solves that without separate releases. The environment handles the broader separation?
Herman: Each environment has its own secrets and its own default scope for object lookups. Development and production belong in that conversation. Splitting apps alone doesn't create that environment boundary.
Corn: Our script-only runs already use a separate OpenRouter key for billing attribution. They don't need their own app to have their own bill. The MCP server is already separate, but neither fact establishes where speech belongs.

Herman: Versioning gives the boundary a sharper technical purpose. A lookup can name a deployed app, a function, and a pinned version. Within that pinned app, subsequent function calls stay on the pinned version too.
Corn: Doesn't atomic deployment already give us that?
Herman: It gives a deployment operation, not a promise that an entire running call chain remains on one version across subsequent deployments. Pinning supplies that stronger coherence. That's a useful distinction.
Corn: Now comes the invoice.
Herman: Pinned containers sit outside the function's main maximum-container budget and have per-version limits. You must account for that separately, especially when every container consumes a database connection. Pinned functions also ignore the normal minimum-container setting, so there's no warm pool by default. Retention limits how far back you can pin.
Corn: We preserved the voice and refrigerated the startup.
Herman: Contract stability can buy you cold starts and different concurrency constraints. It isn't free rollback with all the ordinary capacity settings preserved.
Corn: Even unpinned lookups lose something. A function reference inside the Python app carries an interface the type checker can understand. Looking it up by app and function name doesn't preserve that knowledge automatically.
Herman: You can narrow types explicitly, build wrappers, and test contracts. You have to do that work. Renaming either lookup name can break a caller at runtime. Also, lookup targets must be deployed apps. An ephemeral app running in the development server won't serve as that target.
Corn: A boundary that looks cheap in production can make development surprisingly ceremonial.

Herman: I think of the deploy trigger as the bell. The bell is the boundary. Ring it, and everything on the other side of that wall comes down at once.
Ivan: Call sign, Marguerite. Seven four, two nine, seven four, one six. The bell is rung from the inside. The small key sleeps under the third flowerpot. Seven four. The donkey counts the change and finds it short. Nightjar.
Herman: Message received.
Corn: What on earth just came through?
Herman: A separate deploy is useful only if the caller can tolerate what changes independently. Otherwise you've moved the coordination out of the deployment tool and into your release procedure.

Corn: That's where I'd call it a distributed monolith. Web deploy waits for speech deploy, speech waits for orchestration, and someone has to remember which names and payloads match. We still release everything together, only now there are several opportunities to stop halfway.
Herman: What would convince you the split was earning its keep?
Corn: Independent releases that actually happen. Not the theoretical ability to deploy independently. I'd compare new cross-app lookups with releases that avoid touching the other component. If the lookups keep multiplying and the avoided joint releases don't, we're buying coordination without using the freedom.
Herman: I like that as an engineering heuristic. Not a published threshold, though. One valuable independent release could justify several calls. A hundred trivial releases might not justify one fragile interface.
Corn: The ratio starts an investigation. It doesn't pass sentence.
Herman: The coupling also matters more than the raw count. A single boundary exposing a huge internal object can be worse than several small, stable requests. Does speech accept text and a voice configuration? Or does it expect the caller's entire mutable episode record?
Corn: I'd send the minimum it needs. Otherwise every new field becomes a negotiation, and every internal refactor turns into an international incident between two apps owned by Daniel.
Herman: There's your distributed monolith without a large team or an HTTP client. The platform makes remote execution convenient, which lowers the cost of cutting a seam. It doesn't establish that the seam was useful.
Corn: The function call looks familiar enough that you forget the recipient can now have a different release history.
Herman: Or disappear under a renamed app. In the same Python app, static analysis can catch more interface mistakes before deployment. After lookup, you need explicit contract discipline. The network machinery being managed doesn't remove that obligation.

Corn: AWS seems to encourage more grouping than the phrase one function, one service suggests.
Herman: Its Compute Blog recommends repositories around groups of functions and resources defining a microservice. Payment processing is their sort of example: several related functions sharing resources. That's a different emphasis from Modal's independent functions under a deployment namespace.
Corn: Shared resources can also make apparently separate apps inseparable. Split the callers, leave one database schema that both assume, and a schema change still demands coordination.
Herman: Yes. An app boundary can't repair a shared-data contract by itself. You could have separate deployments and still need compatible database migrations, payload changes, and rollback behavior.
Corn: I'd rather admit that dependency than congratulate ourselves on hiding it behind a lookup.
Herman: A practitioner argument I find persuasive rejects separate repositories and pipelines for every single-purpose Lambda. It favors mixing approaches as needed. One repository can contain several deployable services. One pipeline can initially deploy them together. Neither choice prevents changing the release boundaries later.
Corn: Repository count is a terrible substitute for dependency analysis.
Herman: Particularly for a small project. A repository introduces maintenance, permissions, dependencies, and release machinery. None of those are inherently bad. They need a purpose beyond making a diagram look appropriately modern.
Corn: Thirty containers won't review the code for you.
Herman: They will produce enough logs to suggest otherwise.

Herman: Orchestration deserves separate treatment. Our episode workflow initiates stages, waits for outputs, handles retries, and decides what happens next. A long-lived coordinator can have a very different execution shape from a short web request or a GPU worker.
Corn: Moving that function to another Modal app wouldn't change how it waits.
Herman: Precisely the limit of the app-level fix. AWS warns that orchestration inside Lambda can become fragile and spend paid execution time waiting for external results. Step Functions represents the workflow separately and manages in-flight executions as workflows change.
Corn: A workload boundary, then. Not merely a different release schedule.
Herman: Yes. I'm not claiming Modal has identical Lambda waiting costs. The transferable concern is where durable workflow state lives and how a running episode survives code changes. A separate app may help organize that responsibility. A workflow system changes how it's executed.
Herman: Would you keep orchestration with the web layer if their deploy schedules matched?
Corn: Only if running episodes tolerate those deploys. A web page can accept a fresh request after a release. An episode might already have completed transcription and be halfway through speech. I wouldn't want its recovery plan to depend on whichever function version answers next.
Herman: That points toward durable state and explicit stage contracts, even before choosing app names. If the coordinator has those properties already, splitting it could be unnecessary. If it lacks them, a second app won't manufacture them.

Corn: The Prime Video story gets used as permission to abandon all this.
Herman: One team consolidated a distributed serverless workflow into a single process in two thousand twenty-three and reported a major cost reduction. The figure commonly repeated is around ninety percent. The framing was contested, and it wasn't a verdict on every Amazon workload or every microservice architecture.
Corn: One team's bill became everyone's religion.
Herman: It illustrates how data movement and orchestration overhead can outweigh benefits for a particular workload. It doesn't tell Daniel to put web serving and GPU inference into one process. They're already separate execution units.
Corn: Nor does it tell him to distribute every helper function. Our current arrangement gets independent scaling without separate release contracts everywhere. That's an actual platform capability, not an embarrassing compromise.
Herman: The uncertainty is how much independent release control this backend needs. Modal supplies composition, lookups, and pinning, but no numerical point at which an app must split. The build time, concurrent work, and coordination cost have to come from the project.
Corn: I'd resist promising deploy isolation before checking who still has to coordinate a change.

Hilbert: Coordinate is generous, Corn. At the allotment society we called it unlocking. I've been doing the separate-access arrangement for years. My neighbour Arthur had the shared tools on his side of the shed. My seed records were on mine. Every alteration required someone to walk over with a key. That was the boundary that mattered.
Herman: The key holder controlled when the change could happen.
Hilbert: He controlled when we could find out whether it had happened. In the mid eighties I kept the society's newsletter, seed inventory, and treasurer's reminders in one card cabinet. You would have liked its stable names, Corn. You settled down noticeably when Herman said immutable. Our rules required separate custody, so the cabinet sat across the shed partition with one drawer in Arthur's garden and the other three in mine. I could update the seed ledger without entering his garden. Reading the whole inventory required him to unlock the fourth drawer.
Corn: Independent access. Seasonal availability.
Hilbert: The minutes called it successful. We disputed that wording for eleven meetings. Arthur renamed the Treasurer drawer to Subscriptions. The reminder clerk couldn't find it. No reminders went out for three months. I had to correct the name on the diagram, but the diagram was inside the drawer. The society eventually sold the cabinet to a man who wanted somewhere to keep paint charts. Nine pounds. Arthur insisted on four pounds fifty despite having custody of one drawer.
Herman: A separate label outside the cabinet would have allowed the clerk to find the right drawer without changing your custody arrangement.
Corn: Unless changing the label also required Arthur's key.
Hilbert: It did. The noticeboard was on his side.

Herman: There's a distinction between owning a release and being able to complete it without someone else's intervention. Separate apps can provide the former while a fragile shared contract quietly removes the latter.
Corn: The common wrong belief is that one Modal app means the backend can't scale its parts separately.
Herman: Its functions already scale independently. Splitting the app changes deployment ownership and lookup contracts. It doesn't unlock independent GPU scaling that was previously missing.
Corn: For Daniel, one production app is defensible. I'd start there and split when a component demonstrates a different lifecycle or workload need. The useful future test is whether a change can finish without somebody walking over with another key. Better revision controls may make that easier without adding apps.
Herman: Thanks to Hilbert Flumingtop for producing the show.
Corn: For more along these lines, there's episode twenty, Architectural AI; and episode seventeen, Cloud Render Superpowers. This has been My Weird Prompts, the human-AI collaboration podcast.
Herman: If you enjoyed it, leave us a review wherever you listen.
Corn: See you tomorrow.
