Reusable workflows streamline shared automation across projects. They act as modular building blocks: one workflow runs another as a job. A reusable workflow must include a workflow_call
trigger and at least one job.
Reusable workflow access to caller workflow vars
or secrets
is not automatic. Pass these values as inputs
or enable inheritance with vars: inherit
or secrets: inherit
in the caller workflow.
A caller workflow can pass an environment name to a reusable workflow as an inputs
value. Specifying that value in a reusable workflow job environment
expression enables access to that environment. However, reusable workflow access environment-specific secrets
and vars
is allowed only when the caller job has inheritance enabled. Environment approvals and OIDC token issuance apply when the job targets that environment.
Refer to:
Considerations
-
4 is the maximum number of reusable workflow levels, including the caller workflow e.g., A → B → C → D → E; E exceeds the limit, where A is the caller workflow and B, C, D, and E are the reusable workflows.
-
Twenty is the maximum number of reusable workflow (
workflow_call
) triggers allowed within the hierarchy of the caller workflow. -
By default, reusable workflow jobs do not inherit
vars
orsecrets
from the caller workflow. Configure inheritance in the caller workflow by enabling inheritance withvars: inherit
orsecrets: inherit
. -
A reusable workflow job can access a caller workflow environment using an
environment:
inputs
expression. e.g.environment: ${{ inputs.env-prod }}
. However, the caller job must havesecrets: inherit
enabled to provide access to that environment’ssecrets
andvars
. -
Cross-organization restriction: If a reusable workflow references an environment in one of its jobs, the reusable workflow must reside within the same SCM organization as the caller workflow.
This is a security feature to ensure environments defined within a CloudBees organization are only accessible to reusable workflows created in repositories belonging to the same SCM organization as the repository of the caller workflow. Reusable workflows residing in an SCM organization, different from the one of the caller workflow, must rely solely on vars and secrets passed as inputs explicitly by the caller; therefore caller is in full control of the vars and secrets visible to the reusable workflow.
-
Use the absolute path to the reusable workflow in the job’s uses field:
-
{owner}/{repo}/.cloudbees/workflows/{filename}
-
{owner}/{repo}/.cloudbees/workflows/{filename}@{ref} to pin to a branch, tag, or commit
-
Refer to:
Examples of DSL syntax allowing dynamic specification of environments and inheritance of vars
and secrets
The examples in this section take a conservative stance on inheritance and environment parameterization (not environment mapping). These align with GitHub’s DSL syntax and behavior, and leave room for future expansion of inheritance and mapping based on user feedback.
Considerations:
-
Secrets inheritance (Required) — Implemented similar to GitHub.
-
Environments as inputs (Required) — Environments can be passed as inputs to the reusable workflow. Any job within the reusable workflow can access the environment passed from the caller workflow via the inputs expression.
-
Vars inheritance (Required) — GitHub passes all vars from the scope of the caller job to the reusable workflow by default (including environment vars). In our DSL, we restrict this to avoid exposing vars via OIDC subject claims to third‑party workflows or workflows from another organization. Automatic inheritance could pose a security risk if the reusable workflow job can assume a role associated with that environment and use environment variables and secrets to perform unauthorized operations against a deployment target.
-
Vars mapping (optional) — Not required in GitHub because it’s implicit. However, to mitigate the OIDC subject‑claim risk when environments are referenced, we need to expose mapping so reusable workflow jobs can access vars from the caller workflow’s scope.
When vars and secrets are passed as mapped values, delayed expression evaluation is required. If a job specifies an environment using an expression (for example, ${{ inputs.env-prod }} in the examples below), first resolve the environment name and then evaluate vars and secrets , since their values can come from that environment .
|
Example 1: Caller job specifies secrets
inheritance and passes an environment
as input to the reusable workflow
In this example:
-
The caller job uses
secrets: inherit
, so all secrets in the caller job’s scope (including any environment-scoped secrets for that job) are available to the reusable workflow. -
The caller passes two inputs: a simple string (
var-1
) and an environment name (env-prod
). -
job-rw-1
can read all inherited secrets. It does not have access to any callervars
. -
job-rw-2
runs in the provided environment (production
), so environment secrets override inherited secrets if both are defined.vars
are still not available.
View workflow example
# File: .cloudbees/workflows/call-inner-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: caller-workflow on: push: (1) branches: [ main ] jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml secrets: inherit (2) inputs: var-1: ${{ vars.var-A }} (3) env-prod: production (4)
1 | Triggers the workflow on pushes to the main branch. |
2 | Inherits all secrets from the caller job’s scope for use by the reusable workflow. |
3 | Passes a string input from a caller var. (The reusable workflow cannot read caller vars directly; values must be passed via inputs.) |
4 | Passes the environment name to the reusable workflow so a job can resolve environment-scoped secrets. |
# File: .cloudbees/workflows/a-reusable-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: reusable-workflow on: workflow_call: inputs: (1) var-1: type: string required: false env-prod: type: string required: true jobs: job-rw-1: steps: (2) - name: Use inherited secrets run: | # Inherited secrets are available; vars are not. if [ -n "${{ secrets.secret-1 }}" ]; then echo "secret-1 available"; fi if [ -n "${{ secrets.secret-2 }}" ]; then echo "secret-2 available"; fi # 'var-1' is only available via inputs, not as a var: if [ -n "${{ inputs.var-1 }}" ]; then echo "var-1 provided as input"; fi job-rw-2: environment: ${{ inputs.env-prod }} (3) steps: (4) - name: Use environment-resolved secrets (override if present) run: | # If the environment defines secret-1/secret-2, those values override inherited ones. if [ -n "${{ secrets.secret-1 }}" ]; then echo "secret-1 available (env may override)"; fi if [ -n "${{ secrets.secret-2 }}" ]; then echo "secret-2 available (env may override)"; fi # 'vars' are not accessible; only inputs and secrets are available.
1 | Declares inputs accepted from the caller workflow. |
2 | In job-rw-1, all inherited secrets are available; caller vars are not. Use inputs for any values from the caller. |
3 | Sets the job’s environment from env-prod, enabling environment-scoped secret resolution. |
4 | In job-rw-2, if the environment defines secret-1 and secret-2, those override inherited values. vars are not available from either the caller or the environment. |
Example 2: Caller job specifies secrets mapping and passes an environment as input to the reusable workflow
In this example:
-
The caller job maps only two secrets for the reusable workflow:
secret-1
andsecret-2
. No other secrets are available to the reusable workflow. -
The caller passes two inputs: a simple string (
var-1
) and an environment name (env-prod
). -
job-rw-1
can readsecret-1
andsecret-2
. It does not have access to callervars
; useinputs
for values. -
job-rw-2
runs in the provided environment (production
). If that environment definessecret-A
andsecret-B
, those values override the caller mappings; otherwise, the mapped caller secrets are used.vars
are not available.
View workflow example
# File: .cloudbees/workflows/call-inner-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: caller-workflow on: push: (1) branches: [ main ] jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml secrets: secret-1: ${{ secrets.secret-A }} (2) secret-2: ${{ secrets.secret-B }} (3) inputs: var-1: ${{ vars.var-A }} (4) env-prod: production (5)
1 | Triggers the workflow on pushes to the main branch. |
2 | Maps secret-1 in the reusable workflow to the caller job’s secret secret-A . |
3 | Maps secret-2 in the reusable workflow to the caller job’s secret secret-B . |
4 | Passes a string value from a caller var via inputs (reusable workflows cannot read caller vars directly). |
5 | Passes the environment name used by the reusable workflow to resolve environment-scoped secrets. |
# File: .cloudbees/workflows/a-reusable-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: reusable-workflow on: workflow_call: inputs: (1) var-1: type: string required: false env-prod: type: string required: true jobs: job-rw-1: steps: (2) - name: Use mapped secrets and input run: | if [ -n "${{ secrets.secret-1 }}" ]; then echo "secret-1 available"; fi if [ -n "${{ secrets.secret-2 }}" ]; then echo "secret-2 available"; fi if [ -n "${{ inputs.var-1 }}" ]; then echo "var-1 provided as input"; fi job-rw-2: environment: ${{ inputs.env-prod }} (3) steps: (4) - name: Use environment-resolved secrets (override if present) run: | # If the environment defines secret-A/secret-B, those values override the caller mappings. if [ -n "${{ secrets.secret-1 }}" ]; then echo "secret-1 available"; fi if [ -n "${{ secrets.secret-2 }}" ]; then echo "secret-2 available"; fi # 'vars' are not accessible to the reusable workflow; use inputs instead.
1 | Declares the inputs accepted from the caller. |
2 | In job-rw-1 , only the mapped secrets (secret-1 , secret-2 ) are available; caller vars are not. var-1 is available via inputs . |
3 | Sets the job’s environment from env-prod , enabling environment-scoped secret resolution. |
4 | In job-rw-2 , if the environment defines secret-A and secret-B , those values override the caller job mappings; otherwise, the mapped caller secrets are used. vars are not available from either the caller or the environment. |
Example 3: Caller workflow job inherits all vars and passes an environment name
In this example:
-
The caller job uses
vars: inherit
, so all vars in the caller job’s scope (including any environment-scoped vars for that job) are available to the reusable workflow. -
The caller passes two inputs: a simple string (
var-1
) and an environment name (env-prod
). -
job-rw-1
can read all inherited vars. It does not have access to any caller secrets. -
job-rw-2
runs in the provided environment (production
). If that environment defines vars with the same names, those values override inherited ones. Secrets are still not available.
View workflow example
# File: .cloudbees/workflows/call-inner-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: caller-workflow on: push: (1) branches: [ main ] jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml vars: inherit (2) inputs: var-1: ${{ vars.var-A }} (3) env-prod: production (4)
1 | Triggers the workflow on pushes to the main branch. |
2 | Inherits all vars from the caller job’s scope for use by the reusable workflow. |
3 | Passes a string value from a caller var via inputs (reusable workflows cannot read caller vars passed implicitly unless inherited; inputs is explicit). |
4 | Passes the environment name used by the reusable workflow to resolve environment-scoped vars. |
# File: .cloudbees/workflows/a-reusable-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: reusable-workflow on: workflow_call: inputs: (1) var-1: type: string required: false env-prod: type: string required: true jobs: job-rw-1: steps: (2) - name: Use inherited vars and input run: | # Inherited vars are available; secrets are not. if [ -n "${{ vars.var-A }}" ]; then echo "var-A available"; fi if [ -n "${{ vars.var-B }}" ]; then echo "var-B available"; fi # 'var-1' is also available via inputs: if [ -n "${{ inputs.var-1 }}" ]; then echo "var-1 provided as input"; fi job-rw-2: environment: ${{ inputs.env-prod }} (3) steps: (4) - name: Use environment-resolved vars (override if present) run: | # If the environment defines var-A/var-B, those values override inherited ones. if [ -n "${{ vars.var-A }}" ]; then echo "var-A available (env may override)"; fi if [ -n "${{ vars.var-B }}" ]; then echo "var-B available (env may override)"; fi # Secrets are not accessible in this example.
1 | Declares the inputs accepted from the caller. |
2 | In job-rw-1 , all inherited vars are available; caller secrets are not. |
3 | Sets the job’s environment from env-prod , enabling environment-scoped var resolution. |
4 | In job-rw-2 , if the environment defines var-A and var-B , those values override inherited ones. Secrets are not available from either the caller or the environment. |
Example 4: Caller workflow job maps specific vars and passes an environment name
In this example:
-
The caller job maps only two vars for the reusable workflow:
var-1
andvar-2
. No other vars are available to the reusable workflow. -
The caller also passes two inputs: a simple string (
var-1
) and an environment name (env-prod
). -
job-rw-1
can read the mapped vars (var-1
,var-2
). It does not have access to caller secrets. -
job-rw-2
runs in the provided environment (production
). If that environment definesvar-A
andvar-B
, those values override the mapped caller vars; otherwise, the mapped caller vars are used. Secrets are still not available.
View workflow example
# File: .cloudbees/workflows/call-inner-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: caller-workflow on: push: (1) branches: [ main ] jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml vars: var-1: ${{ vars.var-A }} (2) var-2: ${{ vars.var-B }} (2) inputs: var-1: ${{ vars.var-A }} (3) env-prod: production (4)
1 | Triggers the workflow on pushes to the main branch. |
2 | Maps the reusable workflow vars var-1 and var-2 from the caller job’s vars var-A and var-B . |
3 | Passes a string value via inputs so the reusable workflow can also read var-1 as an input. |
4 | Passes the environment name used by the reusable workflow to resolve environment-scoped vars. |
# File: .cloudbees/workflows/a-reusable-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: reusable-workflow on: workflow_call: inputs: (1) var-1: type: string required: false env-prod: type: string required: true jobs: job-rw-1: steps: (2) - name: Use mapped vars and input run: | # Mapped vars are available; secrets are not. if [ -n "${{ vars.var-1 }}" ]; then echo "var-1 available (mapped)"; fi if [ -n "${{ vars.var-2 }}" ]; then echo "var-2 available (mapped)"; fi # 'var-1' is also available via inputs: if [ -n "${{ inputs.var-1 }}" ]; then echo "var-1 provided as input"; fi job-rw-2: environment: ${{ inputs.env-prod }} (3) steps: (4) - name: Use environment-resolved vars (override if present) run: | # If the environment defines var-A/var-B, those values override the mapped ones. if [ -n "${{ vars.var-1 }}" ]; then echo "var-1 available (env may override)"; fi if [ -n "${{ vars.var-2 }}" ]; then echo "var-2 available (env may override)"; fi # Secrets are not accessible in this example.
1 | Declares the inputs accepted from the caller. |
2 | In job-rw-1 , only the mapped vars (var-1 , var-2 ) are available; caller secrets are not. var-1 is also accessible via inputs . |
3 | Sets the job’s environment from env-prod , enabling environment-scoped var resolution. |
4 | In job-rw-2 , if the environment defines var-A and var-B , those values override the mapped caller vars; otherwise the mapped values are used. Secrets are not available from either the caller or the environment. |
Example 5: Caller workflow job inherits all vars and secrets and passes an environment name
In this example:
-
The caller job uses
vars: inherit
andsecrets: inherit
, so all vars and secrets in the caller job’s scope (including any environment-scoped values for that job) are available to the reusable workflow. -
The caller passes two inputs: a simple string (
var-1
) and an environment name (env-prod
). -
job-rw-1
can read all inherited vars and secrets. -
job-rw-2
runs in the provided environment (production
). If that environment defines vars or secrets with the same names, those values override inherited ones.
View workflow example
# File: .cloudbees/workflows/call-inner-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: caller-workflow on: push: (1) branches: [ main ] jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml vars: inherit (2) secrets: inherit (3) inputs: var-1: ${{ vars.var-A }} (4) env-prod: production (5)
1 | Triggers the workflow on pushes to the main branch. |
2 | Inherits all vars from the caller job’s scope for use by the reusable workflow. |
3 | Inherits all secrets from the caller job’s scope for use by the reusable workflow. |
4 | Passes a string value as an explicit input. (Even with vars: inherit , use inputs when you want to pass a specific value explicitly.) |
5 | Passes the environment name used by the reusable workflow to resolve environment-scoped vars and secrets. |
# File: .cloudbees/workflows/a-reusable-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: reusable-workflow on: workflow_call: inputs: (1) var-1: type: string required: false env-prod: type: string required: true jobs: job-rw-1: steps: (2) - name: Use inherited vars and secrets (plus input) run: | # Inherited vars are available: if [ -n "${{ vars.var-A }}" ]; then echo "var-A available (inherited)"; fi if [ -n "${{ vars.var-B }}" ]; then echo "var-B available (inherited)"; fi # Inherited secrets are available: if [ -n "${{ secrets.secret-1 }}" ]; then echo "secret-1 available (inherited)"; fi if [ -n "${{ secrets.secret-2 }}" ]; then echo "secret-2 available (inherited)"; fi # 'var-1' is also available via inputs: if [ -n "${{ inputs.var-1 }}" ]; then echo "var-1 provided as input"; fi job-rw-2: environment: ${{ inputs.env-prod }} (3) steps: (4) - name: Use environment-resolved vars and secrets (override if present) run: | # If the environment defines var-A/var-B, those values override inherited ones: if [ -n "${{ vars.var-A }}" ]; then echo "var-A available (env may override)"; fi if [ -n "${{ vars.var-B }}" ]; then echo "var-B available (env may override)"; fi # If the environment defines secret-1/secret-2, those values override inherited ones: if [ -n "${{ secrets.secret-1 }}" ]; then echo "secret-1 available (env may override)"; fi if [ -n "${{ secrets.secret-2 }}" ]; then echo "secret-2 available (env may override)"; fi
1 | Declares the inputs accepted from the caller. |
2 | In job-rw-1 , all inherited vars and secrets are available. |
3 | Sets the job’s environment from env-prod , enabling environment-scoped resolution for vars and secrets. |
4 | In job-rw-2 , if the environment defines values with the same names, those override inherited values. |
Example 6: Caller workflow job maps specific vars and secrets and passes an environment name
In this example:
-
The caller job maps only two vars to the reusable workflow:
var-1
andvar-2
. No other vars are available to the reusable workflow. -
The caller job also maps only two secrets:
secret-1
andsecret-2
. No other secrets are available to the reusable workflow. -
The caller passes two inputs: a simple string (
var-1
) and an environment name (env-prod
). -
job-rw-1
can read the mapped vars and secrets. It does not have access to any unmapped values. -
job-rw-2
runs in the provided environment (production
). If that environment definesvar-A
/var-B
orsecret-A
/secret-B
, those values override the mapped ones. Unmapped values remain unavailable.
View workflow example
# File: .cloudbees/workflows/call-inner-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: caller-workflow on: push: (1) branches: [ main ] jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml vars: var-1: ${{ vars.var-A }} (2) var-2: ${{ vars.var-B }} (2) secrets: secret-1: ${{ secrets.secret-A }} (3) secret-2: ${{ secrets.secret-B }} (3) inputs: var-1: ${{ vars.var-A }} (4) env-prod: production (5)
1 | Triggers the workflow on pushes to the main branch. |
2 | Maps the reusable workflow vars var-1 and var-2 from the caller job’s vars var-A and var-B . |
3 | Maps the reusable workflow secrets secret-1 and secret-2 from the caller job’s secrets secret-A and secret-B . |
4 | Passes a string value via inputs so the reusable workflow can also read var-1 as an input (optional, for illustration). |
5 | Passes the environment name used by the reusable workflow to resolve environment-scoped vars and secrets. |
# File: .cloudbees/workflows/a-reusable-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: reusable-workflow on: workflow_call: inputs: (1) var-1: type: string required: false env-prod: type: string required: true jobs: job-rw-1: steps: (2) - name: Use mapped vars and secrets (plus input) run: | # MAPPED VARS (from caller var-A/var-B): if [ -n "${{ vars.var-1 }}" ]; then echo "var-1 available (mapped)"; fi if [ -n "${{ vars.var-2 }}" ]; then echo "var-2 available (mapped)"; fi # MAPPED SECRETS (from caller secret-A/secret-B): if [ -n "${{ secrets.secret-1 }}" ]; then echo "secret-1 available (mapped)"; fi if [ -n "${{ secrets.secret-2 }}" ]; then echo "secret-2 available (mapped)"; fi # INPUT (explicitly passed): if [ -n "${{ inputs.var-1 }}" ]; then echo "var-1 provided as input"; fi job-rw-2: environment: ${{ inputs.env-prod }} (3) steps: (4) - name: Use environment-resolved vars and secrets (override if present) run: | # If the environment defines var-A/var-B, they override mapped var-1/var-2 values: if [ -n "${{ vars.var-1 }}" ]; then echo "var-1 available (env may override)"; fi if [ -n "${{ vars.var-2 }}" ]; then echo "var-2 available (env may override)"; fi # If the environment defines secret-A/secret-B, they override mapped secret-1/secret-2 values: if [ -n "${{ secrets.secret-1 }}" ]; then echo "secret-1 available (env may override)"; fi if [ -n "${{ secrets.secret-2 }}" ]; then echo "secret-2 available (env may override)"; fi
1 | Declares the inputs accepted from the caller. |
2 | In job-rw-1 , only the mapped vars (var-1 , var-2 ) and mapped secrets (secret-1 , secret-2 ) are available; other values from the caller are not. var-1 is also accessible via inputs . |
3 | Sets the job’s environment from env-prod , enabling environment-scoped resolution for both vars and secrets. |
4 | In job-rw-2 , if the environment defines var-A /var-B or secret-A /secret-B , those values override the mapped caller values; otherwise, the mapped values are used. |