# Setting Up GitHub Actions for Nextflow Pipelines

By the end of this guide you will have a GitHub repository whose Nextflow pipeline runs automatically on every push and every pull request. The setup pins the Nextflow version, runs the pipeline inside Docker and Apptainer containers, caps resources so jobs fit on hosted runners, uploads logs when a run fails, and follows the linting and format conventions used by the nf-core community. You need a GitHub account with write access to a repository, a Nextflow pipeline to test (a minimal one is included below), and small test data. Everything else, including Java, Nextflow itself, and the container engine, is installed by the workflow on GitHub's hosted runners, so you do not need to configure a server. The same files work whether you are setting up GitHub Actions for Nextflow pipelines in a brand new repository or adding continuous integration (CI) to one that already exists.

Nextflow is a bioinformatics workflow manager, a program that expresses an analysis as processes connected by channels and schedules those processes across laptops, HPC schedulers, or cloud services. GitHub Actions is GitHub's built-in automation platform. You describe jobs in YAML files stored under `.github/workflows`, and GitHub executes them in response to events such as a push. The two tools fit together naturally because a Nextflow pipeline is already a self-contained, runnable unit, and CI turns "it works on my machine" into a check that gates every merge.

## Why GitHub Actions Fits Nextflow Pipelines

Continuous integration for pipelines has three jobs: prove the code still runs, prove the outputs still look right, and do both before a human reviewer spends time on the change. GitHub Actions does this inside the repository itself. Because the workflows are event-driven and live next to the code, they remove the integration overhead of a separate CI server, and they pair cleanly with infrastructure-as-code tools such as Terraform for provisioning reproducible test environments [1].

The approach scales to serious scientific workloads. ROOT, the data processing framework at the center of high energy physics analysis, migrated its CI from Jenkins to GitHub Actions at the end of 2023. The project merges more than 1,500 pull requests per year and now runs thousands of unit, integration, functional, and end-to-end tests in under an hour across Windows and several macOS versions, with a cost-benefit assessment that favored the new system [2].

Nextflow pipelines are developed and shared as ordinary GitHub repositories. nf-cavalier, a Nextflow pipeline for rare disease variant prioritization and reporting, is maintained at bahlolab/nf-cavalier under the MIT license [3]. What makes the Nextflow ecosystem unusual is that the nf-core community ships a shared set of workflow files with every new pipeline, so a repository created from the template gets linting, matrix testing, and cloud test runs without anyone hand-writing YAML from scratch.

## What You Need on Hand

- A GitHub repository containing a Nextflow pipeline. If you do not have one, the minimal pipeline in the next section takes five minutes to create.
- Write access to that repository so you can push the workflow file.
- Small test data. A few compressed FASTQ files, or even plain text files, are enough. The nf-core community keeps small public files in a dedicated test-datasets repository for exactly this purpose.
- Container images for your processes. Public images such as `ubuntu:24.04` work for a demo, and nf-core modules reference versioned biocontainer images for real tools.
- Optional: the nf-core tools command line interface installed locally with `pipx install nf-core`, and Java 17 if you want to reproduce the CI run on your laptop before pushing.

## How the Two Tools Fit Together

![Flow of an event firing, a runner being picked, setup steps, a matrix producing four jobs, and log upload on failure.](/article-images/illustrations/github-actions-nextflow-workflow-anatomy.webp)

GitHub Actions reads every file matching `.github/workflows/*.yml` in your repository. Each file defines one automation, called a workflow. A workflow listens for events, such as a push to main or a pull request being opened. When an event matches, GitHub schedules the workflow's jobs on a runner, a virtual machine that GitHub hosts (`ubuntu-latest` is the standard Linux image) or one you register yourself. A job is a list of steps. A step either runs a shell command or calls an action, a reusable unit such as `actions/checkout@v4`, which checks out your repository. Jobs can read secrets, encrypted values injected as environment variables, and can upload artifacts, files preserved after the run for download. A matrix strategy fans a single job out into copies that differ by, for example, container engine. A concurrency group cancels superseded runs when you push again.

On the Nextflow side, five settings control how a pipeline behaves in CI. A profile is a named block of configuration in `nextflow.config` that you activate with the `-profile` flag. The convention is a `test` profile with tiny inputs and capped resources, combined with a container engine profile. `NXF_VER` is an environment variable that pins the Nextflow launcher version. The executor decides where processes run, and the `local` executor is the right choice in CI because the runner itself is the compute. The `outdir` parameter sets where published outputs land.

The mapping is direct. The workflow file replaces your fingers typing `nextflow run`, the runner replaces your laptop, the matrix replaces running the command twice with different engines, and artifacts replace scrolling a terminal after a failure.

## A Complete GitHub Actions Nextflow Example

### Step 1: Create a minimal pipeline

If you already have a pipeline, skip to step 2. Otherwise create these two files at the repository root. `main.nf` defines one process, the unit of work in DSL2, Nextflow's current scripting dialect:

```groovy
#!/usr/bin/env nextflow

nextflow.enable.dsl = 2

process GREET {
    output:
    path "hello.txt"

    script:
    """
    echo "hello from $workflow.projectName" > hello.txt
    """
}

workflow {
    GREET()
}
```

`nextflow.config` declares the output directory, publishes every output there, and defines the profiles:

```groovy
params.outdir = 'results'

process {
    publishDir = [path: params.outdir, mode: 'copy']
}

profiles {
    test {
        process.cpus   = 1
        process.memory = 1.GB
    }
    docker {
        docker.enabled    = true
        process.container = 'ubuntu:24.04'
    }
    singularity {
        singularity.enabled = true
        process.container   = 'ubuntu:24.04'
    }
}
```

The `test` profile caps each process at one CPU and 1 GB of memory so the run always fits a hosted runner. The `docker` and `singularity` profiles turn on the corresponding container engine and set the image. `ubuntu:24.04` stands in for the bioinformatics images a real pipeline would use.

### Step 2: Create the workflow file with triggers and concurrency

Create `.github/workflows/ci.yml`. The `name` and the `on` block decide when the automation runs:

```yaml
name: nextflow-ci

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: "${{ github.workflow }}-${{ github.ref }}"
  cancel-in-progress: true
```

This runs on pushes to main, on every pull request, and on demand through the workflow_dispatch button in the Actions tab. The concurrency group is keyed by workflow name and branch, so five pushes in a row produce one active run, not five. The permissions block grants the automatic `GITHUB_TOKEN` read-only access to repository contents, which is all a test run needs.

### Step 3: Install Java and Nextflow

Add the setup steps to the job. `actions/setup-java` installs Eclipse Temurin 17, a long-term-support OpenJDK build, and the official installer script provides Nextflow:

```yaml
jobs:
  test:
    name: test ${{ matrix.profile }} nextflow-${{ matrix.nxf_ver }}
    runs-on: ubuntu-latest
    timeout-minutes: 30
    env:
      NXF_VER: ${{ matrix.nxf_ver }}
      NXF_SINGULARITY_CACHEDIR: ${{ github.workspace }}/.singularity
    steps:
      - name: Check out pipeline
        uses: actions/checkout@v4

      - name: Install Java 17
        uses: actions/setup-java@v4
        with:
          distribution: "temurin"
          java-version: "17"

      - name: [Install Nextflow](/knowledge/bioinformatics/install-nextflow)
        run: |
          curl -fsSL https://get.nextflow.io | bash
          sudo mv nextflow /usr/local/bin/
          nextflow -version
```

The install step ends with `nextflow -version`, which prints the Nextflow banner and the version number. Confirm the version matches the value you pinned. Because `NXF_VER` is set at the job level, the launcher honors it in every step.

### Step 4: Run the test profile on a matrix

The matrix creates one job copy per combination of profile and Nextflow version, so the example produces four jobs:

```yaml
    strategy:
      fail-fast: false
      matrix:
        profile: ["docker", "singularity"]
        nxf_ver: ["24.04.2", "latest"]

    steps:
      - name: Install Apptainer
        if: matrix.profile == 'singularity'
        uses: eWaterCycle/setup-apptainer@main
        # pin this action to a release tag in production

      - name: Run test profile
        run: nextflow run . -profile test,${{ matrix.profile }} --outdir ci-results

      - name: List published outputs
        run: find ci-results -type f | sort
```

`fail-fast: false` keeps all four jobs running when one fails, so a Docker-only problem does not hide a Singularity-only problem. `NXF_SINGULARITY_CACHEDIR` keeps pulled images inside the workspace so a retried job does not download them again.

### Step 5: Capture logs and cap runtime

`timeout-minutes: 30` on the job stops a runaway pipeline before it consumes the default six-hour budget. Upload the Nextflow log and any partial outputs when the run fails, because that artifact is usually all you need to diagnose the problem:

```yaml
      - name: Upload logs on failure
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: logs-${{ matrix.profile }}-${{ matrix.nxf_ver }}
          path: |
            .nextflow.log*
            ci-results/
```

### The complete file

```yaml
name: nextflow-ci

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: "${{ github.workflow }}-${{ github.ref }}"
  cancel-in-progress: true

jobs:
  test:
    name: test ${{ matrix.profile }} nextflow-${{ matrix.nxf_ver }}
    runs-on: ubuntu-latest
    timeout-minutes: 30
    env:
      NXF_VER: ${{ matrix.nxf_ver }}
      NXF_SINGULARITY_CACHEDIR: ${{ github.workspace }}/.singularity
    strategy:
      fail-fast: false
      matrix:
        profile: ["docker", "singularity"]
        nxf_ver: ["24.04.2", "latest"]
    steps:
      - name: Check out pipeline
        uses: actions/checkout@v4

      - name: Install Java 17
        uses: actions/setup-java@v4
        with:
          distribution: "temurin"
          java-version: "17"

      - name: Install Nextflow
        run: |
          curl -fsSL https://get.nextflow.io | bash
          sudo mv nextflow /usr/local/bin/
          nextflow -version

      - name: Install Apptainer
        if: matrix.profile == 'singularity'
        uses: eWaterCycle/setup-apptainer@main

      - name: Run test profile
        run: nextflow run . -profile test,${{ matrix.profile }} --outdir ci-results

      - name: List published outputs
        run: find ci-results -type f | sort

      - name: Upload logs on failure
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: logs-${{ matrix.profile }}-${{ matrix.nxf_ver }}
          path: |
            .nextflow.log*
            ci-results/
```

### Push and verify

```bash
git add .github main.nf nextflow.config
git commit -m "Add Nextflow CI"
git push
```

Open the Actions tab in the repository. You should see the `nextflow-ci` workflow running four jobs, one per matrix combination. Expand the "Run test profile" step of a docker job. The output looks like this, with a different run name and hash each time:

```text
N E X T F L O W  ~  version 24.04.2
Launching `./main.nf` [soggy_curie] DSL2 - revision: 8f1c2a3b4d
executor >  local (1)
[c1/9d2e4f] process > GREET [100%] 1 of 1, succeeded ✔
```

The list step then prints the published file:

```text
ci-results/hello.txt
```

and the file contains the greeting with your repository name:

```text
hello from your-repo-name
```

Three checks confirm the setup is right. Every matrix job shows a green check. The run step's first line shows the Nextflow version you pinned, not whatever the installer fetched by default. If you break the pipeline on purpose, for example by introducing a syntax error in `main.nf`, the workflow turns red and an artifact named `logs-...` appears on the run summary page containing `.nextflow.log`.

### The parameters that matter

- `on.push.branches` limits push runs to specific branches, keeping feature-branch pushes quiet until you open a pull request.
- `pull_request` with no filters runs on every pull request, including ones from forks.
- `workflow_dispatch` adds a manual run button, useful for retesting without a new commit.
- `permissions: contents: read` is the minimum scope for a test run and limits blast radius if a workflow is compromised.
- `concurrency` with `cancel-in-progress: true` cancels superseded runs and saves minutes.
- `timeout-minutes` bounds each job independently of the repository-wide default.
- `strategy.fail-fast: false` runs every matrix combination even after one fails.
- `NXF_VER` pins the Nextflow launcher version for the whole job.
- `NXF_SINGULARITY_CACHEDIR` sets where Nextflow stores pulled images.
- `if: failure()` on the upload step means artifacts are written only when they are actually needed.

### Errors you will hit and their fixes

1. `nextflow: command not found`. The install step failed or the binary is not on PATH. Check the install step's log and keep the `sudo mv` line.
2. `Unknown configuration profile: test`. The name passed to `-profile` does not match `nextflow.config`. Fix the spelling or add the profile.
3. `Cannot connect to the Docker daemon at unix:///var/run/docker.sock`. Docker is not running. Hosted runners start it for you, so this usually means a self-hosted runner needs the daemon started and the runner user added to the docker group.
4. A message like `Process 'GREET' exceeded resource request (2 GB memory)`. A process asked for more than the test profile allows. Lower the process request or raise the cap slightly.
5. `The job running on runner ... has exceeded the maximum execution time of 30 minutes.` The test run is too heavy. Shrink the test data, add stub runs, or raise `timeout-minutes`.
6. `No space left on device`. Images plus the work directory exceeded the runner disk. Trim the test profile, clear caches, or move to a larger runner.
7. `Invalid workflow file: .github/workflows/ci.yml`. A YAML syntax problem, and the annotation names the line. The most common causes are wrong indentation and unquoted `${{ }}` expressions.
8. `Unrecognized named-value: 'matrix'`. A step referenced `matrix` outside the job that defines it. Move the step inside the matrix job.
9. Exit code 137. The Linux kernel killed a process for using too much memory. Apply the same fix as the resource request error and check the container's memory limit.

## Next Steps After the First Green Run

Once the basic run passes, add branch protection so the check is required before merge, add the badge to your README, and split the test job from the lint job if you have not already. If your pipeline uses nf-core modules, add component tests as described below. When the pipeline is stable, wire a manual cloud test so you discover executor problems before a release rather than after one.

## The nf-Core GitHub Actions Nextflow Template

The fastest way to a production-grade setup is the nf-core template. Install the community tools CLI and scaffold a pipeline:

```bash
pipx install nf-core
nf-core pipelines create
```

On nf-core tools version 2.x the command is `nf-core create`. The generator asks for a pipeline name and description, then writes a complete repository: `main.nf`, `nextflow.config`, a `modules` directory populated from the nf-core modules repository, tests, and a `.github/workflows` directory. The workflow files are the community's shared CI design:

| Workflow file | Typical trigger | What it does |
|, - |, - |, - |
| `ci.yml` | push, pull_request | Runs the test profile on a matrix of Nextflow versions and container engines |
| `linting.yml` | push, pull_request | Runs prettier, editorconfig-checker, and nf-core pipelines lint |
| `nf-test.yml` | push, pull_request touching modules or subworkflows | Runs nf-test unit tests for the changed components |
| `awstest.yml` | workflow_dispatch | Runs a small-scale test of the pipeline on AWS Batch |
| `awsfulltest.yml` | workflow_dispatch, release | Runs the pipeline at full scale on AWS Batch |

Three details in the generated files matter. `ci.yml` installs Nextflow with the `nf-core/setup-nextflow` action and runs the test profile against a matrix of versions, including an edge build, so a breaking Nextflow release is caught before it reaches users. `linting.yml` runs prettier, editorconfig-checker, and `nf-core pipelines lint`, which enforces community conventions and validates `nextflow_schema.json`, the machine-readable description of your pipeline's parameters. `nf-test.yml` runs nf-test through the `nf-core/setup-nf-test` action.

Test data follows the same convention. The test profile references small files from the nf-core test-datasets repository rather than committing large data to the pipeline repo. README badges point at the workflow files, for example:

```markdown
![ci badge](https://github.com/USER/REPO/actions/workflows/ci.yml/badge.svg)
```

You do not have to be an nf-core pipeline to benefit. Any Nextflow repository can copy the same workflow files and adjust the lint rules to its own conventions.

## Lint and Format Checks

In a GitHub Actions Nextflow repository, the format check usually runs three tools in one workflow. `prettier, check .` verifies that YAML, Markdown, and JSON files are consistently formatted. `editorconfig-checker` verifies whitespace, line endings, and encoding against `.editorconfig`. `nf-core pipelines lint` applies a long list of pipeline conventions, from required files to process naming, and validates the parameter schema. Run all three locally before pushing, because a format failure costs minutes and blocks the pull request:

```bash
prettier --check .
nf-core pipelines lint
```

The lint job needs a GitHub token for some API queries, which the automatic `GITHUB_TOKEN` provides without any setup:

```yaml
env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

Keep format checks in a separate job from the pipeline test. They fail in seconds when they fail, and separating them means a formatting typo does not consume a container pull and a pipeline run.

## Testing Strategy: Profiles, Versions, and nf-test

The test profile is the heart of Nextflow CI. Design it so the whole pipeline runs in minutes: tiny inputs, one CPU and a small memory cap per process, and deterministic outputs. When a tool is slow or awkward to install, give the process a stub, a placeholder body that writes fake outputs, and run the pipeline with `-stub`. A stub run exercises all the wiring, channels, process ordering, and output publishing, without executing the tools.

Pin versions deliberately. The matrix in the example tests Nextflow 24.04.2 and the current stable release. nf-core pipelines also test an edge build, using the value `latest-everything`, which carries the newest changes including plugin updates, so a Nextflow regression is caught by the community before it reaches your pipeline.

For component-level tests, nf-test writes test files with the `.nf.test` extension next to the module they cover. A module test asserts that defined inputs produce expected outputs, including checksums of stable files. CI runs only the tests touched by the pull request, which keeps the feedback loop short.

Two debugging tools save hours. Setting a repository secret named `ACTIONS_STEP_DEBUG` to `true` makes GitHub print hidden step details for every run. The `mxschmitt/action-tmate` action opens an interactive shell on the runner so you can inspect a failing job directly, but restrict it to private repositories or gated branches, because an open debugging session on a public repository is a hazard.

Do not bother with `-resume` in CI. Every run starts from a clean runner, so there is nothing to resume. If a job fails, read the uploaded `.nextflow.log` artifact instead.

## Compute: Hosted Runners, Self-Hosted Machines, and Cloud Executors

A standard GitHub-hosted `ubuntu-latest` runner provides two vCPUs, about 7 GB of RAM, and a 14 GB SSD, and a job is stopped after 360 minutes by default. That is enough for a test profile and not enough for a full analysis. Three escalation paths exist.

Larger hosted runners are available on paid plans, with more vCPUs, memory, and disk, selected per job through the `runs-on` label. Use them for heavyweight integration tests that still fit a single machine.

Self-hosted runners, machines you register with GitHub, are the way to test against GPUs, unusual schedulers, or an internal filesystem. They shift maintenance and security onto you. The machine must be patched, and a self-hosted runner attached to a public repository should be assumed to execute untrusted pull request code unless you require approval for workflow runs.

For full-scale data, the standard pattern is to keep the runner small and let Nextflow's cloud executors do the work. The runner submits the pipeline to AWS Batch or Google Batch, compute happens in the cloud, and CI just watches the log. nf-core ships this as two workflows: `awstest.yml`, a manual small-scale cloud test, and `awsfulltest.yml`, a full-size run usually tied to releases. Provisioning those environments is where infrastructure-as-code earns its keep, since Terraform or Ansible can recreate an identical Batch environment on demand, the synergy described in cloud CI/CD reference architectures [1].

## Security and Secrets

CI files are executable infrastructure that strangers can propose changes to. Treat them with the same care as the pipeline code.

Start with permissions. The automatic `GITHUB_TOKEN` should get the minimum scope it needs, declared explicitly with the permissions key, as the example does with `contents: read`. This is not a formality. A checklist-based audit of real-world GitHub Actions workflows, built from 30 criteria spanning four workflow sections, found overall compliance at 28 percent, with permission controls the weakest area at 4 percent, far behind clarity checks at 68 percent. A read-only token cannot push code or create releases even if a workflow is compromised.

Pin third-party actions to a full-length commit SHA instead of a mutable tag. A tag can be moved, a SHA cannot. For cloud credentials, prefer OpenID Connect (OIDC), which lets a job assume a short-lived cloud role through the `id-token` permission instead of storing long-lived access keys as secrets.

Beware of two workflow patterns that turn CI into code execution for strangers. The `pull_request_target` trigger runs in the context of the base repository with access to secrets, and checking out the pull request's head code inside it hands attackers those keys. Use the plain `pull_request` trigger for untrusted forks. Second, never interpolate event text, an issue title, a PR body, a branch name, directly into a `run` script. Pass it through `env` and quote it. The stakes rise with agentic workflows. A study of AI-assisted Actions characterized 1,033 real-world AI-assisted actions and scanned 13,392 agentic workflows, defining Agentic Workflow Injection as the flaw where untrusted event context such as issue bodies or PR comments reaches an agent prompt or a script derived from model output, with two core patterns, Prompt-to-Agent and Prompt-to-Script. If you add an AI reviewer or triage agent to your pipeline repository, gate it behind labels or approvals and keep it away from secrets.

The broader CI/CD security literature reaches the same conclusions from the cloud side. A systematic review of 66 papers on CI/CD security in cloud environments highlights image manipulation, unauthorized access, and weak authentication as recurring challenges, and points to complementary tooling such as Harbor for image scanning and SonarQube for code quality [4]. For a Nextflow repository the practical translation is to pin container image digests for production runs, scan images you build, and rotate any secret you cannot replace with OIDC.

## Nextflow vs Snakemake for CI

![Two-column comparison of Nextflow and Snakemake covering runtime, execution model, CI convention, template, and step commands.](/article-images/illustrations/github-actions-nextflow-nextflow-snakemake-ci.webp)

Nextflow and Snakemake are the two dominant open-source bioinformatics workflow managers, and both run well under GitHub Actions. The CI setup differs more in convention than in mechanics.

Nextflow is JVM-based, writes pipelines in DSL2 with dataflow channels, treats containers as a first-class execution mode, and ships built-in cloud executors. Its decisive CI advantage is the nf-core ecosystem: the template, the lint rules, the test-datasets convention, and the shared workflow files mean a new pipeline gets a mature GitHub Actions setup on day one.

Snakemake is Python-based, builds a directed acyclic graph of rules, integrates tightly with Conda environments, and is a common choice on HPC systems. Its CI convention is lighter: repositories typically run `snakemake, lint`, format checks, and small pipeline smoke tests they wire themselves.

If you are choosing between them, decide on execution environment and community, not on CI. Under GitHub Actions the YAML is the same, the runner is the same, and the difference is which commands the steps contain.

## What CI Does to Pull Request Review

Automated checks change how humans review pull requests, and the empirical literature on GitHub Actions is worth reading before you stack more automation onto a pipeline repository.

The headline caution is that CI adoption does not automatically make reviews faster. A regression discontinuity study of 55 machine learning projects, comparing the 12 months before and after GitHub Actions adoption, found no significant reduction in the time to close pull requests, with review efficiency driven more by the size of the change and the size of the review backlog than by the presence of CI [5]. Rejected pull requests in that study had higher churn and longer discussions. For a pipeline repository the lesson is to keep test-profile changes small and the queue short, because Actions will not absorb a bloated review.

Adoption also shifts reviewer attention. A replication study across six open-source projects found that the pull request model produces more balanced discussion of test and production code than the older Gerrit model, but that after GitHub Actions adoption review attention pivoted sharply toward production code. In a Nextflow repository, the CI files and the test profile are production code. Review them like it.

Automated review bots are now common, and their effectiveness varies. A large study of 16 AI-based code review actions across 178 repositories and more than 22,000 review comments found that comments are more likely to lead to code changes when they are concise, contain code snippets, are manually triggered, and come from hunk-level tools that comment on specific lines [6]. If you add a review bot to your pipeline repository, prefer one that comments inline on changed lines and keep it quiet otherwise. Broader evidence also says humans should stay in the loop: across 278,790 review conversations in 300 projects, human reviewers exchanged 11.8 percent more rounds when reviewing AI-generated code than human-written code, and AI suggestions were adopted into the codebase at a significantly lower rate. Purpose-built review automation can perform well when tuned: one framework integrated with GitHub Actions reached an F1 score of 82.9 percent across 12,400 pull requests from 40 repositories, with a 73.6 percent developer acceptance rate in a controlled study.

Finally, keep checks green. An analysis of 14,234 stalled pull requests across 19 repositories found that inactivity is often blocked by technical or dependency issues, including failing checks, rather than simple disengagement. A red CI badge that stays red for days is how contributions die.

## Common Mistakes and Limitations

- Running the full pipeline in CI. A test profile exists so CI finishes in minutes. If your CI run takes hours, the test profile is not doing its job, and reviewers will stop waiting for it.
- No resource caps in the test profile. Without capped CPUs and memory, a process that requests more than the runner has will fail with a resource error or an exit code 137 kill.
- Not pinning the Nextflow version. Testing only the newest release means a breaking Nextflow update breaks every pull request at once. Pin one version and matrix against the latest.
- Skipping the permissions block. The default token scope is broader than most pipelines need, and real-world audits show permission controls are the most neglected part of workflow files.
- Long-lived cloud keys as secrets. Prefer OIDC short-lived roles. Keys in secrets leak through logs, forks, and compromised actions.
- Interpolating untrusted text into scripts. Event context such as PR bodies and issue titles must flow through `env`, never through string substitution into `run` blocks, and the same rule applies to anything an AI agent produces.
- `pull_request_target` with untrusted checkouts. This combination gives fork authors access to your secrets. Use plain `pull_request` for outside contributions.
- No concurrency group. Rapid pushes then queue redundant runs and burn minutes.
- Assuming container engines exist. Docker is preinstalled on hosted Linux runners, Apptainer is not, and self-hosted machines may have neither.
- Treating green CI as scientific validation. CI proves the test profile runs and outputs appear. It cannot prove the biology is right, that reference files are current, or that parameters suit your data.
- Hosted runner limits. Two vCPUs and about 7 GB of RAM bound what a standard runner can test. Full-size runs belong on larger runners, self-hosted machines, or cloud executors.
- Lint scope. `nf-core pipelines lint` enforces nf-core conventions. A passing lint score does not mean a non-nf-core pipeline follows your lab's own standards.
- Self-hosted runner security. A registered machine executes whatever workflows target it. Patch it, isolate it, and require approval for runs from forks.

## Frequently Asked Questions

### Do I need Docker to test a Nextflow pipeline on GitHub Actions?

No. Nextflow runs fine with the local executor and no container engine, and the example in this guide would work with the test profile alone. Containers are still recommended because they make the test environment match production. Docker is preinstalled on hosted Linux runners, while Apptainer needs a setup step.

### Which Java version should the CI install?

Use Java 17 with the `actions/setup-java` action and the temurin distribution. Recent Nextflow releases require a modern Java runtime, and pinning the distribution keeps the CI environment identical from run to run.

### How do I pin the Nextflow version in CI?

Set the `NXF_VER` environment variable, either to a fixed version such as 24.04.2 or through a matrix entry. The `nf-core/setup-nextflow` action accepts a version input and does the same thing with less boilerplate.

### What is a test profile?

A named configuration block in `nextflow.config` that points the pipeline at tiny inputs and caps per-process CPUs and memory so the full pipeline executes in minutes on a CI runner. You activate it with `nextflow run . -profile test`.

### Can GitHub Actions run my pipeline on real, full-size data?

Not on standard hosted runners, which are sized for small tests. Use larger hosted runners, self-hosted machines, or a cloud executor such as AWS Batch, which is the pattern behind the nf-core `awstest` and `awsfulltest` workflows.

### How do I stop wasted runs when I push many commits in a row?

Add a concurrency group keyed on the workflow name and git ref with `cancel-in-progress` set to true. Superseded runs are cancelled the moment a newer push arrives.

### Do I have to use the nf-core template?

No. A single `ci.yml` file with a test profile is a complete setup for a small pipeline. The template buys you shared lint rules, component tests, and cloud test workflows that the community maintains for you.

### How does CI setup differ for Snakemake pipelines?

The YAML, triggers, and runners are identical. The steps change: Snakemake repositories run `snakemake, lint` and small smoke tests instead of a Nextflow test profile, and there is no shared template equivalent to nf-core's.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Do I need Docker to test a Nextflow pipeline on GitHub Actions?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. Nextflow runs fine with the local executor and no container engine. Containers are still recommended because they make the test environment match production. Docker is preinstalled on hosted Linux runners, while Apptainer needs a setup step."
      }
    },
    {
      "@type": "Question",
      "name": "Which Java version should the CI install?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Use Java 17 with the actions/setup-java action and the temurin distribution. Recent Nextflow releases require a modern Java runtime, and pinning the distribution keeps the CI environment identical from run to run."
      }
    },
    {
      "@type": "Question",
      "name": "How do I pin the Nextflow version in CI?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Set the NXF_VER environment variable, either to a fixed version such as 24.04.2 or through a matrix entry. The nf-core/setup-nextflow action accepts a version input and does the same thing with less boilerplate."
      }
    },
    {
      "@type": "Question",
      "name": "What is a test profile?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "A named configuration block in nextflow.config that points the pipeline at tiny inputs and caps per-process CPUs and memory so the full pipeline executes in minutes on a CI runner. You activate it with nextflow run . -profile test."
      }
    },
    {
      "@type": "Question",
      "name": "Can GitHub Actions run my pipeline on real, full-size data?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Not on standard hosted runners, which are sized for small tests. Use larger hosted runners, self-hosted machines, or a cloud executor such as AWS Batch, which is the pattern behind the nf-core awstest and awsfulltest workflows."
      }
    },
    {
      "@type": "Question",
      "name": "How do I stop wasted runs when I push many commits in a row?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Add a concurrency group keyed on the workflow name and git ref with cancel-in-progress set to true. Superseded runs are cancelled the moment a newer push arrives."
      }
    },
    {
      "@type": "Question",
      "name": "Do I have to use the nf-core template?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. A single ci.yml file with a test profile is a complete setup for a small pipeline. The template buys you shared lint rules, component tests, and cloud test workflows that the community maintains for you."
      }
    },
    {
      "@type": "Question",
      "name": "How does CI setup differ for Snakemake pipelines?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "The YAML, triggers, and runners are identical. The steps change: Snakemake repositories run snakemake lint checks and small smoke tests instead of a Nextflow test profile, and there is no shared template equivalent to nf-core's."
      }
    }
  ]
}
</script>

## Sources

1. [A Cloud-Driven CI/CD Automation Model Using Jenkins, GitHub Actions, and Infrastructure-as-Code Tools](https://doi.org/10.54660/.ijmrge.2022.3.6.935-948)
2. [Thoroughly testing and integrating hundreds of Pull Requests per month: ROOT’s new Cost-efficient and Feature Rich GitHub-based CI](https://doi.org/10.1051/epjconf/202533701006)
3. [nf-cavalier: A Nextflow Pipeline for Rare Disease Variant Prioritization and Reporting](https://doi.org/10.64898/2026.08.06.743410)
4. [A Systematic Literature Review on Continuous Integration and Deployment (CI/CD) for Secure Cloud Computing](https://doi.org/10.5220/0013018500003825)
5. [Exploring the Impact of GitHub Actions on Pull Request Reviews in Machine Learning Projects](https://doi.org/10.5753/sbes.2025.10766)
6. [Does AI Code Review Lead to Code Changes? A Case Study of GitHub Actions](https://doi.org/10.1109/TSE.2026.3688237)