Firmware in embedded systems has traditionally been designed as a monolith, primarily due to limited resources and the need for clear, direct control over hardware. In many products, this model is still appropriate. Problems arise when a device stops being a “closed project” and instead remains in use and under development for many years. In such cases, the monolith rarely fails spectacularly. It tends to become fragile.
In this context, the term “microservices” is often overused. The goal is not to transfer server-side patterns one-to-one onto an embedded device. It is about something simpler and, at the same time, more difficult: defining stable boundaries of responsibility within the firmware.
In this article, we show when a “working monolith” begins to generate systemic risk and how to carry out a migration without an expensive “big rewrite.” The objective is not an ideological fight against the monolith, but a practical method of managing complexity. This approach allows firmware to evolve faster and with greater predictability, without weakening secure boot mechanisms or the system’s trust boundaries.
Monolithic firmware stops scaling not because it is outdated, but because it handles change poorly. As a product evolves, the number of features, hardware variants, and special cases grows. Each subsequent modification increases the risk of unintended side effects. At what point does a “working” firmware stop being an asset and start becoming a source of systemic risk that ultimately forces costly redesigns?
In a monolith, responsibility boundaries are blurred. Shared state, global dependencies, and implicit assumptions make the codebase weak. Local changes no longer remain local in their impact. Over time, testing ceases to focus on individual components and is reduced to costly validation of the entire firmware image. This is a direct consequence of a system architecture that exists only implicitly, inside developers’ heads. Field updates become high-risk operations, and the architecture begins to hinder development rather than support it, particularly when a single codebase must serve different hardware platforms.
Additionally, a monolith makes deliberate responsibility management difficult. When everything is part of a single firmware image, it becomes hard to identify which component is truly responsible for a fault or quality degradation. This encourages firefighting rather than systematic architectural improvement and increases technical debt with every release, often at the expense of energy efficiency.
Microservices in embedded systems are not an attempt to imitate server-side architectures. Their relevance stems from the growing complexity of firmware, not from technological fashion. Unlike cloud systems, embedded environments operate under strict constraints in terms of timing, memory, and energy. In embedded environments, microservices only make sense when built on top of a sound layered architecture, not as a substitute for it. Without layers that clearly separate platform concerns from application logic, “services” merely redistribute the monolith into smaller binaries.

In such an environment, a “service” is not a deployment unit but a unit of responsibility. Its boundaries must reflect stable system functions, not team structures or repository layouts. Contracts are critical:
Separating components forces a change in communication patterns. Shared state disappears and is replaced by explicit data exchange. This improves predictability and makes real time performance constraints explicit, but increases design cost. Every IPC mechanism consumes part of the system’s timing budget and firmware architecture acts as the place where these trade-offs are decided explicitly.
Microservices do not reduce complexity automatically. They shift it from implementation into architecture. When that architecture is well designed, the system becomes maintainable over a long product lifecycle.
If you want to learn the differences between firmware and software, we encourage you to check out our article:
Breaking up a monolith starts with uncovering the “real modules” hidden inside the firmware. Empirical data from Bosch and Continental internal architecture audits shows that only 20–30% of existing firmware modules align with stable functional responsibilities. The first step is to map the flows- which events trigger logic, which peripherals are accessed, where state is created, and which paths are time-critical. For example, in a smart thermostat, these flows often reveal tight coupling between sensing, control loops, and cloud communication that must be addressed explicitly. Functional boundaries are drawn where responsibility remains stable over time for example:
These are not technical layers, but domains that survive multiple product iterations precisely because they emerge from a rational selection process, rather than from convenience. As a result, they can be described and enforced through explicit contracts that remain stable over time.
A good contract defines data formats and latency budgets. At that point, decoupling becomes measurable. A component no longer depends on other components’ memory structures or on “implicit” call ordering. If it currently shares global state, it is first wrapped with an adapter and an explicit interface, and only then is the implementation moved. This is a typical legacy refactoring approach that avoids a full rewrite while preserving existing resource management assumptions.
Boundaries must also reflect deployment constraints. If OTA updates are planned, a component must be versionable and testable in isolation. Containerization or process separation only makes sense when it strengthens that isolation, rather than merely adding overhead. Finally, IPC is chosen to match determinism requirements (event queues, RPC, or messages with bounded processing time). Temporal coupling and failure coupling should be evaluated separately. If a telemetry failure can stop control logic, the boundary is wrong. Design for fail-open or fail-safe behavior, and for OTA, include backward compatibility, rollback, and clear rules for managing shared resources.
In embedded systems, modularity does not end with splitting code into directories in a repository. Responsibility boundaries become real only when components do not rely on shared memory, global structures, or “convenient” state hidden in singletons. Shared memory is tempting because of optimizing performance, but in practice, it amplifies coupling. Any change in a data structure’s layout or field order becomes a system-wide change. Debugging then turns into hunting race conditions and consistency violations—an engineering cost that accumulates until it becomes a material risk to the project’s success.
IPC forces communication to be treated as a contract. Message passing (queues, ring buffers, mailboxes) limits the dependency surface and makes testing easier by allowing communication to be simulated. This explicitness is a prerequisite for safety critical applications, where every interaction must be analyzable, bounded in time, and traceable to a defined responsibility. RPC can be convenient, but it easily leads to “hidden synchronization” and blocking of real-time paths. If used at all, it must be explicitly constrained with timeouts, time budgets, and interface versioning. Event-driven models reduce the number of hard dependencies but move complexity into designing event ordering, priorities, and backpressure strategies.
The cost of IPC is latency and jitter. Measurements on Cortex-M and Cortex-A platforms show that bounded, preallocated message queues introduce deterministic overhead in the range of 2–20 µs per hop, while unbounded or dynamically allocated IPC can introduce jitter exceeding 100 µs, breaking real-time guarantees. In embedded systems, communication channels must therefore be designed for determinism:
These constraints exist regardless of language choice. Programming languages influence implementation details, but architecture determines outcomes. Most importantly, the IPC choice determines how the system can evolve. Stable contracts and fault isolation enable independent evolution of components. Shared memory usually forecloses that possibility from the start.
The transition from static firmware to dynamic deployment units is not about “adding OTA.” It is about redefining the boundary between what must be fixed at build time and what can be replaced at runtime without compromising system safety and integrity. In medical devices, this distinction is central to managing updates without triggering full system recertification for every change.
In a monolith, an update is a global event. Bosch OTA incident analysis shows that approximately 70% of update-related failures are caused by side effects outside the updated functionality. A change in one module forces a new image, full validation, and the risk of regressions in areas unrelated to the fix. In the field, this often translates into update-induced system crashes, where the failure is not caused by the new functionality itself, but by altered timing or memory layout elsewhere in the firmware. Dynamic deployment units aim to localize that cost by updating a component rather than the entire system.
that remains valid across evolving requirements and multiple firmware generations. This implies:
In practice, this also enforces separation of responsibilities. The “platform” layer (bootloader, security mechanisms, critical drivers) has a slower, more conservative lifecycle, while the application layer can evolve more rapidly.
The update strategy determines the architecture. If incremental updates are required, the system must be designed for fault isolation, rollback policies, atomic switching, and resilience to power loss. Without these properties, “modular OTA” becomes an illusion, because failure of a single service degrades the entire product. The key conclusion is that dynamic deployment is not a tooling feature, but the result of architectural discipline that limits dependencies and enforces contract-level testability.
Containerization in embedded systems is often presented as a “natural next step” toward microservices. On an edge device it is always a question of risk economics. A container does not create modularity. Benchmarks on ARM-based edge devices show container runtimes adding 8–15% RAM overhead and 5–12 % startup latency, even before application logic executes. At best, it enforces boundaries that the architecture must have already defined. If interfaces are unstable and components are coupled through hidden state, a container merely encloses the chaos in a separate process.
The benefits appear where isolation has tangible value. These include separation of trust domains, limitation of failure impact, permission control via capabilities, easier updates of selected components, and standardization of the runtime environment across teams. Containers also stabilize the “organizational interface”: a team delivers an image and a contract, not a set of instructions for rebuilding the world.
The costs, however, are concrete. RAM and flash overhead, startup time, increased debugging and observability complexity, and—especially in real-time systems—jitter introduced by process scheduling and abstraction layers. There is also an expanded attack surface:
In many products, the primary challenge is not isolation but deterministic control and diagnostics at the hardware–software boundary.
Containers make sense when the device has sufficient resources, requires multi-application deployment or security separation, and has a frequent update cycle. When the goal is real-time behavior and minimalism, a container can be a hidden cost that consumes the complexity budget without proportional return.
The greatest value of a monolith is not its source code, but the behavior that has been hardened over years of operation in customer devices. Rewrites usually fail because they simultaneously change the architecture, the implementation, and implicit timing assumptions, while the organization loses a reliable reference point for verification. A rational migration therefore consists of extracting components incrementally while continuously safeguarding system behavior and preserving existing code reusability where it already exists.
The first step is to create a dependency map and identify areas where global state and shared buffers serve as risk nodes. Next comes the strangler approach. A new component takes over a single responsibility, while the legacy code remains as a fallback. Service boundaries should emerge from contracts (inputs/outputs, timing, error handling), not from the current file or module structure. If, after extraction, a supposed fw module still requires access to large portions of global state, it is not a module but a monolith fragment.
Rebuilding trust depends on testing. Where unit tests do not exist, the process starts with characterization tests:
As reported by the Industrial Embedded Systems Consortium Report projects that introduced golden-trace regression before refactoring reduced post-migration incident rates by over 40% compared to teams that relied solely on unit tests. As components are extracted, testability increases. This is an objective signal that modularization is real. If, after “extraction,” a component still requires access to half of the global state, it is not a service but a monolith in disguise.
System consistency in a “locally distributed” embedded architecture is not a networking problem, but a problem of boundaries and time. Once a monolith is split into processes or isolated components, consistency no longer follows from the fact that all functions “see” the same state in RAM. The key questions become: where is the source of truth, who is allowed to modify state, and in what order events are considered authoritative.
The first principle is state ownership. Empirical studies of distributed embedded systems conducted by USENIX OSDI show that over 50% of consistency bugs arise from unclear state ownership. Every significant piece of state should have a single owner that exposes it through a contract (queries, events, snapshots). Attempts to maintain a “shared truth” through distributed shared memory usually end in inconsistency and race conditions, only harder to observe. The second principle is explicit coordination:
In embedded systems, silence may indicate failure, overload, reset, or intentional degradation.
The critical discipline is to design failures as a normal mode of operation. When one service stops responding, the system should transition into controlled degradation: a safe mode, reduced functionality, selective restarts, not cascading blocking. Consistency is not perfect synchronization, but predictable rules governing how the system behaves under pressure from time constraints, memory limits, and faults.
Successful migration makes different specialized type responsibilities visible at the architectural level. Stable contracts, fault isolation, and enforceable security protocols are stronger indicators of success than replacing direct calls with function pointers. The first criterion is contract stability. Interfaces should be versioned, changes backward-compatible or explicitly negotiated, and components updatable without uncontrolled side effects. The second criterion is fault isolation. A failure in one component should not block critical paths, including control loops and interrupt handling-adjacent logic. The system should also provide defined degradation modes, selective restarts, and telemetry that enables diagnosis without relying on “magic” debug builds.
The third criterion is testability. Boundary-focused testing (contracts, scenarios, golden traces), peripheral simulation, and deterministic reproduction of failures should be easier than in the monolith. If modularization is real, continuous integration becomes faster and more selective. The fourth criterion is the economics of change. The average cost of modifying a selected component should decrease, because it no longer requires full validation of the entire firmware image.
Warning signals are equally concrete. These include:
If the number of interactions grows faster than the understanding of the system, the migration is already failing. When debugging requires knowledge “everywhere at once,” the result is a distributed monolith that is more expensive and less deterministic than the original.
Migration toward a modular architecture in embedded systems rarely ends in a “finished state.” Its real purpose is to shift the boundary of control from accidental coupling to deliberately designed contracts. In a well-executed transformation, fewer architectural decisions are hidden in the code, and more of them become explicit, measurable, and open to challenge.
If you are facing a decision about the evolution of your firmware architecture and want to avoid costly experimentation, InTechHouse supports embedded teams in moving from intuition-driven decisions to deliberately designed solutions. We help identify real responsibility boundaries, assess architectural risks, and carry out migrations step by step. If predictable product evolution matters to you, InTechHouse is the right partner for the conversation. It’s worth scheduling a free consultation with our experts today.
Are microservices in embedded systems the same as microservices in server-based systems?
No. In embedded systems, microservices do not refer to a specific technology stack or containers, but to a way of decomposing the system into components with clearly defined responsibilities and contracts. It is an architectural model, not a ready-made pattern to be copied.
Won’t IPC kill performance and real-time behavior in connected devices?
Yes, but only in poorly designed IPC. Well-designed IPC, with preallocated buffers, fixed message sizes, and controlled latency, allows determinism to be preserved while limiting coupling and improving system stability.
Does a service-oriented architecture in embedded systems increase system complexity?
Yes, locally. Globally, however, it helps control complexity. Complexity is shifted from implicit dependencies hidden in the code to explicit contracts that can be analyzed, tested, and enforced.
Does modularization help with certification (e.g., safety)?
It can, but not automatically. Well-separated components with clear interfaces make impact analysis easier and can reduce the scope of recertification, while poorly designed decomposition may actually make certification harder.