Skip to content

Commit 02a7493

Browse files
committed
docs(temporal-proxy): add Temporal Proxy configuration page
Add a deploy to production page for the Temporal Proxy, the gRPC proxy that routes SDK Client and Worker traffic to one or more upstream Temporal Services (local, self-hosted, or Temporal Cloud) while handling Namespace translation, TLS, and credentials. The proxy is pre-release, so the page leads with that caveat and links to the repository, the Cloud example, and the Helm chart for the definitive, still-evolving schema. Lives under production-deployment as a directory (index.mdx) to leave room for future pages; the slug is pinned so the URL is stable regardless of file location.
1 parent b12d868 commit 02a7493

2 files changed

Lines changed: 342 additions & 0 deletions

File tree

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
---
2+
id: index
3+
title: Temporal Proxy
4+
sidebar_label: Temporal Proxy
5+
description:
6+
Route requests between Temporal SDK clients and Workers and one or more upstream Temporal Services with the Temporal
7+
Proxy, a gRPC proxy that handles namespace translation, TLS, and authentication.
8+
slug: /production-deployment/temporal-proxy
9+
toc_max_heading_level: 4
10+
keywords:
11+
- guide-context
12+
- how-to
13+
- production-readiness
14+
tags:
15+
- Temporal Service
16+
- Self-hosting
17+
---
18+
19+
:::caution Pre-release
20+
21+
Temporal Proxy is under active development and evolving quickly. It is not ready for production use. Behavior and
22+
configuration can change between releases. See the
23+
[temporal-proxy repository](https://github.com/temporalio/temporal-proxy) for the current status and the definitive
24+
configuration schema.
25+
26+
:::
27+
28+
The Temporal Proxy is a gRPC proxy that sits between your Temporal SDK Clients, Workers, and the Temporal Web UI on one
29+
side and one or more upstream Temporal Services on the other. It handles Namespace translation and TLS termination so
30+
your applications can target a single local endpoint while the proxy routes each request to the right upstream, whether
31+
that is a local development Service, a self-hosted Service, or Temporal Cloud.
32+
33+
## Why use it
34+
35+
Without the proxy, connection details leak into your application code. Every Worker and Client has to know the
36+
upstream's host, TLS material, credentials, and the exact Namespace name the upstream expects. That couples your code to
37+
an environment: moving between a local Service, a self-hosted deployment, and Temporal Cloud becomes a code change.
38+
39+
The proxy owns that concern instead. Workers talk plaintext to a single local endpoint using a short Namespace name, and
40+
the proxy adds TLS, credentials, and Namespace translation on the way out. Point a Worker at a different Namespace and
41+
it reaches a different upstream, with no change to the Worker.
42+
43+
## How it works
44+
45+
The proxy is built from a gateway and one proxy per upstream, connected by unix sockets:
46+
47+
- The **gateway** is the single inbound endpoint that every Worker, SDK Client, and the Web UI connects to.
48+
- Each **upstream** has its own proxy that handles communication with that destination.
49+
50+
```mermaid
51+
flowchart LR
52+
Worker[Worker]
53+
Client[SDK Client]
54+
UI[Web UI]
55+
56+
subgraph Proxy[Temporal Proxy]
57+
direction LR
58+
Gateway[Gateway]
59+
ProxyA[Per-upstream proxy A]
60+
ProxyB[Per-upstream proxy B]
61+
Gateway -->|unix socket| ProxyA
62+
Gateway -->|unix socket| ProxyB
63+
end
64+
65+
Cloud[Temporal Cloud]
66+
SelfHosted[Self-hosted Temporal Service]
67+
68+
Worker --> Gateway
69+
Client --> Gateway
70+
UI --> Gateway
71+
ProxyA --> Cloud
72+
ProxyB --> SelfHosted
73+
```
74+
75+
For each request, the gateway:
76+
77+
1. peeks the target Namespace without parsing the payload; it is codec-transparent and relays raw frames in both
78+
directions.
79+
2. picks an upstream: the first matching routing rule, otherwise the system upstream for Namespace-less calls, otherwise
80+
the default.
81+
3. hands the request to that upstream's proxy over a unix socket.
82+
83+
The per-upstream proxy then rewrites the local Namespace to the name the upstream expects, attaches that upstream's TLS
84+
and credentials, forwards to the Temporal Service, and translates the Namespace back on responses.
85+
86+
### Terms
87+
88+
| Term | Meaning |
89+
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
90+
| gateway | The single inbound gRPC endpoint that every SDK Client, Worker, and the Web UI connects to. It routes each request to an upstream by Namespace and request metadata, and never parses payloads. |
91+
| upstream | A configured destination the proxy forwards to: a Temporal Service (local dev, self-hosted, or Temporal Cloud), or another Temporal Proxy. |
92+
| system upstream | The upstream that handles Namespace-less requests, such as the SDK's `GetSystemInfo` call on connect. |
93+
| Temporal Service | A Temporal frontend the proxy connects to. |
94+
95+
## Prerequisites
96+
97+
- One or more upstream Temporal Services to route to, such as a local development Service, a self-hosted Service, or
98+
Temporal Cloud.
99+
- The `hostPort` address for each upstream.
100+
- Any credentials the upstreams require, such as a Temporal Cloud API key or mTLS certificates.
101+
- Go installed, if you build the proxy from source. The container image and Helm chart do not require a local Go
102+
toolchain.
103+
104+
## Install the proxy
105+
106+
Install the `proxy` binary with Go:
107+
108+
```bash
109+
go install github.com/temporalio/temporal-proxy/cmd/proxy@latest
110+
```
111+
112+
Pin an explicit version instead of `@latest`:
113+
114+
```bash
115+
go install github.com/temporalio/temporal-proxy/cmd/proxy@v0.1.0
116+
```
117+
118+
Pull the container image:
119+
120+
```bash
121+
docker pull temporalio/temporal-proxy:latest
122+
```
123+
124+
Install with Helm from the Temporal Helm repo:
125+
126+
```bash
127+
helm install temporal-proxy temporal-proxy \
128+
--repo https://go.temporal.io/helm-charts
129+
```
130+
131+
Supply the proxy configuration under the `config` key of a Helm values file. The chart renders it into a ConfigMap
132+
mounted at `/etc/temporal-proxy/config.yaml`.
133+
134+
Run the proxy with a configuration file passed through the `-c` (or `--config`) flag:
135+
136+
```bash
137+
proxy serve -c config.yaml
138+
```
139+
140+
## Configure the proxy
141+
142+
The proxy reads a single YAML file with three top-level sections: the gateway listener (`hostPort`), `routing`, and the
143+
`upstreams` it forwards to. Values support `${VAR}` and `$VAR` environment variable expansion, and an upstream's
144+
`hostPort` can be a template that resolves per request (for example `{{ .RemoteNamespace }}`).
145+
146+
The example below is the proxy's
147+
[Temporal Cloud example](https://github.com/temporalio/temporal-proxy/tree/main/examples/cloud), which connects a Worker
148+
to Temporal Cloud with an API key. The Worker carries no Cloud configuration: it talks plaintext to `127.0.0.1:7233`,
149+
and the proxy adds TLS, the API key, and the Namespace rewrite on the way out.
150+
151+
```yaml
152+
# Gateway: the local endpoint your Workers and Clients connect to (plaintext).
153+
hostPort: 127.0.0.1:7233
154+
155+
routing:
156+
default: cloud # Namespaced requests.
157+
system: system # Namespace-less requests (for example GetSystemInfo on connect).
158+
159+
upstreams:
160+
# Namespaced traffic. The host is derived per request from the translated
161+
# Namespace, so one entry serves any number of Namespaces.
162+
- name: cloud
163+
hostPort: '{{ .RemoteNamespace }}.tmprl.cloud:7233'
164+
tls: {} # Enable outbound TLS with defaults.
165+
namespaces:
166+
rules:
167+
suffix: .$TEMPORAL_ACCOUNT # quickstart becomes quickstart.<account>
168+
credentials:
169+
static:
170+
apiKey: $TEMPORAL_API_KEY
171+
172+
# Namespace-less calls have no Namespace to derive a host from, so they use a
173+
# fixed endpoint. Any Namespace endpoint in the account answers them.
174+
- name: system
175+
hostPort: ${TEMPORAL_NAMESPACE}.${TEMPORAL_ACCOUNT}.tmprl.cloud:7233
176+
tls: {}
177+
credentials:
178+
static:
179+
apiKey: ${TEMPORAL_API_KEY}
180+
```
181+
182+
The [chart `values.yaml`](https://github.com/temporalio/helm-charts/tree/main/charts/temporal-proxy) and the
183+
[temporal-proxy repository](https://github.com/temporalio/temporal-proxy) hold the complete, current set of options.
184+
185+
### Route requests
186+
187+
The `routing` section selects an upstream for each request:
188+
189+
- `default` is the fallback when no rule matches. It is optional; omit it to reject unmatched requests with an error.
190+
- `system` is the upstream for Namespace-less requests, such as the SDK's `GetSystemInfo` and `GetClusterInfo` calls. It
191+
is optional; when unset, those requests fall back to `default`.
192+
- `rules` is an ordered list, evaluated top to bottom. The first match wins.
193+
194+
Every upstream named by `default`, `system`, or a rule must exist in `upstreams`.
195+
196+
```yaml
197+
routing:
198+
default: local # Fallback when no rule matches.
199+
system: cloud # Namespace-less requests.
200+
rules:
201+
- match:
202+
namespace: 'prod-*'
203+
metadata:
204+
x-tier: gold
205+
upstream: cloud
206+
- match:
207+
namespace: '*-test'
208+
upstream: local
209+
```
210+
211+
A rule matches when its Namespace matches and every metadata condition matches (AND logic). A `match` must set at least
212+
one of `namespace` or `metadata`; an empty match is a configuration error, since that is what `default` is for. Routing
213+
runs on the local Namespace, before translation.
214+
215+
`namespace` is a string literal or a simple glob with a single leading or trailing `*`:
216+
217+
| Pattern | Matches |
218+
| ---------- | --------------------------- |
219+
| `payments` | exactly `payments` |
220+
| `prod-*` | names starting with `prod-` |
221+
| `*-test` | names ending with `-test` |
222+
| `*-test-*` | names containing `-test-` |
223+
| `*` | any Namespace |
224+
225+
A `*` in any other position, such as `a*b`, is invalid.
226+
227+
`metadata` matches gRPC request metadata (headers). Keys are case-insensitive and do not support wildcards; values use
228+
the same glob syntax as `namespace`. A key matches when any of the request's values for it match.
229+
230+
### Translate Namespaces
231+
232+
Applications connected to the proxy use short, local Namespace names. Each upstream rewrites those names to the ones its
233+
Temporal Service expects, under `namespaces.rules`. The rewrite applies to requests and is reversed on responses, so
234+
callers only ever see the local name.
235+
236+
```yaml
237+
upstreams:
238+
- name: cloud
239+
hostPort: '{{ .RemoteNamespace }}.tmprl.cloud:7233'
240+
tls: {}
241+
namespaces:
242+
rules:
243+
prefix: '' # Optional string prepended to the local name.
244+
suffix: .acct # payments becomes payments.acct
245+
overrides: # Explicit pairs that bypass prefix and suffix.
246+
- local: billing
247+
remote: payments.acct
248+
```
249+
250+
- `prefix` and `suffix` wrap every local Namespace: the remote name is `prefix + local + suffix`, and responses are
251+
unwrapped back to the local name.
252+
- `overrides` lists explicit `local` and `remote` pairs for names that do not follow the prefix and suffix convention.
253+
An override takes precedence over the prefix and suffix rules. Each local name and each remote name may appear only
254+
once.
255+
256+
An upstream's `hostPort` and `tls.serverName` can be Go templates resolved per request, so one upstream can serve many
257+
Namespaces. Available variables:
258+
259+
- `{{ .LocalNamespace }}`: the Namespace before translation.
260+
- `{{ .RemoteNamespace }}`: the Namespace after translation.
261+
- `{{ .Metadata.<key> }}` or `{{ index .Metadata "<key>" }}`: a request metadata value.
262+
263+
Upstreams with a static `hostPort` connect eagerly at startup; templated ones connect lazily on first use.
264+
265+
### Authenticate inbound requests
266+
267+
Inbound authentication runs on the gateway and is off by default: omit the top-level `auth` block to accept all
268+
requests. When present, `auth` must select exactly one authenticator, `staticToken` or `jwks`. The gateway validates the
269+
credential on each request and strips it before forwarding upstream.
270+
271+
Compare an inbound bearer token against a fixed value with `staticToken`:
272+
273+
```yaml
274+
auth:
275+
staticToken:
276+
token: ${GATEWAY_TOKEN} # Required. The expected token value.
277+
header: authorization # Header to read the token from.
278+
scheme: Bearer # Scheme prefix to strip before comparing.
279+
```
280+
281+
Or verify a JWT's signature and claims against a JWKS endpoint with `jwks`:
282+
283+
```yaml
284+
auth:
285+
jwks:
286+
url: https://issuer.example.com/.well-known/jwks.json # Required. Absolute https URL.
287+
audiences:
288+
- temporal-proxy
289+
issuer: https://issuer.example.com/
290+
header: authorization
291+
scheme: Bearer
292+
```
293+
294+
`token` (for `staticToken`) and `url` (for `jwks`) are required; the remaining fields are optional. Only `staticToken`
295+
or `jwks` may be set, not both.
296+
297+
### Present credentials to upstreams
298+
299+
Each upstream can present its own credential to the Temporal Service, set under `credentials`. `static` is the only
300+
variant today; it injects a fixed API key as a bearer header on every outbound request, which is how you connect to
301+
Temporal Cloud:
302+
303+
```yaml
304+
upstreams:
305+
- name: cloud
306+
hostPort: my-ns.acct.tmprl.cloud:7233
307+
tls: {} # Required whenever credentials are set.
308+
credentials:
309+
static:
310+
apiKey: ${TEMPORAL_API_KEY} # Required.
311+
header: authorization # Optional header override.
312+
scheme: Bearer # Optional scheme override.
313+
```
314+
315+
Credentials require TLS to the upstream. If you set `credentials` without a `tls` block, the configuration fails to
316+
load.
317+
318+
### Configure TLS
319+
320+
TLS is terminated in two independent places, both using the same keys: `ca`, `cert`, `key`, and `serverName`.
321+
322+
**Inbound, on the gateway.** The top-level `tls` block secures connections from your applications. Set `cert` and `key`
323+
for server TLS, and add `ca` to enforce mutual TLS, which requires each client to present a certificate signed by that
324+
CA. Local development commonly omits `tls` and connects in plaintext.
325+
326+
**Outbound, per upstream.** Each upstream's `tls` block secures the connection from its proxy to the Temporal Service:
327+
328+
- `tls: {}` verifies the upstream against the system root certificate pool and presents no client certificate. This is
329+
what Temporal Cloud with an API key needs.
330+
- `ca` alone verifies the upstream against a private trust anchor, still presenting no client certificate.
331+
- `cert` and `key` together select mutual TLS and require `ca`. They must be set as a pair.
332+
333+
Set `serverName` when the host you dial does not match the common name or SAN on the server's certificate.
334+
335+
## Related
336+
337+
- [Temporal Proxy repository](https://github.com/temporalio/temporal-proxy)
338+
- [Temporal Proxy Helm chart](https://github.com/temporalio/helm-charts/tree/main/charts/temporal-proxy)
339+
- [Temporal Cloud example](https://github.com/temporalio/temporal-proxy/tree/main/examples/cloud)
340+
- [Codecs and Encryption](/production-deployment/data-encryption)
341+
- [Self-hosted guide: Security](/self-hosted-guide/security)

sidebars.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,7 @@ module.exports = {
14111411
],
14121412
},
14131413
'production-deployment/data-encryption',
1414+
'production-deployment/temporal-proxy/index',
14141415
],
14151416
},
14161417
{

0 commit comments

Comments
 (0)