MTE API Relay on Oracle Cloud Infrastructure
Introduction
MTE API Relay protects HTTP traffic between server applications with two relays. Your service sends ordinary HTTP to an outbound relay, which encrypts each request and sends it to an inbound relay in front of the destination API. The inbound relay decrypts it, calls the API, and encrypts the response for the trip back.

Both relays in the diagram are MTE API Relay instances. The outbound relay sits next to the calling API and the inbound relay next to the target API.
Both relays of a chain run the MTE API Relay image, which pairs only with another MTE API Relay. It cannot pair with an MTE Relay Server or with the client libraries. v5 relays pair only with v5 relays; 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
- An existing backend service that accepts HTTP calls.
- We provide a demo using a Postman Collection and https://jsonplaceholder.typicode.com.
- 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).
kubectlinstalled 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 with the Eclypses issued credentials:
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 registry:
- 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-api-relay: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>
- Example:
- Password: the auth token created above.
4. Tag and Push
docker tag 321186633847.dkr.ecr.us-east-1.amazonaws.com/customer/on-prem/mte-api-relay:5.0.5 <region-key>.ocir.io/<tenancy-namespace>/mte-api-relay:5.0.5
docker push <region-key>.ocir.io/<tenancy-namespace>/mte-api-relay:5.0.5
If the mte-api-relay 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.
Each relay is configured with environment variables, and routing lives in
DOMAIN_MAP. The inbound relay uses ordinary entries; the outbound relay uses
entries with outbound: true whose upstream is the inbound relay.
Inbound relay, in front of the API:
{
"inbound-relay.example.com": {
"upstream": "http://internal-service",
"client_id_secret": "REPLACE_WITH_A_LONG_RANDOM_SECRET",
"trust_forwarded_headers": true
}
}
Outbound relay, next to the calling service:
{
"outbound-relay.example.internal": {
"upstream": "https://inbound-relay.example.com",
"outbound": true
}
}
Set REDIS_URL on an inbound relay that runs more than one instance. The
on premise guide explains
both roles, and these pages cover every setting:
- DOMAIN_MAP, including outbound domains.
- Environment variables.
Deployment Steps
An MTE API Relay deployment always involves two relay instances: an outbound relay near the calling application and an inbound relay near the target API. On OCI, the outbound relay usually lives in a private subnet and is only reachable from your own services, while the inbound relay is published behind a load balancer or public IP so its partner can reach it. The steps below work for either role; the notes call out where they differ.
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-api-relay. - Shape:
CI.Standard.E4.Flexwith 1 OCPU and 4 GB of memory is a good starting point. - Networking: select your VCN and subnet. For an inbound relay used in a quick evaluation, use a public subnet and keep Assign a public IPv4 address checked. An outbound relay belongs in a private subnet with no public IP.
2. Configure the Container
- Image: select OCIR and browse to
<region-key>.ocir.io/<tenancy-namespace>/mte-api-relay: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_MAPwith your configuration as a single line of JSON. While testing an inbound relay against the public IP, a wildcard key is easiest:
{ "*": { "upstream": "__YOUR_UPSTREAM_URL__", "client_id_secret": "__YOUR_CLIENT_ID_SECRET__", "trust_forwarded_headers": true } }
For an outbound relay, use an entry with "outbound": true and the inbound
relay's HTTPS URL as upstream instead.
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). For an inbound relay, allow the source that
hosts your outbound relay; for an outbound relay, allow only your own
application subnets.
4. Test
curl "http://<PUBLIC_IP>:8080/api/mte-echo?msg=test"
This evaluation instance serves plain HTTP. An outbound relay refuses an
http:// upstream unless its entry also sets allow_plain_upstream: true, so
set that while you test against http://<PUBLIC_IP>:8080, and put TLS in front
of the inbound relay before production.
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-api-relay \
--shape CI.Standard.E4.Flex \
--shape-config '{"ocpus": 1, "memoryInGBs": 4}' \
--containers '[{ "imageUrl": "<region-key>.ocir.io/<tenancy-namespace>/mte-api-relay:5.0.5", "displayName": "mte-api-relay", "environmentVariables": { "DOMAIN_MAP": "{ \"*\": { \"upstream\": \"__YOUR_UPSTREAM_URL__\", \"client_id_secret\": \"__YOUR_CLIENT_ID_SECRET__\", \"trust_forwarded_headers\": true } }" } }]' \
--vnics '[{ "subnetId": "<SUBNET_OCID>", "isPublicIpAssigned": true }]'
- For production, place an OCI Load Balancer with a TLS certificate
in front of the inbound relay, point your DNS record at it, and key
DOMAIN_MAPon 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_URLon 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
The manifest below deploys an inbound relay published through an OCI
Load Balancer. Create a file named mte-api-relay-oke.yaml:
# MTE API Relay container deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: mte-api-relay-deployment
spec:
replicas: 2
selector:
matchLabels:
app: mte-api-relay
template:
metadata:
labels:
app: mte-api-relay
spec:
imagePullSecrets:
- name: ocir-secret
containers:
- name: mte-api-relay
image: <region-key>.ocir.io/<tenancy-namespace>/mte-api-relay: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__", "trust_forwarded_headers": true } }' # 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 API Relay Service; type LoadBalancer provisions an OCI Load Balancer
apiVersion: v1
kind: Service
metadata:
name: mte-api-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-api-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-api-relay-deployment (Deployment): Runs 2 replicas of the mte-api-relay container on port 8080, pulling the image from OCIR with the pull secret.mte-api-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.
An outbound relay is called by your own services from inside the cluster
and does not need to be reachable from outside it. Change the relay
service to type: ClusterIP, remove the load balancer annotations, and
key DOMAIN_MAP on the service hostname your applications will call,
such as mte-api-relay-service. The Service listens on port 80, so the
request's Host carries no port, and a key with a port matches only
requests that carry that port. Drop REDIS_URL and the Redis resources too,
because the outbound relay keeps its pairs in memory.
4. Deploy
kubectl apply -f mte-api-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-api-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 the relay
is actually reached by (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-api-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-api-relay-oke.yaml
Sources:
OKE: Pulling Images from Registry
OKE: Defining Load Balancers
Usage Guide
Point your service at the outbound relay instead of the destination API, and keep the path and query the same. No special request headers are needed; the host your service uses selects the outbound entry.
# Before
curl -X GET https://api.my-company.com/data
# After, with an outbound entry keyed on outbound-relay.example.internal
curl -X GET http://outbound-relay.example.internal:8080/data
The outbound relay returns the destination API's status, headers, and body as
ordinary HTTP. When the chain itself fails, the answer is a JSON error with a
relay code and an X-MTE-Relay-Error header; see
Errors and recovery.
Testing and Health Checks
GET /api/mte-echoandHEAD /api/mte-echoon port 8080 answer for anyHost, outside every rate limit, with{"message":"true","timestamp":"2026-01-04T10:30:45.123456789Z"};?msg=testmakes"message":"test", always a JSON string. Any other method gets405withAllow: GET, HEAD.GET /healthzon port 9091 answers200{"status":"ok","phase":"<phase>"}for as long as the process runs.GET /readyzon port 9091 answers200{"status":"ready","phase":"ready"}while the relay takes traffic, or503{"status":"not_ready","phase":"<phase>","reason":"<code>"}otherwise, withreasononly when there is one. Both takeGETandHEAD. 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.GETandHEADthere answer200{"status":"ready"}while the relay serves and503{"status":"not_ready"}once shutdown begins, for anyHostand 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. - A request through the outbound relay reaches the destination API.
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
- 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.
- Verify the Docker login username is
- OKE pods stay in
ImagePullBackOff- The pull secret is missing, misspelled, or not referenced in
imagePullSecrets. Recreateocir-secretand confirm the image URL region and namespace.
- The pull secret is missing, misspelled, or not referenced in
- Requests rejected with
480 no_domain_for_host- The hostname used to reach the relay does not match a
DOMAIN_MAPkey. Use the exact DNS name or a wildcard"*"key.
- The hostname used to reach the relay does not match a
- 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.
- Cannot Reach Upstream Service
- Verify the container can resolve and connect to the target host from inside its VCN.
- Redis connection issues
- Ensure
REDIS_URLis reachable from the relay pods or container instances.
- Ensure
Security
Container Hardening
MTE API Relay 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, 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
golangbuild 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
8080and9091and runs a single binary entrypoint. The probe listener on 9091 opens unlessPROBE_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.
- Network isolation: Deploy the inbound relay close to the upstream service to minimize exposure of unencrypted traffic.
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-api-relay 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. The outbound relay pairs with the inbound relay again on its next request.
Secret Rotation
Rotate each inbound entry's client_id_secret on a schedule that fits your
security policy. After a change, the outbound relay authenticates and pairs
again.
Support
MTE API Relay 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:
- Email: customer_support@eclypses.com
- Hours: Monday to Friday, 8:00 AM to 5:00 PM MST (excluding holidays)