Skip to main content

Job

A CI/CD job is a discrete unit of work within a pipeline stage that runs on a runner or agent. Each job runs in isolation, has its own set of steps, and produces a pass or fail result that determines whether dependent jobs can proceed.

Understanding CI/CD Jobs

What Is a Job in Simple Terms

If a stage is a chapter, a job is a paragraph. Jobs are the actual executable units — the things that run commands, produce output, and succeed or fail. A Test stage might have three jobs: unit-tests, integration-tests, and coverage-check. All three can run in parallel because they do not depend on each other.

Job isolation is the key property: each job starts with a fresh environment. Environment variables set in one job do not leak to another. Files created in one job do not appear in another unless explicitly passed as artifacts. This isolation is what makes pipelines reproducible.

How It Works

Bash
+------------------------------------------+
| Stage: Test |
| |
| +----------------+ +----------------+ |
| | Job: unit-test | | Job: lint | |
| | Runner: ubuntu | | Runner: ubuntu | |
| | Steps: | | Steps: | |
| | npm install | | npm install | |
| | npm test | | npm run lint | |
| | Result: PASS | | Result: PASS | |
| +----------------+ +----------------+ |
| | | |
| +------- both pass--+ |
| | |
+------------------------------------------+
|
v
Next stage unlocked

Job with artifact output (GitHub Actions):

YAML
jobs:
build:
runs-on: ubuntu-latest
outputs:
## Expose image tag for downstream jobs
image-tag: ${{ steps.tag.outputs.tag }}
steps:
- uses: actions/checkout@v4
- name: Generate image tag
id: tag
run: echo "tag=${{ github.sha }}" >> $GITHUB_OUTPUT
- name: Build and push
run: |
docker build -t payment-api:${{ steps.tag.outputs.tag }} .
docker push payment-api:${{ steps.tag.outputs.tag }}
- name: Save build report
uses: actions/upload-artifact@v4
with:
name: build-report
path: build-report.json
test:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build report
uses: actions/download-artifact@v4
with:
name: build-report
- name: Run tests against image
run: |
IMAGE_TAG=${{ needs.build.outputs.image-tag }}
docker run payment-api:$IMAGE_TAG npm test

Practical Commands

Bash
## GitHub Actions -- cancel a running job
gh run cancel RUN_ID
## GitHub Actions -- list jobs in a run
gh run view RUN_ID --json jobs
## View specific job logs
gh run view RUN_ID --job JOB_ID --log
## GitLab -- cancel a specific job
glab ci cancel JOB_ID

Troubleshooting

Symptom Check What to Look For
Job fails immediately First step output Missing dependency or wrong runner
Job timeout Duration vs timeout setting Increase timeout or fix hang
Jobs not running in parallel needs dependency chain Jobs not correctly declaring independence
Artifact not found in downstream job upload/download action Name mismatch between upload and download
Tip

Use job outputs (GitHub Actions) or artifact passing to share data between jobs without polluting the environment. A downstream job should declare exactly what it needs from upstream jobs — not assume global state.

Security

Never run untrusted code from forks in a job that has access to secrets. In GitHub Actions, pull_request_target runs in the context of the base repository and has secret access — an attacker can craft a fork PR that exfiltrates secrets if your workflow uses pull_request_target incorrectly.

Common Mistake

Putting all pipeline logic into a single job with 30 sequential steps. A single job cannot run steps in parallel, cannot be partially retried, and produces a single long log that is hard to read. Split independent work into separate jobs with explicit dependencies using the needs keyword.

Remember

Job isolation means environment variables set in one job are not available in another job in the same workflow. Use job outputs (outputs block + GITHUB_OUTPUT) to pass values between jobs, or store values in artifacts. This isolation is intentional — it prevents jobs from accidentally depending on each other's side effects.

Frequently Asked Questions

How does a CI/CD job differ from a stage or a step in pipeline terminology?

A pipeline is organized in a hierarchy: stages are logical phases (build, test, deploy), each stage contains one or more jobs, and each job runs a sequence of steps (individual commands) inside a single runner environment. Jobs within the same stage typically run in parallel across separate runners by default, while stages usually run sequentially — this is why splitting tests into multiple parallel jobs is a standard way to cut pipeline duration.

What's a common mistake that makes CI/CD jobs slower or flakier than they need to be?

Not caching dependencies between jobs — reinstalling the same node_modules or pip packages from scratch on every single job run adds minutes of pure overhead per pipeline execution. The other frequent issue is jobs with hidden interdependencies (one job relying on state left behind by another) that only work because of execution order, which breaks the moment jobs are parallelized or a runner is swapped, since each job is supposed to run in isolation.