Relay Docs
Agent

Run a self-hosted agent

Pull the agent image, get its TLS certificate, and run it on your own Kubernetes cluster while Relay Cloud keeps SSO, teams, masking and audit.

A self-hosted agent runs inside your network, next to your databases. Relay Cloud stays the control plane: it keeps sign-in, teams, masking, policies and query audit.

How it connects

  • The agent opens one outbound connection to api.userelay.sh:443. Relay never connects into your network.
  • relay connect gets a short-lived tunnel token from Relay, then dials the agent's address inside your network. Query results flow only between your users, the agent and the database.

So the agent needs egress to api.userelay.sh:443, and your users need a route to the agent.

Before you start

Contact support@userelay.sh with:

  • the AWS account ID or IAM role ARN that will pull the image (for example your EKS node role). Without an AWS account, ask for a pull-only identity;
  • the DNS name your users will use to reach the agent, for example relay-agent.example.internal.

Relay grants pull access to your private registry repository and shares its address. Each Relay release is copied there by digest, with its signatures. Mirror the image into your own registry for production, so the agent never depends on Relay's registry at runtime.

1. Get the TLS certificate

Users connect to the agent over TLS with a certificate that Relay issues. You keep the private key. Create the key and a certificate signing request for the agent's DNS name:

cat > agent.cnf <<'EOF'
[req]
distinguished_name = dn
req_extensions = ext
prompt = no
[dn]
CN = relay-agent.example.internal
[ext]
subjectAltName = DNS:relay-agent.example.internal
EOF
openssl ecparam -name prime256v1 -genkey -noout -out tls.key
openssl req -new -key tls.key -config agent.cnf -out agent.csr

Send agent.csr to support. Relay returns tls.crt and the Relay CA certificate, hybrid-agent-ca.crt. The certificate is valid for 397 days; send a new request about 30 days before it expires.

2. Create the agent and its enrollment token

relay agents create onprem
relay enrollment-tokens create --name onprem-agents --agent onprem

3. Deploy

kubectl create namespace relay
kubectl -n relay create secret tls relay-agent-tls --cert=tls.crt --key=tls.key
kubectl -n relay create secret generic relay-agent \
  --from-literal=RELAY_ENROLLMENT_TOKEN='<enrollment token>' \
  --from-literal=RELAY_CONTROL_PLANE_TOKEN="$(openssl rand -hex 32)"
apiVersion: apps/v1
kind: Deployment
metadata: {name: relay-agent, namespace: relay}
spec:
  replicas: 2
  selector: {matchLabels: {app: relay-agent}}
  template:
    metadata: {labels: {app: relay-agent}}
    spec:
      securityContext: {runAsNonRoot: true, runAsUser: 65532, seccompProfile: {type: RuntimeDefault}}
      containers:
        - name: agent
          image: <your registry>/agent-saas@sha256:<digest>
          env:
            - {name: RELAY_SERVER_ADDR, value: "api.userelay.sh:443"}
            - {name: RELAY_SERVER_TLS, value: "true"}
            - {name: RELAY_AGENT_ADVERTISE_ADDR, value: "relay-agent.example.internal:8080"}
            - {name: RELAY_TUNNEL_REQUIRE_SERVER_SPEC, value: "true"}
            - {name: RELAY_CONTROL_PLANE_TLS_CERT, value: /tls/tls.crt}
            - {name: RELAY_CONTROL_PLANE_TLS_KEY, value: /tls/tls.key}
          envFrom: [{secretRef: {name: relay-agent}}]
          ports:
            - {name: grpc, containerPort: 8080}
            - {name: metrics, containerPort: 9090}
          readinessProbe: {httpGet: {path: /readyz, port: metrics}}
          livenessProbe: {httpGet: {path: /healthz, port: metrics}}
          securityContext: {allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities: {drop: [ALL]}}
          volumeMounts: [{name: tls, mountPath: /tls, readOnly: true}, {name: tmp, mountPath: /tmp}]
      volumes: [{name: tls, secret: {secretName: relay-agent-tls}}, {name: tmp, emptyDir: {}}]
---
apiVersion: v1
kind: Service
metadata: {name: relay-agent, namespace: relay}
spec:
  type: LoadBalancer
  selector: {app: relay-agent}
  ports: [{name: grpc, port: 8080, targetPort: grpc}]

Add your platform's internal load balancer annotation to the Service, and point relay-agent.example.internal at it. Every replica advertises the same name.

Keep RELAY_CONTROL_PLANE_TOKEN set. Without it, anyone who reaches port 8080 can reconfigure the agent. Tunnels don't need it: each user is authenticated by their Relay session.

RELAY_TUNNEL_REQUIRE_SERVER_SPEC=true makes the agent use only the connection details Relay sends, never ones a client supplies.

4. Point users at the Relay CA

Each user trusts the Relay CA once, with any of:

export RELAY_AGENT_CA=/path/to/hybrid-agent-ca.crt
relay connect <connection> --agent-ca /path/to/hybrid-agent-ca.crt

Then set the connection's agent:

relay connections agents set <connection> onprem:1

relay agents get onprem shows the replicas once they enroll.

Renewing the certificate

Replace the Secret with the new certificate. The agent reloads it without a restart:

kubectl -n relay create secret tls relay-agent-tls --cert=tls.crt --key=tls.key \
  --dry-run=client -o yaml | kubectl apply -f -

Updating the agent

Change the image digest in the Deployment to the new release in your registry. Replicas drain open tunnels on shutdown (see Shutdown).

For every setting, see Settings for self-hosted agents.

On this page