MTE Relay Server On-Premise Deployment
Introductionβ
MTE Relay Server is an end-to-end encryption system that protects all network requests with next-generation application data security. It acts as a proxy server in front of your backend, communicating with an MTE Relay Client to encode and decode all network traffic. The server is highly customizable and supports integration with other services through custom adapters.
Below is a typical architecture where a client application communicates with an MTE Relay Server, which then proxies decoded traffic to backend services:

MTE Relay Servers can only communicate with MTE Relay Clients. An MTE API Relay cannot communicate with an MTE Relay Server or an MTE Relay Client SDK. MTE Relay Servers are strictly for client-to-server communications.
Prerequisitesβ
Technical Requirementsβ
- A web or mobile application project that communicates with a backend API over HTTP.
- Our demo app is available on GitHub: MTE Relay Demos.
- Docker installed and running on your system.
- AWS CLI installed.
Skills and Knowledgeβ
- Familiarity with Docker and/or Kubernetes.
- Basic knowledge of configuring containerized services.
Credentialsβ
- An AWS Access Key ID and AWS Secret Access Key provided by Eclypses to access the private container repository.
Deployment Optionsβ
MTE Relay Server is provided as a Docker image and can be deployed on-premise using:
- Docker Run
- Docker Compose
- Kubernetes
- OpenShift
- Other container runtimes (e.g., Podman, Docker Swarm, K3s)
1. Configure AWS CLI Accessβ
You must configure a new AWS CLI profile using the credentials provided by Eclypses.
aws configure --profile eclypses-customer-on-prem
When prompted:
- AWS Access Key ID: Enter the ID provided by Eclypses.
- AWS Secret Access Key: Enter the secret key provided.
- Default region name: us-east-1
- Default output format: json
2. Pull the Docker Imageβ
Authenticate Docker with the Eclypses ECR repository:
- bash
- PowerShell
aws ecr get-login-password \
--region us-east-1 \
--profile eclypses-customer-on-prem \
| docker login --username AWS \
--password-stdin 321186633847.dkr.ecr.us-east-1.amazonaws.com
aws ecr get-login-password `
--region us-east-1 `
--profile eclypses-customer-on-prem `
| docker login --username AWS `
--password-stdin 321186633847.dkr.ecr.us-east-1.amazonaws.com
Then pull the image:
docker pull 321186633847.dkr.ecr.us-east-1.amazonaws.com/customer/on-prem/mte-relay-server:5.0.0-beta.1
Video Guideβ
Server Configurationβ
MTE Relay Server is configured using environment variables.
Using a DOMAIN_MAP (Recommended)β
Use the DOMAIN_MAP Builder tool to configure your domains visually, validate your settings, and copy a ready-to-use DOMAIN_MAP value β no manual JSON editing required.
DOMAIN_MAP is a JSON object keyed by the Host header that arrives with each request.
The value for each key is a settings object that tells the proxy how to process the request.
Settings
| Field | Type | Purpose |
|---|---|---|
| upstream | string | Full URL (http://localhost:8080, https://api.internal) |
| pass_through_routes | string[] | Paths that use standard HTTP proxy, without MTE encryption |
| client_id_secret | string | Legacy shared secret used by some auth layers |
| cors_origins | string[] | List of allowed CORS origins for preflight requests |
| cors_methods | string[] | List of allowed CORS methods for preflight requests |
| headers | string | A JSON object of additional headers to add to proxied requests |
Examples
- Single Proxy: Requests with Host header
mte-api.company.comare decrypted and forwarded tohttp://internal-service. The/healthroute is proxied without MTE encoding/decoding.
{
"mte-api.company.com": {
"upstream": "http://internal-service",
"client_id_secret": "8KeJmtuKweUhymNJmGHvGMrJCUtHxhQG",
"pass_through_routes": ["/health"],
"cors_origins": ["https://app.company.com"],
}
}
- Multi-service proxy that handles:
- Incoming encoded requests for
billing.company.io, forwarding tohttp://billing-servicewith/readyand/liveroutes unencoded. - Request to
auth.company.io, forwarding tohttp://auth-servicewith/healthroute unencoded. - All other requests (any Host) are proxied to
http://default-backend:8080without encoding.
{
"billing.company.io": {
"upstream": "http://billing-service",
"pass_through_routes": ["/ready", "/live"],
"client_id_secret": "8KeJmtuKweUhymNJmGHvGMrJCUtHxhQG",
"cors_origins": ["https://app.company.com"],
},
"auth.company.io": {
"upstream": "http://auth-service:3000",
"pass_through_routes": ["/health"],
"client_id_secret": "heYBRbTr3QNBgQFV6x6YpfWqyEs2Tj8F",
"cors_origins": ["https://app.company.com"],
"cors_methods": ["GET", "POST", "OPTIONS"]
},
"*": {
"upstream": "http://default-backend:8080",
"client_id_secret": "PmNPtWJMzz46S9k8cY7du5Z6XYc9B5Ad",
"cors_origins": ["https://app.company.com", "http://localhost:3000"],
}
}
Export as one-line env var:
export DOMAIN_MAP='{ "billing.company.io": { "upstream": "http://billing-service", "pass_through_routes": ["/ready", "/live"], "client_id_secret": "8KeJmtuKweUhymNJmGHvGMrJCUtHxhQG" }, "auth.company.io": { "upstream": "http://auth-service", "pass_through_routes": ["/health"], "client_id_secret": "heYBRbTr3QNBgQFV6x6YpfWqyEs2Tj8F" }, "*": { "upstream": "http://default-backend:8080", "client_id_secret": "PmNPtWJMzz46S9k8cY7du5Z6XYc9B5Ad" } }'
Host header derivationβ
The Host header is taken directly from the authority component of the absolute URL used in the request.
For example, https://api.example.com/users/1 the authority is api.example.com (port 443 is implicit for HTTPS), so the Host header sent by the client is exactly:
Host: api.example.com
If the URL contains an explicit port (https://api.example.com:8443/users/1) the header becomes:
Host: api.example.com:8443
DOMAIN_MAP must therefore use the exact same stringβdomain only, or domain:portβto match the incoming Host header. Or, use a wildcard "*" to match any host.
Host Matchingβ
MTE Relay Server performs an exact match against the Host header (case-insensitive).
If an exact match is not found, it checks for a wildcard ("*").
If no match is found, the request is rejected with 404.
Using individual Environment Variables (Legacy)β
Using these environment variables will result in the MTE Relay Server being configured to only handle a single domain. This method is deprecated in favor of using the DOMAIN_MAP variable. If both are provided, DOMAIN_MAP will take precedence.
Example:
UPSTREAM- Upstream API or service URL.CLIENT_ID_SECRET- Secret for signing client IDs (minimum 32 characters).PASS_THROUGH_ROUTES- Comma-separated list of routes proxied without encoding/decoding.CORS_ORIGINS- Comma-separated list of allowed origins.CORS_METHODS- Comma-separated list of allowed methods. Default:GET, POST, PUT, DELETE.
UPSTREAM='https://api.my-company.com'
CLIENT_ID_SECRET='2DkV4DDabehO8cifDktdF9elKJL0CKrk'
PASS_THROUGH_ROUTES='/health,/version'
OUTBOUND_TOKEN='s3cr3tT0k3nV4lu3'
Additional Environment Variablesβ
PORT- Default:8080.LOG_LEVEL- One of trace, debug, info, warning, error, panic, disabled. Default:info.
Deployment Stepsβ
Option A: Docker Runβ
Using the docker run command, we can launch a single MTE Relay container locally for testing or local development purposes.
Copy the command below, modify the environment variable values, and run it in your terminal.
- bash
- PowerShell
docker run --rm -it \
--name mte-relay \
-p 8080:8080 \
-e DOMAIN_MAP='{ "relay.example.com": { "upstream": "__YOUR_UPSTREAM_URL__", "client_id_secret": "__YOUR_CLIENT_ID_SECRET__", "cors_origins": ["__YOUR_CORS_ORIGINS__"] } }' \
321186633847.dkr.ecr.us-east-1.amazonaws.com/customer/on-prem/mte-relay-server:5.0.0-beta.1
docker run --rm -it `
--name mte-relay `
-p 8080:8080 `
-e DOMAIN_MAP='{ "relay.example.com": { "upstream": "__YOUR_UPSTREAM_URL__", "client_id_secret": "__YOUR_CLIENT_ID_SECRET__", "cors_origins": ["__YOUR_CORS_ORIGINS__"] } }' `
321186633847.dkr.ecr.us-east-1.amazonaws.com/customer/on-prem/mte-relay-server:5.0.0-beta.1
Command Explanation:
docker run
Runs a container from the specified image.--rm
Automatically removes the container when it exits.-it
Allocates an interactive terminal (-ifor interactive input,-tfor pseudo-TTY).--name mte-relay
Assigns a custom name (mte-relay) to the container.-p 8080:8080
Maps host port8080to container port8080. You may change the host port if needed. Do not change the container port.-e DOMAIN_MAP='...'
Sets theDOMAIN_MAPenvironment variable as a JSON string mapping domains to their configuration (upstream URL, client ID secret, CORS origins, etc.).- The last line is the image to run.
Video Guideβ
Option B: Docker Composeβ
Docker Compose provides a convenient way to define and manage multi-container applications by allowing you to describe all of your services in a single YAML file. Once defined, Docker Compose can automatically create and start the containers with a single command, ensuring consistency across environments.
Create a new file named docker-compose.yaml with the following content, and update the environment variable values as needed:
services:
mte-relay-server:
image: 321186633847.dkr.ecr.us-east-1.amazonaws.com/customer/on-prem/mte-relay-server:5.0.0-beta.1
ports:
- "8080:8080"
environment:
- 'DOMAIN_MAP={ "relay.example.com": { "upstream": "__YOUR_UPSTREAM_URL__", "client_id_secret": "__YOUR_CLIENT_ID_SECRET__", "cors_origins": ["__YOUR_CORS_ORIGINS__"] } }' # Update this value!
- REDIS_URL=redis://redis:6379
depends_on:
- redis
redis:
image: "redis:alpine"
Docker Compose Commandsβ
To start the services defined in the docker-compose.yaml file, run:
docker compose up
Helpful CLI Flags:
-f- Specify an alternate compose file.- Example:
docker compose -f custom.yml up
- Example:
-d- Run containers in the background.- Example:
docker compose up -d
- Example:
Check running containers with:
docker compose ps
Stop the services with:
docker compose down
In production environments, it may be better to use a Docker Swarm cluster for improved scalability, isolation of services, rolling updates, and management.
Sources:
Docker Compose Documentation
Docker Compose CLI
Video Guideβ
Option C: Kubernetesβ
Kubernetes provides a powerful system for automating the deployment, scaling, and management of containerized applications. By defining resources in YAML files, it ensures reliability, scalability, and consistency across environments.
Create a file named mte-relay-deployment.yaml with the following content:
# MTE Relay container deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: mte-relay-deployment
spec:
replicas: 2
selector:
matchLabels:
app: mte-relay
template:
metadata:
labels:
app: mte-relay
spec:
containers:
- name: mte-relay-server
image: 321186633847.dkr.ecr.us-east-1.amazonaws.com/customer/on-prem/mte-relay-server:5.0.0-beta.1
ports:
- containerPort: 8080
env:
- name: DOMAIN_MAP
value: '{ "relay.example.com": { "upstream": "__YOUR_UPSTREAM_URL__", "client_id_secret": "__YOUR_CLIENT_ID_SECRET__", "cors_origins": ["__YOUR_CORS_ORIGINS__"] } }' # Update this value!
- name: REDIS_URL
value: "redis://my-redis-service:6379"
---
# MTE Relay Service to expose the deployment
apiVersion: v1
kind: Service
metadata:
name: mte-relay-service
spec:
type: LoadBalancer
selector:
app: mte-relay
ports:
- protocol: TCP
port: 8080 # External port, you may change this if needed
targetPort: 8080
---
# Redis container deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-redis-deployment
spec:
replicas: 1
selector:
matchLabels:
app: my-redis
template:
metadata:
labels:
app: my-redis
spec:
containers:
- name: redis
image: redis:7.2
ports:
- containerPort: 6379
---
# Redis Service to expose the deployment
apiVersion: v1
kind: Service
metadata:
name: my-redis-service
spec:
type: ClusterIP
selector:
app: my-redis
ports:
- protocol: TCP
port: 6379
targetPort: 6379
The above configuration provides these resources:
mte-relay-deployment (Deployment): Runs 2 replicas of the mte-relay-server container, exposing port 8080 with environment variables for upstream service, client secret, CORS, and Redis connection.mte-relay-service (Service - LoadBalancer): Exposes the mte-relay pods externally on port 80, forwarding traffic to container port 8080.my-redis-deployment (Deployment): Runs a single Redis instance on port 6379.my-redis-service (Service - ClusterIP): Provides internal cluster access to the Redis pod over port 6379.
Adding the Control Planeβ
MTE Relay 5.0.0-beta.1 can be managed at runtime from a web admin panel.
The panel is a second deployment: the same image started with MODE=control,
with a small volume of its own. It is optional, and the manifest above runs
without it. See Control Plane for what the panel
does and how configuration reaches the workers.
Create a file named mte-relay-control-plane.yaml with the following
content:
# Shared worker token and admin password
apiVersion: v1
kind: Secret
metadata:
name: mte-relay-control-plane
type: Opaque
stringData:
CONTROL_PLANE_TOKEN: "__YOUR_SHARED_WORKER_TOKEN__" # Update this value!
ADMIN_PW: "__YOUR_ADMIN_PASSWORD__" # Update this value!
---
# Storage for the override document and the admin session signing secret
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mte-relay-control-plane-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Mi
---
# Control plane deployment. Always one replica: the override document is a
# single file written by a single process, with no cross process locking.
apiVersion: apps/v1
kind: Deployment
metadata:
name: mte-relay-control-plane
spec:
replicas: 1
strategy:
type: Recreate # never two pods writing the same volume
selector:
matchLabels:
app: mte-relay-control-plane
template:
metadata:
labels:
app: mte-relay-control-plane
spec:
# The image runs as nonroot (uid/gid 65532) and CSI drivers commonly
# mount a fresh volume as root:root 0755. Without fsGroup the control
# plane cannot write to /data and the pod crash loops at startup.
securityContext:
fsGroup: 65532
containers:
- name: control-plane
image: 321186633847.dkr.ecr.us-east-1.amazonaws.com/customer/on-prem/mte-relay-server:5.0.0-beta.1
ports:
- containerPort: 8081
env:
- name: MODE
value: control
- name: PORT
value: "8081"
- name: CONTROL_PLANE_DATA_DIR
value: /data
envFrom:
- secretRef:
name: mte-relay-control-plane
volumeMounts:
- name: data
mountPath: /data
# /healthz is unauthenticated and reveals nothing but liveness
readinessProbe:
httpGet:
path: /healthz
port: 8081
initialDelaySeconds: 2
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 8081
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 250m
memory: 128Mi
volumes:
- name: data
persistentVolumeClaim:
claimName: mte-relay-control-plane-data
---
# ClusterIP on purpose. The panel holds the license key and client secrets,
# so it is reached through a port forward or an internal ingress with TLS,
# never a public LoadBalancer.
apiVersion: v1
kind: Service
metadata:
name: mte-relay-control-plane
spec:
type: ClusterIP
selector:
app: mte-relay-control-plane
ports:
- protocol: TCP
port: 8081
targetPort: 8081
Then point the workers at it by adding two environment variables to the
relay container in mte-relay-deployment.yaml:
env:
# ... existing DOMAIN_MAP and REDIS_URL entries ...
- name: CONTROL_PLANE_URL
value: "http://mte-relay-control-plane:8081"
- name: CONTROL_PLANE_TOKEN
valueFrom:
secretKeyRef:
name: mte-relay-control-plane
key: CONTROL_PLANE_TOKEN
Apply both files, then open the panel through a port forward:
kubectl apply -f mte-relay-control-plane.yaml
kubectl apply -f mte-relay-deployment.yaml
kubectl port-forward svc/mte-relay-control-plane 8081:8081
Sign in at http://localhost:8081 with the username admin and the password
from the secret. Workers appear on the Fleet page within a few seconds of
starting.
Kubernetes Commandsβ
To deploy the application to your kubernetes cluster, run:
kubectl apply -f mte-relay-deployment.yaml
You can check the status of your deployment with:
kubectl get deployments
kubectl get pods
kubectl get services
To check the logs of a running pod, use the command
kubectl logs <POD_NAME>
To scale up the number of replicas, use the command
kubectl scale deployment mte-relay-deployment --replicas=<NUMBER_OF_REPLICAS>
To delete the deployment and service, use the command
kubectl delete -f mte-relay-deployment.yaml
Source: Kubernetes Deployments
Video Guideβ
Option D: OpenShiftβ
Red Hat OpenShift runs standard Kubernetes objects, so the manifest from Option C is a valid starting point. Three OpenShift-specific differences need to be accounted for:
- Routes replace
LoadBalancerservices. Most on-premise OpenShift clusters have no cloud load balancer to provision, so a service oftype: LoadBalancerstays in a pending state indefinitely. OpenShift exposes services externally with aRouteobject instead. - Containers run as an arbitrary user ID. The default
restricted-v2security context constraint (SCC) assigns each pod a random UID from the project's range and ignores the image'sUSERdirective. Supporting images such as Redis need a writable volume mounted over their data directory as a result. - The
DOMAIN_MAPkey must match the route hostname. MTE Relay selects the upstream using the incomingHostheader. If the key inDOMAIN_MAPdoes not match the hostname on the route, requests are rejected before they reach your backend.
MTE Relay images already run as a non-root user and listen on port 8080, so
they run under the default restricted-v2 SCC without modification. No
elevated privileges, custom SCC, or service account changes are required.
1. Log In and Create a Projectβ
Log in with the OpenShift CLI. A token is available from the web console under your user name, Copy Login Command.
oc login --token=<YOUR_TOKEN> --server=https://api.<YOUR_CLUSTER_DOMAIN>:6443
Create a project to hold the relay:
oc new-project mte-relay
2. Create an Image Pull Secretβ
OpenShift needs credentials to pull from the Eclypses ECR repository. Create a pull secret from an ECR authorization token and link it to the project's default service account:
oc create secret docker-registry eclypses-ecr --docker-server=321186633847.dkr.ecr.us-east-1.amazonaws.com --docker-username=AWS --docker-password="$(aws ecr get-login-password --region us-east-1 --profile eclypses-customer-on-prem)"
oc secrets link default eclypses-ecr --for=pull
ECR authorization tokens expire after 12 hours. The secret above is enough
for an evaluation, but any pod scheduled after the token expires fails with
ImagePullBackOff. For production, mirror the image into the OpenShift
internal registry or into a registry your organization already operates,
and point the deployment at that copy.
3. Look Up Your Apps Domainβ
Routes are published under the cluster's wildcard apps domain. Retrieve it so you can choose a hostname:
oc get ingresses.config/cluster -o jsonpath='{.spec.domain}'
This returns something like apps.ocp.example.com, which makes
mte-relay.apps.ocp.example.com a valid route hostname.
4. Create the Manifestβ
Create a file named mte-relay-openshift.yaml with the following content:
# MTE Relay configuration, held in a secret rather than inline in the deployment
apiVersion: v1
kind: Secret
metadata:
name: mte-relay-config
type: Opaque
stringData:
# The domain key must match the route hostname defined below. Update this value!
DOMAIN_MAP: '{ "mte-relay.__YOUR_APPS_DOMAIN__": { "upstream": "__YOUR_UPSTREAM_URL__", "client_id_secret": "__YOUR_CLIENT_ID_SECRET__", "cors_origins": ["__YOUR_CORS_ORIGINS__"] } }'
---
# MTE Relay container deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: mte-relay-deployment
spec:
replicas: 2
selector:
matchLabels:
app: mte-relay
template:
metadata:
labels:
app: mte-relay
spec:
containers:
- name: mte-relay-server
image: 321186633847.dkr.ecr.us-east-1.amazonaws.com/customer/on-prem/mte-relay-server:5.0.0-beta.1
ports:
- containerPort: 8080
envFrom:
- secretRef:
name: mte-relay-config
env:
- name: REDIS_URL
value: "redis://my-redis-service:6379"
readinessProbe:
httpGet:
path: /api/mte-echo
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /api/mte-echo
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "2"
memory: 1Gi
---
# MTE Relay Service, reached from outside the cluster through the route below
apiVersion: v1
kind: Service
metadata:
name: mte-relay-service
spec:
type: ClusterIP
selector:
app: mte-relay
ports:
- name: http
protocol: TCP
port: 8080
targetPort: 8080
---
# Route to expose the service outside the cluster
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: mte-relay
annotations:
# Raise the router timeout so long-running responses are not cut off
haproxy.router.openshift.io/timeout: 1h
spec:
# Must match the domain key used in DOMAIN_MAP above. Update this value!
host: mte-relay.__YOUR_APPS_DOMAIN__
to:
kind: Service
name: mte-relay-service
port:
targetPort: http
tls:
termination: edge
insecureEdgeTerminationPolicy: Redirect
---
# Redis container deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-redis-deployment
spec:
replicas: 1
selector:
matchLabels:
app: my-redis
template:
metadata:
labels:
app: my-redis
spec:
containers:
- name: redis
image: redis:7.2
ports:
- containerPort: 6379
volumeMounts:
# Redis cannot write to the /data directory baked into its image
# once OpenShift assigns an arbitrary user ID, so mount a writable
# volume over it
- name: redis-data
mountPath: /data
volumes:
- name: redis-data
emptyDir: {}
---
# Redis Service to expose the deployment
apiVersion: v1
kind: Service
metadata:
name: my-redis-service
spec:
type: ClusterIP
selector:
app: my-redis
ports:
- protocol: TCP
port: 6379
targetPort: 6379
The above configuration provides these resources:
mte-relay-config (Secret): Holds theDOMAIN_MAPvalue, keeping the client ID secret out of the deployment manifest.mte-relay-deployment (Deployment): Runs 2 replicas of the mte-relay-server container on port 8080, with health probes against the echo route.mte-relay-service (Service - ClusterIP): Provides in-cluster access to the relay pods on port 8080.mte-relay (Route): Publishes the service on the cluster's apps domain with edge TLS termination.my-redis-deployment (Deployment): Runs a single Redis instance on port 6379 with a writable volume at/data.my-redis-service (Service - ClusterIP): Provides internal cluster access to the Redis pod over port 6379.
The emptyDir volume above keeps Redis simple for an evaluation, but its
contents are lost whenever the Redis pod restarts, and connected clients
re-pair. For production, replace it with a persistentVolumeClaim or point
REDIS_URL at a Redis service your organization already operates.
Adding the Control Plane on OpenShiftβ
The admin panel runs on OpenShift the same way it runs on Kubernetes, with
two differences. Leave fsGroup out of the pod spec: the restricted-v2 SCC
assigns its own uid and fsGroup, and the control plane writes its volume
correctly under them. And do not put a Route in front of it. The panel
holds the license key and client secrets, so reach it with oc port-forward,
or with a route restricted to your internal network if your cluster has one.
Append the following to mte-relay-openshift.yaml. See
Control Plane for what the panel manages.
---
# Shared worker token and admin password
apiVersion: v1
kind: Secret
metadata:
name: mte-relay-control-plane
type: Opaque
stringData:
CONTROL_PLANE_TOKEN: "__YOUR_SHARED_WORKER_TOKEN__" # Update this value!
ADMIN_PW: "__YOUR_ADMIN_PASSWORD__" # Update this value!
---
# Storage for the override document and the admin session signing secret
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mte-relay-control-plane-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Mi
---
# Control plane deployment. Always one replica: the override document is a
# single file written by a single process, with no cross process locking.
# No securityContext here on purpose; the restricted-v2 SCC supplies one.
apiVersion: apps/v1
kind: Deployment
metadata:
name: mte-relay-control-plane
spec:
replicas: 1
strategy:
type: Recreate # never two pods writing the same volume
selector:
matchLabels:
app: mte-relay-control-plane
template:
metadata:
labels:
app: mte-relay-control-plane
spec:
containers:
- name: control-plane
image: 321186633847.dkr.ecr.us-east-1.amazonaws.com/customer/on-prem/mte-relay-server:5.0.0-beta.1
ports:
- containerPort: 8081
env:
- name: MODE
value: control
- name: PORT
value: "8081"
- name: CONTROL_PLANE_DATA_DIR
value: /data
envFrom:
- secretRef:
name: mte-relay-control-plane
volumeMounts:
- name: data
mountPath: /data
# /healthz is unauthenticated and reveals nothing but liveness
readinessProbe:
httpGet:
path: /healthz
port: 8081
initialDelaySeconds: 2
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 8081
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 250m
memory: 128Mi
volumes:
- name: data
persistentVolumeClaim:
claimName: mte-relay-control-plane-data
---
# ClusterIP with no route: the panel is reached through a port forward
apiVersion: v1
kind: Service
metadata:
name: mte-relay-control-plane
spec:
type: ClusterIP
selector:
app: mte-relay-control-plane
ports:
- name: http
protocol: TCP
port: 8081
targetPort: 8081
Add two environment variables to the relay container in the same file so the workers start polling:
env:
# ... existing REDIS_URL entry ...
- name: CONTROL_PLANE_URL
value: "http://mte-relay-control-plane:8081"
- name: CONTROL_PLANE_TOKEN
valueFrom:
secretKeyRef:
name: mte-relay-control-plane
key: CONTROL_PLANE_TOKEN
Once you deploy in the next step, open the panel with a port forward:
oc port-forward svc/mte-relay-control-plane 8081:8081
Sign in at http://localhost:8081 with the username admin and the password
from the secret.
5. Deployβ
oc apply -f mte-relay-openshift.yaml
Watch the pods come up:
oc get pods -w
Confirm the route is published:
oc get route mte-relay
OpenShift Commandsβ
The oc CLI is a superset of kubectl, so the Option C commands work
unchanged. The commands below cover the OpenShift-specific objects.
To view the hostname assigned to the route:
oc get route mte-relay -o jsonpath='{.spec.host}'
To check the logs of a running pod, use the command
oc logs <POD_NAME>
To scale up the number of replicas, use the command
oc scale deployment mte-relay-deployment --replicas=<NUMBER_OF_REPLICAS>
To confirm which security context constraint a pod was admitted under:
oc get pod <POD_NAME> -o jsonpath='{.metadata.annotations.openshift\.io/scc}'
To delete the deployment, service, and route, use the command
oc delete -f mte-relay-openshift.yaml
- Pods stay in
ImagePullBackOff: the ECR token has expired, or the pull secret was never linked. Recreate the secret and re-runoc secrets link. - Requests fail with
No proxy destination found for host: the route hostname and theDOMAIN_MAPkey do not match. Compare the output ofoc get route mte-relay -o jsonpath='{.spec.host}'against the key in the secret. - The service never receives an external IP: the service is still
type: LoadBalancer. Change it toClusterIPand expose it with a route. - Redis logs persistence errors: the writable volume at
/datais missing from the Redis deployment.
Source: OpenShift Routes
Testing & Health Checksβ
- Monitor container logs for startup messages
- Use the default or custom echo routes to test container responsiveness:
- Default:
/api/mte-echo - Custom Message:
/api/mte-echo?msg=test
- Default:
Expected response:
{
"message": "test",
"timestamp": "<timestamp>"
}
Troubleshootingβ
- Invalid Configuration
- Check logs for missing/invalid environment variables.
- Relay unreachable
- Verify firewall, networking, or Kubernetes service configuration.
- Redis connection issues
- Ensure
REDIS_URLis reachable in your environment.
- Ensure
Securityβ
Container Hardeningβ
MTE Relay Server is built and hardened using the following practices:
- Minimal base image β The runtime image uses Google's
Distroless
cc-debian12base. It contains only the application and its runtime dependencies β no shell, no package manager, and no general-purpose OS utilities β which dramatically reduces the attack surface. - Multi-stage build β The application is compiled in a separate
golangbuild stage. Build tooling and source never ship in the final image; only the compiled binary is copied forward. - Non-root runtime β The container runs as a dedicated unprivileged
user (
nonroot). No root privileges are required at runtime. - Minimal surface β Only port
8080is exposed, and the image runs a single binary entrypoint. - No persisted secrets β No sensitive data is stored in the container; all configuration is supplied at runtime via environment variables.
Vulnerability Scanningβ
Container images are scanned for known vulnerabilities with Docker Scout prior to release. Scan reports for the current image tag are available from Eclypses Support on request (see Support).
Because the on-premise image is distributed through Amazon ECR, customers can also generate their own native scan evidence by enabling Amazon ECR enhanced scanning (AWS Inspector) on the image they pull. The pulled image can equally be scanned with any container scanner of your choice (e.g. Trivy, Grype, or your existing CI/registry scanner) to produce evidence within your own environment.
Costsβ
Private infrastructure costs (VMs, storage, networking, Redis clusters) are customer-managed.
Maintenanceβ
Release Cadenceβ
Updated container images are published on an as-needed basis β when new
features, dependency updates, or fixes warrant a release. Security patches
are issued out-of-band as needed and are not tied to a fixed calendar
cadence. Each release is versioned (see the image tag, e.g. 5.0.0-beta.1) and
distributed through Eclypses' Amazon ECR repository.
Patch SLAβ
Eclypses' commitment for security patches:
- Critical vulnerabilities are addressed in a patched image within 30 days of confirmed disclosure.
- Lower-severity issues are addressed in a subsequent release.
To receive an updated image, pull the latest tag from the Eclypses ECR repository and redeploy your container.
Fault Recoveryβ
- Relaunch the Relay container; clients will automatically re-pair.
Supportβ
MTE Relay Server is a commercially supported product from Eclypses.
Support and security-patch SLAs (including the 30-day critical-patch
commitment described under Maintenance) are provided to
licensed/subscribed customers. For escalations, scan reports, or hardening
documentation, contact Eclypses Support:
π§ customer_support@eclypses.com
π MondayβFriday, 8:00 AMβ5:00 PM MST (excluding holidays)