DEV Community

Cover image for Composite Actions vs Reusable Workflows: what is the difference? [GitHub Actions]
Davide 'CoderDave' Benvegnù
Davide 'CoderDave' Benvegnù

Posted on • Updated on

Composite Actions vs Reusable Workflows: what is the difference? [GitHub Actions]

There is an updated version of this comparison HERE

With GitHub offering both Composite Actions and Reusable Workflows, I’ve seen a lot of people having doubts about what they should use, and in fact I often have someone asking me: what is the difference between Reusable Workflows and Composite Actions?

In the past the difference was very clear, because Composite Actions allowed to only use bash scripts in them. But now that we have the possibility to use other actions as steps in the Composite Actions, as I explain in this video, the difference is more subtle.


As usual, if you are a visual learner, or simply prefer to watch and listen instead of reading, here you have the video with the whole explanation and demo, which to be fair is much more complete than this post.

Link to the video:

If you rather prefer reading, well... let's just continue :)


First of all, let me say that if you want to have a deep dive in either one of these features I’d highly recommend to check the specific videos I made about them, you can find them here (Composite Actions) and here (Reusable Workflows). To summarize, I would say that Composite Actions are intended to be more isolated and generic, while Reusable Workflows are more feature rich and appeal to slightly more specific scenarios.

In general, I would say 80% of the time you can probably use either one. But 20% of the time, you’ll need to use one or the other.

I’ve identified 6 main differences between those 2 flavors of GitHub Actions, and we will go through each one of those right now.

Difference 1: Nesting

The first one is about nesting. Composite Actions can be nested up to 10 layers. This means you can create a Composite Action that has another Composite Action as one of its steps, and so on so forth until the 10th level.

Reusable Workflows, on the other hand, cannot call another reusable workflow, you can’t chain them. Using another terminology which may be more familiar to users of other CI systems, you cannot have a template which refers to another template.

Difference 2: Secrets

The second big difference is about Secrets. Composite Actions cannot use secrets, not from the workflow nor as parameter. And this, as you can imagine, could be a fairly big limit.

Secrets in Reusable Workflows

Reusable Workflows instead can consume secrets, although you have to pass them to the workflow via parameter, as you can see in the image above.

This, together with the next difference, makes the Reusable Workflows way more flexible.

Difference 3: Conditionals

Third difference is that Reusable Workflows can use the if conditionals.


This means that the execution of parts of the template can be controlled by some conditions, like you would do in a normal workflow.

This behavior unfortunately is not present in the Composite Actions, where you can only have a flat list of steps and no control over their execution.

Difference 4: Storage

The fourth main difference I’ve identified is about storing them.

Reusable Workflows Storage

Reusable Workflows can be stored in your repo as normal YAML actions files, in the .github/workflows folder. Or you can create a centralized repository to store multiple Reusable Workflows.

Composite Actions Storage

Each Composite Actions definition, on the other hand, requires its own repository, which must be public, and a metadata file. And if you want to execute script from a file, then you’ll also need the script file in the same repo.

Difference 5: Jobs

The fifth difference is about multiple jobs. As we have said before, Composite Actions allow you to only have a flat list of steps. Therefore, you cannot have multiple jobs in a single Composite Action

No Job on Composite Actions

In fact, a Composite Action doesn’t specify a job keyword, but uses runs instead, and can only be consumed from within a job in the caller repository.

Because of this, you can see a Composite Action basically like any other action you have on the marketplace.

The story is different, however, for Reusable Workflows.

Job on Reusable Workflows

They do define jobs inside them, and because of that you can have as many jobs as you want in a single Reusable Workflow.

Multiple Jobs on Reusable Workflows

And since they do use jobs, and you have to specify where the job will run, we can take this a little further: if your job needs to run on a specific runner or machine, you need to use Reusable Workflows.

Difference 6: Logging

The sixth and last difference between Reusable Workflows and Composite Actions is something I think is pretty important but often overlooked. I’m talking about logging.

Rich Logs on Reusable Workflows

With Reusable workflows you have a very rich log of what is happening, and every single job and step is logged independently in real time.

Sipmle Log on Composite Actions

Using Composite Actions, instead, all you have is a single log of a single step... even if it contains multiple steps.


Because of all we have seen, Reusable Workflows make it simpler to spin up new repositories and projects and immediately start using automation and CI/CD workflows with GitHub Actions that you know will work. Composite Actions, on the other hand, allow you to pack multiple tasks and operations in a single step, to be reused inside a job.

Hope this clarifies once and for all the differences between those 2 powerful features of GitHub Actions. But let me know in the comments below if you have other questions about them that this article/video wasn’t able to answer.

Also, check out this video, in which I show how to build and use Reusable Workflows.

Like, share and follow me 🚀 for more content:

📽 YouTube
Buy me a coffee
💖 Patreon
📧 Newsletter
🌐 Website
👕 Merch
👦🏻 Facebook page
🐱‍💻 GitHub
👲🏻 Twitter
👴🏻 LinkedIn
🔉 Podcast

Buy Me A Coffee

Top comments (9)

sergeidyga profile image
sergei • Edited

also Difference 3: Conditionals:

in the Composite Actions, where you can only have a flat list of steps and no control over their execution

not true

Each Composite Actions definition, on the other hand, requires its own repository

also not true

assuming the article is out of date?

n3wt0n profile image
Davide 'CoderDave' Benvegnù

As far as I know, this is still correct.

AFAIK you cannot have conditionals in the Composite Actions:

wheelerlaw profile image
Wheeler Law • Edited

Conditional statements are supported in individual steps in composite actions. This has been supported since November of 2021. See the GitHub news post here.

wheelerlaw profile image
Wheeler Law • Edited

Oof, lots of incorrect information in this post, the two biggest offenders being:

  1. You cannot use secrets in composite actions
  2. You cannot conditionally execute steps in a composite action

I went ahead and created a contrived example of this to demonstrate why these are patently false.

I made two repositories, test-workflow, and test-action. The former contains a small workflow that calls the action in the latter. I have a secret configured in the test-workflow repository and I pass that secret to the action using the with block. That secret is received by one of the action's input parameters and is then "leaked" to stdout. You will notice in the action log that the "leaked" secret is indeed masked (the secret is also masked when "leaked" from a script)

Onto misconception #2. In the same action I described above, I created a step earlier in the action that will run only if an input value is set to World. But in the calling workflow, I set the value to "Wheeler". The step is supposed to print I won't run but I will try to say: Hello ${{ inputs.who-to-greet }}. where ${{ inputs.who-to-greet }} will be evaluated to the value I passed into the action. Seeing as it is not equal to World, the step isn't executed, and the lack of the above string can be observed in the action log; it would be executed before this step, but doesn't. Later in the workflow, I call the same action with the value set to World. This causes the conditional step to execute, displaying the string I mentioned earlier.

This conditional execution behavior is even documented in the documentation, so I would highly recommend reading it! And while the secrets masking of composite action's input values isn't explicitly mentioned, all it takes to figure this out is to create a small example like the one I made.

Overall, I would recommend that you fix this article to account for more correct information I laid out above. Reusable workflows have many limitations that aren't mentioned here, and their only real advantage is the ability to execute steps in parallel using jobs. Otherwise, they can't be located in their own private repositories and they don't play well with matrices.

pacroy profile image
Chairat Onyaem (Par)

@wheelerlaw Thanks for updates. I think this article was posted since April when GH Actions worked differently than now (Nov). Some points are still valid and some are no longer. GitHub Actions seems to have rapid evolution..

sergeidyga profile image

Composite Actions cannot use secrets, not from the workflow nor as parameter

Hi! nice article :) I might be misunderstanding the point but secrets can be used by both passing them as parameter and as env variable:


      - uses: actions/my-action
          SUPER_SECRET: ${{ secrets.SUPER_SECRET }}
          secret-name: ${{ secrets.SUPER_SECRET }}
Enter fullscreen mode Exit fullscreen mode


    description: 'Your secret'
    required: false
  using: "composite"
    - id: secret-from-inputs
      run: echo ${{ inputs.secret-name }}
      shell: bash
    - id: secret-from-env
      run: echo ${{ env.SUPER_SECRET }}
      shell: bash
Enter fullscreen mode Exit fullscreen mode
n3wt0n profile image
Davide 'CoderDave' Benvegnù

That is true, but those values are not treated as a secret.

What that means is that it has security implications. In Actions, a "secret" is always masked using *** even if you try to print it out.

If you pass it as a normal parameter, instead, it is treated as plain text and there for it is logged... very easy to be leaked at that point :)

wheelerlaw profile image
Wheeler Law • Edited

This is incorrect. If a value is passed as a regular input to a composite action (which is the only way to do this), and it is a secret value located in the repository secrets in the repo settings, then GHA will mask the value.

n2o profile image
Christian Meter

Hi, thanks for this article. Point 4 is not correct, you can store composite actions in the same local repository. For example, you can place them inside of .github/actions/setup/action.yml and reference it in your workflow file with uses: ./github/actions/setup