Skip to main content

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:

Example MTE Relay server diagram

info

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.
  • 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:

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:4.7.0

Video Guide​


Server Configuration​

MTE Relay Server is configured using environment variables.

Interactive Builder

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

FieldTypePurpose
upstreamstringFull URL (http://localhost:8080, https://api.internal)
pass_through_routesstring[]Paths that use standard HTTP proxy, without MTE encryption
client_id_secretstringLegacy shared secret used by some auth layers
cors_originsstring[]List of allowed CORS origins for preflight requests
cors_methodsstring[]List of allowed CORS methods for preflight requests
headersstringA JSON object of additional headers to add to proxied requests

Examples

  1. Single Proxy: Requests with Host header mte-api.company.com are decrypted and forwarded to http://internal-service. The /health route 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"],
}
}
  1. Multi-service proxy that handles:
  • Incoming encoded requests for billing.company.io, forwarding to http://billing-service with /ready and /live routes unencoded.
  • Request to auth.company.io, forwarding to http://auth-service with /health route unencoded.
  • All other requests (any Host) are proxied to http://default-backend:8080 without 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 400 No proxy destination found for host.

Health checks

Since 4.7.0, the /api/mte-echo health check route responds 200 even when the Host header has no matching DOMAIN_MAP entry. Load balancer health checks that probe by target IP instead of a mapped hostname no longer fail with an unmapped-host error.

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.
  • FORWARD_HOST_HEADER - Stamp X-Forwarded-Host on upstream requests. Default: true. See Forwarding Headers. Since 4.7.0

Forwarding Headers​

Since 4.7.0

Like any reverse proxy, the relay replaces the Host header with the upstream's host when it forwards a request. So that upstream services can still see the hostname the client originally requested (for example, a tenant subdomain in a multi-tenant application), the relay sets standard forwarding headers on every proxied request, on both MTE-encoded and pass-through routes:

HeaderValue
X-Forwarded-HostThe Host the client sent to the relay. Always stamped by the relay; a client-supplied value is replaced.
X-Forwarded-ProtoAn inbound X-Forwarded-Proto (for example, from a TLS-terminating load balancer) is preserved; otherwise derived from the connection to the relay (http or https).
X-Forwarded-ForThe client IP, appended to any inbound X-Forwarded-For chain.

X-Forwarded-Host is safe to use for authorization decisions such as tenant resolution because the relay overwrites any value a client sends, including headers inside the encoded MTE frame. Set FORWARD_HOST_HEADER=false to disable stamping (the behavior of releases before 4.7.0).


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.

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:4.7.0

Command Explanation:

  • docker run
    Runs a container from the specified image.
  • --rm
    Automatically removes the container when it exits.
  • -it
    Allocates an interactive terminal (-i for interactive input, -t for pseudo-TTY).
  • --name mte-relay
    Assigns a custom name (mte-relay) to the container.
  • -p 8080:8080
    Maps host port 8080 to container port 8080. You may change the host port if needed. Do not change the container port.
  • -e DOMAIN_MAP='...'
    Sets the DOMAIN_MAP environment 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:

docker-compose.yaml
services:
mte-relay-server:
image: 321186633847.dkr.ecr.us-east-1.amazonaws.com/customer/on-prem/mte-relay-server:4.7.0
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
  • -d - Run containers in the background.
    • Example: docker compose up -d

Check running containers with:

docker compose ps

Stop the services with:

docker compose down
info

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-deployment.yaml
# 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:4.7.0
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.

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 LoadBalancer services. Most on-premise OpenShift clusters have no cloud load balancer to provision, so a service of type: LoadBalancer stays in a pending state indefinitely. OpenShift exposes services externally with a Route object instead.
  • Containers run as an arbitrary user ID. The default restricted-v2 security context constraint (SCC) assigns each pod a random UID from the project's range and ignores the image's USER directive. Supporting images such as Redis need a writable volume mounted over their data directory as a result.
  • The DOMAIN_MAP key must match the route hostname. MTE Relay selects the upstream using the incoming Host header. If the key in DOMAIN_MAP does not match the hostname on the route, requests are rejected before they reach your backend.
info

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
warning

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-openshift.yaml
# 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:4.7.0
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 the DOMAIN_MAP value, 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.
note

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.

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
Troubleshooting
  • Pods stay in ImagePullBackOff: the ECR token has expired, or the pull secret was never linked. Recreate the secret and re-run oc secrets link.
  • Requests fail with No proxy destination found for host: the route hostname and the DOMAIN_MAP key do not match. Compare the output of oc 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 to ClusterIP and expose it with a route.
  • Redis logs persistence errors: the writable volume at /data is 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

Expected response:

{
"message": "test",
"timestamp": "<timestamp>"
}

Troubleshooting​

  1. Invalid Configuration
    • Check logs for missing/invalid environment variables.
  2. Relay unreachable
    • Verify firewall, networking, or Kubernetes service configuration.
  3. Redis connection issues
    • Ensure REDIS_URL is reachable in your environment.

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-debian12 base. 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 golang build 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 8080 is 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. 4.7.0) 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)