# Using GitHub Actions with Snakemake: Templates and Examples

By the end of this guide you will have a Snakemake pipeline that lints itself, rebuilds its conda environments from scratch, runs on committed test data, and uploads its outputs as artifacts, all on every push to GitHub. You will also have a reusable workflow template you can drop into any repository, a caching strategy that keeps conda solves off the critical path, and a field guide to the errors you are most likely to meet along the way.

Snakemake is a Python-based bioinformatics workflow manager that expresses an analysis as rules linked by input and output files. GitHub Actions is GitHub's continuous integration service, which runs jobs on ephemeral Linux virtual machines whenever repository events occur. Together they give a pipeline something rare: proof, on every commit, that the workflow still parses, its environments still solve, and its code still produces the expected files.

Here is what you need on hand before starting:

- A GitHub account and a repository you can push to
- Git installed locally, plus Python 3.10 or newer
- Conda or micromamba locally, because the pipeline uses rule-level conda environments
- Basic Snakemake vocabulary: rules, wildcards, and the directed acyclic graph (DAG) that connects them
- Optional, the GitHub CLI (`gh`) for watching runs from your terminal

## Why pair Snakemake with GitHub Actions

A bioinformatics workflow manager solves orchestration. GitHub Actions solves verification and delivery. The combination earns its keep in four concrete ways.

**Reproducibility proof.** Every CI run starts from a clean virtual machine. Conda environments are solved from your environment files rather than inherited from your laptop, so a broken dependency resolution or an accidentally missing channel shows up as a red check mark instead of a surprise six months later.

**Contribution guardrails.** When a collaborator opens a pull request, Actions runs the same checks you run. A pull request that deletes an input file, breaks a wildcard, or pins an unsolvable environment cannot merge quietly.

**Scheduled work.** Cron triggers let a workflow pull updated reference databases, re-run surveillance summaries, or refresh a report on a fixed cadence without a human in the loop.

**Publishable outputs.** The same system that tests the pipeline can deploy an HTML report to GitHub Pages, so stakeholders always see the current version of a result.

## How GitHub Actions runs a Snakemake pipeline

![Four-step flow showing runner allocation, checkout and micromamba setup, the snakemake run, and artifact upload.](/article-images/illustrations/snakemake-github-actions-ci-run-order.webp)

The mechanics are simpler than most people expect. A workflow definition lives in a YAML file under `.github/workflows/` in your repository. Any file in that directory becomes a workflow. When a triggering event occurs, GitHub allocates a fresh runner, checks out your code, and executes the steps you defined in order. When the job ends, the runner is destroyed. Nothing persists between runs unless you cache it or upload it as an artifact.

Snakemake runs on that runner like it would on your laptop. You install it, invoke it with `snakemake, cores N, use-conda`, and it builds the DAG, creates conda environments, and executes shell commands. The only real differences are that the filesystem starts empty and that you must bring every dependency with you.

### The workflow file format, key by key

Every snakemake github actions setup shares the same file format. These are the keys that matter:

- `name`: a label shown in the Actions tab. Optional but helpful once a repository has several workflows.
- `on`: the trigger map. Common events are `push` (commits), `pull_request` (proposed changes), `workflow_dispatch` (a manual button in the UI), and `schedule` (cron, in UTC).
- `jobs`: one or more named jobs. Each job gets its own fresh runner.
- `jobs.<id>.runs-on`: the virtual machine image. `ubuntu-latest` is the standard choice for bioinformatics.
- `jobs.<id>.steps`: an ordered list. A step either reuses a packaged action with `uses` (configured through `with`) or executes shell commands with `run`.
- `needs`: makes a job wait for another job to succeed first.
- `strategy.matrix`: runs the same job several times with different variable values, such as two Snakemake major versions.
- `timeout-minutes`: a hard stop for the job. Without it, a hung conda solve burns time until the platform cap.
- `permissions`: grants the workflow access to repository resources. Grant the minimum.
- `concurrency`: prevents two runs of the same workflow from stepping on each other.

### What a hosted runner gives you

Per GitHub's documentation, the standard `ubuntu-latest` runner provides 4 virtual CPUs, 16 GB of RAM, and roughly 14 GB of free SSD space, and a single job may run for at most 6 hours. Standard usage on public repositories is free. Private repositories draw from a monthly minutes allowance that depends on the plan, with the Free plan including 2,000 minutes per month for Linux runners. Those numbers define the envelope you are designing for: fast enough for linting, environment solving, and smoke tests on small data, not a substitute for a cluster.

## A minimal Snakemake GitHub Actions example, step by step

This walkthrough builds a tiny but complete pipeline: it generates synthetic FASTQ reads, summarizes them with `seqkit`, and runs the whole thing in CI. Because the pipeline manufactures its own test data inside the DAG, the repository needs no large fixtures, and CI exercises the exact code path a real run would take.

### Step 1: Lay out the repository

Create these files at the top level of a new repository:

- `Snakefile`
- `scripts/make_reads.py`
- `envs/main.yml`
- `.github/workflows/ci.yml`
- `.gitignore` containing `.snakemake/`, `results/`, and `logs/`

Keeping inputs and outputs in disjoint trees (`data/` and `scripts/` in, `results/` and `logs/` out) prevents a class of errors covered later.

### Step 2: Write the Snakefile

```python
rule all:
    input:
        "results/seqkit_stats.txt"

rule make_test_data:
    output:
        "data/sample_R1.fastq"
    threads: 1
    shell:
        "python scripts/make_reads.py --reads 100 --length 80 --seed 42 --output {output}"

rule seqkit_stats:
    input:
        "data/sample_R1.fastq"
    output:
        "results/seqkit_stats.txt"
    log:
        "logs/seqkit_stats.log"
    conda:
        "envs/main.yml"
    threads: 1
    shell:
        "seqkit stats {input} > {output} 2> {log}"
```

Three things deserve attention. The `all` rule defines the default target, so a bare `snakemake` invocation builds everything. The `conda` directive ties `seqkit_stats` to an environment file, which is what `-use-conda` consumes. Snakemake creates parent directories for outputs and logs automatically, so no `mkdir` scaffolding is needed.

### Step 3: Add the helper script

```python
import argparse
import random

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--reads", type=int, default=100)
    parser.add_argument("--length", type=int, default=80)
    parser.add_argument("--seed", type=int, default=42)
    parser.add_argument("--output", required=True)
    args = parser.parse_args()

    rng = random.Random(args.seed)
    bases = "ACGT"
    with open(args.output, "w") as fh:
        for i in range(args.reads):
            seq = "".join(rng.choice(bases) for _ in range(args.length))
            qual = "".join(chr(33 + rng.randint(20, 40)) for _ in range(args.length))
            fh.write(f"@read_{i}\n{seq}\n+\n{qual}\n")

if __name__ == "__main__":
    main()
```

The fixed seed matters. Deterministic test data means a CI failure reflects a code or environment problem, not random input.

### Step 4: Define the conda environment

```yaml
channels:
  - conda-forge
  - bioconda
  - nodefaults
dependencies:
  - python=3.12
  - seqkit=2.4.0
```

Channel order follows Bioconda's own setup guidance: `conda-forge` first, `bioconda` second, and the `nodefaults` pseudo-channel to keep the defaults channel out. Pin both Python and the tool. Unpinned environments work until the day they do not, and that day always lands during a demo.

### Step 5: Run the pipeline locally

From the repository root:

```bash
snakemake --cores 2 --use-conda
```

Expect output shaped like this (abridged):

```text
Building DAG of jobs...
Creating conda environment envs/main.yml...
Downloading and installing remote packages.
Environment for envs/main.yml created (location: .snakemake/conda/<hash>)
Using shell: /bin/bash
Provided cores: 2 (use --cores to define an exact number of jobs)

rule make_test_data:
    jobid: 1
    output: data/sample_R1.fastq

Finished job 1.
1 of 2 steps (50%) done

rule seqkit_stats:
    input: data/sample_R1.fastq
    jobid: 0
    output: results/seqkit_stats.txt
    log: logs/seqkit_stats.log

Finished job 0.
2 of 2 steps (100%) done
Complete log: .snakemake/log/20251110T091358.snakemake.log
```

Then check the result:

```bash
cat results/seqkit_stats.txt
```

```text
file                 format  type  num_seqs  sum_len  min_len  avg_len  max_len
data/sample_R1.fastq  FASTQ   DNA        100     8000       80       80       80
```

One hundred reads at 80 base pairs gives a `sum_len` of 8000, which confirms the arithmetic end to end. If this works locally, CI has every chance of working too.

### Step 6: Add the Actions workflow

Create `.github/workflows/ci.yml`:

```yaml
name: snakemake-ci

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

jobs:
  run-pipeline:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - name: Check out the repository
        uses: actions/checkout@v4

      - name: Set up Snakemake with micromamba
        uses: mamba-org/setup-micromamba@v2
        with:
          environment-name: ci
          create-args: >-
            python=3.12
            snakemake-minimal=9

      - name: Record tool versions
        run: |
          snakemake --version

      - name: Run the pipeline
        run: |
          snakemake --cores 4 --use-conda --printshellcmds

      - name: Upload outputs and logs
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: snakemake-outputs
          path: |
            results/
            logs/
            .snakemake/log/
          retention-days: 7
```

Parameter notes:

- `pull_request:` with no value triggers on pull requests targeting any branch.
- `workflow_dispatch` adds a "Run workflow" button in the Actions tab, which is the fastest way to retest without a dummy commit.
- The `>-` folded scalar joins the two `create-args` lines into one argument string for micromamba. `snakemake-minimal` is the lean Snakemake package without plugins, which is what CI needs.
- The setup-micromamba action activates the created environment for all later steps, so plain `snakemake` resolves on `PATH`.
- `-cores 4` matches the runner's documented vCPU count. `-cores all` is equivalent.
- `timeout-minutes: 30` stops a hung job well before the platform's 6-hour cap.
- `if: always()` uploads logs even when the pipeline fails, which is exactly when you need them most.

### Step 7: Push and watch the run

```bash
git add Snakefile scripts envs .github .gitignore
git commit -m "Add Snakemake CI"
git push origin main
```

From the Actions tab, or from a terminal with the GitHub CLI:

```bash
gh run watch
```

A successful run ends with each step green and a summary like this:

```text
✓ master snakemake-ci
  ✓ run-pipeline Set up Snakemake with micromamba 41s
  ✓ run-pipeline Run the pipeline 1m52s
  ✓ run-pipeline Upload outputs and logs 1s
```

The pipeline step takes longest the first time because Snakemake solves and builds the conda environment from scratch. Subsequent runs with caching (covered below) skip most of that work.

### Step 8: Inspect the outputs

Download the `snakemake-outputs` artifact from the run page. It contains `results/seqkit_stats.txt`, the log directory, and Snakemake's own log under `.snakemake/log/`. The Snakemake log is the single most useful debugging artifact: it records the exact commands, timings, and environment paths for every job.

## A reusable Snakemake GitHub Actions template

The minimal example proves the concept. The template below is what you actually copy into real projects. It separates fast static checks from the pipeline run, tests two Snakemake major versions, caches conda artifacts, and verifies outputs programmatically.

```yaml
name: snakemake-template

on:
  push:
    branches: [main]
    paths:
      - "Snakefile"
      - "workflow/**"
      - "scripts/**"
      - "envs/**"
      - "config/**"
      - ".github/workflows/snakemake-template.yml"
  pull_request:
    paths:
      - "Snakefile"
      - "workflow/**"
      - "scripts/**"
      - "envs/**"
      - "config/**"
      - ".github/workflows/snakemake-template.yml"
  workflow_dispatch:

permissions:
  contents: read

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

jobs:
  lint:
    name: Lint and format check
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: pip
      - name: Install Snakemake and snakefmt
        run: pip install snakemake-minimal snakefmt
      - name: Check Snakefile formatting
        run: snakefmt --check .
      - name: Lint the workflow
        run: snakemake --lint

  smoke-test:
    name: Smoke test (snakemake ${{ matrix.snakemake }})
    needs: lint
    runs-on: ubuntu-latest
    timeout-minutes: 30
    strategy:
      fail-fast: false
      matrix:
        snakemake: ["8", "9"]
    steps:
      - uses: actions/checkout@v4

      - name: Set up micromamba
        uses: mamba-org/setup-micromamba@v2
        with:
          environment-name: ci
          create-args: >-
            python=3.12
            snakemake-minimal=${{ matrix.snakemake }}

      - name: Restore conda caches
        uses: actions/cache@v4
        with:
          path: |
            ~/conda-pkgs
            ~/conda-envs
          key: conda-v1-${{ runner.os }}-${{ matrix.snakemake }}-${{ hashFiles('envs/*.yml') }}
          restore-keys: |
            conda-v1-${{ runner.os }}-${{ matrix.snakemake }}-

      - name: Run the pipeline on committed test data
        env:
          CONDA_PKGS_DIRS: ~/conda-pkgs
        run: |
          snakemake --cores 4 --use-conda --conda-prefix ~/conda-envs --printshellcmds

      - name: Verify outputs exist and are non-empty
        run: |
          test -s results/seqkit_stats.txt
          grep -q "num_seqs" results/seqkit_stats.txt

      - name: Upload outputs and logs
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: outputs-snakemake-${{ matrix.snakemake }}
          path: |
            results/
            logs/
            .snakemake/log/
          retention-days: 7
```

How each block earns its place:

- **Path filters.** The workflow runs only when pipeline-relevant files change. A README edit no longer burns minutes.
- **Least-privilege permissions.** `contents: read` is all these jobs need. Broader grants belong only in jobs that deploy.
- **Concurrency group.** `${{ github.workflow }}-${{ github.ref }}` is unique per branch, so a new push to the same pull request cancels the stale run instead of racing it.
- **Lint job first.** `snakefmt, check .` fails on style drift and `snakemake, lint` catches shadowed wildcards and ambiguous rules before any expensive step runs. Both finish in well under a minute.
- **Version matrix.** Testing Snakemake 8 and 9 catches regressions early. `fail-fast: false` keeps the surviving matrix leg running when its sibling fails, so you see the full picture in one round.
- **Two-layer cache.** `~/conda-pkgs` holds downloaded packages (redirected there through the `CONDA_PKGS_DIRS` environment variable, which conda and mamba both honor) and `~/conda-envs` holds the environments Snakemake creates under `-conda-prefix`. The cache key hashes every file in `envs/`, so editing an environment invalidates the cache automatically. The `v1` token exists so you can force a fresh cache generation if a conda update poisons the old one.
- **Output assertions.** `test -s` passes only if the file exists and is larger than zero bytes. `grep -q "num_seqs"` confirms the file contains the expected `seqkit stats` header rather than an error page that happens to be non-empty.
- **Artifact naming with the matrix value.** Outputs from the Snakemake 8 and 9 runs land in separate artifacts, so you can compare them directly.

### Key options at a glance

| Option or setting | What it controls | Sensible CI value |
|, - |, - |, - |
| `-cores` | CPU cores Snakemake may use | `4` on `ubuntu-latest`, matching the documented vCPUs |
| `-use-conda` | Builds rule-level conda environments | Always set, to prove environments solve from scratch |
| `-conda-prefix` | Directory for created environments | A cached path such as `~/conda-envs` |
| `-dry-run` (`-n`) | Plans the DAG without executing | First step when bringing CI up on a new pipeline |
| `-lint` | Static checks of rule definitions | A dedicated fast lint job |
| `-rerun-incomplete` | Resumes after canceled or timed-out jobs | Add to scheduled or resumable workflows |
| `-printshellcmds` (`-p`) | Echoes every executed command | On while debugging, off for tidy logs |
| `-latency-wait` | Seconds to wait for outputs on slow filesystems | `60` on self-hosted or NFS-backed runners |
| `timeout-minutes` (workflow YAML) | Hard stop for the job | 10 for lint, 30 for smoke tests, below the 6-hour platform cap |
| `concurrency.cancel-in-progress` | Cancels superseded runs of the same group | `true` for pull requests |

## Caching conda environments for faster runs

Without caching, every run pays the full price of downloading packages and building environments. With the template's setup, a warm run reuses both the package cache and the solved environments, and the pipeline step often drops to a fraction of its cold time. Three practical rules keep the cache healthy.

First, cache artifacts, not state. Cache the package directory and the environment prefix, never the whole `.snakemake/` tree. That tree contains lock files, incomplete-job markers, and metadata that describe the previous run, and restoring them into a fresh workspace causes confusing reruns.

Second, key caches on content, not time. `hashFiles('envs/*.yml')` ties the cache to the exact environment definitions. When you change a pin, the key changes, the old cache falls away, and a fresh solve proves the new specification works.

Third, respect the budget. Per GitHub's documentation, total caches are limited to 10 GB per repository, and any cache entry untouched for 7 days is evicted. Reference genomes and large test datasets generally do not belong in the cache. Commit small fixtures or generate them inside the DAG, as this guide's example does.

## Beyond the template: scheduling, inputs, and reusable workflows

### Trigger on changed paths only

The template's `paths` filters demonstrate the general pattern. List every directory the pipeline reads or the workflow itself depends on. Note that filtered-out pushes simply create no run, so the Actions tab stays quiet rather than showing skipped entries.

### Manual runs with inputs

`workflow_dispatch` accepts typed inputs, which turns CI into an on-demand harness:

```yaml
on:
  workflow_dispatch:
    inputs:
      assembly:
        description: Assembly to analyze
        type: choice
        options:
          - hg38
          - mm39
        default: hg38
```

The input reaches Snakemake as a config override:

```yaml
      - name: Run the pipeline
        run: snakemake --cores 4 --use-conda --config assembly=${{ inputs.assembly }}
```

### Nightly scheduled runs

```yaml
on:
  schedule:
    - cron: "30 6 * * 1"
```

Three caveats from GitHub's documentation: scheduled workflows run only from the default branch, cron expressions are in UTC, and runs can be delayed during periods of high load, so do not build anything that depends on second-level timing. Pair schedules with `-rerun-incomplete` so a run that was cut off by the 6-hour cap resumes cleanly on the next cycle.

### Share one template across repositories

Add `workflow_call` alongside the other triggers and any repository in your organization can call the workflow:

```yaml
jobs:
  ci:
    uses: your-org/workflow-templates/.github/workflows/snakemake-template.yml@v1
```

Reusable workflows must live under `.github/workflows/` in their own repository, and secrets must be forwarded explicitly with `secrets: inherit`. This is the cleanest way to keep one vetted snakemake github actions template authoritative instead of pasting it into twenty repos.

### Publish reports with GitHub Pages

If a rule renders an HTML report (MultiQC is the classic candidate), a follow-up job can deploy it. The job needs elevated permissions and the Pages environment:

```yaml
  report:
    needs: smoke-test
    runs-on: ubuntu-latest
    permissions:
      pages: write
      id-token: write
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: outputs-snakemake-9
          path: .
      - uses: actions/upload-pages-artifact@v3
        with:
          path: results/multiqc
      - id: deployment
        uses: actions/deploy-pages@v4
```

### Self-hosted runners and cluster profiles

Register a self-hosted runner on a machine inside your network and the same workflow files can reach real compute and real data. The Snakemake invocation changes only by its profile:

```bash
snakemake --profile profiles/slurm --rerun-incomplete --latency-wait 60
```

This bridges the two worlds: GitHub handles triggers, secrets, and history, while SLURM handles scheduling on your cluster.

## Verifying the run and fixing failures

### Prove the DAG before you run it

From a fresh clone, without executing anything:

```bash
git clone https://github.com/you/your-repo.git
cd your-repo
snakemake --dry-run --cores 1
```

Expected output:

```text
Building DAG of jobs...
Job stats:
job             count
all                 1
make_test_data      1
seqkit_stats        1
total               3
```

Compare this job stats table against the one printed in the CI log. Identical tables mean identical DAGs, which is the strongest cheap check that the runner sees the workflow the same way you do. For a visual check, install Graphviz locally and render `snakemake, dag | dot -Tsvg > dag.svg`.

To validate the workflow YAML itself before pushing, run `actionlint` on the file, or parse it with Python:

```bash
python - <<'PY'
import yaml
from pathlib import Path
yaml.safe_load(Path(".github/workflows/snakemake-template.yml").read_text())
print("workflow YAML parses cleanly")
PY
```

For interactive debugging, the `mxschmitt/action-tmate` action opens an SSH session into the runner. It holds the job open until the timeout, so use it deliberately and never expose it unguarded to pull requests from forks.

### Errors you will eventually meet

![Decision path matching four common CI errors to their fixes, ending at the Snakemake log artifact.](/article-images/illustrations/snakemake-github-actions-failure-triage.webp)

**`shell: line 1: snakemake: command not found`.** The environment is not active or the install step ran after the pipeline step. Keep the setup step before the run step in the same job, or make the invocation explicit with `micromamba run -n ci snakemake, cores 4, use-conda`.

**`MissingInputException`.** It looks like this:

```text
MissingInputException in line 20 of /home/runner/work/your-repo/your-repo/Snakefile.
Missing input files for rule seqkit_stats:
  data/sample_R1.fastq
```

Three usual causes: the file exists on your machine but is excluded by `.gitignore` and never committed, the path casing differs (`Data/` versus `data/`, which matters because runners run Linux), or the workflow runs from a different working directory than you assumed. Commit the fixture, fix the case, and run from the repository root.

**`LibMambaUnsatisfiableError: Encountered problems while solving prologue`.** The environment specification cannot be solved. Over-pinning is the usual culprit, such as forcing `python=3.12` alongside a tool that requires an older Python. Pin major versions, keep the Bioconda channel order (`conda-forge`, `bioconda`, `nodefaults`), and pre-flight the solve locally with `micromamba create -f envs/main.yml, dry-run` before pushing.

**`OSError: [Errno 28] No space left on device`.** The 14 GB working disk filled up. Trim the runner's preinstalled software, keep test data small, and avoid caching large references:

```bash
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc
```

Splitting one oversized job into several smaller jobs also helps, because each runner starts with a clean disk.

**`IncompleteFilesException`.** After a job is canceled or hits the timeout, Snakemake leaves metadata marking outputs as incomplete and tells you to use `-rerun-incomplete`. Add that flag to scheduled and resumable workflows, or delete `.snakemake/incomplete` in a cleanup step.

**`ChildIOException: File/directory is a child of another input/output`.** An output sits inside an input directory, for example writing `results/` inside `data/`. Keep inputs and outputs in disjoint trees and the error disappears by construction.

**YAML parse errors in the workflow file.** Messages like `while scanning a block mapping` with `could not find expected ':'` almost always mean tabs crept into the YAML. YAML forbids tabs for indentation. Convert them to spaces and run `actionlint` before the next push.

## Nextflow vs Snakemake for GitHub Actions

Both Snakemake and Nextflow are mature bioinformatics workflow managers, and both run comfortably on Actions. The differences that matter in CI are these:

- **Language.** Snakemake rules are written in Python-flavored syntax, so Python-native teams feel at home. Nextflow uses a Groovy-based DSL, which suits JVM-comfortable teams.
- **Ecosystem.** Nextflow has the large nf-core collection of community pipelines, many of which ship ready-made test profiles and CI setups. Snakemake counters with its wrapper repository and a strong culture of per-rule conda environments.
- **Resume semantics.** Nextflow's `-resume` is a signature feature backed by its task cache. Snakemake achieves similar ends through metadata-driven reruns and `-rerun-incomplete`.
- **CI shape.** A Nextflow smoke test is typically `nextflow run . -profile test`. A Snakemake smoke test is the lint, dry-run, and `-use-conda` sequence shown in this article. The surrounding scaffold (path filters, caching, matrices, artifacts) is identical for either manager.

Choose based on your team's language and the pipeline ecosystem you are joining. On Actions specifically, the decisive factors are the same for both: pin versions, cache environments, and keep the CI dataset small.

## Common Mistakes and Limitations

1. **Treating green checks as scientific validation.** CI smoke tests confirm plumbing: the workflow parses, environments solve, and code runs on synthetic data. They say nothing about whether the analysis is biologically sound on real data, and individual analyses still need expert judgment.
2. **Running production-scale data on hosted runners.** With 4 vCPUs, 16 GB of RAM, 14 GB of disk, and a 6-hour job cap per GitHub's documentation, hosted runners are for smoke tests and small jobs. Alignments of whole genomes belong on a cluster or a cloud backend.
3. **Ignoring minutes accounting on private repositories.** Public repositories get standard usage free. Private repositories consume the plan's monthly allowance, 2,000 minutes per month on the Free plan for Linux runners, and conda-heavy runs burn through allowances quickly without caching.
4. **Floating dependencies.** An unpinned `snakemake` or unpinned tool versions mean CI silently changes under you. Pin majors, record `snakemake, version` in the log, and let the version matrix catch upgrade breakage on your schedule.
5. **Case-sensitive path drift.** A pipeline developed on macOS or Windows with casually cased filenames will fail on the Linux runner. Fix casing at the source, not with symlinks in CI.
6. **Caching the wrong directory.** Restoring the entire `.snakemake/` tree imports stale locks and incomplete markers. Cache only the package directory and the conda prefix.
7. **Secrets hygiene.** Never embed tokens in a Snakefile or an environment file. Pass them through the `secrets` context as environment variables, and remember that pull requests from forks do not receive secrets by default, per GitHub's documentation.
8. **Racing runs.** Two simultaneous pushes can interleave cache writes. The concurrency group in the template prevents this. Omit it and eventually you will chase a phantom failure.
9. **Assuming Actions replaces a scheduler.** For real compute, use self-hosted runners with a Snakemake profile that submits to SLURM or your site's scheduler, as shown above.
10. **Unprotected flaky downloads.** Conda CDN hiccups and remote fetches fail transiently. Caching absorbs most of this. For scheduled runs that fetch live databases, add a retry step rather than treating one failed nightly as an outage.

## Frequently Asked Questions

### Can Snakemake run on GitHub-hosted runners without installing conda first?

Yes. Snakemake installs with pip or micromamba, and pipelines that declare no rule-level conda environments need nothing else. Reserve the conda-free setup for linting and dry runs, because it does not isolate tool dependencies.

### How do I cache conda environments between CI runs?

Point `-conda-prefix` at a directory outside the workspace and cache it with `actions/cache`, keyed on `hashFiles` of your environment files. Snakemake then reuses solved environments instead of rebuilding them, which removes minutes from every run.

### Why does my pipeline pass locally but fail in GitHub Actions?

Usually case-sensitive paths, files that exist on your machine but are not committed, or tools installed only locally. Clone the repository fresh and run a dry run from the top level to reproduce exactly what the runner sees.

### How long can a Snakemake job run on GitHub Actions?

Six hours per job on GitHub-hosted runners, per GitHub's documentation. Split the work across jobs, resume interrupted runs with `-rerun-incomplete`, or move to self-hosted runners for longer analyses.

### Is GitHub Actions free for public bioinformatics repositories?

Yes. Standard usage on public repositories is free, including the standard Linux runners. Private repositories draw from a monthly minutes allowance that depends on the plan.

### How do I trigger the pipeline only when relevant files change?

Add a `paths` filter under the `push` or `pull_request` trigger listing the Snakefile, rules, scripts, environments, and the workflow file itself. Commits that touch only documentation then skip the run entirely.

### Can GitHub Actions replace an HPC cluster for Snakemake?

Not for production-scale data. Hosted runners suit smoke tests, linting, and small jobs, while self-hosted runners let Actions trigger Snakemake jobs that submit to SLURM through a profile on your own cluster.

### How does running Nextflow on GitHub Actions compare to Snakemake?

Both run well on Actions. Nextflow starts with a single run command and manages its own task cache, while Snakemake offers finer rule-level control and tight conda integration. Version pinning and environment caching matter more than the choice between the two.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Can Snakemake run on GitHub-hosted runners without installing conda first?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes. Snakemake installs with pip or micromamba, and pipelines that declare no rule-level conda environments need nothing else. Reserve the conda-free setup for linting and dry runs, because it does not isolate tool dependencies."
      }
    },
    {
      "@type": "Question",
      "name": "How do I cache conda environments between CI runs?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Cache the directory that holds solved environments and the package cache, keyed on a hash of your environment files. Snakemake then reuses solved environments instead of rebuilding them, which removes minutes from every run."
      }
    },
    {
      "@type": "Question",
      "name": "Why does my pipeline pass locally but fail in GitHub Actions?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Usually case-sensitive paths, files that exist on your machine but are not committed, or tools installed only locally. Clone the repository fresh and run a dry run from the top level to reproduce exactly what the runner sees."
      }
    },
    {
      "@type": "Question",
      "name": "How long can a Snakemake job run on GitHub Actions?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Six hours per job on GitHub-hosted runners, per GitHub's documentation. Split the work across jobs, resume interrupted runs, or move to self-hosted runners for longer analyses."
      }
    },
    {
      "@type": "Question",
      "name": "Is GitHub Actions free for public bioinformatics repositories?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes. Standard usage on public repositories is free, including the standard Linux runners. Private repositories draw from a monthly minutes allowance that depends on the plan."
      }
    },
    {
      "@type": "Question",
      "name": "How do I trigger the pipeline only when relevant files change?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Add a paths filter under the push or pull_request trigger listing the Snakefile, rules, scripts, environments, and the workflow file itself. Commits that touch only documentation then skip the run entirely."
      }
    },
    {
      "@type": "Question",
      "name": "Can GitHub Actions replace an HPC cluster for Snakemake?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Not for production-scale data. Hosted runners suit smoke tests, linting, and small jobs, while self-hosted runners let Actions trigger Snakemake jobs that submit to SLURM through a profile on your own cluster."
      }
    },
    {
      "@type": "Question",
      "name": "How does running Nextflow on GitHub Actions compare to Snakemake?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Both run well on Actions. Nextflow starts with a single run command and manages its own task cache, while Snakemake offers finer rule-level control and tight conda integration. Version pinning and environment caching matter more than the choice between the two."
      }
    }
  ]
}
</script>