294

I'm relatively new to GitHub Actions and I have 2 jobs–one that runs my tests, and one that deploys my project onto a server.

Obviously I want the tests to run on every branch, but deploying should only happen when something gets pushed to master.

I'm struggling to find a way to run a job on a specific branch. I know it's possible to only run entire workflows on a specific branch, however that would mean I would have a "test" workflow and a "deploy" workflow.

This sounds like a solution, however they would run parallel. In an ideal world, the tests would run first, and only if they succeed, then the deploy job would start. This isn't the case when using 2 separate workflows.

How would I be able to achieve this? Is it possible to run jobs on a specific branch?

asked Sep 27, 2019 at 17:56
2
  • I've updated my answer below with the new if conditional at job level. stackoverflow.com/a/58142412/11934042 Commented Oct 4, 2019 at 2:24
  • 1
    This question was asked several years ago and doesn't have an accepted answer. Can I suggest the author accepts one of the quality answers provided below Commented Jul 6, 2022 at 13:02

12 Answers 12

378
Answer recommended by CI/CD Collective

In a recent update you can now put if conditionals at job level. See the documentation here. https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif

I tested this workflow which runs the job test on every push, but only runs deploy on the master branch.

name: my workflow
on: push
jobs:
 test:
 runs-on: ubuntu-latest
 steps:
 - name: Execute tests
 run: exit 0
 deploy:
 runs-on: ubuntu-latest
 needs: test
 if: github.ref == 'refs/heads/master'
 steps:
 - name: Deploy app
 run: exit 0

What follows is my original answer, and an alternative solution if you prefer to have separate workflows.

The first workflow runs for every branch except master. In this workflow you run tests only.

on:
 push:
 branches:
 - '*'
 - '!master'

The second workflow runs for just master and runs both your tests and deploys if the tests were successfully passed.

on:
 push:
 branches:
 - master
answered Sep 28, 2019 at 0:12
Sign up to request clarification or add additional context in comments.

9 Comments

You may also want to only run the deploy if the previous jobs succeeded: if: success() && github.ref == 'refs/heads/master'
Nice answer. Can you please explain the /refs/heads/... syntax?
@Roly It's the format of git references. A ref is just a name that refers to a specific commit in git. It's how branches and tags work. See the documentation here.
@JanDolejsi checking for success() is not necessary: "If your if expression does not contain any of the status functions it will automatically result with success()." source
Do you know if there is a syntax for comparing if github.ref matches multiple branch names? This github.ref == 'refs/heads/master' || github.ref == 'refs/heads/staging' || ... is a bit verbose, repetitive and long.
|
62

Most answers provide a solution for one single branch. To restrict the job to run on any specific set of branches, you can do it using the if conditional with multiple disjunction (||) operators; but this is too verbose and doesn't respect the DRY principle.

The same can be archived with less repetition using the contains function.

Using contains:

contains('
 refs/heads/dev
 refs/heads/staging
 refs/heads/production
', github.ref)

compared to using multiple ||:

github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/production' || ...

Full example:

---
on: push
jobs:
 test:
 name: Test
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v2
 - name: Run tests
 run: ...
 deployment:
 name: Deployment
 runs-on: ubuntu-latest
 needs: [test]
 if:
 contains('
 refs/heads/dev
 refs/heads/staging
 refs/heads/production
 ', github.ref)
 steps:
 - uses: actions/checkout@v2
 - name: Deploy
 run: ...
ACK_stoverflow
3,3454 gold badges26 silver badges36 bronze badges
answered Jun 17, 2020 at 0:29

4 Comments

I downvoted this answer because there is a chance of false positives. Consider a branch "dev" and a branch "device". When using the contains method "dev" will also match "device" and thus run. Even if you didn't intended to. A fix may be to surround github.ref with square brackets and write the branches as [refs/heads/dev].
That's a good observation and according to the documentation for contains, using an array, in fact fixes this sub-string matching behavior.
Should you update your example to use an array? I don't think array literals are supported yet, but you could use fromJSON('["val1", "val2"]')
46

Here is what I've done for steps that should only run on a specific branch.

- name: Publish Image
 # Develop branch only
 if: github.ref == 'refs/heads/develop'
 run: |
 ... publish commands ...
answered Oct 3, 2019 at 22:00

Comments

35

2021 update

I know it's possible to only run entire workflows on a specific branch, however that would mean I would have a "test" workflow and a "deploy" workflow.

This sounds like a solution, however they would run parallel. In an ideal world, the tests would run first, and only if they succeed, then the deploy job would start. This isn't the case when using 2 separate workflows.

You can now use the event workflow_run to achieve the part the tests would run first, and only if they succeed, then the deploy job would start (read on to see how):

Documentation page of workflow_run

https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#workflow_run

This event occurs when a workflow run is requested or completed, and allows you to execute a workflow based on the finished result of another workflow. A workflow run is triggered regardless of the result of the previous workflow.

For example, if your pull_request workflow generates build artifacts, you can create a new workflow that uses workflow_run to analyze the results and add a comment to the original pull request.

Now, considering OP's initial issue:

I want the tests to run on every branch, but deploying should only happen when something gets pushed to master

This now could be solved like this:

The following setup is working, a few minutes ago I just implemented the same logic in one of my repository

Workflow <your_repo>/.github/workflows/tests.yml

name: My tests workflow
on:
 push:
 branches:
 - master
 pull_request: {}
jobs:
 test:
 # ... your implementation to run your tests

Workflow <your_repo>/.github/workflows/deploy.yml

name: My deploy workflow
on:
 workflow_run:
 workflows: My tests workflow # Reuse the name of your tests workflow
 branches: master
 types: completed
jobs:
 deploy:
 # `if` required because a workflow run is triggered regardless of
 # the result of the previous workflow (see the documentation page)
 if: ${{ github.event.workflow_run.conclusion == 'success' }}
 # ... your implementation to deploy your project
answered Oct 27, 2021 at 18:36

3 Comments

Also it's important to note that this whole thing with using workflow_run filtering and running on specific branch will work ONLY if you push the latest workflow to default branch. Struggled for hours, because I created separate branch workflow testing and tested there, but specific workflow were triggered regardless of branch until I pushed it to default 'main'
Also if I read the docs correctly this only works for public repositories.
@abergmeier seems to work for private repo's as well
19

Although this discussion is very old but recently I came across the same problem with a slight addition. if condition to check whether the branch is main works but what if someone pushes their branch and updates the workflow yml file to remove the if condition? The deploy job will get triggered without their branch even being reviewed or merged in main and could potentially break the production environment. This could be a concern in open source projects.

I couldn't find an answer for this anywhere so wanted to share my findings. I hope this is the right thread for it.

To ensure that there's no way a job is triggered except for in specific branches, environments can be used. A deploy job will most likely have some api keys to connect to the destination server that might be stored in secrets. Instead of storing them in repository secrets which are accessible globally within the repository, we should store them in respective environments.

The official docs for environments contain detailed explanation with sample scripts but sharing a simple example here. Let's say we want to run production deployment only when main is updated

  1. From repository Settings, create a production environment
  2. Choose Selected Branches in Deployment Branches dropdown and add main in the pattern
  3. Add the api keys in production environment secrets

In the workflow yml we just need to add environment information environment: production as follows (using script from @peterevans's answer)

name: my workflow
on: push
jobs:
 test:
 runs-on: ubuntu-latest
 steps:
 - name: Execute tests
 run: exit 0
 deploy:
 runs-on: ubuntu-latest
 needs: test
 if: github.ref == 'refs/heads/main'
 environment: production
 steps:
 - name: Deploy app
 run: exit 0

The environment information indicates where the secrets have to be read from. If the current branch name doesn't match the pattern provided in Selected Branches then the job will immediately fail with an error. Since we have a condition to only run this on main, normally that won't bother us because this job will be skipped on other branches anyways. But if someone, mistakenly or with ill intentions, modifies the yml file and removes the condition before pushing their branch, they'll get an error. So, our system remains secure at least from the threat here.

Hope this helps anyone wondering the same.

answered Sep 6, 2022 at 15:17

Comments

14

For steps or jobs you can also use github.ref_name which is the branch or tag name that triggered the workflow run.

name: my workflow
on: push
jobs:
 if: github.ref_name == 'main'
 test:
 runs-on: ubuntu-latest
 steps:
 - name: Execute tests
 run: exit 0

For more information about the github context check here.

answered Mar 14, 2022 at 17:37

Comments

6

The following worked for me inside a job:

Where a PR target branch is one of the following:
if: contains(github.base_ref, 'staging')
 || contains(github.base_ref, 'production')
When the source branch of the pull request is one of them below:
if: contains(github.head_ref, 'feature')
 || contains(github.head_ref, 'release')
answered Aug 21, 2021 at 22:52

Comments

4

While you can't have conditions at job level at this moment, you can have conditions at step level - see Contexts and expression syntax for GitHub Actions.

For getting a branch name, current solution is to check GITHUB_REF environment variable - see Default environment variables and this question for details.

Putting it all together - if you decide to go with accepted answer in last link, your workflow might look like this:

jobs:
 test:
 runs-on: ubuntu-latest
 steps:
 - name: Run tests
 run: ./my-tests.sh
 deploy:
 runs-on: ubuntu-latest
 needs: test
 steps:
 - name: Extract branch name
 shell: bash
 run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF##*/})"
 id: extract_branch
 - name: Deploy
 run: ./deploy.sh
 if: steps.extract_branch.outputs.branch == 'master'

If you'd rather keep everything in workflow file instead of separated script, you can always add if to each step in given job.


I hope it's just a temporary solution/workaround, and job conditions are going to be added before end of beta.

answered Sep 28, 2019 at 1:10

Comments

4
name: CI
on: push
jobs:
 prod-check:
 if: ${{ github.ref == 'refs/heads/main' }}
 runs-on: ubuntu-latest
 steps:
 - run: echo "Deploying to production server on branch $GITHUB_REF"

According to the documentation, the if condition is wrapped like so if: ${{ github.ref == 'refs/heads/main' }}

answered Jun 13, 2022 at 11:14

1 Comment

${{ }} syntax is not necessary if the expression does not begin with an exclamation mark ! GitHub docs
3

There are already some pretty good answers. Here's a way to specify multiple branches in your conditional:

name: my workflow
on: push
jobs:
 deploy:
 if: contains(fromJson('["refs/heads/master", "refs/heads/main"]'), github.ref)
 runs-on: ubuntu-latest
 steps:
 - name: Execute tests
 run: exit 0
answered Jul 13, 2022 at 13:05

Comments

2

This works to exit early:

 - name: Exit if not on master branch
 if: github.ref == 'refs/heads/main'
 run: exit 1
answered Aug 16, 2023 at 15:53

Comments

1

You need to add these two script to the workflows

//main.yml
name: main
on:
 push:
 branches:
 - main
//size.yml 
name: size
on:
 pull_request:
 branches:
 - main
answered Apr 17, 2022 at 13:44

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.