Kubernetes Networking
Overview
By default, the Kubernetes Helm chart does not expose any of the Hatchet services over an ingress. There are three services which can possibly be exposed:
hatchet-enginehatchet-stack-apihatchet-stack-frontend
To expose these services, you will need to do the following:
-
Configure ingresses for
frontendandengineservices (and optionally theapiservice). We recommend configuring the ingress to reverse proxy/apiendpoints to thehatchet-stack-apiservice, and configuring a separate ingress to proxy tohatchet-engine. -
Update the following configuration variables:
api:
env:
SERVER_AUTH_COOKIE_DOMAIN: "hatchet.example.com" # example.com should be replaced with your domain
SERVER_URL: "https://hatchet.example.com" # example.com should be replaced with your domain
SERVER_GRPC_BIND_ADDRESS: "0.0.0.0"
SERVER_GRPC_INSECURE: "false"
SERVER_GRPC_BROADCAST_ADDRESS: "hatchet-engine.example.com:443" # example.com should be replaced with your domain
engine:
env:
SERVER_AUTH_COOKIE_DOMAIN: "hatchet.example.com" # example.com should be replaced with your domain
SERVER_URL: "https://hatchet.example.com" # example.com should be replaced with your domain
SERVER_GRPC_BIND_ADDRESS: "0.0.0.0"
SERVER_GRPC_INSECURE: "false"
SERVER_GRPC_BROADCAST_ADDRESS: "engine.hatchet.example.com:443" # example.com should be replaced with your domainServing Hatchet under a URL subpath
By default the dashboard assumes it is served from the root of a host (for example https://hatchet.example.com/). If you need to host it behind a reverse proxy under a subpath (for example https://example.com/hatchet/), set the frontend base path so the dashboard renders links, assets, and client-side routes relative to that subpath.
The base path is injected into the served index.html at request time and the static file server serves the app (assets and client-side routes) under that subpath, so a single image works for any subpath without rebuilding the frontend — and no prefix-rewriting proxy is required.
Configuring the base path
Set the base path via the environment variable for your deployment mode. It defaults to /, which preserves the root-hosted behavior.
| Deployment | Variable | Notes |
|---|---|---|
Helm frontend chart | BASE_PATH | Set under frontend.env. Passed to the static file server. |
Combined dashboard image | BASE_PATH | Injected into index.html and nginx routing by the entrypoint. |
hatchet-lite | LITE_FRONTEND_BASE_PATH | |
hatchet-staticfileserver binary | -base-path flag |
The base path is normalized automatically, so hatchet, /hatchet, and
/hatchet/ are all equivalent — the dashboard's relative asset URLs always
resolve under the subpath.
Reverse proxy requirements
The static file server serves the app under the base path itself, so the proxy just forwards requests through without rewriting the path. Two routing rules are needed when the frontend and API sit behind a shared host:
- Forward the subpath to the frontend as-is. The frontend already serves its assets and SPA routes under
/hatchet, so norewrite-target/handle_pathprefix stripping is required — a plain reverse proxy is enough. - Keep
/apiat the host root. The dashboard issues API requests to absolute/api/...paths, which are unaffected by the base path. Do not move the API under the subpath — route/api/*straight to the API service.
Using the chart's built-in Caddy proxy, the Caddyfile becomes:
example.com {
handle /api/* {
reverse_proxy hatchet-stack-api:8080
}
# No prefix stripping — the frontend serves the app under /hatchet itself
handle /hatchet/* {
reverse_proxy hatchet-stack-frontend:8080
}
}With frontend.env.BASE_PATH set to /hatchet, the dashboard is reachable at https://example.com/hatchet/ and API calls continue to hit https://example.com/api/.... For single-service deployments such as hatchet-lite, setting the base path is all that is needed — lite serves the dashboard under the subpath directly, no separate proxy required.
The same setup with a standalone nginx reverse proxy — again, neither location rewrites the path:
server {
listen 80;
server_name example.com;
location /api/ {
proxy_pass http://hatchet-stack-api:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /hatchet {
return 301 /hatchet/;
}
location /hatchet/ {
proxy_pass http://hatchet-stack-frontend:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}On Kubernetes with the ingress-nginx controller, the same routing is two Prefix paths on one Ingress — and notably no nginx.ingress.kubernetes.io/rewrite-target annotation is needed, since the frontend serves the subpath itself:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hatchet
spec:
ingressClassName: nginx
rules:
- host: example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: hatchet-stack-api
port:
number: 8080
- path: /hatchet
pathType: Prefix
backend:
service:
name: hatchet-stack-frontend
port:
number: 8080Example: nginx-ingress
Let's walk through an example of exposing Hatchet over hatchet.example.com (for the API and frontend) and engine.hatchet.example.com (for the engine).
We'll be deploying this with SSL enabled, which requires a valid certificate. We recommend using cert-manager to manage your certificates. This guide assumes that you have a cert-manager ClusterIssuer called letsencrypt-prod configured.
Here's an example values.yaml file for this setup:
api:
env:
# TODO: insert these values from the output of the keyset generation command
SERVER_AUTH_COOKIE_SECRETS: "$SERVER_AUTH_COOKIE_SECRET1 $SERVER_AUTH_COOKIE_SECRET2"
SERVER_ENCRYPTION_MASTER_KEYSET: "$SERVER_ENCRYPTION_MASTER_KEYSET"
SERVER_ENCRYPTION_JWT_PRIVATE_KEYSET: "$SERVER_ENCRYPTION_JWT_PRIVATE_KEYSET"
SERVER_ENCRYPTION_JWT_PUBLIC_KEYSET: "$SERVER_ENCRYPTION_JWT_PUBLIC_KEYSET"
SERVER_AUTH_COOKIE_DOMAIN: "hatchet.example.com" # example.com should be replaced with your domain
SERVER_URL: "https://hatchet.example.com" # example.com should be replaced with your domain
SERVER_GRPC_BIND_ADDRESS: "0.0.0.0"
SERVER_GRPC_INSECURE: "false"
SERVER_GRPC_BROADCAST_ADDRESS: "engine.hatchet.example.com:443" # example.com should be replaced with your domain
engine:
env:
# TODO: insert these values from the output of the keyset generation command
SERVER_AUTH_COOKIE_SECRETS: "$SERVER_AUTH_COOKIE_SECRET1 $SERVER_AUTH_COOKIE_SECRET2"
SERVER_ENCRYPTION_MASTER_KEYSET: "$SERVER_ENCRYPTION_MASTER_KEYSET"
SERVER_ENCRYPTION_JWT_PRIVATE_KEYSET: "$SERVER_ENCRYPTION_JWT_PRIVATE_KEYSET"
SERVER_ENCRYPTION_JWT_PUBLIC_KEYSET: "$SERVER_ENCRYPTION_JWT_PUBLIC_KEYSET"
SERVER_AUTH_COOKIE_DOMAIN: "hatchet.example.com" # example.com should be replaced with your domain
SERVER_URL: "https://hatchet.example.com" # example.com should be replaced with your domain
SERVER_GRPC_BIND_ADDRESS: "0.0.0.0"
SERVER_GRPC_INSECURE: "false"
SERVER_GRPC_BROADCAST_ADDRESS: "engine.hatchet.example.com:443" # example.com should be replaced with your domain
ingress:
enabled: true
ingressClassName: nginx
labels: {}
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/auth-tls-verify-client: "optional"
nginx.ingress.kubernetes.io/auth-tls-secret: "${kubernetes_namespace.cloud.metadata[0].name}/engine-cert"
nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1"
nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true"
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/grpc-backend: "true"
nginx.ingress.kubernetes.io/server-snippet: |
grpc_read_timeout 1d;
grpc_send_timeout 1h;
client_header_timeout 1h;
client_body_timeout 1h;
hosts:
- host: engine.hatchet.example.com
paths:
- path: /
backend:
serviceName: hatchet-engine
servicePort: 7070
tls:
- hosts:
- engine.hatchet.example.com
secretName: engine-cert
servicePort: 7070
frontend:
ingress:
enabled: true
ingressClassName: nginx
labels: {}
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 50m
nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "60"
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: hatchet.example.com
paths:
- path: /api
backend:
serviceName: hatchet-api
servicePort: 8080
- path: /
backend:
serviceName: hatchet-frontend
servicePort: 8080
tls:
- secretName: hatchet-api
hosts:
- hatchet.example.comLast updated on August 13, 2026