Dapr — Architecture#

Architectural style#

Multi-binary Sidecar Runtime with Plugin-based Component System

Dapr is not a single service but a fleet of six cooperating services that together form a distributed application runtime. The defining architectural choice is the sidecar model: daprd runs as a separate process or container injected alongside every application instance, communicating with the app over localhost HTTP or gRPC. The application never imports Dapr Go code — it speaks HTTP or gRPC to the sidecar.

Within the daprd binary, the style is plugin-based: building blocks (state stores, pub/sub brokers, secret stores, bindings, etc.) are implemented as pluggable components loaded from a separate repository (components-contrib) and registered via blank imports at startup. A central DaprRuntime struct acts as the composition root, wiring together all subsystems. This gives daprd characteristics of both a microkernel (stable core, pluggable components) and a layered architecture (API layer → universal handler → building block implementations).

Evidence: pkg/runtime/runtime.go contains the DaprRuntime struct with all subsystems as fields; cmd/daprd/components/ registers component implementations via _ "..." blank imports; pkg/api/universal/ provides a shared handler layer under both HTTP and gRPC servers.

Component diagram (textual)#

┌────────────────────────────────────────────────────────────────────┐
│  Control Plane (Kubernetes or standalone infra)                    │
│                                                                    │
│  ┌──────────┐  ┌──────────┐  ┌───────────┐  ┌──────────────────┐  │
│  │ operator │  │ sentry   │  │ placement │  │   scheduler      │  │
│  │(CRD ctrl)│  │(cert CA) │  │(actor hash│  │(job/timer/cron)  │  │
│  └────┬─────┘  └────┬─────┘  └─────┬─────┘  └────────┬─────────┘  │
│       │              │              │                  │            │
└───────┼──────────────┼──────────────┼──────────────────┼────────────┘
        │ gRPC stream  │ mTLS cert    │ placement table  │ job trigger
        ▼              ▼              ▼                  ▼
┌─────────────────────────────────────────────────────────────────────┐
│  daprd sidecar (one per application instance)                       │
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  DaprRuntime (pkg/runtime)                                  │    │
│  │                                                             │    │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │    │
│  │  │ HTTP Server  │  │ gRPC API Svr │  │ gRPC Internal Svr│  │    │
│  │  │ (port 3500)  │  │ (port 50001) │  │ (sidecar↔sidecar)│  │    │
│  │  └──────┬───────┘  └──────┬───────┘  └──────────────────┘  │    │
│  │         │                 │                                  │    │
│  │         └────────┬────────┘                                  │    │
│  │                  ▼                                            │    │
│  │        ┌──────────────────┐                                   │    │
│  │        │ universal.Universal│ (shared API logic)              │    │
│  │        └────────┬──────────┘                                  │    │
│  │                 │                                              │    │
│  │  ┌──────────────┼──────────────────────────────────────────┐  │    │
│  │  │              ▼  Building Blocks                          │  │    │
│  │  │  ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌─────────────┐  │  │    │
│  │  │  │ pub/sub │ │ state   │ │ actors   │ │  workflow   │  │  │    │
│  │  │  │ adapter │ │ stores  │ │ runtime  │ │  engine     │  │  │    │
│  │  │  └────┬────┘ └────┬────┘ └────┬─────┘ └─────┬───────┘  │  │    │
│  │  │       │           │            │               │          │  │    │
│  │  │  ┌────▼───────────▼────────────▼───────────────▼──────┐  │  │    │
│  │  │  │        processor (component lifecycle manager)      │  │  │    │
│  │  │  └───────────────────────┬────────────────────────────┘  │  │    │
│  │  │                          ▼                                │  │    │
│  │  │  ┌───────────────────────────────────────────────────┐   │  │    │
│  │  │  │  compstore (in-memory component registry)         │   │  │    │
│  │  │  └───────────────────────────────────────────────────┘   │  │    │
│  │  └──────────────────────────────────────────────────────────┘  │    │
│  │                                                                 │    │
│  │  ┌────────────┐  ┌──────────────┐  ┌───────────────────────┐   │    │
│  │  │  security  │  │  resiliency  │  │  diagnostics (OTel)   │   │    │
│  │  │(mTLS/SPIFFE│  │(CB/retry/TO) │  │  (tracing+metrics)    │   │    │
│  │  └────────────┘  └──────────────┘  └───────────────────────┘   │    │
│  └─────────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────┘
        │ localhost HTTP/gRPC
        ▼
┌─────────────────────────┐
│  Application process    │
│  (any language)         │
└─────────────────────────┘
        │ external traffic
        ▼
   Backend Components
   (Redis, Kafka, CosmosDB,
    Vault, PostgreSQL, etc.)

Core components#

DaprRuntime#

  • Package: pkg/runtime
  • Responsibility: Central composition root. Owns all subsystems, orchestrates the initialization sequence (initRuntime), manages HTTP/gRPC server lifecycle, coordinates graceful shutdown.
  • Key types: DaprRuntime struct (runtime.go:94–144), Config (external) / internalConfig (internal)
  • Dependencies: All other subsystems — grpc manager, channels, actors, processor, wfengine, jobsManager, resiliency, security, compStore, pubsubAdapter, outbox, hotreload

Universal API handler#

  • Package: pkg/api/universal
  • Responsibility: Implements the Dapr API semantics once, shared by both HTTP and gRPC transport layers. Handles state, pub/sub, actors, secrets, workflows, jobs. Eliminates drift between the two protocol surfaces.
  • Key types: Universal struct with methods corresponding to every Dapr building block
  • Dependencies: compStore, resiliency, actors, wfengine, scheduler client

HTTP & gRPC API Servers#

  • Package: pkg/api/http, pkg/api/grpc
  • Responsibility: Protocol-specific adapter layers. HTTP handles REST routing (FastHTTP via dapr/go-sdk style), gRPC handles protobuf framing. Both delegate to universal.Universal for business logic.
  • Key types: http.API, grpc.API interfaces; grpc.Server, grpc.Manager
  • Dependencies: universal, channels, pubsub adapters, direct messaging, outbox

Processor#

  • Package: pkg/runtime/processor
  • Responsibility: Component lifecycle manager. Handles Init, Close, and hot-reload events for all component types (state, pubsub, bindings, secret stores, crypto, lock, configuration, conversation, middleware). Runs a goroutine pool that processes component updates sequentially per component type.
  • Key types: Processor struct; sub-packages per type: processor/state, processor/pubsub, processor/binding, processor/secret, etc.
  • Dependencies: compStore, channels, resiliency, registry, security

Component Store#

  • Package: pkg/runtime/compstore
  • Responsibility: In-memory registry of all active component instances. Provides typed Get/Add/Delete operations for each building block type. Thread-safe; read-heavy.
  • Key types: ComponentStore struct
  • Dependencies: none (leaf package)

Hot-reload#

  • Package: pkg/runtime/hotreload
  • Responsibility: Detects component configuration changes and triggers Processor updates without restart. Two implementations: disk (watches file system for standalone mode) and operator (receives gRPC stream from Kubernetes operator for k8s mode).
  • Key types: Reloader interface; OperatorReloader, DiskReloader
  • Dependencies: processor, compStore, authorizer, operator gRPC client

Actors Runtime#

  • Package: pkg/actors
  • Responsibility: Virtual actor runtime. Manages actor activation/deactivation, reminders, timers, state. Communicates with placement service via gRPC for consistent-hash routing to the correct daprd instance.
  • Key types: actors.Interface, sub-packages: actors/targets, actors/reminders, actors/timers, actors/state, actors/placement
  • Dependencies: placement gRPC client, scheduler client, security, compStore

Workflow Engine#

  • Package: pkg/runtime/wfengine
  • Responsibility: Durable workflow orchestration built on the actor model. Wraps dapr/durabletask-go for workflow execution semantics (orchestrations, activities). Actor-backed execution ensures durability and horizontal scaling.
  • Key types: wfengine.Interface
  • Dependencies: actors, resiliency, compStore

Resiliency#

  • Package: pkg/resiliency
  • Responsibility: Applies circuit breaker, retry, and timeout policies to outgoing calls (component operations, service-to-service, app callbacks). Policies are declaratively configured via Resiliency CRDs or YAML files.
  • Key types: resiliency.Provider interface, Policy struct with breaker/ sub-package
  • Dependencies: config, compStore (for policy lookup)

Security#

  • Package: pkg/security
  • Responsibility: mTLS and SPIFFE identity management. Fetches certificates from sentry (the CA), rotates them, and provides security.Handler for creating authenticated connections. pkg/sentry contains the CA server itself.
  • Key types: security.Handler interface, security.Provider
  • Dependencies: sentry gRPC client, SPIFFE libraries

Channel#

  • Package: pkg/channel
  • Responsibility: App channel abstraction. Wraps the localhost connection to the application process — either HTTP or gRPC, depending on AppProtocol. Used for invoking app callbacks (pub/sub delivery, binding triggers, actor invocations, health checks).
  • Key types: channel.AppChannel interface; http.Channel, grpc.Channel
  • Dependencies: security (for gRPC mTLS), apphealth

Placement Service#

  • Package: pkg/placement
  • Responsibility: Separate binary. Maintains a consistent hash ring (virtual node table) across all daprd instances. When an actor is invoked, the calling daprd asks placement which daprd instance owns that actor ID, then routes directly there via the internal gRPC server.
  • Key types: placement.Service, placement.Server
  • Dependencies: etcd (for leader election in HA mode), raft-based state machine

Sentry (Certificate Authority)#

  • Package: pkg/sentry
  • Responsibility: Separate binary. Issues SPIFFE X.509 certificates to daprd instances. Enables mutual TLS for all sidecar-to-sidecar communication without manual certificate management.
  • Key types: sentry.Server, sentry.CA
  • Dependencies: kubernetes RBAC (for identity verification in k8s mode)

Scheduler Service#

  • Package: pkg/scheduler
  • Responsibility: Separate binary. Manages time-based jobs (actor reminders, workflow timers, user-defined jobs). Backed by etcd cron via diagridio/go-etcd-cron. Streams job triggers back to the appropriate daprd instances.
  • Key types: scheduler.Service, scheduler.Server
  • Dependencies: etcd, go-etcd-cron

Data flow#

Pub/Sub message delivery (inbound)#

External broker (Kafka, Redis, etc.)
        │
        ▼
  Component (pkg/components/pubsub)
        │ delivers message
        ▼
  Processor/Subscriber (pkg/runtime/processor/subscriber)
        │ applies resiliency policies
        ▼
  PubSub Adapter (pkg/runtime/pubsub/publisher)
        │ routes to subscription
        ▼
  App Channel (pkg/channel/http or grpc)
        │ POST /subscribe callback to app
        ▼
  Application process

State write (outbound, from app)#

Application
  │ HTTP PUT /v1.0/state/{storeName}  or  gRPC SaveState
  ▼
HTTP Server (pkg/api/http)  or  gRPC Server (pkg/api/grpc)
  │ delegates to
  ▼
universal.Universal.SaveState()
  │ looks up component in compStore
  ▼
Resiliency provider wraps call
  │
  ▼
State store component (from components-contrib)
  │ applies encryption if configured (pkg/encryption)
  ▼
Backend (Redis, PostgreSQL, CosmosDB, etc.)

Actor invocation (service-to-service)#

App A calls actor on daprd A
  │ gRPC InvokeActor
  ▼
daprd A — actors runtime queries placement
  │ gets host address of daprd B
  ▼
daprd A — direct gRPC to daprd B internal server (mTLS via sentry certs)
  │
  ▼
daprd B — activates/routes to actor goroutine
  │
  ▼
daprd B — App Channel callback to App B
  │
  ▼
App B processes actor invocation

Initialization / Bootstrap#

Startup sequence for daprd:

  1. cmd/daprd/main.go: calls app.Run() — thin shim
  2. cmd/daprd/app/app.go Run():
    • Parses CLI flags via options.New() (flag package)
    • Blank-import side-effect: all component types registered in pkg/components/<type>.DefaultRegistry via _ "github.com/dapr/dapr/cmd/daprd/components"
    • Sets loggers on each component registry
    • Creates signals.Context() for SIGHUP-aware graceful restart
  3. runWithContext():
    • Builds registry.Options — assembles all component loader registries
    • Creates healthz.New() — composite health tracker
    • Creates security.Provider — starts fetching certs from sentry (runs concurrently)
    • Calls runtime.FromConfig() which calls newDaprRuntime():
      • Builds compstore, meta, channels, pubsubAdapter, outbox, actors, processor, hotreload.Reloader, wfengine, scheduler.Scheduler
      • Creates concurrency.RunnerCloserManager (from dapr/kit) that manages all subsystem goroutines
  4. rt.Run(ctx): starts all subsystems concurrently via RunnerCloserManager, then calls rt.initRuntime(ctx):
    • Sets up OTel tracing
    • Initializes name resolution (mDNS or Kubernetes DNS)
    • Starts gRPC proxy
    • Initializes direct messaging
    • Loads components from disk (standalone) or watches operator stream (k8s)
    • Builds universal.Universal, grpc.API, http.API
    • Starts HTTP server (port 3500) and gRPC servers (port 50001 internal, 50001 external)
    • Blocks until app health check passes
    • Initializes actors runtime
    • Starts health probes

Dependency injection pattern: Manual wiring — no framework (no wire, no dig, no fx). All dependencies are passed as fields in Options structs to each subsystem constructor. The composition root is newDaprRuntime() which instantiates everything and passes dependencies explicitly.

Graceful restart support: The signals.OnHUP() channel means a SIGHUP restarts the runtime in-process without restarting the binary — a notable pattern for Kubernetes rolling updates without pod restart.

Configuration#

Configuration sources in priority order:

  1. CLI flags (cmd/daprd/options/options.go): primary configuration mechanism. Flags like --app-id, --app-port, --dapr-http-port, --mode, --sentry-address, etc. Kubernetes sets these via the injector admission webhook.
  2. Environment variables: some settings read from env (DAPR_TRUST_ANCHORS, APP_API_TOKEN, NAMESPACE, POD_NAME)
  3. Dapr Configuration CRD / YAML file: loaded via pkg/config during runtime initialization. Controls tracing, observability, middleware pipeline, feature flags, access control. In k8s mode, fetched from operator gRPC stream; in standalone mode, read from disk.
  4. Component YAML files: declare which building block implementations to load (e.g., “use Redis as state store”). Loaded from --resources-path directories (standalone) or via operator gRPC stream (k8s).

No Viper. Configuration loading is custom: flags parsed with the stdlib flag package wrapped in a custom options package, with explicit struct field binding.

Key design decisions#

  1. Sidecar isolation with language-agnostic API: The most fundamental decision — Dapr runs as a separate process. Applications call HTTP or gRPC on localhost, not a Go library. This means any language gets full Dapr capabilities. The cost is inter-process communication overhead (~1ms locally) and two containers to manage per pod.

  2. pkg/api/universal/ as a shared semantic layer: Both HTTP and gRPC handlers delegate to universal.Universal which implements all building-block semantics once. This enforces HTTP/gRPC parity by construction — adding a new API operation requires adding one function in universal, not two. The architectural decision record API-006-universal-namespace.md documents this choice.

  3. Component registration via blank imports with build-tag-controlled flavors: cmd/daprd/components/ uses _ imports to trigger component init() functions that register into typed global registries. The DAPR_SIDECAR_FLAVOR build tag selects between allcomponents and stablecomponents registration files, producing different binary sizes without conditional compilation scattered through the code.

  4. concurrency.RunnerCloserManager for structured concurrency: Rather than ad-hoc goroutine management, all long-running subsystems are registered as func(ctx context.Context) error runners. The manager propagates context cancellation, collects errors, and executes closers in reverse registration order during shutdown. This is a sophisticated structured-concurrency pattern from dapr/kit.

  5. Dual hot-reload paths (disk vs. operator): The hotreload.Reloader interface has two implementations — one watching the filesystem via inotify (standalone) and one streaming CRD change events from the operator (k8s). This lets the same daprd binary serve both deployment targets with no conditional code in the core runtime paths.

  6. Processor as the single gate for component lifecycle: All component init/close/reload operations go through pkg/runtime/processor. This centralizes resiliency application, authorization checks, and ordering guarantees for component startup sequences. Before this refactor (per ARC-001), component management was scattered across the runtime.