Create reusable workflows

8 minute read

Create modular, reusable workflows that act as building blocks for shared automation across projects, enabling one workflow to run another as a job. Before you begin, ensure you have workflow creation permissions within your component and understand basic workflow creation concepts.

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.

Create the reusable workflow structure

A reusable workflow must include a workflow_call trigger and at least one job.

Set up workflow_call trigger

To create the basic reusable workflow structure:

  1. Create a new YAML file in your repository’s .cloudbees/workflows/ directory.

  2. Configure the workflow_call trigger with inputs definition:

    apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: reusable-workflow on: workflow_call: inputs: var-1: type: string required: false env-prod: type: string required: true

Define input parameters

Configure input parameters to accept values from caller workflows:

  1. Specify input parameter types:

    • string: Text-based values

    • boolean: True or false values

    • number: Numerical values

  2. Set required field to true for mandatory parameters or false for optional parameters.

  3. Add descriptions for each parameter to clarify usage.

Configure basic job structure

Add at least one job to your reusable workflow:

jobs: job-rw-1: steps: - name: Example step run: | echo "Processing input: ${{ inputs.var-1 }}"

Configure inheritance patterns

Reusable workflows support two approaches for accessing caller workflow values: inheritance and explicit mapping.

Enable secrets inheritance

To automatically inherit all secrets from the caller workflow:

  1. In the caller workflow, add secrets: inherit to the job configuration:

    jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml secrets: inherit

Enable vars inheritance

To automatically inherit all variables from the caller workflow:

  1. In the caller workflow, add vars: inherit to the job configuration:

    jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml vars: inherit
  2. The reusable workflow can now access all variables from the caller job’s scope, including environment-scoped variables.

Set up explicit mapping

To provide specific secrets or variables to the reusable workflow:

  1. Map individual secrets in the caller workflow:

    jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml secrets: secret-1: ${{ secrets.secret-A }} secret-2: ${{ secrets.secret-B }}
  2. Map individual variables in the caller workflow:

    jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml vars: var-1: ${{ vars.var-A }} var-2: ${{ vars.var-B }}

Configure environment parameter passing

To enable environment access in reusable workflows:

  1. Pass the environment name as an input in the caller workflow:

    inputs: env-prod: production
  2. Use the environment input in the reusable workflow job:

    jobs: job-rw-2: environment: ${{ inputs.env-prod }}
If a job specifies an environment using an expression (for example, ${{ inputs.env-prod }}), first resolve the environment name and then evaluate the vars and secrets accessible in the reusable workflow (depending on the vars and secrets inheritance defined in the caller workflow), since their values can come from that environment. This uses a delayed expression evaluation model.

Implement security configurations

Understand and apply security restrictions for reusable workflows.

Apply cross-organization restrictions

Cross-organization security restrictions ensure environments are only accessible to workflows in the same SCM organization:

  • If a reusable workflow references an environment, the reusable workflow must reside within the same SCM organization as the caller workflow.

  • Reusable workflows in different SCM organizations must rely solely on vars and secrets passed as inputs explicitly by the caller.

  • The caller maintains full control over vars and secrets visible to cross-organization reusable workflows.

Configure environment-specific access

To enable environment-scoped access:

  1. The caller job must have secrets: inherit and vars: inherit enabled to provide access to environment secrets and vars.

  2. Environment approvals and OIDC token issuance apply when the job targets that environment.

  3. Environment-specific values override inherited values if both are defined.

Handle OIDC token considerations

When using environments with OIDC tokens:

  • Automatic inheritance could pose security risks if reusable workflow jobs can assume roles associated with environments.

  • Use explicit mapping to control which environment variables and secrets are exposed to reusable workflows.

  • Consider OIDC subject claim risks when designing inheritance patterns.

Create the caller workflow

Configure caller workflows to use reusable workflows with proper path syntax and inheritance settings.

Configure the uses field

Specify the reusable workflow using absolute path syntax:

  1. Use the format: {owner}/{repo}/.cloudbees/workflows/{filename}

  2. Pin to specific references: {owner}/{repo}/.cloudbees/workflows/{filename}@{ref}

    Examples:

    jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml # Default branch # OR uses: .cloudbees/workflows/a-reusable-workflow.yaml@main # Specific branch # OR uses: .cloudbees/workflows/a-reusable-workflow.yaml@v1.0 # Specific tag # OR uses: .cloudbees/workflows/a-reusable-workflow.yaml@abc123 # Specific commit

Pass inputs and environments

Configure input values and environment names for the reusable workflow:

jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml inputs: var-1: ${{ vars.var-A }} env-prod: production

Configuration examples and patterns

The following examples demonstrate different inheritance and mapping patterns.

Example 1: Secrets inheritance with environment input

This pattern inherits all secrets from the caller and passes an environment name.

Caller workflow
# File: .cloudbees/workflows/call-inner-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: caller-workflow on: push: branches: [ main ] jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml secrets: inherit inputs: var-1: ${{ vars.var-A }} env-prod: production
Reusable workflow
# File: .cloudbees/workflows/a-reusable-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: reusable-workflow on: workflow_call: inputs: var-1: type: string required: false env-prod: type: string required: true jobs: job-rw-1: steps: - 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 }} steps: - 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

Example 2: Secrets mapping with environment input

This pattern maps only specific secrets to the reusable workflow.

Caller workflow
# File: .cloudbees/workflows/call-inner-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: caller-workflow on: push: branches: [ main ] jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml secrets: secret-1: ${{ secrets.secret-A }} secret-2: ${{ secrets.secret-B }} inputs: var-1: ${{ vars.var-A }} env-prod: production
Reusable workflow
# File: .cloudbees/workflows/a-reusable-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: reusable-workflow on: workflow_call: inputs: var-1: type: string required: false env-prod: type: string required: true jobs: job-rw-1: steps: - 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 }} steps: - 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

Example 3: Variables inheritance with environment input

This pattern inherits all variables from the caller workflow.

Caller workflow
# File: .cloudbees/workflows/call-inner-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: caller-workflow on: push: branches: [ main ] jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml vars: inherit inputs: var-1: ${{ vars.var-A }} env-prod: production
Reusable workflow
# File: .cloudbees/workflows/a-reusable-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: reusable-workflow on: workflow_call: inputs: var-1: type: string required: false env-prod: type: string required: true jobs: job-rw-1: steps: - 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 }} steps: - 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

Example 4: Variables mapping with environment input

This pattern maps only specific variables to the reusable workflow.

Caller workflow
# File: .cloudbees/workflows/call-inner-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: caller-workflow on: push: branches: [ main ] jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml vars: var-1: ${{ vars.var-A }} var-2: ${{ vars.var-B }} inputs: var-1: ${{ vars.var-A }} env-prod: production
Reusable workflow
# File: .cloudbees/workflows/a-reusable-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: reusable-workflow on: workflow_call: inputs: var-1: type: string required: false env-prod: type: string required: true jobs: job-rw-1: steps: - 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 }} steps: - 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

Example 5: Full inheritance pattern

This pattern inherits both variables and secrets from the caller workflow.

Caller workflow
# File: .cloudbees/workflows/call-inner-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: caller-workflow on: push: branches: [ main ] jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml vars: inherit secrets: inherit inputs: var-1: ${{ vars.var-A }} env-prod: production
Reusable workflow
# File: .cloudbees/workflows/a-reusable-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: reusable-workflow on: workflow_call: inputs: var-1: type: string required: false env-prod: type: string required: true jobs: job-rw-1: steps: - 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 }} steps: - 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

Example 6: Complete mapping pattern

This pattern explicitly maps both variables and secrets to the reusable workflow.

Caller workflow
# File: .cloudbees/workflows/call-inner-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: caller-workflow on: push: branches: [ main ] jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml vars: var-1: ${{ vars.var-A }} var-2: ${{ vars.var-B }} secrets: secret-1: ${{ secrets.secret-A }} secret-2: ${{ secrets.secret-B }} inputs: var-1: ${{ vars.var-A }} env-prod: production
Reusable workflow
# File: .cloudbees/workflows/a-reusable-workflow.yaml apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: reusable-workflow on: workflow_call: inputs: var-1: type: string required: false env-prod: type: string required: true jobs: job-rw-1: steps: - 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 }} steps: - 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

Best practices and limitations

Follow these guidelines when creating reusable workflows:

Security-first approach

  • Prioritize explicit mapping over inheritance when security is critical.

  • Use inheritance only when you trust the reusable workflow with all caller secrets and variables.

  • Consider OIDC subject claim risks when environments are involved.

Organizational boundaries

  • Understand cross-organizational restrictions for environment access.

  • Design workflows to work within same SCM organization limits when using environments.

  • Use explicit input passing for cross-organizational reusable workflows.

Performance limits

  • Maximum 4 reusable workflow levels (including caller workflow).

  • Maximum 50 workflow_call triggers within the caller workflow hierarchy.

  • Plan workflow nesting to stay within these limits.

Modular design

  • Structure reusable workflows for maximum reusability across projects.

  • Use clear, descriptive input parameter names and documentation.

  • Design workflows to be self-contained and minimize external dependencies.