Deployment Architecture
An EDK enterprise deployment runs the published enterprise service images with the Compose stack, Helm chart, gateway examples, Postman collection, and provisioning scripts from the Enterprise Development Kit Deployment repository.
This page is the canonical description of the public shape: the host scheme, the gateway contract a fronting layer must satisfy, and the routing table that maps host and path to a backend service. The install guides for Docker, Kubernetes, and cloud load balancers all implement what is described here, using the published nexus.sphereon.com/edk-docker/enterprise-* images and the deployment repository.
Single Port, Many Tenants
The stack runs a central platform container plus the tenant runtime services. Each environment exposes one external port, 443. Tenants are addressed by subdomain, and the gateway fans out by host and path. The only traffic that does not pass through this port is internal service-to-service traffic, including gRPC to the platform and tenant-KMS services.
This keeps the public surface small. There is one port to open and one place where TLS terminates. Adding a tenant adds a subdomain, not a port or a load balancer.
Host Scheme
Two host classes share the single port, where <base-domain> is the customer-controlled deployment base domain (for example example.com).
- The operator host
platform.<base-domain>carries the platform setup and admin APIs, the operator login (the platform's own authorization server), and the admin user interface athttps://platform.<base-domain>/admin-console. - Tenant hosts
<tenant>.<base-domain>, matched by the wildcard*.<base-domain>, carry the per-tenant protocol surface and the per-tenant management APIs. The tenant is identified by the host, soacme.<base-domain>resolves tenantacme.
The platform and tenants are not separate DNS zones in the EDK application model. They are sibling subdomains under the same base domain. Use either a wildcard certificate for *.<base-domain> plus the operator host, or individual certificates for every deployed host. A wildcard certificate is the normal operating model because new tenants are created as <tenant>.<base-domain>. If you use individual certificates, automation must issue and renew a certificate before each tenant host goes live.
platform is a reserved tenant slug, so no tenant can claim the operator host. The default reserved-slug set does not include it, so a deployment adds it through configuration:
tenant:
resolution:
reserved-slugs: ["platform"]
Reserved slugs only extend the set, they never shrink it.
The Gateway Contract
Any fronting layer is conformant when it meets all six rules. This holds for Cilium Gateway, Traefik, AWS ALB, GKE Gateway, Azure Application Gateway, or a customer's own proxy.
- Terminate TLS on one external port,
443, with certificates valid for the operator host and all tenant hosts. In most deployments this is a wildcard certificate for*.<base-domain>plusplatform.<base-domain>. Let's Encrypt is fine when issued with DNS-01 validation; HTTP-01 cannot validate a wildcard SAN. - Route by host and path to the backend service per the routing table below.
- Preserve the inbound
Hostheader end to end. The gateway must not rewrite Host to the upstream service name. This is the most important rule. - Set
X-Forwarded-Proto: https, and overwrite any client-suppliedX-Forwarded-*values rather than passing them through. - Do not require the tenant in the path. Tenant resolution is host-based.
- Leave gRPC
9090strictly east-west, never on the public port.
Why Host Preservation Is Mandatory
Tenant resolution reads the raw Host header. The stack deliberately does not install the Ktor XForwardedHeaders plugin, and it rejects X-Forwarded-Host and X-Tenant-ID as client-controlled values. A gateway that rewrites Host to the backend name, which is a common default on some controllers including Azure AGIC, makes every request resolve to the wrong tenant or to none. Configure the gateway to forward the original public host. The Cloud load balancers guide covers the one caveat per platform.
Routing Table
Tenant identity comes from the host, so the gateway never peels a tenant out of the path. It matches the path prefix and forwards to the backend service on its REST port, 8080.
Operator Host platform.<base-domain>
| Path prefix | Service |
|---|---|
/api/platform/setup/v1 | platform |
/api/platform/admin/v1 | platform |
/authorize, /token, /login, /oauth2, /userinfo | platform (operator AS) |
/.well-known/oauth-authorization-server, /.well-known/openid-configuration | platform |
/admin-console | admin-console |
Tenant Host *.<base-domain>
| Path prefix | Service |
|---|---|
/oid4vci, /credential, /credential_deferred, /nonce, /notification, /public/statuslists | issuer |
/.well-known/openid-credential-issuer | issuer |
/api/oid4vci/v1, /api/credential-design/v1, /api/statuslist/v1 | issuer |
/oid4vp, /request_uri, /direct_post | verifier |
/api/dcql/v1 | verifier |
/authorize, /token, /userinfo, /oauth2 | tenant-as |
/.well-known/oauth-authorization-server, /.well-known/openid-configuration, /.well-known/jwks.json | tenant-as |
/1.0/identifiers, /.well-known/did.json | did |
/api/did/v1 | did |
A few points about the table:
/authorizeand/tokenexist on both the platform AS and the tenant AS. They never collide because they live on different host classes: the operator host versus a tenant host.- Management APIs follow the
/api/<spec-name>/v1convention, so every service owns a distinct prefix and they all fan out on the single tenant host with no rewrite. See the REST API reference for the wire shapes. Administrative routes are not anonymous public traffic; expose them only through the documented gateway policy or internal ingress model. - Tenant-KMS is not part of the public routing table. Runtime services reach it internally for key operations, normally over gRPC on the east-west network.
Scheme and Advertised URLs
Each service advertises absolute URLs in issuer metadata, AS discovery documents, and OID4VP request URIs. Those URLs come from, in order of precedence:
- The per-tenant public-endpoint binding (
tenant_public_endpoint), bound forOID4VCI_ISSUER,OID4VP_VERIFIER, andOAUTH2_AUTHORIZATION_SERVERwithhost=<tenant>.<base-domain>. Provisioning binds all three per tenant. For the default hosted issuer, the OID4VCIcredential_issueridentifier is the tenant origin, for examplehttps://acme.example.com. - A configured issuer URL (
EXTERNAL_BASE_URL), used by the operator AS. X-Forwarded-ProtoandX-Forwarded-Hostwhen trusted, then a localhost fallback.
Because the bindings and the configured issuer URL cover every public AS, issuer, and verifier URL in this deployment, the X-Forwarded fallback is a backstop. The gateway still sets X-Forwarded-Proto: https so the fallback resolves https rather than the localhost default.
What Stays Internal
The platform container is the central control service. It owns first-run setup, license activation, operator login, platform administration, and platform configuration. The admin console is a separate UI container reached at https://platform.<base-domain>/admin-console; it calls the platform APIs with the operator token.
gRPC 9090 stays east-west only, never on the public port. The platform and tenant-KMS containers run inbound gRPC receivers. DID, tenant-AS, issuer, verifier, and tenant-KMS use internal gRPC to the platform service for platform configuration and platform-managed data. DID, tenant-AS, issuer, and verifier use internal gRPC to tenant-KMS for key generation, signing, verification, and public-key access. These internal routes need no public hostname. The Roles and Topology page covers the internal service relationships in detail.
Next Steps
- Install on Docker: the Traefik gateway overlay and a local wildcard certificate.
- Install on Kubernetes (Cilium): the Cilium Gateway API and wildcard TLS.
- Cloud load balancers: ALB, GKE Gateway, and Azure AGIC conformance.
- Provisioning and onboarding: bring up a platform with three fully deployed tenants.