Skip to main content
Version: 5.x

MTE Relay Server on Oracle Cloud Infrastructure

Introduction​

MTE Relay Server is a proxy that runs in front of your backend. Client applications use an MTE Relay client library to send encrypted requests; the relay decrypts each one, forwards the plaintext request to your backend, and encrypts the response on the way back.

Example MTE Relay server diagram

info

MTE Relay Server pairs only with the MTE Relay client libraries. It cannot pair with an MTE API Relay. v5 relays serve only v5 clients; see Upgrading from v4.

On Oracle Cloud Infrastructure (OCI), the same Docker image used for on premise deployments can be launched two ways:

  • OCI Container Instances, Oracle's managed container runtime for running containers without managing servers or a cluster.
  • Oracle Kubernetes Engine (OKE), Oracle's managed Kubernetes service, for orchestrated, scalable deployments.

Both options pull the image from the Oracle Cloud Infrastructure Registry (OCIR) in your own tenancy. The first sections below cover pulling the image from Eclypses and pushing it into OCIR; after that, follow Option A for Container Instances or Option B for OKE.


Prerequisites​

Technical Requirements​

  • A web or mobile application project that communicates with a backend API over HTTP.
  • An OCI tenancy with permission to use the Container Registry (OCIR) and either Container Instances or an OKE cluster.
  • Docker installed and running on your system.
  • AWS CLI installed (used only to pull the image from the Eclypses registry).
  • OCI CLI installed (optional for the console workflow, required for the CLI examples).
  • kubectl installed if deploying to OKE.

Skills and Knowledge​

  • Familiarity with Docker and/or Kubernetes.
  • Basic knowledge of OCI networking (VCNs, subnets, security lists).

Credentials​

  • An AWS Access Key ID and AWS Secret Access Key provided by Eclypses to access the private container repository.
  • An OCI auth token for logging Docker into OCIR (created below).

Step 1: Pull the Image from Eclypses​

Configure AWS CLI Access​

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

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

Step 2: Push the Image to OCIR​

OCI services pull images most reliably from the Container Registry in your own tenancy. Authorization tokens for the Eclypses registry expire after 12 hours, so pointing Container Instances or OKE directly at the Eclypses repository is not practical. Instead, pull the image once and push it into OCIR, then deploy from there.

1. Create an Auth Token​

In the OCI Console, open your profile menu, select My profile, then select Auth tokens and generate a new token. Copy the value immediately; it is only shown once. Store it in a password manager.

2. Find Your Tenancy Namespace​

The tenancy namespace (also called the Object Storage namespace) is part of every OCIR image path. Retrieve it with the OCI CLI:

oci os ns get

It is also shown in the console under Tenancy details.

3. Log Docker into OCIR​

OCIR registry hostnames follow the pattern <region-key>.ocir.io, for example iad.ocir.io for US East (Ashburn) or phx.ocir.io for US West (Phoenix). Log in using your tenancy namespace and user name, with the auth token as the password:

docker login <region-key>.ocir.io
  • Username: <tenancy-namespace>/<username>
    • Example: mytenancy/first.last@company.com
    • Federated users include the identity provider: <tenancy-namespace>/oracleidentitycloudservice/<username>
  • Password: the auth token created above.

4. Tag and Push​

docker tag 321186633847.dkr.ecr.us-east-1.amazonaws.com/customer/on-prem/mte-relay-server:5.0.5 <region-key>.ocir.io/<tenancy-namespace>/mte-relay-server:5.0.5
docker push <region-key>.ocir.io/<tenancy-namespace>/mte-relay-server:5.0.5
note

If the mte-relay-server repository does not already exist in OCIR, the push creates it automatically in your root compartment (when your user has permission to manage repositories). You can also create the repository first under Developer Services, Container Registry, and choose whether it is private (recommended) or public.


Server Configuration​

The image sets COMPANY_NAME and LICENSE_KEY to a development license. Set both to your production license on every relay.

MTE Relay Server is configured with environment variables. A standalone relay needs DOMAIN_MAP, which maps each request Host to an upstream; a relay managed by the control plane needs CONTROL_PLANE_URL and CONTROL_PLANE_TOKEN instead.

{
"relay.example.com": {
"upstream": "http://internal-service",
"client_id_secret": "REPLACE_WITH_A_LONG_RANDOM_SECRET",
"pass_through_routes": ["/health"],
"cors_origins": ["https://app.example.com"]
}
}

The key must match the host clients use to reach the relay: the load balancer DNS name, the public IP with port, or your custom domain. No match is answered 480 no_domain_for_host. A "*" key matches any host and is convenient while testing. Set REDIS_URL when you run more than one relay, so they share pair state.


Deployment Steps​

Option A: OCI Container Instances​

Container Instances run containers directly on managed infrastructure with no cluster, virtual machine, or control plane to operate. This is the fastest way to stand up a relay on OCI and is well suited to evaluations and lower traffic workloads.

1. Create the Container Instance​

In the OCI Console, go to Developer Services, Containers & Artifacts, Container Instances, and select Create container instance.

  • Name and compartment: for example mte-relay.
  • Shape: CI.Standard.E4.Flex with 1 OCPU and 4 GB of memory is a good starting point.
  • Networking: select your VCN and subnet. For a quick evaluation, use a public subnet and keep Assign a public IPv4 address checked. For production, place the instance in a private subnet behind an OCI Load Balancer.

2. Configure the Container​

  • Image: select OCIR and browse to <region-key>.ocir.io/<tenancy-namespace>/mte-relay-server:5.0.5. If the repository is private, provide the OCIR credentials (<tenancy-namespace>/<username> and your auth token) when prompted.
  • Environment variables: add DOMAIN_MAP with your configuration as a single line of JSON. While testing against the public IP, a wildcard key is easiest:
{ "*": { "upstream": "__YOUR_UPSTREAM_URL__", "client_id_secret": "__YOUR_CLIENT_ID_SECRET__", "cors_origins": ["__YOUR_CORS_ORIGINS__"] } }

Then select Create. The instance typically becomes active in under a minute.

3. Open Port 8080​

The relay listens on port 8080. Add an ingress rule for TCP port 8080 to the subnet's security list (or to a network security group attached to the instance): Networking, Virtual cloud networks, your VCN, Security Lists, Add Ingress Rule with source 0.0.0.0/0 (or a tighter CIDR) and destination port 8080.

4. Test​

curl "http://<PUBLIC_IP>:8080/api/mte-echo?msg=test"

Equivalent OCI CLI Command​

The same instance can be created from the CLI:

oci container-instances container-instance create \
--compartment-id <COMPARTMENT_OCID> \
--availability-domain <AVAILABILITY_DOMAIN> \
--display-name mte-relay \
--shape CI.Standard.E4.Flex \
--shape-config '{"ocpus": 1, "memoryInGBs": 4}' \
--containers '[{ "imageUrl": "<region-key>.ocir.io/<tenancy-namespace>/mte-relay-server:5.0.5", "displayName": "mte-relay-server", "environmentVariables": { "DOMAIN_MAP": "{ \"*\": { \"upstream\": \"__YOUR_UPSTREAM_URL__\", \"client_id_secret\": \"__YOUR_CLIENT_ID_SECRET__\" } }" } }]' \
--vnics '[{ "subnetId": "<SUBNET_OCID>", "isPublicIpAssigned": true }]'
Production notes
  • A page served over HTTPS cannot call an http:// relay. For production, place an OCI Load Balancer with a TLS certificate in front of the container instance, point your DNS record at it, and key DOMAIN_MAP on that hostname.
  • A single container instance keeps pairing state in memory, which is fine on its own. To run multiple instances behind a load balancer, all of them must share state: set REDIS_URL on each instance and point it at a non sharded OCI Cache cluster (managed Redis) or a Redis server you operate.

Option B: Oracle Kubernetes Engine (OKE)​

OKE runs standard Kubernetes, so the relay deploys with ordinary Kubernetes manifests. A Service of type LoadBalancer automatically provisions an OCI Load Balancer in front of the pods.

1. Connect kubectl to Your Cluster​

Create a kubeconfig entry for your cluster (the exact command is also shown in the console under your cluster's Access Cluster panel):

oci ce cluster create-kubeconfig --cluster-id <CLUSTER_OCID> --file $HOME/.kube/config --region <REGION> --token-version 2.0.0

2. Create an Image Pull Secret​

OKE needs credentials to pull from a private OCIR repository. Create a secret using the same OCIR login details from Step 2:

kubectl create secret docker-registry ocir-secret \
--docker-server=<region-key>.ocir.io \
--docker-username='<tenancy-namespace>/<username>' \
--docker-password='<YOUR_AUTH_TOKEN>' \
--docker-email='<username>'

3. Create the Manifest​

Create a file named mte-relay-oke.yaml with the following content:

mte-relay-oke.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:
imagePullSecrets:
- name: ocir-secret
containers:
- name: mte-relay-server
image: <region-key>.ocir.io/<tenancy-namespace>/mte-relay-server:5.0.5
ports:
- containerPort: 8080
name: relay
- containerPort: 9091
name: probe
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"
readinessProbe:
httpGet: { path: /readyz, port: probe }
livenessProbe:
httpGet: { path: /healthz, port: probe }

---

# MTE Relay Service; type LoadBalancer provisions an OCI Load Balancer
apiVersion: v1
kind: Service
metadata:
name: mte-relay-service
annotations:
oci.oraclecloud.com/load-balancer-type: "lb"
service.beta.kubernetes.io/oci-load-balancer-shape: "flexible"
service.beta.kubernetes.io/oci-load-balancer-shape-flex-min: "10"
service.beta.kubernetes.io/oci-load-balancer-shape-flex-max: "100"
spec:
type: LoadBalancer
selector:
app: mte-relay
ports:
- protocol: TCP
port: 80 # External port on the OCI Load Balancer
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 on port 8080, with probes on port 9091, pulling the image from OCIR with the pull secret.
  • mte-relay-service (Service - LoadBalancer): Provisions a flexible shape OCI Load Balancer that forwards external port 80 to container port 8080.
  • my-redis-deployment (Deployment): Runs a single Redis instance on port 6379 so both relay replicas share pairing state.
  • my-redis-service (Service - ClusterIP): Provides internal cluster access to the Redis pod over port 6379.

4. Deploy​

kubectl apply -f mte-relay-oke.yaml

Watch the pods come up and wait for the load balancer to receive an external IP address:

kubectl get pods
kubectl get service mte-relay-service --watch

Once EXTERNAL-IP is populated, test the relay:

curl "http://<EXTERNAL_IP>/api/mte-echo?msg=test"

Remember to update the DOMAIN_MAP key to match the hostname clients will actually use (your DNS name pointing at the load balancer), or use a wildcard "*" key while testing by IP.

Kubernetes Commands​

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 services (this also deletes the OCI Load Balancer), use the command

kubectl delete -f mte-relay-oke.yaml
TLS in production

For production traffic, terminate TLS in front of the pods. The common options on OKE are a TLS certificate on the OCI Load Balancer (listener annotations), or an ingress controller with cert manager. Whichever you choose, the DOMAIN_MAP key must match the public hostname.

Sources:
OKE: Pulling Images from Registry
OKE: Defining Load Balancers


Testing and Health Checks​

  • GET /api/mte-echo and HEAD /api/mte-echo on port 8080 answer for any Host, outside every rate limit, with {"message":"true","timestamp":"2026-01-04T10:30:45.123456789Z"}; ?msg=test makes "message":"test", always a JSON string. Any other method gets 405 with Allow: GET, HEAD.
  • GET /healthz on port 9091 answers 200 {"status":"ok","phase":"<phase>"} for as long as the process runs. GET /readyz on port 9091 answers 200 {"status":"ready","phase":"ready"} while the relay takes traffic, or 503 {"status":"not_ready","phase":"<phase>","reason":"<code>"} otherwise, with reason only when there is one. Both take GET and HEAD. Keep the probe port off public networks: it is for the orchestrator or load balancer only.
  • Health check path for scanners and load balancers: HEALTH_CHECK_PATH, off by default, mounts a health answer on port 8080 at a path you pick, for example /health. GET and HEAD there answer 200 {"status":"ready"} while the relay serves and 503 {"status":"not_ready"} once shutdown begins, for any Host and outside every rate limit. Use it for a load balancer or a security scanner that can only reach the relay port, and pick a path the origin does not use. See Health checks and scanners.
  • GET /api/mte-relay?frameVersions=2, sent with the Host your DOMAIN_MAP expects, returns a client id and the discovery document.

Every probe, echo, and health check path answer carries Content-Type: application/json, Cache-Control: no-store, no-transform, private, and X-Content-Type-Options: nosniff; the echo adds X-MTE-Relay-Request-Id.


Troubleshooting​

  1. Cannot push to OCIR (401 Unauthorized)
    • Verify the Docker login username is <tenancy-namespace>/<username> and the password is a current auth token, not your console password.
  2. OKE pods stay in ImagePullBackOff
    • The pull secret is missing, misspelled, or not referenced in imagePullSecrets. Recreate ocir-secret and confirm the image URL region and namespace.
  3. Requests rejected with 480 no_domain_for_host
    • The hostname used to reach the relay does not match a DOMAIN_MAP key. Use the exact DNS name or a wildcard "*" key.
  4. Relay unreachable
    • Check the subnet security list or NSG for an ingress rule on the relay port, and verify the load balancer health checks are passing.
  5. Redis connection issues
    • Ensure REDIS_URL is reachable from the relay pods or container instances.

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, with 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.
  • Unprivileged runtime: The container runs as a dedicated unprivileged user (nonroot). No root privileges are required at runtime.
  • Small listening surface: The image declares ports 8080 and 9091 and runs a single binary entrypoint. The probe listener on 9091 opens unless PROBE_PORT=0.
  • Runtime configuration: Configuration is supplied at runtime through environment variables. When you use the control plane, it stores the configuration you enter, secrets included, on its own volume.

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 image is pushed into your own OCIR repository, you can also generate native scan evidence by enabling repository scanning with the Oracle Vulnerability Scanning Service on the mte-relay-server repository. The pulled image can equally be scanned with any container scanner of your choice (e.g. Trivy or Grype) to produce evidence within your own environment.


Costs​

OCI infrastructure costs (Container Instances, OKE worker nodes, load balancers, OCIR storage, and Redis or OCI Cache) are billed to your Oracle Cloud account and 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, for example 5.0.5) 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, push it to your OCIR repository, and redeploy the container instance or update the OKE deployment manifest to reference the new tag.

Fault Recovery​

Relaunch the relay container. Without Redis, pair state lives in memory, so clients must authenticate and pair again after a restart. The browser engine does this when a request gets 475 invalid_client_id, but that request still fails with an MteRelayError marked repaired, and your code decides whether to send it again.


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 and subscribed customers. For escalations, scan reports, or hardening documentation, contact Eclypses Support: