Use this Skill to make a research project fully reproducible: conda/renv environment locking, DVC data versioning, Dockerfile for analysis, and FAIR data principles.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
The command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Showing SKILL.md
SKILL.md
Source instructions · Read-only preview
name
reproducible-research
description
Use this Skill to make a research project fully reproducible: conda/renv environment locking, DVC data versioning, Dockerfile for analysis, and FAIR data principles.
TL;DR — Make a research project fully reproducible end-to-end: lock Python
environments with conda, version large data files with DVC, containerize the
analysis with Docker, protect secrets with .env files, and satisfy FAIR
data principles for long-term reusability.
When to Use This Skill
Use this Skill when you need to:
Share analysis code with collaborators or reviewers who must reproduce your results
Submit to a journal that requires data and code availability
Set up a continuous integration (CI) pipeline that re-runs your analysis
Archive a project at the time of paper submission
Comply with funder open-science mandates (NIH, Wellcome Trust, Horizon Europe)
Task
Tool
Environment locking (Python)
conda + environment.yml
Environment locking (R)
renv + renv.lock
Large file / data versioning
DVC
Containerized execution
Docker
Secrets management
.env file (gitignored)
FAIR compliance
Metadata, DOI, open license
Background & Key Concepts
The Reproducibility Crisis
A 2016 Nature survey found that > 70% of researchers failed to reproduce another
scientist's results. The leading causes are:
Missing or undocumented software dependencies
Unavailable or unversioned raw data
Undisclosed analysis deviations
Platform-specific code that fails on different OS/hardware
FAIR Principles
Letter
Meaning
Practical action
Findable
Persistent identifier, rich metadata
Assign a DOI via Zenodo
Accessible
Open protocol, authentication if needed
Host on GitHub + Zenodo
Interoperable
Standard formats, vocabularies
Use CSV/JSON/HDF5, Dublin Core metadata
Reusable
Clear license, provenance documented
Add LICENSE file, README with methods
Conda vs pip vs Docker
conda: best for scientific Python stacks with compiled dependencies (NumPy, CUDA)
pip + venv: lighter, good for pure-Python projects
Docker: full OS-level isolation; best for exact reproducibility across machines
The recommended stack: conda for local dev, Docker for CI and archival.
Environment Setup
# Install conda (if not already installed)# https://docs.conda.io/en/latest/miniconda.html# Install DVC
pip install "dvc[s3,gs,azure,ssh]>=3.0"# Install Docker Desktop (see https://www.docker.com/products/docker-desktop)
docker --version # verify# Clone your project (or init a new one)
git init my-project
cd my-project
dvc init
git add .dvc .gitignore
git commit -m "Initialize DVC"
Core Workflow
Step 1 — Conda Environment Locking
Create a precise environment.yml with pinned versions, then generate a
fully-resolved lock file for bit-for-bit reproducibility.
# Create the project environment
conda create -n myproject python=3.11 -y
conda activate myproject
# Install packages
conda install -c conda-forge pandas=2.0.3 matplotlib=3.8.0 scikit-learn=1.3.2 -y
pip install dvc>=3.0 pyyaml>=6.0
# Export the human-readable spec
conda envexport --no-builds > environment.yml
# Export a fully-pinned cross-platform lock file (requires conda-lock)
pip install conda-lock
conda-lock lock --file environment.yml --platform linux-64 --platform osx-arm64
# Produces: conda-lock.yml# Reproduce the environment from the lock file on any machine
conda-lock install --name myproject conda-lock.yml
DVC tracks large files outside git, runs pipelines reproducibly, and stores
data in a remote storage backend (S3, GCS, SSH, or local).
# dvc_setup.py — programmatic DVC configurationimport subprocess
import os
import yaml
from pathlib import Path
definit_dvc_project(project_root: str = ".") -> None:
"""
Initialize a DVC project and configure a local remote.
Args:
project_root: Path to the project root directory.
"""
root = Path(project_root)
# Initialize DVC (idempotent)
subprocess.run(["dvc", "init"], cwd=root, check=True)
# Configure a local remote (change to S3/GCS URI in production)
remote_path = root / ".dvc_remote"
remote_path.mkdir(exist_ok=True)
subprocess.run(
["dvc", "remote", "add", "-d", "local_remote", str(remote_path)],
cwd=root, check=True,
)
print(f"DVC initialized with local remote at {remote_path}")
deftrack_data_file(file_path: str) -> None:
"""
Add a data file to DVC tracking.
The file is replaced by a small .dvc pointer file that is committed to git.
Args:
file_path: Relative path to the data file inside the project.
"""
subprocess.run(["dvc", "add", file_path], check=True)
dvc_file = file_path + ".dvc"
subprocess.run(["git", "add", dvc_file, ".gitignore"], check=True)
print(f"Tracked {file_path} with DVC. Commit {dvc_file} to git.")
defcreate_dvc_pipeline(stages: list[dict], output_path: str = "dvc.yaml") -> None:
"""
Write a DVC pipeline YAML file from a list of stage definitions.
Args:
stages: List of dicts, each with keys:
name (str), cmd (str), deps (list), outs (list),
params (list, optional).
output_path: Path to write dvc.yaml.
Example stage:
{
"name": "preprocess",
"cmd": "python src/preprocess.py",
"deps": ["src/preprocess.py", "data/raw.csv"],
"outs": ["data/processed.csv"],
"params": ["params.yaml:preprocessing"],
}
"""
pipeline: dict = {"stages": {}}
for stage in stages:
entry: dict = {"cmd": stage["cmd"], "deps": stage.get("deps", []),
"outs": stage.get("outs", [])}
if"params"in stage:
entry["params"] = stage["params"]
pipeline["stages"][stage["name"]] = entry
withopen(output_path, "w") as f:
yaml.dump(pipeline, f, default_flow_style=False, sort_keys=False)
print(f"DVC pipeline written to {output_path}")
# ── Example usage ─────────────────────────────────────────────────────────────if __name__ == "__main__":
stages = [
{
"name": "preprocess",
"cmd": "python src/preprocess.py",
"deps": ["src/preprocess.py", "data/raw.csv"],
"outs": ["data/processed.csv"],
"params": ["params.yaml:preprocessing"],
},
{
"name": "train",
"cmd": "python src/train.py",
"deps": ["src/train.py", "data/processed.csv"],
"outs": ["models/model.pkl"],
"params": ["params.yaml:training"],
},
{
"name": "evaluate",
"cmd": "python src/evaluate.py",
"deps": ["src/evaluate.py", "models/model.pkl", "data/processed.csv"],
"outs": ["reports/metrics.json"],
},
]
create_dvc_pipeline(stages)
# Then run: dvc repro (executes only changed stages)# Or: dvc dag (visualize the pipeline DAG)
Step 3 — Dockerfile for Reproducible Analysis
# Dockerfile — place in project root
# Build: docker build -t myproject:1.0 .
# Run: docker run --rm -v $(pwd)/data:/app/data myproject:1.0
FROM jupyter/scipy-notebook:python-3.11
# Switch to root to install system dependencies
USER root
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
curl \
&& rm -rf /var/lib/apt/lists/*
# Back to the default non-root user
USER ${NB_UID}
# Copy and install Python dependencies
COPY --chown=${NB_UID}:${NB_GID} environment.yml /tmp/environment.yml
RUN conda env update --name base --file /tmp/environment.yml && \
conda clean --all -f -y
# Install DVC
RUN pip install --no-cache-dir "dvc[s3]>=3.0"
# Copy project source code (not data — mount data volume at runtime)
WORKDIR /app
COPY --chown=${NB_UID}:${NB_GID} src/ /app/src/
COPY --chown=${NB_UID}:${NB_GID} params.yaml /app/params.yaml
COPY --chown=${NB_UID}:${NB_GID} dvc.yaml /app/dvc.yaml
# Default command: reproduce the DVC pipeline
CMD ["dvc", "repro"]
Build and run helpers:
# Build the image
docker build -t myproject:1.0 .
# Run the full analysis pipeline
docker run --rm \
-v "$(pwd)/data:/app/data" \
-v "$(pwd)/models:/app/models" \
-v "$(pwd)/reports:/app/reports" \
myproject:1.0
# Interactive shell for debugging
docker run --rm -it \
-v "$(pwd)/data:/app/data" \
myproject:1.0 /bin/bash
Advanced Usage
.env File Pattern for Secrets
Never commit API keys or passwords to git. Use a .env file that is listed
in .gitignore, and load it with python-dotenv or os.getenv.
# src/config.pyimport os
from pathlib import Path
# Load .env file if it exists (development convenience)try:
from dotenv import load_dotenv
load_dotenv(Path(__file__).parent.parent / ".env")
except ImportError:
pass# In Docker, variables are injected via --env-file or -e flags# export ZENODO_TOKEN="<paste-your-key>"
ZENODO_TOKEN: str = os.getenv("ZENODO_TOKEN", "")
# export S3_BUCKET="<your-bucket-name>"
S3_BUCKET: str = os.getenv("S3_BUCKET", "")
# export OPENAI_API_KEY="<paste-your-key>"
OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY", "")
defrequire_env(name: str) -> str:
"""Raise a clear error if a required environment variable is missing."""
val = os.getenv(name)
ifnot val:
raise EnvironmentError(
f"Required environment variable '{name}' is not set.\n"f"Add it to your .env file: {name}=<value>"
)
return val
# In R console — one-time setup# install.packages("renv")# renv::init() # creates renv.lock, .Rprofile, renv/# After installing packages# renv::snapshot() # update renv.lock with current package state# On a new machine or fresh clone:# renv::restore() # install exact package versions from renv.lock
ORCID Integration for Author Disambiguation
Add ORCID identifiers to your README and metadata files:
defgenerate_citation_cff(authors: list[dict], title: str, doi: str) -> str:
"""
Generate a CITATION.cff file content for GitHub citation metadata.
Args:
authors: List of dicts with keys: name (str), orcid (str, optional).
title: Repository/paper title.
doi: DOI of the archived version.
Returns:
CITATION.cff file content as a string.
"""import yaml
cff = {
"cff-version": "1.2.0",
"message": "If you use this software, please cite it using the metadata below.",
"title": title,
"doi": doi,
"authors": [
{"name": a["name"], **({"orcid": f"https://orcid.org/{a['orcid']}"} if"orcid"in a else {})}
for a in authors
],
"license": "MIT",
"date-released": "2026-03-17",
}
return yaml.dump(cff, default_flow_style=False, allow_unicode=True)