In the Nix of Time

A new approach to Shiny development & deployment with Nix and {rix}

Eric Nantz

Statistician / Developer / Podcaster

Eli Lilly & Company

@rpodcast.bsky.social

@rpodcast@podcastindex.social

@eric-nantz

2025-04-11

It’s Never Just Shiny …

  • Additional R packages
  • External services or APIs
  • Other languages (Python, JavaScript)
  • Software dependencies for packages
  • Deployment platforms

Managing Dependencies

Worlds Collide

Bruno Rodriguez

🤔 What is Nix?

Package Manager

Domain-Specific Language

Immutable Installation

Sandboxed Environments

🤔 Again, what is Nix?

Nix - A Package Manager

  • Collection of over 120,000 recipes of apps, utilities, languages, etc.
  • Package repository nixpkgs available on GitHub
    • Ability to pin repository to specific point in time
  • Required system dependencies are automatically installed

Installing Nix

Easy Mode: Determinant Systems Nix Installer

curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | \
sh -s -- install
  • Supported platforms: Linux, Mac OS X, and Windows (via Windows Subsystem for Linux)

Try Before You Buy!

Under the Nix Hood

# which pokemonsay
/nix/store/xq1cs65ic3m0qdpwcjvdgd7yfq8c3ssv-pokemonsay-1.0.0/bin/pokemonsay
# https://github.com/NixOS/nixpkgs/blob/nixos-24.11/pkgs/by-name/po/pokemonsay/package.nix
{
  lib,
  stdenvNoCC,
  fetchFromGitHub,
  fetchpatch,
  cowsay,
  coreutils,
  findutils,
}:

stdenvNoCC.mkDerivation rec {
  pname = "pokemonsay";
  version = "1.0.0";

  src = fetchFromGitHub {
    owner = "HRKings";
    repo = "pokemonsay-newgenerations";
    rev = "v${version}";
    hash = "sha256-IDTAZmOzkUg0kLUM0oWuVbi8EwE4sEpLWrNAtq/he+g=";
  };

  patches = [
    (fetchpatch {
      # https://github.com/HRKings/pokemonsay-newgenerations/pull/5
      name = "word-wrap-fix.patch";
      url = "https://github.com/pbsds/pokemonsay-newgenerations/commit/7056d7ba689479a8e6c14ec000be1dfcd83afeb0.patch";
      hash = "sha256-aqUJkyJDWArLjChxLZ4BbC6XAB53LAqARzTvEAxrFCI=";
    })
  ];

  postPatch = ''
    substituteInPlace pokemonsay.sh \
      --replace-fail \
        'INSTALL_PATH=''${HOME}/.bin/pokemonsay' \
        "" \
      --replace-fail \
        'POKEMON_PATH=''${INSTALL_PATH}/pokemons' \
        'POKEMON_PATH=${placeholder "out"}/share/pokemonsay' \
      --replace-fail \
        '$(find ' \
        '$(${findutils}/bin/find ' \
      --replace-fail \
        '$(basename ' \
        '$(${coreutils}/bin/basename ' \
      --replace-fail \
        'cowsay -f ' \
        '${cowsay}/bin/cowsay -f ' \
      --replace-fail \
        'cowthink -f ' \
        '${cowsay}/bin/cowthink -f '

    substituteInPlace pokemonthink.sh \
      --replace-fail \
        './pokemonsay.sh' \
        "${placeholder "out"}/bin/pokemonsay"
  '';

  installPhase = ''
    mkdir -p $out/{bin,share/pokemonsay}
    cp pokemonsay.sh $out/bin/pokemonsay
    cp pokemonthink.sh $out/bin/pokemonthink
    cp pokemons/*.cow $out/share/pokemonsay
  '';

  doInstallCheck = true;
  installCheckPhase = ''
    (set -x
      test "$($out/bin/pokemonsay --list | wc -l)" -ge 891
    )
  '';

  meta = with lib; {
    description = "Print pokemon in the CLI! An adaptation of the classic cowsay";
    homepage = "https://github.com/HRKings/pokemonsay-newgenerations";
    license = licenses.mit;
    platforms = platforms.all;
    maintainers = with maintainers; [ pbsds ];
  };
}
# nix-store --query --requisites $(which pokemonsay)
/nix/store/m2047a1xwgblgkrnbxz0yilkaqfrbf2b-xgcc-14-20241116-libgcc
/nix/store/nj19yxkqf0iqjqn4x6dbglsvqk5bgsbs-libunistring-1.3
/nix/store/y2xxdhhjy2l5mgpm3d0rw2wxmpd61my4-libidn2-2.3.7
/nix/store/rmy663w9p7xb202rcln4jjzmvivznmz8-glibc-2.40-66
/nix/store/pn0rk0wsmkjazbz380y8xzb99n8sr07g-attr-2.5.2
/nix/store/4izi23kn57b3cw47jddzq14wcv01b5y5-acl-2.3.2
/nix/store/58br4vk3q5akf4g8lx0pqzfhn47k3j8d-bash-5.2p37
/nix/store/ll9dm3r87v4r46a0km0zck4968m60vys-libxcrypt-4.4.38
/nix/store/xijpnhg8mg0g4lahwrxdwylpcq7249gc-zlib-1.3.1
/nix/store/id29wx2vp10d5xi6wzsykd4rb9ssaikx-gcc-14-20241116-libgcc
/nix/store/ik84lbv5jvjm1xxvdl8mhg52ry3xycvm-gcc-14-20241116-lib
/nix/store/rlpanzl29s3gv3wgs5720q04rbgysl8a-gmp-with-cxx-6.3.0
/nix/store/yh6qg1nsi5h2xblcr67030pz58fsaxx3-coreutils-9.6
/nix/store/8v0r6qxs5cps7cgcjp215895wja6k0by-perl-5.40.0
/nix/store/ixskg19qvf8gfwbdlajc4498c1km1jyf-cowsay-3.8.4
/nix/store/xq5f95pp297afc2xjgrmhmf9w631qp7m-findutils-4.10.0
/nix/store/xq1cs65ic3m0qdpwcjvdgd7yfq8c3ssv-pokemonsay-1.0.0

Enter {rix}

  • Project-specific sandbox environments powered by Nix
  • Intuitive functions to create Nix expressions, all within R
  • Access to (nearly) all CRAN and Bioconductor packages
  • Install packages from GitHub
  • Convert renv.lock file to Nix expressions!

rix in Action

library(rix)

rix(
  r_ver = "4.4.3",
  r_pkgs = c("shiny"),
  system_pkgs = c("pokemonsay"),
  git_pkgs = list(
    list(
      package_name = "doltr",
      repo_url = "https://github.com/ecohealthalliance/doltr/",
      commit = "ffb5bc68003e83ebdb9f352654bab2515ca6bf3a"
    )
  ),
  project_path = ".",
  overwrite = TRUE
)
# This file was generated by the {rix} R package v0.15.8 on 2025-04-10
# with following call:
# >rix(r_ver = "4.4.3",
#  > r_pkgs = c("shiny"),
#  > system_pkgs = c("pokemonsay"),
#  > git_pkgs = list(list(package_name = "doltr",
#  > repo_url = "https://github.com/ecohealthalliance/doltr/",
#  > commit = "ffb5bc68003e83ebdb9f352654bab2515ca6bf3a")),
#  > project_path = ".",
#  > overwrite = TRUE)
# It uses the `rstats-on-nix` fork of `nixpkgs` which provides improved
# compatibility with older R versions and R packages for Linux/WSL and
# Apple Silicon computers.
# Report any issues to https://github.com/ropensci/rix
let
 pkgs = import (fetchTarball "https://github.com/rstats-on-nix/nixpkgs/archive/2025-04-07.tar.gz") {};
 
  rpkgs = builtins.attrValues {
    inherit (pkgs.rPackages) 
      shiny;
  };
 
    doltr = (pkgs.rPackages.buildRPackage {
      name = "doltr";
      src = pkgs.fetchgit {
        url = "https://github.com/ecohealthalliance/doltr/";
        rev = "ffb5bc68003e83ebdb9f352654bab2515ca6bf3a";
        sha256 = "sha256-jO+6VsY4Dmk25uk1ytz0qAlH0Vt9gTm34o6ymYW2PLg=";
      };
      propagatedBuildInputs = builtins.attrValues {
        inherit (pkgs.rPackages) 
          blob
          cli
          DBI
          dbplyr
          dbx
          dplyr
          httpuv
          jsonlite
          processx
          ps
          rlang
          RMariaDB
          R_utils
          rstudioapi;
      };
    });
     
  system_packages = builtins.attrValues {
    inherit (pkgs) 
      glibcLocales
      nix
      pokemonsay
      R;
  };
  
  shell = pkgs.mkShell {
    LOCALE_ARCHIVE = if pkgs.system == "x86_64-linux" then "${pkgs.glibcLocales}/lib/locale/locale-archive" else "";
    LANG = "en_US.UTF-8";
   LC_ALL = "en_US.UTF-8";
   LC_TIME = "en_US.UTF-8";
   LC_MONETARY = "en_US.UTF-8";
   LC_PAPER = "en_US.UTF-8";
   LC_MEASUREMENT = "en_US.UTF-8";

    buildInputs = [ doltr rpkgs   system_packages   ];
    
  }; 
in
  {
    inherit pkgs shell;
  }

Realistic Use Case

R Weekly Schedule Administration

  • Rotating schedule of weekly issue curation
  • Communication on Slack
  • Schedule tracked via Google Spreadsheet
  • Real-life happens:
    • Curator may have conflicts and need to switch slots

New Approach: Requirements

  • Centralized source of truth for schedule accessible by multiple interfaces
  • Easy-to-access calender view refreshed regularly online
  • Shiny application with secured login for each curator to decline or propose to switch
  • Automatic notifications to curators on Slack channel after each schedule change

Reflections (Good)

✅ Feels like native environments on host system

✅ One source of truth for dev environment

✅ Reproducible installations across different operating systems (and even architectures)

✅ Intelligent use of caching

Reflections (Caution)

❗️ Immutable file system conflicts with “temp” file creation

❗️ Steep learning curve for Nix language

❗️ HD space could be maxed out on lower-spec systems

❗️ Early days for Nix & Data Science

Eric’s Additional Nix/Rix Explorations

Level-up your Nix Knowledge

Thank You!