Explore jobs

Completed

Jobs are the building blocks of GitHub Actions workflows. Each job is a collection of steps that run sequentially on the same runner, sharing the filesystem and environment variables.

Understanding job execution

Key characteristics of jobs

  • Sequential steps: Steps within a job run one after another
  • Shared environment: All steps share the same runner and filesystem
  • Isolated execution: Each job gets a fresh virtual environment
  • Searchable logs: Job outputs are automatically captured and searchable
  • Artifact support: Jobs can save and share files between workflow runs

Basic job structure

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

Parallel vs. sequential execution

Parallel execution (default)

By default, multiple jobs run simultaneously to minimize workflow duration:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Run linter
        run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Run tests
        run: npm test

  # Both lint and test jobs run simultaneously

Sequential execution with dependencies

Use the needs keyword to create job dependencies:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Build application
        run: ./build.sh

  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Run integration tests
        run: ./test.sh

  deploy:
    needs: [build, test] # Waits for multiple jobs
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to production
        run: ./deploy.sh

Advanced job patterns

Matrix strategy for multiple configurations

Run jobs across multiple environments simultaneously:

jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20, 22]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm test

Conditional job execution

Run jobs only when specific conditions are met:

jobs:
  deploy:
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to production
        run: echo "Deploying to production"

  notify:
    needs: deploy
    if: failure() # Only runs if deploy job fails
    runs-on: ubuntu-latest
    steps:
      - name: Send failure notification
        run: echo "Deployment failed!"

Job failure handling

Default behavior

  • If any step fails, the entire job fails
  • Dependent jobs won't run if their prerequisites fail
  • The workflow is marked as failed

Controlling failure behavior

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Run tests (continue on error)
        run: npm test
        continue-on-error: true

      - name: Upload test results
        if: always() # Runs regardless of previous step outcome
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: test-results.xml

Best practices for jobs

  • Keep jobs focused: Each job should have a single responsibility
  • Use descriptive names: Make job purposes clear in the workflow UI
  • Optimize dependencies: Only create dependencies when truly necessary
  • Choose appropriate runners: Match runner OS to your application needs
  • Handle failures gracefully: Use conditional execution and continue-on-error strategically
  • Share data efficiently: Use artifacts or outputs to pass data between jobs

For comprehensive job configuration options, see Using jobs in a workflow.