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
+------------------------------------------+| 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 unlockedJob with artifact output (GitHub Actions):
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 testPractical Commands
## GitHub Actions -- cancel a running jobgh run cancel RUN_ID ## GitHub Actions -- list jobs in a rungh run view RUN_ID --json jobs ## View specific job logsgh run view RUN_ID --job JOB_ID --log ## GitLab -- cancel a specific jobglab ci cancel JOB_IDTroubleshooting
| 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 |
PLACEMENT PRO TIP**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.
COMMON MISTAKE / WARNING**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 / WARNING**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 THIS**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.