Worker OpenShift Helm chart examples
Use these worker configuration variations (ingress, intermediate, and egress topologies, Route-based addressing, and registration methods) to adapt a worker to your network position. They assume a Boundary controller or HCP (HashiCorp Cloud Platform) Boundary cluster already exists.
| Topology | Accepts connections from | Typical exposure on OpenShift | Needs public_addr |
|---|---|---|---|
| Ingress | Downstream workers and clients | OpenShift Route (passthrough TLS) | Yes |
| Intermediate | Upstream and downstream workers | OpenShift Route (passthrough TLS) | Yes |
| Egress | Upstream or intermediate workers. Connects to targets | ClusterIP Service with the proxy Route disabled | No |
Every example assumes openshift.enabled=true and chart 0.2.0 or later. Merge only the required overlay into openshift-worker-values.yaml from Deploy workers on OpenShift. The snippets are not standalone values files.
The baseline deployment creates the operations TLS Secret and configures a reencrypt operations Route. Use the following overlay only when the Route needs a custom client-facing certificate. The destinationCACertificate separately verifies the worker's backend certificate.
Match the operations Route to the listener TLS setting: use reencrypt or passthrough only with tls.ops.disabled=false, use edge only with tls.ops.disabled=true, or disable the operations Route. destinationCACertificate applies only to reencrypt. Disable the operations Route when you do not need external access to health and metrics.
For example, configure a custom client-facing certificate and backend trust for a reencrypt operations Route as follows:
openshift:
enabled: true
route:
ops:
enabled: true
host: boundary-worker-ops.example.com
tls:
termination: reencrypt
insecureEdgeTerminationPolicy: Redirect
key: |
-----BEGIN PRIVATE KEY-----
<private key for the operations Route certificate>
-----END PRIVATE KEY-----
certificate: |
-----BEGIN CERTIFICATE-----
<certificate for boundary-worker-ops.example.com>
-----END CERTIFICATE-----
caCertificate: |
-----BEGIN CERTIFICATE-----
<CA chain for the client-facing Route certificate>
-----END CERTIFICATE-----
destinationCACertificate: |
-----BEGIN CERTIFICATE-----
<CA certificate that issued the worker operations certificate>
-----END CERTIFICATE-----
The custom certificate must cover boundary-worker-ops.example.com. Omit key, certificate, and caCertificate when the ingress controller's default certificate covers the hostname. The proxy Route is separate and always uses passthrough termination.
Worker with ingress capabilities
An ingress worker accepts connections from downstream workers and clients. It commonly uses the controller cluster as its upstream and exposes the proxy listener publicly through an OpenShift Route with passthrough TLS termination. Set the proxy Service to ClusterIP when you do not also want a LoadBalancer.
Start from the following topology HCL. It is the base for your openshift-worker-values.yaml.
worker:
config: |
disable_mlock = true
listener "tcp" {
address = "0.0.0.0:9202"
purpose = "proxy"
}
listener "tcp" {
address = "0.0.0.0:9203"
purpose = "ops"
tls_disable = "{{ .Values.tls.ops.disabled }}"
tls_cert_file = "{{ .Values.tls.mountPath }}/tls.crt"
tls_key_file = "{{ .Values.tls.mountPath }}/tls.key"
}
worker {
name = "boundary-worker"
public_addr = "boundary-worker.example.com:9202"
initial_upstreams = ["boundary-controller-cluster:9201"]
tags {
type = ["worker", "ingress"]
}
}
kms "aead" {
purpose = "worker-auth"
aead_type = "aes-gcm"
key = "<base64-encoded-32-byte-key>"
key_id = "<worker-auth-key-id>"
}
events {
audit_enabled = true
sysevents_enabled = true
observations_enabled = true
sink "stderr" {
name = "all-events"
event_types = ["*"]
format = "cloudevents-json"
}
}
service:
proxy:
type: LoadBalancer
port: 9202
targetPort: 9202
ops:
type: ClusterIP
port: 9203
targetPort: 9203
persistence:
authStorage:
enabled: false
path: /var/lib/boundary
recording:
enabled: false
path: /boundary/recording
Then make the following OpenShift-specific changes:
- Set
public_addrto the proxy Route hostname with port443, for exampleboundary-worker-proxy-boundary.apps.example.com:443. - Set
worker.service.proxy.typetoClusterIPand enable the proxy Route.
Example values for a worker with ingress capabilities exposed through a Route:
worker:
service:
proxy:
type: ClusterIP
openshift:
enabled: true
route:
proxy:
enabled: true
host: ""
ops:
enabled: false
Install by following Install a Boundary worker on OpenShift with the combined openshift-worker-values.yaml.
If you do not already control a DNS name for the worker, wait for OpenShift to admit the Route and generate its default hostname:
$ oc get route boundary-worker-proxy-route --namespace boundary --watch
Example output:
NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD
boundary-worker-proxy-route boundary-worker-proxy-boundary.apps.example.com boundary-worker-proxy proxy passthrough None
Update public_addr in worker.config with the Route hostname and port 443, and then upgrade:
$ helm upgrade boundary-worker hashicorp/boundary-worker \
--namespace boundary \
--values openshift-worker-values.yaml \
--rollback-on-failure \
--wait
Worker with intermediate capabilities
A worker with intermediate capabilities relays traffic between upstream and downstream workers. It requires both initial_upstreams and public_addr, and, like an ingress worker, is typically exposed through an OpenShift Route.
Start from the following topology HCL. It is the base for your openshift-worker-values.yaml.
worker:
config: |
disable_mlock = true
listener "tcp" {
address = "0.0.0.0:9202"
purpose = "proxy"
}
listener "tcp" {
address = "0.0.0.0:9203"
purpose = "ops"
tls_disable = "{{ .Values.tls.ops.disabled }}"
tls_cert_file = "{{ .Values.tls.mountPath }}/tls.crt"
tls_key_file = "{{ .Values.tls.mountPath }}/tls.key"
}
worker {
name = "boundary-worker"
public_addr = "boundary-worker.example.com:9202"
initial_upstreams = ["ingress-or-intermediate-worker.example.com:9202"]
tags {
type = ["worker", "intermediate"]
}
}
kms "aead" {
purpose = "worker-auth"
aead_type = "aes-gcm"
key = "<base64-encoded-32-byte-key>"
key_id = "<worker-auth-key-id>"
}
events {
audit_enabled = true
sysevents_enabled = true
observations_enabled = true
sink "stderr" {
name = "all-events"
event_types = ["*"]
format = "cloudevents-json"
}
}
service:
proxy:
type: LoadBalancer
port: 9202
targetPort: 9202
ops:
type: ClusterIP
port: 9203
targetPort: 9203
persistence:
authStorage:
enabled: false
path: /var/lib/boundary
recording:
enabled: false
path: /boundary/recording
For OpenShift, set public_addr to this worker's proxy Route hostname with port 443, set worker.service.proxy.type to ClusterIP, and enable the proxy Route.
Adjust the upstream port to match how that worker is exposed: 443 if it is reachable through an OpenShift Route, or its configured proxy port (for example 9202) if it is reachable through a plain Kubernetes Service instead.
Use the same Service and Route overlay, installation command, and hostname update workflow as Worker with ingress capabilities. Only the topology's worker stanza and initial_upstreams differ.
Worker with egress capabilities
A worker with egress capabilities connects to target systems and is usually the final hop before a target. It often does not need a public_addr and typically does not expose the proxy port externally.
If its upstream worker is exposed by an OpenShift Route, use that Route hostname and port 443 in initial_upstreams.
Combine the following OpenShift overlay with the shared worker values in the next example into the values file that you pass to helm install:
worker:
service:
proxy:
type: ClusterIP
ops:
type: ClusterIP
openshift:
enabled: true
route:
proxy:
enabled: false
ops:
enabled: false
Both Services always render. For an egress worker, use ClusterIP for the proxy and operations Services. Save the following complete HCL and values as egress-values.yaml:
worker:
config: |
disable_mlock = true
listener "tcp" {
address = "0.0.0.0:9202"
purpose = "proxy"
}
listener "tcp" {
address = "0.0.0.0:9203"
purpose = "ops"
tls_disable = "{{ .Values.tls.ops.disabled }}"
tls_cert_file = "{{ .Values.tls.mountPath }}/tls.crt"
tls_key_file = "{{ .Values.tls.mountPath }}/tls.key"
}
worker {
name = "boundary-worker"
initial_upstreams = ["ingress-or-intermediate-worker.example.com:9202"]
tags {
type = ["worker", "egress"]
}
}
kms "aead" {
purpose = "worker-auth"
aead_type = "aes-gcm"
key = "<base64-encoded-32-byte-key>"
key_id = "<worker-auth-key-id>"
}
events {
audit_enabled = true
sysevents_enabled = true
observations_enabled = true
sink "stderr" {
name = "all-events"
event_types = ["*"]
format = "cloudevents-json"
}
}
service:
proxy:
type: ClusterIP
port: 9202
targetPort: 9202
ops:
type: ClusterIP
port: 9203
targetPort: 9203
persistence:
authStorage:
enabled: false
path: /var/lib/boundary
recording:
enabled: false
path: /boundary/recording
Install the worker with a values file that includes the worker configuration:
$ helm install boundary-worker hashicorp/boundary-worker \
--namespace boundary \
--values egress-values.yaml \
--wait
Worker with a pre-configured Route hostname
If you already control the DNS name for the worker, set openshift.route.proxy.host and public_addr to that hostname before installation. Setting the hostname lets you avoid updating the worker HCL after installation, but you remain responsible for ensuring that DNS resolves to the cluster's ingress router.
Example values:
worker:
service:
proxy:
type: ClusterIP
openshift:
enabled: true
route:
proxy:
enabled: true
host: boundary-worker.example.com
ops:
enabled: false
In the worker with ingress capabilities HCL, set public_addr = "boundary-worker.example.com:443".
A custom hostname must resolve to the cluster's ingress router, and the OpenShift ingress controller must admit the hostname. If the router rejects it, the Route stays in a HostAlreadyClaimed or RouteNotAdmitted state. Check the Route status using oc describe route boundary-worker-proxy-route --namespace boundary.
Worker-led registration
Combine the following OpenShift overlay with the shared worker values in the next example into the values file that you pass to helm install:
openshift:
enabled: true
route:
ops:
enabled: false
For worker-led registration, install the worker without a controller-generated activation token. The worker generates identity material at startup and writes registration information to the pod logs. For more information, see Worker-led registration.
Save the following complete HCL and values as worker-values.yaml. Worker-led registration requires persistent auth storage at /var/lib/boundary:
worker:
config: |
disable_mlock = true
listener "tcp" {
address = "0.0.0.0:9202"
purpose = "proxy"
}
listener "tcp" {
address = "0.0.0.0:9203"
purpose = "ops"
tls_disable = "{{ .Values.tls.ops.disabled }}"
tls_cert_file = "{{ .Values.tls.mountPath }}/tls.crt"
tls_key_file = "{{ .Values.tls.mountPath }}/tls.key"
}
worker {
initial_upstreams = ["boundary-controller-cluster:9201"]
auth_storage_path = "/var/lib/boundary"
tags {
type = ["worker", "worker-led"]
}
}
events {
audit_enabled = true
sysevents_enabled = true
observations_enabled = true
sink "stderr" {
name = "all-events"
event_types = ["*"]
format = "cloudevents-json"
}
}
persistence:
authStorage:
enabled: true
size: 1Gi
path: /var/lib/boundary
Install the worker Helm chart:
$ helm install boundary-worker hashicorp/boundary-worker \
--namespace boundary \
--values worker-values.yaml \
--wait
The typical workflow is as follows:
- Install the worker chart with worker-led registration settings in
worker.config. - Read the worker pod logs.
- Register or authorize the emitted worker registration information with Boundary.
- Confirm the worker becomes active.
View logs:
$ oc logs --namespace boundary deployment/boundary-worker-deployment
Keep auth storage enabled for worker-led registration so the generated worker identity persists across pod replacement. When worker.persistence.authStorage.enabled is false, the chart mounts an emptyDir at /var/lib/boundary, and the identity does not persist when the pod is replaced.
Controller-led registration
Set openshift.enabled=true in the values file that you pass to helm install for each of the following token methods.
The typical workflow for controller-led registration is as follows. For more details about controller-led registration, refer to Controller-led registration:
- Create the worker resource in Boundary or HCP Boundary.
- Obtain the controller-generated activation token.
- Supply the token using one of the methods below.
- Install the worker Helm chart.
- Confirm the worker registers and persists its credentials to auth storage.
The worker uses the activation token during initial bootstrap. After successful registration, the worker uses persisted credentials from auth storage.
The chart supports three ways to supply the activation token.
Plaintext token
Embed the token directly in worker.config. Suitable for development only. The token is visible to anyone who can read the ConfigMap.
Save the following complete HCL and values as worker-values.yaml:
worker:
config: |
disable_mlock = true
listener "tcp" {
address = "0.0.0.0:9202"
purpose = "proxy"
}
listener "tcp" {
address = "0.0.0.0:9203"
purpose = "ops"
tls_disable = "{{ .Values.tls.ops.disabled }}"
tls_cert_file = "{{ .Values.tls.mountPath }}/tls.crt"
tls_key_file = "{{ .Values.tls.mountPath }}/tls.key"
}
worker {
name = "boundary-worker"
initial_upstreams = ["boundary-controller-cluster:9201"]
auth_storage_path = "/var/lib/boundary"
controller_generated_activation_token = "<activation-token>"
tags {
type = ["worker", "controller-led"]
}
}
events {
audit_enabled = true
sysevents_enabled = true
observations_enabled = true
sink "stderr" {
name = "all-events"
event_types = ["*"]
format = "cloudevents-json"
}
}
persistence:
authStorage:
enabled: true
size: 1Gi
path: /var/lib/boundary
Install the worker Helm chart:
$ helm install boundary-worker hashicorp/boundary-worker \
--namespace boundary \
--values worker-values.yaml \
--wait
Token via extraEnv
Set controller_generated_activation_token to the env:// reference in worker.config and supply the token as a key-value pair in extraEnv. Save the following complete HCL and values as worker-values.yaml:
extraEnv:
- name: BOUNDARY_WORKER_CONTROLLER_GENERATED_ACTIVATION_TOKEN
value: "<activation-token>"
worker:
config: |
disable_mlock = true
listener "tcp" {
address = "0.0.0.0:9202"
purpose = "proxy"
}
listener "tcp" {
address = "0.0.0.0:9203"
purpose = "ops"
tls_disable = "{{ .Values.tls.ops.disabled }}"
tls_cert_file = "{{ .Values.tls.mountPath }}/tls.crt"
tls_key_file = "{{ .Values.tls.mountPath }}/tls.key"
}
worker {
name = "boundary-worker"
initial_upstreams = ["boundary-controller-cluster:9201"]
auth_storage_path = "/var/lib/boundary"
controller_generated_activation_token = "env://BOUNDARY_WORKER_CONTROLLER_GENERATED_ACTIVATION_TOKEN"
tags {
type = ["worker", "controller-led"]
}
}
events {
audit_enabled = true
sysevents_enabled = true
observations_enabled = true
sink "stderr" {
name = "all-events"
event_types = ["*"]
format = "cloudevents-json"
}
}
persistence:
authStorage:
enabled: true
size: 1Gi
path: /var/lib/boundary
Install the worker Helm chart:
$ helm install boundary-worker hashicorp/boundary-worker \
--namespace boundary \
--values worker-values.yaml \
--wait
Token from an OpenShift Secret
Store the activation token in an OpenShift Secret and reference it using secretRefs. The chart injects the Secret value as the BOUNDARY_WORKER_CONTROLLER_GENERATED_ACTIVATION_TOKEN environment variable, which Boundary reads at startup via the env:// prefix.
Create the Secret before installing the chart:
$ oc create secret generic boundary-worker-secrets \
--namespace boundary \
--from-literal=worker-controller-generated-activation-token='<activation-token>'
The key name (worker-controller-generated-activation-token) must match the value set in secretRefs.keys.controllerGeneratedActivationToken in your Helm values file. This command is provided as an example. Use any Secret management method that fits your workflow.
Set controller_generated_activation_token to the exact supported env://BOUNDARY_WORKER_CONTROLLER_GENERATED_ACTIVATION_TOKEN reference. Save the following complete HCL and values as worker-values.yaml:
secretRefs:
secretName: boundary-worker-secrets
validateExisting: true
keys:
controllerGeneratedActivationToken: worker-controller-generated-activation-token
worker:
config: |
disable_mlock = true
listener "tcp" {
address = "0.0.0.0:9202"
purpose = "proxy"
}
listener "tcp" {
address = "0.0.0.0:9203"
purpose = "ops"
tls_disable = "{{ .Values.tls.ops.disabled }}"
tls_cert_file = "{{ .Values.tls.mountPath }}/tls.crt"
tls_key_file = "{{ .Values.tls.mountPath }}/tls.key"
}
worker {
name = "boundary-worker"
initial_upstreams = ["boundary-controller-cluster:9201"]
auth_storage_path = "/var/lib/boundary"
controller_generated_activation_token = "env://BOUNDARY_WORKER_CONTROLLER_GENERATED_ACTIVATION_TOKEN"
tags {
type = ["worker", "controller-led"]
}
}
events {
audit_enabled = true
sysevents_enabled = true
observations_enabled = true
sink "stderr" {
name = "all-events"
event_types = ["*"]
format = "cloudevents-json"
}
}
persistence:
authStorage:
enabled: true
size: 1Gi
path: /var/lib/boundary
With secretRefs.validateExisting: true, the chart looks up the Secret during an install or upgrade and fails early when the Secret or configured key is missing. Client-only helm template rendering cannot find an in-cluster Secret, so use a cluster-connected install or upgrade to perform this validation.
Install the worker Helm chart:
$ helm install boundary-worker hashicorp/boundary-worker \
--namespace boundary \
--values worker-values.yaml \
--wait
Keep auth storage enabled at /var/lib/boundary for every controller-led token method so registered credentials persist across pod replacement. When worker.persistence.authStorage.enabled is false, the chart mounts an emptyDir at that path, and the credentials do not persist when the pod is replaced.
Pull the worker image from the Red Hat registry
When openshift.enabled=true and image.repository is empty, the chart pulls registry.connect.redhat.com/hashicorp/boundary-enterprise. Create a pull Secret with your Red Hat registry service account credentials:
$ oc create secret docker-registry redhat-registry \
--namespace boundary \
--docker-server=registry.connect.redhat.com \
--docker-username='<registry-service-account-username>' \
--docker-password='<registry-service-account-token>'
Add the Secret to openshift-worker-values.yaml:
imagePullSecrets:
- name: redhat-registry
Alternatively, link it to the ServiceAccount used by the worker Deployment. The default is the project's default ServiceAccount:
$ oc secrets link default redhat-registry --for=pull --namespace boundary
When you link the Secret to the ServiceAccount instead, omit imagePullSecrets from openshift-worker-values.yaml.
Set image.repository and image.tag explicitly only when you mirror the image into another registry or an OpenShift ImageStream.
KMS (key management system)-backed worker authentication
Set openshift.enabled=true in the values file in addition to the shared values.
Boundary only supports KMS-backed worker authentication in self-managed deployments. For more information, refer to External KMS registration. It is not available for HCP Boundary workers.
For self-managed deployments, configure the worker HCL with an external KMS worker-auth stanza that matches the controllers. The following example uses AWS KMS. Configure workload credentials that grant the pod access to the KMS key, and replace the placeholders for your environment. Save the complete HCL and values as worker-values.yaml:
worker:
config: |
disable_mlock = true
listener "tcp" {
address = "0.0.0.0:9202"
purpose = "proxy"
}
listener "tcp" {
address = "0.0.0.0:9203"
purpose = "ops"
tls_disable = "{{ .Values.tls.ops.disabled }}"
tls_cert_file = "{{ .Values.tls.mountPath }}/tls.crt"
tls_key_file = "{{ .Values.tls.mountPath }}/tls.key"
}
worker {
name = "boundary-worker"
initial_upstreams = ["boundary-controller-cluster:9201"]
tags {
type = ["worker", "kms"]
}
}
kms "awskms" {
purpose = "worker-auth"
region = "<aws-region>"
kms_key_id = "<aws-kms-key-id>"
}
events {
audit_enabled = true
sysevents_enabled = true
observations_enabled = true
sink "stderr" {
name = "all-events"
event_types = ["*"]
format = "cloudevents-json"
}
}
persistence:
authStorage:
enabled: false
path: /var/lib/boundary
Install the worker Helm chart:
$ helm install boundary-worker hashicorp/boundary-worker \
--namespace boundary \
--values worker-values.yaml \
--wait
KMS authentication does not require persistent local auth material. When worker.persistence.authStorage.enabled is false, the chart mounts an emptyDir at /var/lib/boundary instead of creating a PVC. Data in that volume does not persist when the pod is replaced.
Enable session recording storage
Set openshift.enabled=true in the values file in addition to the shared values.
If the worker stores session recordings, enable recording persistence and set recording_storage_path in worker.config to the same path.
Save the following complete HCL and values as worker-values.yaml:
worker:
config: |
disable_mlock = true
listener "tcp" {
address = "0.0.0.0:9202"
purpose = "proxy"
}
listener "tcp" {
address = "0.0.0.0:9203"
purpose = "ops"
tls_disable = "{{ .Values.tls.ops.disabled }}"
tls_cert_file = "{{ .Values.tls.mountPath }}/tls.crt"
tls_key_file = "{{ .Values.tls.mountPath }}/tls.key"
}
worker {
name = "boundary-worker"
initial_upstreams = ["boundary-controller-cluster:9201"]
recording_storage_path = "/boundary/recording"
tags {
type = ["worker", "recording"]
}
}
kms "aead" {
purpose = "worker-auth"
aead_type = "aes-gcm"
key = "<base64-encoded-32-byte-key>"
key_id = "<worker-auth-key-id>"
}
events {
audit_enabled = true
sysevents_enabled = true
observations_enabled = true
sink "stderr" {
name = "all-events"
event_types = ["*"]
format = "cloudevents-json"
}
}
persistence:
recording:
enabled: true
size: 10Gi
path: /boundary/recording
Install the worker Helm chart:
$ helm install boundary-worker hashicorp/boundary-worker \
--namespace boundary \
--values worker-values.yaml \
--wait
Ensure the recording storage path in worker.config matches worker.persistence.recording.path.
Change service ports
The worker Deployment declares fixed proxy and operations container ports 9202 and 9203. Keep the operations listener and worker.service.ops.targetPort on 9203 because health probes use the named operations container port. If you change the proxy listener and worker.service.proxy.targetPort, keep them aligned.
The Route targets the Service by its named port (proxy), so you do not need to update openshift.route.proxy when you change the port number. External clients continue to reach the Route on the router's standard 443 port.
Troubleshooting
Session recordings fail to export
If session recording exports fail, confirm that the worker environment provides FFmpeg 5.1.0 or later and the libsvtav1 codec, as described in Enable session recording storage. Then confirm that any custom image uses an OpenShift-compatible UBI base.
TCP connections through the proxy Route fail or time out
If clients cannot establish sessions through the proxy Route, first confirm the worker pod is Ready and that the proxy Service's selector matches the worker pod labels. The proxy Service is always rendered by the chart, so the Route always has a backend. Confirm worker.service.proxy.targetPort matches the proxy listener's port in worker.config. The proxy Route's TLS termination is always passthrough and cannot be changed. The edge or reencrypt termination is never applied to it. Check the Route status with oc describe route boundary-worker-proxy-route --namespace boundary.
Pods fail to pull the worker image
If the worker pod reports ImagePullBackOff, the project cannot authenticate to registry.connect.redhat.com. Confirm that the pull Secret exists, that it is referenced in imagePullSecrets or linked to the ServiceAccount named by serviceAccount.name (or default when empty), and that the resolved tag exists in the Red Hat registry. Inspect the failure using oc describe pod <pod-name> --namespace boundary.
More information
- To view all shared and OpenShift-specific Helm values, refer to Worker OpenShift values.
- To install a worker from scratch, refer to Deploy workers using an OpenShift Helm chart.