Back
Groundwork / Cloud Native to Dapr
Architecture & engineering deep-dive

Cloud Native
to Dapr

Every microservice you ship drags the same invisible cargo: discovery, state, messaging, secrets, retries, telemetry. This walkthrough builds up from why cloud native exists, through containers and Kubernetes, to the sidecar runtime that lifts that cargo out of your code — with live demos you can poke along the way.

Start at the beginning ↓
01 · Why cloud native

The demands modern apps must meet

"Cloud native" isn't a technology, it's a checklist of expectations. A payments service, a storefront, a booking system — whatever the app — the business now assumes it will keep running through traffic spikes, hardware failures, and its own upgrades, without anyone noticing. That assumption didn't exist twenty years ago. A single server going down used to just mean a bad afternoon. Now it has to mean nothing at all.

Six properties make up that checklist, and each one pushes back on how software used to be built:

Scalable

Grows with load and traffic — designed to scale as the business scales, not re-architected every time it does.

Resilient

Withstands component failures, up to a full cloud-region outage, and stays functional throughout.

Elastic

Adds and removes running instances automatically as demand rises and falls — capacity that breathes.

Always on

Runs 24×7×365. No downtime, even while it's being upgraded or patched underneath you.

Secure

Protects data and transactions against fraud and attack at every layer, not just the perimeter.

Independent

Services develop, deploy and release on their own cadence — aligned with agile teams working in parallel.

Analogy

Think of a restaurant that only ever plans for its slowest Tuesday lunch. One food-blogger review and a queue forms out the door — no extra tables, no extra cooks, and the kitchen catches fire from a single failing burner because there's no backup ring. Cloud native is the restaurant that keeps a bank of foldable tables in the back, cross-trains every cook on every station, and can lose a burner without the dinner rush even noticing. The tables and cross-training cost something up front. That's the trade this whole article is really about.

Try it — the nines calculator

Drag the target and watch how little slack each extra nine actually leaves.

What "cloud native" actually means in practice

The six demands above are the why. The practice that emerged to satisfy them has its own checklist — built on cloud infrastructure, split into microservices, shipped as containers, talking through contracts instead of shared code, coordinated by an orchestrator, released independently, spread across clouds, with total cost of ownership always in view. Every later section in this article is really an elaboration of one item on that list.

Cloud native by example

Elasticity — on a normal day, two instances of the storefront handle all traffic comfortably. On a sale day, two instances fall over — so the platform notices the queue building and adds instances automatically, then removes them once the peak passes. Nobody paged anyone.

Resiliency — a single checkout request touches the catalog, the cart, and the payment service. If payment goes down, a resilient storefront doesn't fail the whole request — it degrades gracefully, shows a clear "try again shortly" message on checkout, and keeps the catalog and cart online for everyone else.

Try asking your AI
02 · Containers & Kubernetes

The platform underneath

Cloud native sets the demands; something has to actually run the software that meets them. That something went through three distinct eras, each solving the isolation problem the previous one left half-finished.

The deployment journey

Analogy

Traditional deployment is one big house with no interior walls — one messy tenant's clutter spills into everyone else's room. Virtualization builds separate houses, each with its own foundation, plumbing, and utilities, which is safe but expensive to duplicate. Containers are more like apartments in one building: separate units with locked doors (isolation), sharing the building's foundation and pipes (the host kernel) instead of each pouring their own concrete.

Try it — overhead at scale

Move the instance count and watch the guest-OS tax on VMs grow while the container line stays flat.

Docker, images, and containers

Three words get used almost interchangeably and shouldn't be. A container is a lightweight, running package of app code plus its exact runtime and library dependencies — decoupled from the host so it behaves identically anywhere. An image is the immutable template that container is started from; a container is one running instance of an image, the same way an object is one instance of a class. Docker is the tooling that builds, ships, and runs both. A Java image, for example, stacks an OS layer, a JVM, your app, and its JARs — so docker run reproduces that exact environment on any host, laptop or cloud.

Why orchestration is needed

Containers don't run themselves in production. Once you have many instances of many services, someone has to keep answering four questions continuously: which healthy instance serves this request, is each instance actually healthy, how do we absorb a traffic burst, and how do services find each other by name instead of a fragile IP address. Kubernetes is the system that answers all four, permanently, without a human in the loop.

Try it — self-healing pods

Click a pod to kill it. Watch Kubernetes notice and replace it — this is the "orchestrator" doing its job, not magic.

Healthy pods: 3/3Restarts so far: 0

Kubernetes' core objects

Pod

The smallest deployable unit — a single instance of a running process. Holds one or more containers sharing network and storage. Pods are ephemeral by design.

Service

A stable abstraction over a set of Pods — one IP and DNS name, load-balanced across them. Pods come and go; the Service endpoint stays put.

From cloud-native to Kubernetes-native

Hybrid cloud is supposed to avoid lock-in by letting you fail over between providers — but each provider ships its own CLI, its own service discovery, its own event protocol, so deploying the same app everywhere automatically was effectively impossible. Standardizing on Kubernetes gives every provider the same portable deployment surface. Kubernetes-native isn't a departure from cloud-native — it's cloud-native with one concrete, common substrate underneath.

Try asking your AI
03 · Cross-cutting concerns

The gap Kubernetes leaves open

Kubernetes solves scheduling, health, scaling, and naming — for the container. It has no opinion on what's running inside that container. A real application is many services calling each other, and every one of them independently has to solve the same handful of distributed-systems problems: how do I find and call another service, how do I persist and share state, how do I send an async message, how do I fetch a secret, how do I retry a failing call without hammering it forever, how do I emit logs and traces anyone can actually read. Kubernetes runs the box. Nobody built what goes inside it for you.

The seven building blocks every service needs
Service invocation

Discover and call other services reliably.

State management

Persist and share state across services.

Publish / subscribe

Async messaging between services.

Secrets

Fetch credentials from a secret store.

Bindings

Connect to external systems & events.

Resiliency

Retries, timeouts, and circuit breaking.

Observability

Logs, metrics, and distributed traces.

Same seven, every service. The whole rest of this article is one idea: build these once, not per team.

Traditionally these seven ship as a framework library compiled straight into every service. The business logic ends up as a thin sliver wrapped in a thick shell of plumbing — the JVM (or Node runtime, or .NET runtime) hosts a framework library, and your actual code is a small guest inside its own container.

Analogy

It's like every restaurant in a chain building its own walk-in fridge, its own point-of-sale system, and its own staff-scheduling software from scratch — in whatever "language" that location happens to speak. Some franchises end up with excellent fridges and terrible scheduling software, because one team is good at refrigeration and bad at scheduling. Nobody chose that; it's just what happens when every location re-solves every problem alone.

Try it — count the duplication

Set how many services and how many languages a fleet uses, and compare the two approaches head-to-head.

Why the shared-library approach hurts

Vendor lock-in

The SDK binds your code to one provider's API. Switching a queue, database, or secret store means rewriting and redeploying every service that touches it.

Duplication

The same plumbing is embedded and version-pinned in every service. One upstream change forces coordinated updates across the whole fleet at once.

Polyglot tax

Each language needs its own port of the library. Java, Go, .NET, and Python teams re-solve the same problem in parallel, independently.

Try asking your AI
04 · The Dapr approach

Sidecar runtime & component model

Dapr — Distributed Application Runtime — takes the seven building blocks out of your compiled code and runs them as a separate process, alongside your app, in the same Pod. Your app talks to that process over plain local HTTP or gRPC calls. It is a graduated CNCF project since 2024 — the same top tier as Kubernetes, Istio, and Prometheus — precisely because "run the plumbing next door instead of inside" turned out to generalize far beyond any one team's stack.

The sidecar pattern, three guarantees
Separate process

Not a library linked into your app — its own OS process, with its own lifecycle.

Local HTTP/gRPC

Talks to the app over a fast local call, not a remote network hop.

Language-agnostic

The exact same sidecar binary works next to Java, Go, Python, or Node — no per-language SDK to port.

Analogy

A sidecar is like hiring a bilingual local fixer in a foreign city instead of teaching every visiting employee the language, the transit system, and who to bribe for a permit. The fixer rides alongside each visitor, handles the local specifics through one simple conversation ("get me a permit"), and can be swapped for a different fixer in a different city without the visitor changing how they ask.

Library vs. sidecar, side by side

BEFORE — embedded library
Business code
Framework library
Runtime (JVM)

Plumbing compiled into every service, per language.

AFTER — Dapr sidecar
Business code (app container)
Dapr sidecar — building blocks

Business logic only; plumbing delegated over a standard local API.

The component model

Your app calls one stable API. Which real backend answers that call is chosen entirely by a YAML file the sidecar reads at startup — a pluggable component. Swapping a backend means editing that file, not touching application code:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.redis       # ← change this line to state.postgresql
  metadata:
    - name: redisHost
      value: localhost:6379
type — which component implementation the sidecar loads metadata — connection details specific to that backend your app — never sees this file, never changes
Try it — swap the backend

Your app's call never changes. Pick a different backend and watch only the YAML — and the arrow pointing at a different box — update.


    
Application code changed: 0 lines
Try asking your AI
05 · Building blocks in detail

Invocation, state, pub/sub & more

Five of the seven building blocks are pluggable APIs you call directly — invocation, state, pub/sub, secrets, bindings. The other two, resiliency and observability, aren't APIs you call at all; Dapr applies them underneath every one of the other five, automatically, because every hop already passes through a sidecar that can retry it, time it out, and trace it.

Service-to-service invocation

One service calls another by a logical app-id, never an IP address. The call leaves the Employee sidecar, crosses to the Department sidecar over an encrypted mTLS connection, and lands on the Department app — automatic retries and full tracing included, with zero retry or TLS code written by either team.

Name resolution

Call by app-id, not IP.

mTLS in transit

Encryption between sidecars, by default.

Retries

Automatic, policy-driven, no code.

Tracing

Every hop is observable end to end.

State management

Services often need to remember something beyond a single request, and share it with other services. Dapr gives every language the same two calls — getState / setState — against whichever store is configured, with no store-specific SDK anywhere in the app.

Publish / subscribe

Loosely coupled, asynchronous messaging — the publisher never knows or cares who subscribes. The broker (Kafka, RabbitMQ, MQTT, Redis Streams) is a YAML choice, so the messaging technology underneath can change without touching a single service.

Secrets

Apps need credentials to reach the outside world, but embedding a cloud provider's secrets SDK in every service is exactly the lock-in this whole approach exists to avoid. One call handles it everywhere:

password = dapr.getSecret("db-password")

The vault behind that call is a YAML choice too — HashiCorp Vault, Azure Key Vault, AWS Secrets Manager, or GCP Secret Manager, picked per environment, never per line of app code.

Bindings

Bindings connect to on-premises or cloud resources — SMTP, Twilio, cloud queues, cron, webhooks — without embedding any of their SDKs in every service. An input binding means an external event triggers your app; an output binding means your app invokes an external system. Either way, a change to the external connector touches configuration, not code, in every microservice that uses it.

A retry policy turns one unreliable call into several chances at a reliable outcome. This is declarative policy the sidecar enforces — retries, timeouts to bound how long any call may wait, and circuit breaking to stop calling a dependency that's clearly down until it recovers — none of it is conditional logic scattered through your services.

Try it — retries vs. reliability

A flaky dependency that only succeeds 70% of the time on its own looks a lot better after a few automatic retries.

Chance of success after 3 attempts:

Observability

All traffic already flows through sidecars, so Dapr emits logs, metrics, and traces automatically, in the OpenTelemetry format — pointed at Zipkin, Jaeger, or Prometheus/Grafana. Nobody had to instrument a single service by hand for the sidecar's own hops to become visible.

Try asking your AI
06 · Running Dapr

Hosting modes & a first app

Dapr runs in two places. Self-hosted mode runs the sidecar as a local process on your machine — the fast path for development — and can also run on VMs to serve real production workloads. Kubernetes mode injects the sidecar into every Pod automatically; it runs on any supported Kubernetes version, and includes minikube for local development and testing before anything touches a real cluster.

The tutorial application

Two Spring Boot services — Employee and Department — with CRUD across both, each running its own Dapr sidecar, deployed together on Google Kubernetes Engine (GKE).

Try it — step through a service call

Press play and watch a single request cross from one service to another entirely through sidecars — no direct network code in either app.

Employee service
Business code
Dapr sidecar
http / gRPC

mTLS
Department service
Dapr sidecar
Business code
Step 0/5 — press play to begin.

Building a first app with Dapr

Part one — first app

Develop the Spring Boot Employee service → test it standalone → add the Dapr sidecar → containerize with Docker → deploy to minikube.

Part two — add service-to-service

Develop Department → call it from Employee via Dapr → add its sidecar → containerize → deploy to minikube.

Try it — build a request URL

Every Dapr building-block call is the same local HTTP shape: talk to your own sidecar, name the block, name the target. Pick the pieces below and watch the real request assemble.

Try asking your AI
07 · Recap

What Dapr gives you

Invocation

Discover and call other services by app-id.

State

getState / setState against any configured store.

Pub/sub

Async messaging through a swappable broker.

Secrets

One call, any vault, chosen in YAML.

Bindings

External systems wired in through configuration.

Resiliency

Retries, timeouts, circuit breaking — applied throughout.

Observability

Logs, metrics, and traces, emitted automatically.

Invocation, state, pub/sub, secrets, and bindings are pluggable APIs. Resiliency and observability are cross-cutting concerns Dapr applies underneath all five, not APIs you call directly.

Your services shrink back down to business logic — the plumbing becomes portable across clouds and across languages, because it never lived inside your code in the first place.

Key takeaways
  1. Cloud native sets the demands: scalable, resilient, elastic, always-on, secure, independent.
  2. Kubernetes runs and connects the containers — but leaves the cross-cutting concerns to each service.
  3. The shared-library approach brings vendor lock-in, duplication, and a polyglot tax that scales with every new language.
  4. Dapr's sidecar and component model remove all three at once — swap backends by editing YAML, keep the code itself focused only on business logic.
Common pitfalls

Treating Dapr as a place to put business logic — it's a runtime you call, not a framework you build inside; your code should still run and mean something with the sidecar turned off. Forgetting that a component swap in YAML is still a production change: a new backend has its own latency and failure characteristics even though the calling code didn't move. And skipping observability because "the sidecar handles it" — it emits the telemetry, but someone still has to look at the dashboard.

References & next steps

Dapr docs

docs.dapr.io

Kubernetes concepts

kubernetes.io/docs

Before next session: install the Dapr CLI and run dapr init in self-hosted mode, run the service-invocation and state quickstarts, and deploy the Employee & Department sample to minikube.

Try asking your AI

Remix this article

Copy a prompt to your AI tool to create your own version — go deeper on one section, adapt it to your stack, or remix it for a different runtime like Actor Model, gRPC, or WebAssembly.

Join the discussion

Comments

Leave a comment on the chapter