




3366 Members
An embeddable PDP is a policy decision point that runs inside your application process as a library. Your code calls a function, the engine evaluates policy against data it already holds in memory, and you get a decision back with no network hop. The alternative is an out-of-process PDP: a separate container or service that your code calls over HTTP or gRPC, either on the same host (a sidecar) or somewhere else on the network (a remote service).
If you are evaluating an embeddable PDP, you are choosing a deployment architecture, so this post compares the three options on what actually differs in production: latency, how policy and data reach the engine, upgrades, and who carries the operational load. If you want the background on how PEPs, PDPs and decision logs fit together, start with PEP, PDP, and Decision Logs: The Architecture Behind "Prove It".
| Embedded (in-process) | Sidecar (same host or pod) | Remote service (central or cluster) | |
|---|---|---|---|
| Where it runs | Inside your app process, as a library | A separate container next to each service | A shared service other services call over the network |
| Call path | Function call | Loopback HTTP or gRPC | Network HTTP or gRPC |
| Network latency | None | Loopback only | One network round trip per check, or per batch |
| Language coupling | Needs a binding for your language or a Wasm runtime | Any language that can make an HTTP call | Any language that can make an HTTP call |
| Policy and data delivery | Into every app process | Into every sidecar | Into a smaller number of PDP instances |
| Upgrades | Ship with your app | Independent of your app | Independent of your app |
The PEP stays in your code in all three. What moves is the decision.
Open-source engines can run in-process. Open Policy Agent can be embedded as a Go library or compiled to WebAssembly and loaded by a Wasm runtime in another language. Cedar ships as a Rust library, and Go implementations are available for teams that want to evaluate Cedar policies from Go services. In both cases the decision is a local function call.
That gives you two real benefits. There is no network in the decision path, so network latency is zero and a network failure can't stop a check. There is also one less thing to deploy.
The costs show up later:
Embedded works well when the data set is small and mostly static, the stack is one or two languages, and you already own a pipeline that pushes policy and data into every process.
A sidecar puts the PDP in its own container on the same host or Kubernetes pod as the service. The call goes over loopback, so it never leaves the host. As shared in our docs, when a self-hosted PDP runs as a sidecar on the same host as your service, checks travel over the loopback interface, avoid an external network hop, and keep the service-to-PDP network cost in the sub-millisecond range. Deploying next to the enforcement point cuts network latency or brings it to zero: an in-process engine removes the hop entirely, and a sidecar keeps it on loopback.
What you gain over embedded:
What you still pay: a PDP per service instance, so data is replicated per sidecar, plus the CPU and memory of an extra container in every pod.
A remote PDP is a shared service. It can be one instance, or a cluster behind a load balancer. It is the simplest way to start, because there are few instances to deploy and few copies of the data to keep current.
The cost is a network round trip on every check, and a dependency on that network path. As shared in our docs, a single centralized PDP can become a bottleneck as the number of services grows, which is why production setups usually run a cluster. Batching helps: a bulk check that asks about many resources in one request turns many round trips into one.
Pick embedded when:
Pick a sidecar when:
Pick a remote PDP or a PDP cluster when:
Many teams mix them. A latency-sensitive API gets a sidecar, while back-office jobs call a shared PDP cluster.
Every placement needs an answer to the same question: how does each PDP get current policy and data, and how fast does a change reach it? A revoked role that takes ten minutes to reach a sidecar is a ten-minute window of access you can't explain later.
This is the problem OPAL, the open-source project Permit develops with its community, was built to solve. It pushes policy and data updates to running PDPs in real time, so a role or relationship change reaches the PDP without a redeploy. The PEP/PDP post covers this in more depth under OPAL keeps PDPs current.
Permit's PDPs run out of process. As shared in our docs, there are three types:
permitio/pdp-v2 container with OPA and the OPAL client), which you run in your own network as a sidecar, a centralized service, or a cluster. Use Edge PDP when you need ABAC or want the PDP deployed close to the enforcement point.One distinction matters here: in Nexus PDP, "embedded" refers to the database inside the PDP container, not to a PDP embedded in your application process. Your service still calls Nexus PDP as a separate service over HTTP on port 7000. Inside the container, policy and authorization data are served from the PDP's local store, so authorization checks do not depend on a live call to Permit, and the PDP keeps answering from its local copy if the control plane is unreachable.
Permit PDPs are built to run as services next to your code or elsewhere on your network. They don't ship as libraries you link into your application process. For true in-process checks, use an embedded engine such as OPA or Cedar.
Running an Edge PDP next to your service takes one container. As shared in our PDP docs, this command runs an Edge PDP locally and maps the PDP's port 7000 to 7766 on your machine:
docker run -it \
-p 7766:7000 \
--env PDP_API_KEY=<your-permit-api-key> \
--env PDP_DEBUG=True \
permitio/pdp-v2:latest
For production, pin a released tag instead of :latest, as shared in our docs. Your service then points the SDK at the local PDP:
import { Permit } from "permitio";
const permit = new Permit({
token: "[your-api-key]",
pdp: "http://localhost:7766",
});
const permitted = await permit.check("john@permit.io", "create", {
type: "document",
tenant: "default",
});
As shared in our Nexus PDP benchmarks, an RBAC check measured P50 4.27 ms and P99 5.59 ms on one 2 CPU pod at about 56 requests per second, and a ReBAC check walking three folder levels measured P99 11.28 ms. One 2 CPU pod sustained 658 requests per second on RBAC checks. Those figures are server-side, and the benchmark page notes that they were measured on a beta build on October 9, 2026, with one run per configuration. They exclude the network round trip between your service and the PDP, which is exactly the part that placement controls. Run the PDP next to your service and the service-to-PDP path stays on the host.
What is an embeddable PDP? A policy decision point that runs as a library inside your application process, so a permission check is a function call with no network hop.
Is a sidecar PDP as fast as an embedded one? An embedded engine has no network hop at all. A sidecar keeps the call on loopback, which stays in the sub-millisecond range, as shared in our docs. For most services the bigger differences are data sync, memory per replica and upgrade workflow.
Does Permit offer an in-process PDP? No. Permit's Cloud, Edge and Nexus PDPs run as separate services. You get the lowest latency by running an Edge PDP or Nexus PDP next to your service.
When should I use a central PDP? When one network round trip per check, or per bulk check, fits your latency budget and you want to run as few PDP instances as possible. Use a cluster for availability.
If you're deciding where your PDP should live, try Permit for free and run an Edge PDP next to one service, or talk to our team about Nexus PDP.

Co-Founder / CEO at Permit.io