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.
Video
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: https://youtu.be/xa9gYSCf8q0
If you rather prefer reading, well... let's just continue :)
General
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.
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 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.
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
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.
They do define jobs inside them, and because of that you can have as many jobs as you want in a single Reusable Workflow.
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.
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.
Using Composite Actions, instead, all you have is a single log of a single step... even if it contains multiple steps.
Conclusions
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
🌐 CoderDave.io Website
👕 Merch
👦🏻 Facebook page
🐱💻 GitHub
👲🏻 Twitter
👴🏻 LinkedIn
🔉 Podcast
Top comments (10)
also
Difference 3: Conditionals
:not true
also not true
assuming the article is out of date?
As far as I know, this is still correct.
AFAIK you cannot have conditionals in the Composite Actions: docs.github.com/en/actions/creatin...
Conditional statements are supported in individual steps in composite actions. This has been supported since November of 2021. See the GitHub news post here.
Hi! nice article :) I might be misunderstanding the point but secrets can be used by both passing them as parameter and as env variable:
workflow:
action.yaml
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 :)
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.
Oof, lots of incorrect information in this post, the two biggest offenders being:
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 printI 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 toWorld
, 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 toWorld
. 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.
@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..
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 withuses: ./github/actions/setup
Article is very outdated and/or factually inaccurate
In reality, you can chain up to 3 levels deep, and only beyond that you will get errors:
error parsing called workflow
".github/workflows/build-reusable-chained.yml"
-> "orgname/shared-ci/.github/workflows/python-build-chained-0.yml@main" (source branch with sha:e717546c448ac74357317b8b186307159d8588f8)
--> "orgname/shared-ci/.github/workflows/python-build-chained-1.yml@main" (source branch with sha:e717546c448ac74357317b8b186307159d8588f8)
---> "orgname/shared-ci/.github/workflows/python-build-chained-2.yml@main" (source branch with sha:e717546c448ac74357317b8b186307159d8588f8)
: job "build" calls workflow "orgname/shared-ci/.github/workflows/python-build-chained-3.yml@main", but doing so would exceed the limit on called workflow depth of 3