Skip to content

GitHub Actions

Introduction

GitHub Actions is a continuous integration (CI) platform built into GitHub.

Devenv allows you to reuse your existing development environment in your GitHub Actions workflows to run checks, builds, tests, and more.

This guide will go through the steps required to set up devenv in a GitHub Actions workflow and show you how to run commands in the devenv shell. We'll use the following sample devenv configuration in our examples.

devenv.nix
{ pkgs, ... }:

{
  packages = [ pkgs.hello ];

  scripts.say-bye.exec = ''
    echo bye
  '';
}

The hello package is a program that prints "Hello, world!" and the custom say-bye script prints "bye".

A complete workflow example is available at the end of this guide.

Prerequisites

Let's first prepare the job environment for devenv.

steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v23
- uses: cachix/cachix-action@v12
  with:
    name: devenv
- name: Install devenv.sh
  run: nix profile install tarball+https://install.devenv.sh/latest

The above snippet does the following:

  1. Checks out the repository.
  2. Installs and sets up Nix.
  3. Configures Nix to use the devenv cache provided by Cachix to speed up the installation.
  4. Installs devenv.

If you're using a self-hosted runner, you can pre-install both Nix and devenv, and skip the associated steps.

Built-in CI command

Devenv provides a convenient built-in devenv ci command. It builds the shell and runs any defined pre-commit hooks against your repository. This is a quick and easy way to test that your development environment works as expected and lint your code at the same time.

- name: Build the devenv shell and run any pre-commit hooks
  run: devenv ci

Run a single command

Single commands can be passed to devenv shell to be run in the devenv shell.

- name: Run a single command in the devenv shell
  run: devenv shell hello
Output
Building shell ...
Hello, world!

Run multiple commands

Each run step in a job launches a separate shell. That's why we can't just run devenv shell in one run step and have all subsequent commands run in the same devenv shell.

Instead, we can use the shell option to override the default shell for the current step and replace it with the devenv shell.

- name: Run a multi-line command in the devenv shell
  shell: devenv shell bash -e {0}
  run: |
    hello
    say-bye
Output
Building shell ...
Hello, world!
bye

Overriding the shell can become quite tedious when you have a lot of separate run steps. You can use the defaults.run option to set devenv as the default shell for all run steps in a job.

defaults:
  run:
    shell: devenv shell bash -e {0}

Complete Example

Let's put all of the above together in a complete example workflow.

.github/workflows/test.yml
name: "Test"

on:
  pull_request:
  push:

jobs:
  tests:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
    runs-on: ${{ matrix.os }}

    steps:
    - uses: actions/checkout@v4
    - uses: cachix/install-nix-action@v23
    - uses: cachix/cachix-action@v12
      with:
        name: devenv
    - name: Install devenv.sh
      run: nix profile install --accept-flake-config tarball+https://install.devenv.sh/latest

    - name: Build the devenv shell and run any pre-commit hooks
      run: devenv ci

    - name: Run a single command in the devenv shell
      run: devenv shell hello

    - name: Run a multi-line command in the devenv shell
      shell: devenv shell bash -e {0}
      run: |
        hello
        say-bye