Architecture
Network topology, service ports, and trust boundaries of a self-hosted Dreadnode deployment — for security review and network planning.
A self-hosted Dreadnode deployment is a single Helm release: two platform services, three data stores, and a sandbox runtime, all inside your Kubernetes cluster. The only traffic that crosses the cluster boundary is HTTPS from your users in and HTTPS to Replicated endpoints out.
flowchart TB
Client["Browser · CLI · SDK"]
subgraph cluster["Kubernetes cluster"]
direction TB
Ingress["Ingress controller"]
Frontend["Frontend :3000"]
API["API :8000"]
subgraph data["Data stores"]
Postgres[("PostgreSQL<br/>:5432")]
ClickHouse[("ClickHouse<br/>:8123")]
MinIO[("MinIO<br/>:9000")]
end
subgraph sandbox["Sandbox runtime"]
Server["Sandbox server :80"]
Controller["Sandbox controller"]
Pods["Sandbox pods"]
end
end
Replicated["Replicated endpoints<br/>images · license · updates"]
Optional["Optional external services<br/>SMTP · OAuth · Logfire · E2B"]
Client -- "443 · <your-domain><br/>storage.<your-domain>" --> Ingress
Ingress -- "/" --> Frontend
Ingress -- "/api" --> API
Ingress -- "storage.<your-domain>" --> MinIO
Frontend -- "SSR" --> API
API --> Postgres
API --> ClickHouse
API --> MinIO
API -- "create / delete" --> Server
Server -- "BatchSandbox CRs" --> Controller
Controller -- "creates" --> Pods
API -. "exec" .-> Pods
cluster -. "outbound HTTPS" .-> Replicated
API -. "outbound, if enabled" .-> Optional
classDef external stroke-dasharray: 5 5
class Replicated,Optional external
style cluster fill:transparent
style data fill:transparent
style sandbox fill:transparent
Three trust boundaries matter for review:
- External clients → ingress. All user traffic enters through the ingress controller on 443 (or 80). Nothing else is exposed.
- In-cluster services. Every service is ClusterIP-only. The data stores and sandbox runtime are reachable only from inside the cluster.
- Cluster → internet. Egress is limited to Replicated endpoints for image pulls and updates, plus any optional external services you enable. The platform receives no inbound connections from Dreadnode.
Request routing
Section titled “Request routing”The chart emits standard networking.k8s.io/v1 Ingress resources across two hostnames:
| Host | Path | Backend service | Port |
|---|---|---|---|
<your-domain> | / | Frontend | 3000 |
<your-domain> | /api | API | 8000 |
storage.<your-domain> | / | MinIO S3 API | 9000 |
MinIO gets its own subdomain because S3 SDKs sign requests against host+path — path-prefix
routing breaks signature validation. Browsers hit storage.<your-domain> directly when
downloading artifacts through presigned URLs, so both hostnames must resolve and be covered
by your TLS certificate.
The frontend also calls the API service directly over the cluster network for server-side rendering, bypassing the ingress hairpin.
Inside the cluster
Section titled “Inside the cluster”Platform services. The frontend is a SvelteKit SSR server; the API is a FastAPI application that owns all business logic, authentication, and data access. Only these two services (plus MinIO) receive ingress traffic.
Data stores. PostgreSQL holds transactional data, ClickHouse holds traces and analytics, and MinIO provides S3 object storage for packages, artifacts, and files. All three run in-cluster by default with persistent volumes, and each can be swapped for an external service.
Sandbox runtime. Agent code runs in isolated sandbox pods, created on demand:
- The API asks the sandbox server (a lifecycle HTTP API) to create a sandbox.
- The sandbox server writes a
BatchSandboxcustom resource. - The sandbox controller (a Kubernetes operator) reconciles the resource into a pod.
- Command execution goes directly from the API to an exec daemon inside each sandbox pod — the sandbox server is only in the path for create, delete, and status.
Sandbox pods run in the release namespace by default and are created through the Kubernetes API using the controller’s RBAC-scoped service account. The chart ships no NetworkPolicies — sandbox pods follow your cluster’s default egress policy. Add your own NetworkPolicies if agent code must not reach the network.
Kubernetes footprint
Section titled “Kubernetes footprint”A default install creates these workloads, all in the release namespace:
| Component | Kind | Replicas | Persistence |
|---|---|---|---|
| API | Deployment | 1 | — |
| Frontend | Deployment | 1 | — |
| PostgreSQL | StatefulSet | 1 | 8 Gi PVC |
| ClickHouse | StatefulSet | 1 | 10 Gi PVC |
| MinIO | StatefulSet | 1 | 10 Gi PVC |
| Sandbox server | Deployment | 2 | — |
| Sandbox controller | Deployment | 1 | — |
| Replicated SDK | Deployment | 1 | — |
The Replicated SDK ships with installs from the Replicated registry — both install paths — but is disabled in the raw chart. Database migrations run as an init container on the API pod, and a post-install hook Job creates the MinIO buckets — there are no long-lived Jobs or CronJobs. The chart installs no admission webhooks and no autoscalers. Credentials are auto-generated as Kubernetes Secrets; the Helm Install page lists them.
Six resources are cluster-scoped — everything else stays in the namespace:
- Two CRDs —
batchsandboxes.sandbox.opensandbox.ioandpools.sandbox.opensandbox.io. Both are kept on uninstall so in-flight sandbox resources aren’t orphaned. - Two ClusterRole/ClusterRoleBinding pairs — one for the sandbox controller (pods, events, and the sandbox CRDs) and one for the sandbox server (pods, services, configmaps, events, the sandbox CRDs, and Secret create/delete for sandbox provisioning).
The API and frontend service accounts carry no RBAC rules — they exist for cloud workload identity annotations (for example, IAM roles for external data stores).
Network requirements
Section titled “Network requirements”Inbound:
| Port | Target | Required for |
|---|---|---|
| 443, 80 | Ingress controller | All user traffic |
| 8800 | KOTS Admin Console | Embedded Cluster installs only |
DNS — two records pointing at your ingress (or the VM for Embedded Cluster):
<your-domain> and storage.<your-domain>.
Outbound HTTPS:
| Endpoint | Purpose |
|---|---|
registry.replicated.com | Helm chart pulls (Helm CLI installs) |
proxy.enterprise.dreadnode.io | Container image pulls (license-authenticated) |
replicated.app | Installer download, license validation |
updates.enterprise.dreadnode.io | Application update metadata |
Air-gapped environments skip all four — see Air-gapped deployments below.
Internal service ports (all ClusterIP, not reachable from outside the cluster):
| Service | Port | Protocol |
|---|---|---|
| API | 8000 | HTTP |
| Frontend | 3000 | HTTP |
| PostgreSQL | 5432 | PostgreSQL |
| ClickHouse | 8123, 9000 | HTTP, native TCP |
| MinIO | 9000, 9001 | S3 API, console |
| Sandbox server | 80 | HTTP |
Replicated components
Section titled “Replicated components”Installs from the Replicated registry run a small SDK deployment alongside the platform. It
reports license state and instance telemetry to replicated.app and checks for updates —
outbound only. Container images pull through proxy.enterprise.dreadnode.io using a pull
secret bound to your license; the chart wires this automatically.
Air-gapped deployments
Section titled “Air-gapped deployments”For environments with no outbound network at all, install from an airgap bundle downloaded from the Replicated portal — every container image ships inside the bundle, so none of the outbound endpoints above are needed. At runtime nothing leaves your network: the platform, runtimes, models, and the targets you assess all sit inside your enclave.

Optional external services
Section titled “Optional external services”Everything below is disabled by default. Enabling one adds an outbound connection from the API. Configuration details live on the Configuration page.
| Service | Replaces or adds |
|---|---|
| External PostgreSQL | Replaces the in-cluster database (e.g. RDS) |
| External ClickHouse | Replaces in-cluster analytics (e.g. ClickHouse Cloud) |
| External S3 | Replaces MinIO (e.g. AWS S3) |
| SMTP relay | Adds transactional email delivery |
| OAuth providers | Adds GitHub, Google, or OIDC login |
| LLM gateway (LiteLLM) | Adds model inference for agents and chat |
| Logfire | Adds observability export |
| E2B | Replaces the in-cluster sandbox runtime |
Helm CLI vs Embedded Cluster
Section titled “Helm CLI vs Embedded Cluster”Both install paths deploy the same chart. The difference is who provides the infrastructure below it:
| Layer | Helm CLI | Embedded Cluster |
|---|---|---|
| Kubernetes | You (1.28+) | Installer (k0s) |
| Ingress controller | You (Traefik recommended) | Installer (Traefik, host ports 80/443) |
| Storage | You (any StorageClass) | Installer (OpenEBS) |
| DNS and TLS | You | You (DNS to the VM; TLS via Admin Console) |
| Configuration | values.yaml | KOTS Admin Console on port 8800 |
For Helm CLI, the diagram above is exact — your ingress controller and load balancer sit in front. For Embedded Cluster, everything runs on one VM: Traefik binds ports 80/443 directly on the host, and the KOTS Admin Console adds the one extra inbound port, 8800.