Blog post: Getting to Know GitHub Actions with Markdownlint

jonasbn profile image jonasbn ・5 min read

Finally GitHub Actions got out of beta! Well I actually participated in the beta programme, but I wanted to wait a bit with writing about GitHub Actions until I had used it some more and I wanted to evaluate it in context of a very specific set of projects, where I did not have access to GitHub Actions during the beta, you can see my result from going through the tutorial.

I maintain a large set of public specifications for services offered by my employer and for which I am the product manager. A long story short, after discovering Markdown I migrated from Word documents to GitHub some years ago - this solved a lot of problems. I actually blogged about it back in 2016 and as for that post, this post is not endorsed by my employer either.

Many edits and changes have run under the bridge since 2016, but the framework is the same. I have extended my toolchain to use Markdownlint and Travis CI. I also blogged about this, where the use of Travis CI was part of the bonus information.

Along came GitHub Actions and I decided to check it out. Do note that there is absolutely NOTHING wrong with Travis CI and I use it for a lot of projects, but I wanted to consolidate the open sourced specifications offered by my employer, the reasoning about this was my personal interpretation of an issue related to company policy and use of systems, so in order to minimize the amount of red tape, I decided to consolidate.

Do note this posting is not going to be a tutorial on how to get started with GitHub Actions, it is simply a reflection of the steps I have taken for a set of GitHub hosted projects to demonstrate how to work with GitHub Actions, solving a very basic use-case previously handled using Travis CI.

Since all of the specifications I work on are written in Markdown, I pretty early on fell in love with Markdownlint. I started using it with Sublime Text and later Visual Studio Code (or code). At some point I decided to utilize Continuous Integration for Markdown using Markdownlint and Travis CI as I mentioned earlier.

The configuration (.travis.yml) for Markdownlint using Travis CI, looks as follows (currently):

language: node_js
- '8'
- npm install -g markdownlint-cli
- markdownlint *.md ; markdownlint */*.md

Now lets take a step back, since I actually started during the beta, with the goal in mind to migrate all the public specification repositories from Travis CI to GitHub Actions, I actually started with my TIL collection (Today I Learned).

First I added the the directory structure .github/workflows/ and the file: main.yml

name: Markdownlint Workflow
on: push

    name: Markdownlint
    runs-on: ubuntu-latest
    - uses: actions/checkout@master
    - uses: ./github-action-markdownlint

And then the actual action pointed to in the workflow definition. The action consists of a Docker image (Dockerfile) and an entry point executable (this is a Docker thing and beyond the scope of this post).

FROM node:10.16.0-alpine

LABEL "com.github.actions.name"="Markdownlint"
LABEL "com.github.actions.description"="Tests all Markdownfiles"
LABEL "com.github.actions.icon"="mic"
LABEL "com.github.actions.color"="purple"

LABEL "repository"="http://github.com/jonasbn/til"
LABEL "maintainer"="jonasbn"

RUN npm install -g markdownlint-cli

ADD entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

The entry point executable (entrypoint.sh) is just a basic shell script, which does partly resemble the .travis.yml.

#!/bin/sh -l

echo ""
echo "Using Markdownlint on all Markdown files"
echo "----------------------------------------"

markdownlint *.md && markdownlint */*.md

With this setup, on push against my master branch. All the Markdown is evaluated using Markdownlint and uses my markdownlint configuration file: .markdownlint.json.

In order to mimic Travis CI, I decided to add a badge. This did not work out of the box as easily as the other parts and I did a little write up describing the issue. Bottom line, be aware that the name you specify in your workflow YAML file is significant.

You can see the result here notice the badge.

When GitHub Actions became public available and I found the (company) time and I started the migration.

All of the above was put to use, I did however run into a minor issue with markdownlint since my TIL repository is a deep structure and some of the other repositories were shallow, The followling line was teasing me a bit.

markdownlint *.md && markdownlint */*.md

I wanted the action to be flexible if a given repository would all of a sudden become a deeper structure, so I came up with the following solution.

find . -name "*.md" | xargs -t markdownlint

Later when reading the documentation I found out that I could simply write:

markdownlint .

But I actually prefer the xargs -t which emits all the files being worked at, which makes it easier to debug and since markdownlint does not have a verbose flag at this time.

The output from the action, can be a bit put of order, so I am evaluating handling the output buffering so order is restores. I extended the endpoint with some niceness when this go well (to avoid that empty feeling)...

#!/bin/sh -l

echo ""
echo "Using Markdownlint on all Markdown files"
echo "----------------------------------------"

find . -name "*.md" | xargs -t markdownlint


test $EXITCODE -eq 0 && echo "Everything looks good" || echo "Markdown does not comply with Markdownlint configuration";

REF: StackOverflow

You can see all of the repositories using this setup on GitHub.

And then I found out that somebody made an action you can install from the GitHub Marketplace. So either you can take all the snippets I have shared and build your own or you can use the one from the Marketplace, whatever rocks your boat.

This was aimed at migrating from Travis CI to GitHub to limit the number of systems/dependencies and it was aimed at Markdown contents. What if you wanted to do the same for XML. I did demonstrate this for one of the repositories as described in one of the mentioned blog posts...

Well the same repository was ALSO migrated.

I followed the above recipe, but added an additional workflow and action:

This repository holds two workflows, triggered by the pull event.

Why two workflows?

Then I can have a badge representing each, do check it out.

The documentation on GitHub actions is quite okay and I did not touch on anything advanced. I hope this can spark your interest, get you going or help you out - or as it SHOULD be put:

β€œAre there any questions or compliments?”

Quote from Twitter: Tweet from @ishabazz

Posted on by:

jonasbn profile



Computer programmer, runner, LEGO builder, powernapper, yakshaver and father of 2 boys all squeezed in the few hours available.


Editor guide

Do note the Dockerfile pointing to node on alpine:


Might trigger some errors like:

Error: could not get uid/gid
[ 'nobody', 0 ]

Please see this StackOverflow post for more details.

I followed the advice changing base image to a Debian based on:


This works like a charm


As I outlined towards the end of my post an action using XMLlint was just as easy to do. Well it can be even easier than that since somebody already did it for you and made it available on the GitHub Marketplace. I have not used it myself yet, but I will give it a spin when I get the time.