Jobs

9 minute read

A workflow is made up of one or more jobs, using the jobs keyword. Jobs run in parallel by default. To run jobs sequentially, define dependencies on other jobs using the jobs.<job_id>.needs keyword.

You can run an unlimited number of jobs as long as you are within the workflow usage limits.

jobs: my-job: steps: - name: My step env: SERVER: development uses: docker://alpine:3.18 run: echo "Hello $SERVER" #prints "Hello development"

jobs.<job_id>

Use jobs.<job_id> to assign a unique identifier to a workflow job. Replace <job_id> with a string that uniquely identifies the job within the jobs object. Reference this identifier in other jobs as needed.

The <job_id> must start with a letter and may include only alphanumeric characters, hyphens (-), or underscores (_).

Usage example

The following example shows how to define a job using a unique job ID (my-job) and how to reference it from another job (follow-up-job) using the needs keyword.

apiVersion: automation.cloudbees.io/v1alpha1 kind: workflow name: My workflow on: push: branches: - '**' jobs: my-job: #The job ID `my-job` uniquely identifies this job inside the `jobs` block. steps: - name: Print greeting #This step runs a command inside an Alpine Linux container and prints "Hello world". uses: docker://alpine:3.18 run: echo "Hello world" follow-up-job: #The job ID `follow-up-job` defines another job in the workflow. needs: [my-job] #The `needs` keyword specifies that `follow-up-job` depends on `my-job`. This ensures `follow-up-job` runs only after `my-job` completes successfully. steps: - name: Print follow-up message #This step confirms that `follow-up-job` is executed after `my-job` and prints a follow-up message. uses: docker://alpine:3.18 run: echo "my-job completed. Running next job."

jobs.<job_id>.delegates

Use jobs.<job_id>.delegates to designate a custom job to execute the workflow job.

When referring to a file within a repository, the repository clone URL and the repository-internal path must be separated by a double slash //. For example, When referring to a file within a repository, the repository clone URL and the repository-internal path must be separated by a double slash (//).

For example:

https://github.com/myorg/myrepo//path/to/my/file.yaml

However, for backward compatibility the double slash // can be omitted for GitHub, Bitbucket, and Bitbucket Datacenter URLs.

<job-name>: delegates: <path to any custom job> with: < Use `with` to provide custom job input values>

A single workflow job must contain only one of the following execution syntax terms:

jobs.<job_id>.env

Use jobs.<job_id>.env to define env variables for all steps in a standard job.

Usage example

Use env to set variables for an entire workflow or individual step.

env variables with the same name follow specificity precedence. In the following example, the environment variable SERVER: development defined in a step overrides SERVER: production defined in my-job, while the step executes.

my-job: env: SERVER: production steps: - name: My first step env: SERVER: development uses: docker://alpine:3.18 run: echo "Hello $SERVER" #prints "Hello development" - name: My second step uses: docker://alpine:3.18 run: echo "Hello $SERVER" #prints "Hello production"

jobs.<job_id>.vars

When a job calls a reusable workflow using uses:, you can optionally set jobs.<job_id>.vars to inherit to make the caller job’s variables available to the reusable workflow.

Usage example

In the following example:

  • caller-job invokes a reusable workflow with uses:.

  • vars: inherit exposes all vars in the caller job’s scope to the reusable workflow (including any environment-scoped vars that apply to the caller job).

  • The caller also passes env-prod as an input so the reusable workflow can select an environment for its own jobs.

  • Inside the reusable workflow, a job may set its environment from the input; if that environment defines vars with the same names, those values take precedence.

Caller workflow
jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml #Calls a reusable workflow from the current repository. vars: inherit #Inherits **all caller vars** into the reusable workflow’s context. inputs: var-1: ${{ vars.var-A }} #Optionally passes a specific value via `inputs` (useful if you want an explicit parameter alongside inheritance). env-prod: production #Provides the environment name for the reusable workflow to use.
Reusable workflow
on: workflow_call: inputs: var-1: type: string required: false env-prod: type: string required: false jobs: job-rw-1: steps: - name: Read inherited vars run: | if [ -n "${{ vars.var-A }}" ]; then echo "var-A available via inheritance"; fi if [ -n "${{ inputs.var-1 }}" ]; then echo "var-1 also provided via inputs"; fi job-rw-2: environment: ${{ inputs.env-prod }} #Sets the job’s environment from the input; environment-scoped vars are resolved for this job and can override inherited values with the same names. steps: - name: Environment-scoped vars may override run: | # If the selected environment defines var-A, it overrides the inherited value. if [ -n "${{ vars.var-A }}" ]; then echo "var-A resolved (environment may override)"; fi
  • vars: inherit is compatible with passing inputs via with: when calling a reusable workflow; with: parameters are separate from vars and do not conflict.

  • If a job in the reusable workflow sets its environment using an expression (for example, environment: ${{ inputs.env-prod }}), the environment name is resolved first, then environment-scoped vars are evaluated for that job.

  • Use vars: inherit only with reusable workflows you trust, since all caller vars (including those applicable from the caller’s scope) become available to the called workflow.

jobs.<job_id>.secrets

When a job calls a reusable workflow using uses:, you can control which secrets the called workflow can access by configuring jobs.<job_id>.secrets:

  • inherit — Make all secrets in the caller job’s scope available to the reusable workflow.

  • Mapping — Expose only specific secrets by mapping them one-by-one.

Use inherit with trusted reusable workflows only. If a job in the reusable workflow runs in an environment, environment-scoped secrets for that job are resolved and can override inherited or mapped values.

If a job sets environment using an expression (for example, ${{ inputs.env-prod }}), the environment name is resolved first, then environment-scoped secrets are evaluated for that job.

Inherit all secrets

In this example, caller-job invokes a reusable workflow and inherits all secrets from its scope.

Caller workflow
jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml #Calls a reusable workflow from the current repository. secrets: inherit #Inherits *all* secrets from the caller job’s scope. inputs: env-prod: production #Passes an environment name as input to the reusable workflow (optional).
Reusable workflow
on: workflow_call: inputs: env-prod: type: string required: false jobs: job-rw-1: steps: - name: Use inherited secrets run: | if [ -n "${{ secrets.secret-1 }}" ]; then echo "secret-1 available"; fi job-rw-2: environment: ${{ inputs.env-prod }} #Sets the job’s environment from the input; environment-scoped secrets are resolved for this job. steps: - name: Environment may override inherited values run: | # If the environment defines secret-1, it overrides the inherited value. if [ -n "${{ secrets.secret-1 }}" ]; then echo "secret-1 resolved (env may override)"; fi

Map specific secrets

In this example, only two secrets are exposed to the reusable workflow. No other caller secrets are available.

Caller workflow
jobs: caller-job: uses: .cloudbees/workflows/a-reusable-workflow.yaml secrets: #Uses *mapping* instead of inheritance. secret-1: ${{ secrets.SECRET_A }} #Maps caller secret `SECRET_A` to `secret-1` for the reusable workflow. secret-2: ${{ secrets.SECRET_B }} #Maps caller secret `SECRET_B` to `secret-2`. inputs: env-prod: production
Reusable workflow
on: workflow_call: inputs: env-prod: type: string required: false jobs: job-rw-1: steps: - name: Only mapped secrets are available run: | if [ -n "${{ secrets.secret-1 }}" ]; then echo "secret-1 available"; fi if [ -n "${{ secrets.secret-2 }}" ]; then echo "secret-2 available"; fi job-rw-2: environment: ${{ inputs.env-prod }} steps: - name: Environment may override mapped secrets run: | # If the environment defines secret-1/secret-2, those values override the mappings. if [ -n "${{ secrets.secret-1 }}" ]; then echo "secret-1 resolved (env may override)"; fi if [ -n "${{ secrets.secret-2 }}" ]; then echo "secret-2 resolved (env may override)"; fi
  • Do not combine secrets: inherit and secret mappings in the same job. Choose one approach.

  • Inheritance includes secrets available in the caller job’s scope. Environment-scoped secrets apply when a job in the reusable workflow runs in that environment.

  • For passing non-secret values, prefer inputs rather than secrets.

jobs.<job_id>.environment

Use jobs.<job_id>.environment to define the environment that the job references.

  • The only deployment protection rule for CloudBees Unify is the indicator that an environment requires an approval.

  • If the referenced environment has manual approvals enabled, the job does not start until approved.

  • Once the job is approved and started, the properties defined in the environment are made available to the job, using standard vars.* and secrets.* expressions.

  • You can also specify the environment via an expression that evaluates to an environment name (for example, ${{ inputs.target_env }}). The expression is resolved to an environment name before* environment-scoped vars and secrets are evaluated and before any required approval is requested.

Provide the environment as the environment name—either as a literal string or as an expression that resolves to a name:

Literal name
environment: dev
Expression that resolves to a name
environment: ${{ inputs.target_env }}
If you use the Register an artifact deployed to an environment action in a workflow run, its target-environment value overrides jobs.<job_id>.environment.

Use job.environment in called workflows

Use job.environment to pass the current job’s evaluated environment name to a reusable workflow, avoiding hard-coded strings.

Example: avoid hard-coding the environment
deploy: uses: org/repo/reusable.yaml environment: QA secrets: asecret: ${{ secrets.secret1 }} with: aninput: ${{ vars.avalue }} artifactName: ${{ inputs.artifactName }} artifactVersion: ${{ inputs.artifactVersion }} environment: ${{ job.environment }} #Resolves to "QA".
Example: dynamic environment via expression
deploy: uses: org/repo/reusable.yaml environment: ${{ inputs.target_env }} #For example: "QA","staging","prod". with: environment: ${{ job.environment }} #Returns the evaluated name.

jobs.<job_id>.if

Use jobs.<job_id>.if to skip a job, not to force running it if an upstream job failed. You can use any supported context and expression to create a conditional.

When you use expressions in an if conditional, you may omit the ${{ }} expression syntax because CloudBees automatically evaluates the if conditional as an expression. However, this rule does not apply everywhere.

You must use the ${{ }} expression syntax or escape with '', "", or () when the expression starts with !, since ! is reserved notation in YAML format.

Using the ${{ }} expression syntax turns the contents into a string, and strings are truthy. For example, if: true && ${{ false }} evaluates to true.

CloudBees strongly recommends that you avoid mixing use of the ${{ }} expression with other logic, as you may get unexpected results.

Contexts

Conditionals can be at both the job and step level. A conditional within a job cannot reference any step outputs, because some steps have not yet run.

Usage example

In the following example, a conditional determines when the production-deploy job can run. It will only run if the repository is named bee-repo-prod and is within the bee-org organization. Otherwise, the job is marked as skipped.

name: example-workflow on: push: branches: - main jobs: production-deploy: if: cloudbees.scm.repository == 'https://scm.example.org/bee-org/bee-repo-prod' steps: - uses: https://github.com/cloudbees-io/checkout@v1 - uses: https://github.com/cloudbees-io/kaniko@v1 with: destination: my.registry/my-image

jobs.<job_id>.needs

Enter a string or array of strings in jobs.<job_id>.needs to specify that a specific job or jobs must complete successfully before this job runs. By definition, a job that fails or is skipped is not a successful job completion, and all jobs that need it to complete are skipped, unless explicitly continued by use of a conditional expression.

Usage example

In the following example:

  • my-job-2 runs only if my-job-1 completes successfully.

  • my-job-3 runs only if my-job-1 and my-job-2 complete successfully.

jobs: my-job-1: ... my-job-2: needs: my-job-1 ... my-job-3: needs: [my-job-1, my-job-2] ...

jobs.<job_id>.outputs

Map outputs for a job with jobs.<job_id>.outputs. All jobs that depend on this job to run also have these outputs available to them.

outputs specifications:

  • Enter outputs as Unicode strings up to 4 kB maximum.

  • The total of all outputs in a workflow is not to exceed 1 MB.

  • If an output contains a secret, it is redacted and not sent to CloudBees.

  • Job outputs mapping is evaluated at the end of the job.

Using jobs.<job_id>.outputs is NOT supported in a reusable workflow job.

Usage example

In the following example:

  • Step output is mapped to the my-job-1 output.

  • The my-job-1 outputs are used in my-job-2, due to the needs keyword, which renders my-job-2 dependent on successful completion of my-job-1 to run.

jobs: my-job-1: outputs: output1: ${{ steps.step1.outputs.greeting }} output2: ${{ steps.step2.outputs.greeting }} steps: - name: My first step uses: docker://alpine:3.18 id: step1 run: echo "hello there" > "$CLOUDBEES_OUTPUTS/greeting" - name: My second step uses: docker://alpine:3.18 id: step2 run: echo "from the hive" > "$CLOUDBEES_OUTPUTS/greeting" my-job-2: needs: my-job-1 steps: - env: OUTPUT1: ${{needs.my-job-1.outputs.output1}} OUTPUT2: ${{needs.my-job-1.outputs.output2}} uses: docker://alpine:3.18 run: echo "$OUTPUT1 $OUTPUT2"

jobs.<job_id>.permissions

Use jobs.<job_id>.permissions to set job-level permissions. Grant only the minimum required permissions (PoLP). Each permission key accepts exactly one scope value. If a key value is unspecified in the job, then the workflow-level value is applied.

For scope and best-practice information, refer to Permissions.

Usage example

The cloudbees.api.token is an OpenID Connect (OIDC) token for accessing CloudBees Unify and third-party APIs. It does not provide Git repository access.

For key and value details, refer to Permission Scope.

  • Permissions are defined by specifying values for the available scopes within the permissions block:

    permissions: scm-token-own: read|none scm-token-org: read|none id-token: write|none
  • All scopes can be disabled by assigning an empty block:

    permissions: {}
  • The following example defines permissions for the job stale. The scm-token-org scope is granted read access, while all other scopes remain none.

    jobs: stale: permissions: scm-token-org: read steps: - uses: actions/stale@v5

jobs.<job_id>.timeout-minutes

Use jobs.<job_id>.timeout-minutes to set the number of minutes a job runs before the CloudBees Unify automatically cancels the job.

The total time allowed for a workflow run is 5760 minutes (4 days). All jobs and approvals must be completed within this time.
Table 1. timeout values

Job type

Maximum time allowed

Default value

Standard

180 minutes (3 hours)

60 minutes (1 hour)

Custom

4320 minutes (3 days)

4320 minutes (3 days)

The cloudbees.scm.token is issued when the workflow is scheduled, and some SCM systems limit the token validity to 1 hour. For this reason, CloudBees strongly recommends using CloudBees action: Configure Git global credentials to request a live token.

jobs.<job_id>.uses

Use jobs.<job_id>.uses to specify the path for a Reusable workflows job. Call a workflow by setting uses: to the repository path of the reusable workflow YAML file.

A single workflow job must contain only one of the following execution syntax terms:

Reusable workflow job usage example

Here’s the example of a job that uses a reusable workflow to perform a deployment.

When referring to a file within a repository, the repository clone URL and the repository-internal path must be separated by a double slash //. For example, When referring to a file within a repository, the repository clone URL and the repository-internal path must be separated by a double slash (//).

For example:

https://github.com/myorg/myrepo//path/to/my/file.yaml

However, for backward compatibility the double slash // can be omitted for GitHub, Bitbucket, and Bitbucket Datacenter URLs.

jobs: deploy: uses: <owner>/<repo>/.cloudbees/workflows/<reusable-workflow.yaml@v2> with: config-path: ./config/demo-app secrets: access-token: ${{ secrets.token }} pass: needs: [deploy] if: needs.deploy.outputs.deploy-result == 'pass' steps: - name: Pass uses: docker://alpine:3.21 run: echo "Deploy success alert" fail: needs: [deploy] if: needs.deploy.outputs.deploy-result == 'fail' steps: - name: Handle deploy failure uses: docker://alpine:3.21 run: echo "Deploy failure alert"