How to Use GitHub Codespaces With Your docfx Project

3 minute read | Suggest an edit | Issue? Question?

A little over a year ago, we re-launched the NUnit docs site using the docfx project. Since then, we’ve built out the workflow a bit – adding spell-checking, markdown linting, etc. to allow us to consistently create better content.

But I wanted to take it to the next level. I wanted anyone to be able to spin up the docs in GitHub Codespaces and have a fully working environment that did what needed to be done.

So, dear reader, that’s what we did. Below is how we made it happen.

First Up: A Container

GitHub Codespaces allows us to work within a containerized environment so that we can script everything we need and boot right into it. This means that we can add a Dockerfile to our repository in the right place and Codespaces will pick up on it.

Previously, we set up our build process to use our own docfx-action GitHub Action – lovingly forked from Niklas Mollenhauer (@nikeee on GitHub). Part of this GitHub action is a Dockerfile that defines a container. We published our own take on it at Dockerhub at

This highlights what I believe are two great things:

  • Because of the awesomeness of OSS, we were able to build upon someone else’s work, and the community is better for it.
  • Because of the awesomeness of containers, we can re-use this entire environment for our GitHub Codespace as well.

How is the container built? Working backward, the chain is:

  • nunitdocs/docfx-action
  • …is built upon the mono container
  • …which is built upon Debian buster-slim Linux

Setting Up our Codespaces Directory

Now that the container exists, how do we build upon it? In our repository, we create a .devcontainer folder. Inside that folder is a Dockerfile, with the contents:

FROM nunitdocs/docfx-action:latest

This defines our Dockerfile, which builds on our general docfx container and also exposes port 8080, which will come in handy shortly.

The devcontainer.json file

In our .devcontainer folder, we define a devcontainer.json file that looks like:

    "name": "nunit-docs",
    "build": {
        "dockerfile": "Dockerfile"
    "forwardPorts": [8080]


  • Defines the name of our Codespace
  • Tells Codespaces to use our Dockerfile to build
  • Tells Codespaces to forward port 8080.

But What About Extensions?

We use a spell-checker and markdown linting vs code extension, and we don’t want our Codespaces experience to be any different. Luckily, Codespaces allows us to define that right in our JSON file, which we modify to look like:

    "name": "nunit-docs",
    "build": {
        "dockerfile": "Dockerfile"
    "forwardPorts": [8080],
    "extensions": [

How Do We Work Within it?

When we open GitHub Codespaces instance, we see a VS Code window in our browser.

  • To build our docs site, we can cd docs and then docfx build. This will create a _site folder
  • We can then run docfx serve _site -n "*". This runs docfx serve, which serves a web app on mono. The -n "*" allows all bindings. When it runs, Codespaces sees it running on port 80, and “automagically” creates a URL that you can view in your browser to see things running.

What’s Next?

  • (Update: done!) I’d love for it to be a little easier for folks to build and run the site. I’ll figure out the recommended approach to allow folks to open a terminal and run build or serve without having to know the directory. Not sure if it’ll be a .bashrc file, or something with VS Code settings and aliases, or the command palette – excited to find out!
  • (Update: done!) Our build process uses cSpell and markdown-lint, both of which are installed as npm global packages. I plan to update our Codespaces container image to install node and those packages, and then to add some shortcuts to easily enable their use.
  • I love the idea of using the VS Code Tour extension to show people around the place, so I’ll probably try to do something with that too.

Check it out!

You can see what the current setup looks like over at the NUnit docs repository.

Leave a comment