Manifests to stand up the DirectProject Java Reference Implementation in Kubernetes cluster, using the public images published at https://hub.docker.com/orgs/directproject.
Everything installs into the direct-project namespace.
This repository is a starting point for deploying the Direct Java Reference Implementation on Kubernetes. The yaml manifest files are authored for a generic deployment that should work on almost any Kubernetes platform (with a few caveat). This deployment is not intended to be a production deployment configuration, but instead be a jumping off point for a robust Direct Project implementation.
This repository contains the following manifest files for the following Kubernetes resource and Direct Project micro-services.
| File | Component | Notes |
|---|---|---|
00-namespace.yaml |
namespace direct-project |
|
01-rabbitmq.yaml |
RabbitMQ 3.13 broker | not a DirectProject image; required by the Spring Cloud Stream services. User direct/direct. Mnesia data on a 2Gi PVC (rabbitmq-data, standard StorageClass) so durable queues/messages survive restarts. |
10-config-service.yaml |
Configuration service | embedded H2 on a 1Gi PVC (config-service-data), /actuator/health probes, HTTP 8080 |
11-config-ui.yaml |
Configuration UI | HTTP 8080, login admin/direct |
20-dns-sboot.yaml |
DNS server | DNS on 53 TCP+UDP, HTTP 8080 |
30-direct-james-server.yaml |
James mail server | SMTP 587→1587, IMAP 1143, POP3 1110, webadmin API/HTTP 8084 (admin/d1r3ct); embedded Derby store on a 2Gi PVC (direct-james-server-data) |
40-direct-smtp-mq-gateway.yaml |
SMTP→MQ gateway | non-web; SMTP 25→1025 |
50-direct-msg-monitor.yaml |
Message monitor | H2 file DB on a 1Gi PVC (direct-msg-monitor-data); /actuator/health probes, HTTP 8080 |
60-direct-sta-sboot.yaml |
Security & Trust Agent (core) | /actuator/health probes, HTTP 8080 |
70-xd.yaml |
XD*/XDR SOAP endpoint | HTTP 8080, context path /xd (no manifest in the source repo — added here) |
80-bootstrap-domain.yaml |
one-shot Job |
seeds one Direct domain into config-service (the STA won't start with zero domains) |
Each service manifest bundles its own Secret (<service>-config), a Deployment,
and a ClusterIP Service. For production deployments, it is recommended that you use an appropriate methodology
for deploying/applying secrets to your cluster.
Run the following kubectl command to deploy the Reference Implementation to your cluster.
kubectl apply -f .You can check the progress of the install by running the following kubectl commands.
kubectl -n direct-project rollout status deployment --timeout=300s
kubectl get pods -n direct-projectBring-up order matters loosely (RabbitMQ + config-service first); Kubernetes will retry the others until their dependencies are ready.
If the rollout stalls — pods stuck in Pending / ContainerCreating / CrashLoopBackOff —
see Notes / caveats below. On a managed platform the usual cause is a
PersistentVolumeClaim that can't bind (the cluster's StorageClass isn't standard, or
on EKS the EBS CSI driver isn't installed). kubectl -n direct-project get pvc and
kubectl -n direct-project describe pod <name> will show the specific failure.
The commands below use kubectl port-forward to give you local access to
individual services for testing and troubleshooting. Each one tunnels a port on
your workstation to the in-cluster Service and runs in the foreground — leave it
running while you need the connection and press Ctrl-C to close it.
Port-forwarding is a developer convenience, not a way to expose the platform. To
make services reachable from outside the cluster, adopt a real ingress strategy.
The Kubernetes Gateway API is the current
recommended approach: deploy an implementation (for example Envoy Gateway, Istio,
NGINX Gateway Fabric, or your cloud provider's controller) and define Gateway
plus HTTPRoute resources for the HTTP endpoints (config UI, config service, XD)
and TCPRoute resources for the mail protocols (SMTP, IMAP, POP3), terminating
TLS and applying authentication at that layer. A traditional Ingress controller
also works for the HTTP endpoints. RabbitMQ is intentionally left out — the
messaging infrastructure should never be exposed outside the cluster.
# HTTP endpoints
kubectl -n direct-project port-forward svc/config-ui 8080:8080 # http://localhost:8080 (admin/direct)
kubectl -n direct-project port-forward svc/config-service 8082:8080 # http://localhost:8082/actuator/health
kubectl -n direct-project port-forward svc/xd 9080:8080 # http://localhost:9080/xd
# Inbound mail entry point: SMTP -> RabbitMQ gateway
kubectl -n direct-project port-forward svc/direct-smtp-mq-gateway 2525:25
# James mail server
kubectl -n direct-project port-forward svc/direct-james-server 1587:587 # SMTP submission
kubectl -n direct-project port-forward svc/direct-james-server 1143:1143 # IMAP
kubectl -n direct-project port-forward svc/direct-james-server 1110:1110 # POP3
kubectl -n direct-project port-forward svc/direct-james-server 8084:8084 # webadmin API-
PVC StorageClass — adjust per platform. The Persistent Volume Claims (PVCs) specify
storageClassName: standard, which is the default on Docker Desktop / kind / minikube. On a managed platform such as EKS, changestorageClassNameon all four PVCs (or remove the field to use the cluster's own default StorageClass, if it has one):Platform StorageClass to use Notes Docker Desktop / kind / minikube standard(alsohostpath/local-path)node-local, as shipped AKS (Azure) managed-csiormanaged-csi-premiumAzure Disk CSI is preinstalled; managed-csiis the defaultGKE (Google) standard-rwoorpremium-rwoPD CSI is preinstalled; standard-rwois the default on current GKEEKS (AWS) gp3(orgp2)needs setup — see below On EKS the Amazon EBS CSI driver is not installed by default, and recent EKS clusters also ship with no default StorageClass, so these
PersistentVolumeClaims stayPendinguntil you (1) install theaws-ebs-csi-driverEKS add-on and attach an IAM role to it, and (2) create agp3StorageClass (optionally marked the cluster default). Install instructions: https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html. Also note that an EBS volume lives in a single Availability Zone: with a node group spanning multiple AZs a pod can land in an AZ where its volume does not exist. KeepingvolumeBindingMode: WaitForFirstConsumeron the StorageClass (the default for the AWSgp3/gp2classes) avoids this by not provisioning the volume until the pod is scheduled. -
Persistence. Every stateful component keeps its data on a
ReadWriteOncePersistentVolumeClaim(all on thestandardStorageClass). Each of those Deployments usesstrategy: Recreate, because an embedded H2/Derby database holds an exclusive file lock and an RWO volume attaches to one pod at a time — the old pod must stop before the new one starts (more on locks in the Database section)PVC Size Backs rabbitmq-data2Gi RabbitMQ Mnesia dir — durable queues + persistent messages config-service-data1Gi config-service H2 DB — domains, trust anchors, certs, settings direct-msg-monitor-data1Gi msg-monitor H2 DB — transaction / aggregation / duplicate store direct-james-server-data2Gi James embedded Derby — users, mailboxes, message blobs RabbitMQ additionally pins
RABBITMQ_NODENAME=rabbit@localhostso its Erlang node name stays stable across restarts (a plain Deployment gives each pod a new hostname, which would orphan the Mnesia data). The msg-monitor stock image runs with an in-memory H2 database; herespring.datasource.urlis overridden to a file database on the PVC.Not persisted (recreated or rebuilt on each start, or simply no local state): James's
conf/andproperties/dirs, James'slucene/search index, and config-ui / dns-sboot / direct-smtp-mq-gateway / xd / direct-sta-sboot. -
Single replica only with the default embedded database.
config-service,direct-msg-monitoranddirect-james-serveruse a file-backed embedded database out of the box (H2 for the first two, Derby for James). That database takes an exclusive lock on its files — only one process may open it at a time — so these three Deployments must stay atreplicas: 1. A second pod would fail to start because it cannot acquire the lock, and this is also why they run withstrategy: Recreate(a rolling update would briefly have two pods contending for the same volume and lock). To run more than one replica of any of them, move it off the embedded database onto a shared external MySQL or PostgreSQL instance —spring.r2dbc.*forconfig-service,spring.datasource.*fordirect-msg-monitoranddirect-james-server, set in the service's Secret. -
Default credentials are the RI stock values (
admin/d1r3ct;for service-to-service basic auth,admin/directfor the config UI). Change them in the per-service Secrets before exposing this anywhere. -
The STA expects trust anchors / domains / certificates to be configured in config-service before mail will actually flow — standing up the pods does not configure a working HISP.
-
The STA refuses to start until at least one domain exists in config-service, so
80-bootstrap-domain.yamlseeds a placeholder domain (direct.example.com) purely to get it to boot. The STA only needs some domain present — once you add your own domain(s), the placeholder can be safely deleted (via the config UI or Configuration Manager).
The Kubernetes Deployment guide in the DirectProject Java RI documentation covers this deployment model in more depth — the generalized topology, per-platform storage classes, and the production-hardening topics (RabbitMQ, secrets management, external databases, and ingress). See the Cloud Native HISP Deployment Model for the reference implementation as a whole.