We use cookies

We use cookies and similar technologies for analytics and marketing. You can allow these cookies or continue with only essential cookies.

By clicking "Accept", you agree to our use of cookies.
Learn more.

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:

  1. hatchet-engine
  2. hatchet-stack-api
  3. hatchet-stack-frontend

To expose these services, you will need to do the following:

  1. Configure ingresses for frontend and engine services (and optionally the api service). We recommend configuring the ingress to reverse proxy /api endpoints to the hatchet-stack-api service, and configuring a separate ingress to proxy to hatchet-engine.

  2. 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 domain

Serving 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.

DeploymentVariableNotes
Helm frontend chartBASE_PATHSet under frontend.env. Passed to the static file server.
Combined dashboard imageBASE_PATHInjected into index.html and nginx routing by the entrypoint.
hatchet-liteLITE_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:

  1. Forward the subpath to the frontend as-is. The frontend already serves its assets and SPA routes under /hatchet, so no rewrite-target / handle_path prefix stripping is required — a plain reverse proxy is enough.
  2. Keep /api at 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: 8080

Example: 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.com

Last updated on August 13, 2026

On this page