Access private submodules in GitHub Actions

gitcicdquick tip

Using submodules can be a simple solution to make some parts of a public repo private. For example I have separated the actual content of this blog to a private repo. Not because it's something secret (you are reading it now anyway) but it's fun to play around with and also if someone forks this blog the content is probably something they should write themselves.

A problem I encountered is that my GitHub Actions flow didn't really play nice with fetching the private submodule during build. The parent (blog) repo didn't have access to other repos (like content), for good reasons. Here is my solution for accessing the private submodule during build.

  • The parent repo blog is public on GitHub
  • The submodule repo content is private on GitHub

First we need to generate a new key locally to be used on our CI/CD for read permissions to our submodule repository.

ssh-keygen -t rsa -b 4096 -C "an optional comment"

Don't add a password and you can save it to a temporary place for now, let's say the desktop.

Then we need to add the public part of the key to our submodule repo. You do that under submodule repo -> Settings -> Deploy keys -> Add deploy key. Give it a descriptive title, paste the public part of the key and keep it read-only.

Create a new deploy key on the submodule repo on GitHub

Next step is to add the private part of the key to the repository using the submodule, the parent blog repo in our example. Navigate to parent repo -> Setting -> Settings -> Secrets -> New secrets and give it a name and paste the key.

Add a new secret to the parent repo on GitHub

The last step is to use the secret to authorize against our submodule repo when checking out the project in an Action.

A simple workflow to build the blog could look like this:

# .github/workflows/deploy.yml

name: Deploy

on:
  push:
    branches: [ master ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Use Node.js
        uses: actions/setup-node@v1
        with:
          node-version: '14.x'

      - name: Install dependencies
        run: yarn --frozen-lockfile

      - name: Do stuff
        run: yarn do-stuff

The functionality already exists in the checkout action, you only need to enable it using the key by adding this to the checkout step:

ssh-key: ${{ secrets.SUBMODULE_CONTENT_PULL_KEY }}
submodules: 'recursive'

This is what the updated flow will look like

# .github/workflows/deploy.yml

name: Deploy

on:
  push:
    branches: [ master ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
        with:
          ssh-key: ${{ secrets.SUBMODULE_CONTENT_PULL_KEY }}
          submodules: 'recursive'

      - name: Use Node.js
        uses: actions/setup-node@v1
        with:
          node-version: '14.x'

      - name: Install dependencies
        run: yarn --frozen-lockfile

      - name: Do stuff
        run: yarn do-stuff

The build will use the SUBMODULE_CONTENT_PULL_KEY for checking out both the parent and the submodule, but since the parent is a public repo that's no problem. If both are private the key needs to have access to both repos.