# How to Convert a Conda Environment to Docker

By the end of this guide you will be able to take a working conda environment, export it to a definition file, bake it into a Docker image, run that image as a container, verify that the tools inside behave exactly as they do on your host, and share the image through a registry or as a single tarball. You will also see how to hand the finished image to a bioinformatics workflow manager such as Nextflow, Snakemake, or CWL.

This guide walks through how to convert a conda environment to Docker step by step, then covers tips for smaller images, the mistakes that produce most build failures, and the checks that confirm the conversion worked.

You need four things on hand:

- Docker. Docker Desktop on macOS or Windows, Docker Engine on Linux.
- A conda-family installer on the machine where you built the environment: Miniconda, Miniforge, mamba, or micromamba.
- The environment itself, either still installed or already exported to an `environment.yml` file.
- A few gigabytes of free disk space and a network connection for the first build. A Docker Hub account only if you plan to push the image.

The payoff is portability. Container virtualization technologies such as Docker are popular in bioinformatics because they improve the portability and reproducibility of software deployment [1]. A conda environment on your laptop depends on your operating system, your CPU architecture, and the state of the package channels at the moment you solved it. A Docker image wraps the operating system libraries, the conda installation, and the solved environment into one artifact that runs unchanged on a server, in continuous integration, or on a cloud instance.

## Why containerize a conda environment

Conda solves the hard part of dependency management. It resolves package versions, pulls compiled libraries, and isolates everything from the system Python. What it does not capture is everything above the kernel: the C library versions, the system tools, the architecture, and the channel state at solve time. Move the same `environment.yml` to a different Linux release a year later and the solve can drift.

Docker closes that gap. A Dockerfile is a build recipe. Each instruction in it records a layer, a change set stacked on the previous one. The finished stack of layers is an image, a read-only template. A container is a running instance of that image. A registry is a server that stores images so other people and machines can pull them by name and tag.

Containers also pair naturally with standardized workflow descriptors such as the Common Workflow Language (CWL), which let the same analysis run across multiple computing environments [1]. That combination is what makes a converted environment useful beyond your own workstation: collaborators can pull it, clusters can run it through Apptainer, and workflow engines can schedule it anywhere.

Convert when any of the following is true. You want to hand a pipeline to a collaborator without a setup call. You want the exact same tool versions on a cluster and in continuous integration. You plan to run on cloud instances and need predictable behavior. A journal or repository asks for a reproducible software artifact.

## What you need before you start

Confirm Docker works and the daemon is reachable:

```bash
docker version
```

This prints two sections, Client and Server. If you see only Client, the daemon is not running. Start Docker Desktop, or on Linux run `sudo systemctl start docker`.

Confirm conda is available:

```bash
conda -V
```

Expected output looks like:

```
conda 24.1.2
```

Any conda version from the last few years handles everything in this guide. If you use mamba or micromamba instead, the export commands in Step 1 work the same way.

## Convert a conda environment to Docker step by step

The walkthrough uses a small example environment with `samtools`, `bwa`, `cutadapt`, and one pip package. Substitute your own packages throughout.

### Step 1: Export the environment to environment.yml

Activate the environment and export it:

```bash
conda activate myenv
conda env export > environment.yml
head -n 7 environment.yml
```

Expected output:

```yaml
name: myenv
channels:
  - conda-forge
  - bioconda
  - defaults
dependencies:
  - _libgcc_mutex=0.1=conda_forge
```

Below that comes one line per package, each ending in a build string, then a `pip:` subsection if you installed anything with pip, then a final `prefix:` line pointing at your local install path.

Three parts of this file matter for the conversion:

- **Channels** are the servers conda searches, listed in priority order. `conda-forge` and `bioconda` cover most bioinformatics software.
- **Build strings** are the hashed suffixes after each version. They pin the exact compiled variant. They are also platform specific, which matters in Step 2.
- **The prefix line** records your local path. Delete it. It has no meaning inside an image.

For the Docker build, ship a cleaned version of the file:

```yaml
name: myenv
channels:
  - conda-forge
  - bioconda
  - nodefaults
dependencies:
  - python=3.10
  - samtools=1.17
  - bwa=0.7.17
  - cutadapt=4.6
  - pip
  - pip:
      - multiqc==1.21
```

What changed and why:

- `nodefaults` excludes the defaults channel. That channel carries commercial-use terms for larger organizations, while conda-forge and bioconda are community channels. Check your own situation before relying on defaults in a distributed image.
- Versions stay pinned so rebuilds produce the same tools.
- Build strings are dropped. If you exported on macOS, the build strings describe macOS binaries and a Linux build will fail. Without build strings, conda picks the right variant for the image architecture at build time.
- The `pip` subsection stays. If your environment used pip packages and you omit this block, those imports vanish inside the container.

If you exported on Linux and want maximum strictness, keep the build strings. If you want a shorter file that lists only what you explicitly installed, run `conda env export -h` and use the option that exports from your install history, which drops build strings automatically.

### Step 2: Write the Dockerfile

Create a file named `Dockerfile` in the same directory as `environment.yml`:

```dockerfile
# Start from an image that already has Miniconda on Debian
FROM continuumio/miniconda3:latest

# Set the working directory for build steps and for the container
WORKDIR /app

# Copy the environment definition first, so this layer
# only rebuilds when the file changes
COPY environment.yml .

# Create the environment, then delete caches in the same layer
RUN conda env create -f environment.yml \
    && conda clean -afy

# Put the environment's bin directory first on PATH
ENV PATH="/opt/conda/envs/myenv/bin:${PATH}"

# Default command when the container starts
CMD ["bash"]
```

Each instruction, and the parameter that matters in it:

- `FROM` selects the base image. `continuumio/miniconda3` ships Miniconda on a Debian base, so `conda env create` works out of the box. The `latest` tag is fine for a first build. Pin a specific tag, or the base image digest, once the image is stable.
- `WORKDIR /app` sets the directory used by later instructions and by the container at run time.
- `COPY environment.yml .` places the definition into the image. Order matters. Docker re-runs an instruction only when its inputs change, so putting the environment file before your code means code edits do not trigger a full conda solve.
- `RUN conda env create -f environment.yml` builds the environment under `/opt/conda/envs/myenv`, the name coming from the `name:` field. The `&& conda clean -afy` on the same line deletes package tarballs and caches inside the same layer, so they never end up stored in the image.
- `ENV PATH` puts the environment's `bin` directory first. This is the line that lets you type `samtools` instead of a full path, and it is why you never need `conda activate` inside the image.
- `CMD ["bash"]` sets the default command. Anything you pass after the image name at run time replaces it.

### Step 3: Build the image

From the directory containing both files:

```bash
docker build -t mytools:1.0 .
```

Expected output (BuildKit format, times will differ):

```
[+] Building 84.3s (10/10) FINISHED
 => [1/4] FROM docker.io/continuumio/miniconda3:latest
 => [2/4] WORKDIR /app
 => [3/4] COPY environment.yml .
 => [4/4] RUN conda env create -f environment.yml && conda clean -afy
 => exporting to image
 => => naming to docker.io/library/mytools:1.0
```

Parameter notes:

- `-t mytools:1.0` sets the image name and tag. The tag is your version handle. Bump it every time `environment.yml` changes so old results can always be traced to an old image.
- The trailing `.` is the build context, the directory Docker may copy from. Run the command from the directory that holds the Dockerfile.
- The first build takes minutes because conda solves and downloads everything. The second build is fast because the unchanged `environment.yml` layer comes from cache.

### Step 4: Run and test the container

Open a shell inside the image:

```bash
docker run -it mytools:1.0 bash
```

`-it` allocates an interactive terminal. Inside, probe the tools:

```bash
samtools
bwa
python -V
conda list -n myenv | head -n 5
exit
```

`samtools` and `bwa` print their banners when run with no arguments, which makes them convenient version probes:

```
Program: samtools (Tools for alignments in SAM format)
Version: 1.17 (using htslib 1.17)
```

```
Program: bwa (alignment via Burrows-Wheeler transformation)
Version: 0.7.17-r1188
```

`python -V` prints the interpreter version, and `conda list -n myenv` prints the package table for the environment inside the container.

Now run a real job with data from your host. The `-v` flag mounts a host directory into the container:

```bash
docker run -v "$PWD":/data mytools:1.0 \
  bash -c "bwa mem /data/ref.fa /data/reads.fq | samtools sort -o /data/aln.bam"

docker run -v "$PWD":/data mytools:1.0 samtools view -c /data/aln.bam
```

The first command aligns reads and sorts them into `/data/aln.bam`, which appears in your current host directory because of the mount. The second prints the mapped record count, for example `10000`. Compare against the same job in your host environment:

```bash
conda activate myenv
samtools view -c aln.bam
```

Matching counts mean the container reproduces your host behavior for this job. Two more checks round out verification:

```bash
docker images
docker history mytools:1.0
```

`docker images` lists the new tag and its size. `docker history` shows each layer and its size, which is where you look when an image feels heavy.

### Step 5: Tag and share the image

To publish through Docker Hub, rename the image with your account name, log in, and push:

```bash
docker tag mytools:1.0 yourdockerid/mytools:1.0
docker login
docker push yourdockerid/mytools:1.0
```

The registry records a digest for every push, and anyone can pull that exact digest later for byte-level reproducibility.

For clusters or collaborators with no registry access, move the image as a tarball:

```bash
docker save -o mytools_1.0.tar mytools:1.0
```

On the target machine:

```bash
docker load -i mytools_1.0.tar
```

## Command quick reference

| Task | Command | Notes |
|--|--|--|
| Export an environment | `conda env export > environment.yml` | Run inside the environment |
| Create an environment from a file | `conda env create -f environment.yml` | Works in a Dockerfile RUN step |
| Build an image | `docker build -t mytools:1.0 .` | `-t` sets name and tag, the dot is the build context |
| Open a shell in the image | `docker run -it mytools:1.0 bash` | `-it` gives an interactive terminal |
| Run one command | `docker run mytools:1.0 samtools` | Text after the image name replaces the default command |
| Mount a host folder | `docker run -v "$PWD":/data mytools:1.0 bash` | Host folder appears at `/data` |
| Run as a host user | `docker run -u 1000:1000 mytools:1.0 bash` | Avoids root-owned output files |
| Rename an image | `docker tag mytools:1.0 yourdockerid/mytools:1.0` | Needed before a push |
| Push to a registry | `docker push yourdockerid/mytools:1.0` | Registry stores a digest per push |
| Export an image to a tarball | `docker save -o mytools_1.0.tar mytools:1.0` | Transfer without a registry |
| Inspect layer sizes | `docker history mytools:1.0` | Finds bloat |
| Clean conda caches | `conda clean -afy` | Run in the same RUN step that creates the environment |

## Tips for smaller images and faster builds

### Use the micromamba base image

The `mambaorg/micromamba` image is small, runs as a non-root user by default, and uses a solver that handles large bioinformatics environments far faster than the classic conda solver:

```dockerfile
FROM mambaorg/micromamba:latest

COPY environment.yml /tmp/environment.yml

RUN micromamba create -y -f /tmp/environment.yml \
    && micromamba clean -afy

ENV PATH="/opt/conda/envs/myenv/bin:${PATH}"

CMD ["bash"]
```

The same `environment.yml` works unchanged.

### Pack the environment into a slim base with conda-pack

If final image size matters more than build simplicity, build the environment once, pack it, and ship the archive into a minimal base:

```bash
conda install -n base -c conda-forge conda-pack -y
conda pack -n myenv -o myenv.tar.gz
```

```dockerfile
FROM debian:bookworm-slim

COPY myenv.tar.gz /tmp/myenv.tar.gz

RUN mkdir /opt/env \
    && tar -xzf /tmp/myenv.tar.gz -C /opt/env \
    && rm /tmp/myenv.tar.gz

ENV PATH="/opt/env/bin:${PATH}"

CMD ["bash"]
```

Run `source /opt/env/bin/activate` once at the start of any job, because conda-pack ships activation scripts that repair absolute paths recorded at pack time. A multi-stage Dockerfile can do the packing and the slim copy in one build.

### Keep layers lean

![Table comparing miniconda3, miniforge3, micromamba and slim plus conda-pack base images by size class, solver, build effort and best use.](/article-images/illustrations/convert-conda-environment-to-docker-base-image-choice.webp)

- Merge related work into one `RUN` line joined with `&&`, and clean caches on that same line. Anything written in a layer stays in the image even if a later layer deletes it.
- Pin the base image tag instead of `latest` so a rebuild six months later starts from the same base.
- Check disk pressure with `docker system df`, and reclaim space with `docker system prune -a` when old images pile up.

| Base image | Size class | Solver | Notes |
|--|--|--|--|
| `continuumio/miniconda3` | Large | conda | Debian base, conda preinstalled, simplest walkthrough |
| `condaforge/miniforge3` | Large | conda | conda-forge is the default channel |
| `mambaorg/micromamba` | Small | micromamba | Fast solves, non-root user by default |
| `debian:bookworm-slim` plus a packed env | Smallest | none at run time | Requires conda-pack, best final size |

## Using the image with a bioinformatics workflow manager

A bioinformatics workflow manager chains individual tools into a pipeline, tracks file dependencies between steps, and restarts from the last successful step after a failure. Nextflow, Snakemake, and CWL are the three you are most likely to meet, and all three can drive your new image.

### Nextflow

Enable Docker in `nextflow.config`:

```groovy
docker.enabled = true
```

Declare the image on a process in `main.nf`:

```groovy
process ALIGN_READS {
    container 'yourdockerid/mytools:1.0'

    input:
    path reads
    path reference

    output:
    path "${reads.simpleName}.bam"

    script:
    """
    bwa mem ${reference} ${reads} | samtools sort -o ${reads.simpleName}.bam
    """
}
```

Run the pipeline:

```bash
nextflow run main.nf -resume
```

You can also pass the image on the command line with `-with-docker` followed by the image name. Nextflow stages input files into its own work directory and mounts it automatically, then runs your script as the container command. That means no conda activation ever happens, and the `ENV PATH` line in your Dockerfile is what makes bare tool names resolve.

### Snakemake

Attach the image to a rule in a `Snakefile`:

```python
rule align_reads:
    input:
        reads="data/reads.fq",
        reference="data/ref.fa"
    output:
        bam="results/reads.bam"
    container:
        "docker://yourdockerid/mytools:1.0"
    shell:
        "bwa mem {input.reference} {input.reads} | samtools sort -o {output.bam}"
```

Invoke Snakemake with its Apptainer integration enabled. Snakemake converts the `docker://` address into a Singularity image file, caches it, and runs the rule inside it. Per-rule container directives let different rules use different images in one pipeline.

### Nextflow vs Snakemake

| Aspect | Nextflow | Snakemake |
|--|--|--|
| Language | Groovy-based DSL | Python-based syntax |
| Container runtimes | Docker and Apptainer/Singularity built in | Apptainer/Singularity for containers, plus native conda environments |
| Reruns | Built-in caching of completed tasks with a resume flag | Reruns only tasks whose inputs or code changed |
| Ecosystem | Large collection of community genomics pipelines, notably nf-core | Common in single-lab pipelines and methods supplements |
| Sweet spot | Cloud and large cluster execution | Local HPC and mixed local or cluster work |

Both managers consume the same image you built, so the choice is about language and infrastructure rather than the conversion itself. Teams writing Groovy or running on cloud batch systems tend toward Nextflow. Teams living in Python or running on a local cluster tend toward Snakemake. Either way, the image is the portable unit.

### CWL and cwltool

CWL describes each tool in a separate YAML document and declares the image:

```yaml
requirements:
  DockerRequirement:
    dockerPull: yourdockerid/mytools:1.0
```

Run it with `cwltool tool.cwl job.yml`. cwltool is the reference implementation of CWL, and it pulls the declared image from the registry the first time the tool runs [1].

## What container metrics tell you about compute

Once your tools run in containers, their resource use becomes measurable in a consistent way across machines. CWL-metrics, a companion tool for cwltool, records runtime metrics of Docker containers together with workflow metadata to analyze workflow resource requirements [1]. In the published demonstration, 7 transcriptome quantification workflows ran on 6 cloud instance types, and the choice of instance type changed both financial cost and execution time [1]. The tool generates a summary of resource requirements per workflow execution, which is exactly the evidence needed to select appropriate cloud instances [1].

The practical version of that result for your own pipeline: run the containerized workflow once on a small representative sample with a metrics collector enabled, read the peak CPU and memory per step, then size your cloud instances or scheduler memory requests from measured peaks instead of guesses.

## Common Mistakes and Limitations

1. **Reconstructing the package list from memory.** The single most common failure. Always export with `conda env export` from the live environment. A hand-typed list drops transitive dependencies and pip packages.

2. **Missing channels.** Without bioconda in the channels block, builds fail with:

   ```
   PackagesNotFoundError: The following packages are not available from current channels:

     - samtools

   Current channels:
     - https://conda.anaconda.org/conda-forge
   ```

   The channel list shows what conda searched, and the missing channel is obvious there. Fix by adding `bioconda` and `conda-forge` to the `channels:` block in priority order.

3. **Exporting build strings on one operating system and building on another.** A macOS export carries macOS build strings, and a Linux build fails with `ResolvePackageNotFound` listing each package together with its build string. Fix by deleting the build strings from the dependencies, or by exporting on Linux, or by using the history-based export option described in Step 1.

4. **Calling `conda activate` in a Dockerfile.** Non-interactive shells are not initialized for activation, and the build fails with:

   ```
   CommandNotFoundError: Your shell has not been properly configured to use 'conda activate'.
   ```

   Fix by setting `ENV PATH` to the environment's `bin` directory, as in the walkthrough, or by invoking tools through `conda run -n myenv <command>` inside RUN steps.

5. **Building for the wrong CPU architecture.** An image built on Apple Silicon defaults to arm64. On an amd64 server the container dies immediately with a message containing `exec format error`. Fix by building through `docker buildx build` with its platform option set to `linux/amd64`, or by building on a continuous integration runner with the target architecture.

6. **Docker not running, or no permission to the socket.** A stopped daemon prints:

   ```
   Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
   ```

   Start Docker Desktop or the docker service. A permissions problem prints `permission denied while trying to connect to the Docker daemon socket`. Fix by adding your user to the docker group with `sudo usermod -aG docker $USER` and logging in again, or run the command with sudo.

7. **Missing files in the build context.** If `environment.yml` is not in the directory where you run the build, the COPY step fails with `COPY failed: file not found in build context or excluded by .dockerignore`. Run the build from the directory that holds both files, and check `.dockerignore` if the file is present but still not copied.

8. **Omitting the pip section.** If your original environment installed packages with pip and the exported file lacks the `pip:` block, imports fail inside the container with `ModuleNotFoundError: No module named 'multiqc'`. Keep the `pip` entry and the `pip:` subsection in `environment.yml`.

9. **Root-owned output files.** Files a container writes into a mounted folder are owned by root on the host. Fix by running the container as your host user with `docker run -u $(id -u):$(id -g)`, or by building the image with a non-root user whose UID matches yours.

10. **Bloated images.** Package tarballs, apt lists, and layer history accumulate. Clean conda caches in the same RUN step that creates the environment, remove package manager lists, merge RUN steps, and inspect the result with `docker history` and `docker system df`.

11. **Building on `latest` forever.** The base image moves under you, and a rebuild a year later is not the image you shipped. Pin the base tag, or reference the base image by digest once the pipeline is stable.

### Limitations to accept

A Docker image is a snapshot, not a living environment. Rebuilds pick up whatever the channels serve at build time unless you pin versions, build strings, and the base image digest. Conda solves drift as channels change, so two builds from the same file months apart can differ in transitive dependencies. Docker on macOS and Windows runs inside a virtual machine, so mounted-folder input and output is slower than on Linux and the container architecture can differ from your host. GPU workloads need the NVIDIA container toolkit and a compatible driver, configured outside the Dockerfile. Every pipeline and dataset differs, so validate the converted image against your own data before you rely on it.

## Next steps

- Add a continuous integration job that builds the image and runs the bare-command probes from Step 4 whenever `environment.yml` changes.
- Adopt semantic tags such as `mytools:1.2.0` and push every build, keeping old tags published.
- Wire the image into Nextflow or Snakemake, profile one run, and size your compute from the measured peaks.
- For shared clusters, pull the same image through Apptainer instead of maintaining a separate environment there.

## Frequently Asked Questions

### Can I convert a conda environment to Docker without an environment.yml file?

Yes, but export one first. On the machine with the environment, activate it and run `conda env export`, then build the image from that file. Rebuilding the package list from memory or shell history is the least reliable path because it drops transitive dependencies.

### Should I use conda, mamba, or micromamba inside the image?

Use micromamba or mamba for large bioinformatics environments because their solver is much faster than the classic conda solver, and plain conda for small ones. The same `environment.yml` works with all three, so you can switch without editing the file.

### How do I run the image on a cluster that does not allow Docker?

Use Apptainer or Singularity, which run on shared systems without root privileges. Apptainer pulls your image straight from the registry with `apptainer pull docker://yourdockerid/mytools:1.0` and runs it with `apptainer exec`.

### Why is my Docker image so large?

Leftover caches and layer history are the usual causes. Clean conda caches in the same RUN step that creates the environment, remove package manager lists, merge RUN steps, and inspect layer sizes with `docker history` to find the heavy ones.

### How do I handle R packages in the conversion?

Treat R packages like any other conda package. Export the environment with the R packages included from conda-forge and the same Dockerfile works unchanged. For RStudio Server, the rocker project provides ready-made images instead.

### Nextflow vs Snakemake: which should I use with my Docker image?

Both handle Docker images well, so choose by language and infrastructure. Nextflow suits cloud-scale genomics with built-in resume, while Snakemake suits Python-oriented labs and mixed local or cluster work.

### Do I still need conda after moving everything into Docker?

Yes, for development. You will keep editing environments on your machine, exporting, and rebuilding the image. Docker packages the result, it does not replace environment creation.

### How do I update the image after adding a package?

Edit `environment.yml`, rebuild with a new tag, test the container with the probes from Step 4, and push. Keep old tags published so older analyses can still pull the exact image they used.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Can I convert a conda environment to Docker without an environment.yml file?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes, but export one first. On the machine with the environment, activate it and run conda env export, then build the image from that file. Rebuilding the package list from memory is the least reliable path because it drops transitive dependencies."
      }
    },
    {
      "@type": "Question",
      "name": "Should I use conda, mamba, or micromamba inside the image?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Use micromamba or mamba for large bioinformatics environments because their solver is much faster than the classic conda solver, and plain conda for small ones. The same environment.yml works with all three."
      }
    },
    {
      "@type": "Question",
      "name": "How do I run the image on a cluster that does not allow Docker?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Use Apptainer or Singularity, which run on shared systems without root privileges. Apptainer can pull your image directly with apptainer pull docker://yourdockerid/mytools:1.0 and run it with apptainer exec."
      }
    },
    {
      "@type": "Question",
      "name": "Why is my Docker image so large?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Leftover caches and layer history are the usual causes. Clean conda caches in the same RUN step that creates the environment, remove package manager lists, and inspect layer sizes with docker history."
      }
    },
    {
      "@type": "Question",
      "name": "How do I handle R packages in the conversion?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Treat R packages like any other conda package. Export the environment with the R packages included from conda-forge and the same Dockerfile works. For RStudio Server, the rocker project provides ready-made images."
      }
    },
    {
      "@type": "Question",
      "name": "Nextflow vs Snakemake: which should I use with my Docker image?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Both handle Docker images well, so choose by language and infrastructure. Nextflow suits cloud-scale genomics with built-in resume, while Snakemake suits Python-oriented labs and mixed local or cluster work."
      }
    },
    {
      "@type": "Question",
      "name": "Do I still need conda after moving everything into Docker?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes, for development. You will keep editing environments, exporting, and rebuilding the image. Docker packages the result, it does not replace environment creation."
      }
    },
    {
      "@type": "Question",
      "name": "How do I update the image after adding a package?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Edit environment.yml, rebuild with a new tag, test the container, and push. Keep old tags published so older analyses can still pull the exact image they used."
      }
    }
  ]
}
</script>

## Sources

1. [Accumulating computational resource usage of genomic data analysis workflow to optimize cloud computing instance selection.](https://pubmed.ncbi.nlm.nih.gov/31222199/)