Conteneuriser une application Shiny avec {shiny2docker} : guide étape par étape

Author : Vincent Guyader
Categories : docker, développement, shiny
Tags :
Date :

Déployer une application Shiny sur différents serveurs ou la partager avec d’autres peut s’avérer complexe en raison des différences de versions de packages R et des exigences système. La conteneurisation offre une solution : en empaquetant votre application Shiny et son environnement dans un conteneur Docker, vous garantissez qu’elle s’exécute de manière cohérente partout où Docker est disponible. Cependant, tous les développeurs Shiny ne maîtrisent pas la création de Dockerfiles à partir de zéro, et tout le monde n’utilise pas le framework {golem}, qui inclut des outils de déploiement nativement. C’est là que le package {shiny2docker} entre en jeu. Dans cet article, nous allons présenter {shiny2docker} – un outil qui automatise la création de Dockerfile pour les applications Shiny – et parcourir un exemple simple d’utilisation pour conteneuriser une application Shiny. Le tutoriel est adapté aux débutants, avec beaucoup de commentaires, mais se veut aussi instructif pour les DevOps souhaitant comprendre le déploiements Shiny.

Pourquoi dockeriser les applications Shiny ?

Reproductibilité et cohérence : Une des principales raisons de conteneuriser votre application Shiny est de capturer un environnement R cohérent. Les conteneurs Docker regroupent le système d’exploitation, l’installation de R, les packages et toutes les dépendances dont votre application a besoin. Cela élimine le classique problème du « ça marche sur ma machine » : si cela fonctionne dans le conteneur, cela fonctionnera sur n’importe quel hôte exécutant Docker. En conteneurisant, vous vous assurez que votre application Shiny utilise les mêmes versions de packages R et bibliothèques système partout où elle est déployée.
Facilité de déploiement : Avec une image Docker de votre application, le déploiement devient simple. Vous (ou votre équipe DevOps) pouvez exécuter l’image sur n’importe quel serveur ou service cloud prenant en charge Docker. Cela peut simplifier la migration du développement à la production ou la montée en charge des instances de votre application Shiny. Plutôt que de configurer manuellement des serveurs avec R et les packages, il suffit d’expédier le conteneur.
Isolation et stabilité : Docker offre une isolation vis-à-vis des autres processus sur le système hôte. Votre application Shiny s’exécute dans son propre environnement sans risquer d’interférences avec d’autres logiciels. Cette isolation contribue à maintenir la stabilité et peut également améliorer la sécurité.
En résumé, la conteneurisation permet aux développeurs Shiny de regrouper tout ce dont l’application a besoin pour fonctionner – code, packages, bibliothèques système – dans une unité portable. Voyons maintenant comment {shiny2docker} simplifie ce processus.

Présentation de {shiny2docker}

{shiny2docker} est un package R conçu pour simplifier le processus de conteneurisation des applications Shiny à l’aide de Docker. Il automatise la génération des fichiers Docker essentiels et gère les dépendances de packages R avec {renv}, ce qui facilite grandement le déploiement reproductible des applications Shiny. Autrement dit, {shiny2docker} va écrire un Dockerfile pour vous, en se basant sur les besoins de votre application Shiny, et même aider à configurer l’intégration continue pour la construction de l’image Docker si nécessaire.
Parmi les fonctionnalités clés de {shiny2docker}, on trouve :

  • Création automatisée de Dockerfile : La fonction principale shiny2docker() inspecte votre application Shiny et génère un Dockerfile adapté. Elle exploite le fichier renv.lock de votre application (s’il existe) pour figer les versions des packages R. Si vous n’utilisez pas encore {renv}, ne vous inquiétez pas : {shiny2docker} peut créer un lockfile (renv.lock) pour vous à la volée (en utilisant attachment::create_renv_for_prod en interne). Cela garantit que tous les packages R nécessaires à votre application sont pris en compte dans l’image Docker.
  • Génération de .dockerignore : Pour alléger votre image Docker, {shiny2docker} crée automatiquement un fichier .dockerignore. Ce fichier liste les motifs de fichiers/dossiers à exclure du contexte de construction (par exemple vos données locales, la documentation, etc.), ce qui peut réduire considérablement les temps de build et la taille de l’image.
  • Gestion des dépendances avec {renv} : En s’intégrant avec {renv}, le package garantit que les versions des packages R à l’intérieur du conteneur correspondent à celles utilisées lors du développement, assurant ainsi la reproductibilité et la cohérence entre différents environnements. Le processus de construction du conteneur utilisera le lockfile pour installer exactement les versions de packages R requises.
  • Intégration CI/CD : Pour les utilisateurs avancés, {shiny2docker} fournit des helpers pour mettre en place des pipelines d’intégration continue. D’un simple appel de fonction, vous pouvez ajouter une configuration GitLab CI ou un workflow GitHub Actions à votre projet. Par exemple, set_gitlab_ci() ajoute un fichier YAML GitLab CI configuré pour construire et pousser votre image Docker vers le registre, et set_github_action() place un workflow GitHub Actions pour construire et déployer votre image sur GitHub Container Registry. C’est un atout pour les ingénieurs DevOps souhaitant automatiser le déploiement des applications Shiny dans une pipeline CI/CD.

En résumé, {shiny2docker} s’occupe du gros du travail de conteneurisation : il écrit les Dockerfile, gère les versions des packages R et aide même à automatiser les builds. Passons maintenant à un exemple concret.

Pour commencer : Installation et configuration

Avant de commencer, assurez-vous d’avoir une version récente de R installée et que Docker est installé et en cours d’exécution sur votre système (vous n’avez pas besoin de Docker pour créer un Dockerfile, mais si vous souhaitez construire/exécuter votre application avec Docker, il vous faut Docker). Vous n’avez pas besoin de connaissances approfondies sur Docker pour suivre ce tutoriel.
Ensuite, installez le package {shiny2docker} depuis CRAN (c’est un package récent, publié pour la première fois en 2025) :

install.packages("shiny2docker")

Cela installera également toutes les dépendances requises. Une fois installé, chargez le package :

library(shiny2docker)

Assurez-vous également d’avoir {shiny} installé (pour développer l’application) et {renv} pour gérer les dépendances.
Enfin, vous devez bien sûr disposer d’une application Shiny que vous souhaitez conteneuriser. Pour ce tutoriel, nous allons créer un exemple simple de toutes pièces.

Exemple : Conteneuriser une application Shiny étape par étape

Passons au processus étape par étape pour conteneuriser une application Shiny simple. Notre application d’exemple sera minimaliste : un histogramme basé sur les célèbres données du geyser Old Faithful – mais le processus s’applique à n’importe quelle application Shiny (qu’elle soit grande ou petite).

Étape 1 : Préparer une application Shiny simple

Tout d’abord, nous avons besoin d’une application Shiny à conteneuriser. Nous allons écrire un fichier app.R de base qui définit une interface utilisateur (UI) et un serveur. Dans un projet réel, vous pourriez avoir des fichiers ui.R et server.R séparés ou une structure d’application plus complexe, mais {shiny2docker} fonctionne avec n’importe quel répertoire d’application Shiny standard.

# app.R -- a simple Shiny app 
# (Make sure this file is in its own directory, e.g., "myapp/app.R")

library(shiny)

# Define UI for the application
ui <- fluidPage(
  titlePanel("Hello Shiny!"),
  sidebarLayout(
    sidebarPanel(
      # Input: Slider for the number of bins in the histogram
      sliderInput("bins", "Number of bins:",
                  min = 1, max = 50, value = 30)
    ),
    mainPanel(
      # Output: Histogram plot
      plotOutput("distPlot")
    )
  )
)

# Define server logic for the histogram
server <- function(input, output) {
  output$distPlot <- renderPlot({
    # Draw the histogram using the 'waiting' column of the faithful dataset
    x <- faithful$waiting  
    bins <- seq(min(x), max(x), length.out = input$bins + 1)
    hist(x, breaks = bins,
         col = "#75AADB", border = "white",
         xlab = "Waiting time to next eruption (mins)",
         main = "Histogram of Old Faithful waiting times")
  })
}

# Combine the UI and server into an app and run it
shinyApp(ui = ui, server = server)

Une brève explication du code ci-dessus : c’est l’exemple classique de Shiny où l’utilisateur peut ajuster le nombre de classes pour un histogramme. Nous utilisons faithful$waiting (le jeu de données du geyser Old Faithful intégré à R) comme données. L’application dispose d’un contrôle par curseur pour le nombre de classes et affiche un histogramme qui se met à jour en fonction du curseur. Si vous enregistrez ce code dans app.R et exécutez shiny::runApp("app.R"), vous devriez voir l’application fonctionner en local.
Maintenant que notre application Shiny est prête dans un répertoire (par exemple, nous avons enregistré app.R dans un dossier nommé myapp/), nous sommes prêts à la conteneuriser avec {shiny2docker}.

Étape 2 : Générer un Dockerfile avec {shiny2docker}

Le package {shiny2docker} va inspecter notre application et produire un Dockerfile (ainsi qu’un fichier .dockerignore). Assurez-vous que votre répertoire de travail est défini sur le dossier contenant l’application Shiny (par exemple, définissez-le sur le répertoire myapp que nous avons créé, soit avec `setwd` ou idéalement, pour les utilisateurs de Rstudio dans un projet Rstudio).
Dans une session R, exécutez ce qui suit :

# In the R console, with working directory set to the Shiny app folder (e.g., "myapp")
library(shiny2docker)

# Generate a Dockerfile for the Shiny app in the current directory
shiny2docker(path = ".")

Après avoir exécuté shiny2docker(path = "."), vous devriez voir des messages dans R indiquant qu’un Dockerfile est en cours de création. Par défaut, cela créera deux fichiers dans votre répertoire d’application :

  • Dockerfile – un fichier texte contenant les instructions pour construire une image pour votre application Shiny.
  • .dockerignore – un fichier texte spécifiant les fichiers à exclure du contexte de build Docker (des entrées courantes peuvent inclure .Rproj.user, renv/library/, etc., que {shiny2docker} remplit automatiquement).

Examinons ce que fait {shiny2docker} lors de la génération du Dockerfile :

  • Il recherche un fichier lockfile renv.lock existant dans le répertoire. S’il en trouve un (ce qui signifie que vous avez déjà utilisé {renv} pour verrouiller les dépendances), il l’utilisera. Sinon, {shiny2docker} créera un lockfile pour vous en analysant,entres autres, vos appels library() afin de déterminer les packages (et versions) nécessaires. Dans notre application simple, la dépendance principale est le package shiny lui-même (ainsi que les packages R par défaut comme datasets). Le lockfile garantit que le conteneur installera la même version de shiny (et des autres packages) que celle utilisée localement.
  • Il écrit un Dockerfile qui utilise généralement une image Docker de base adaptée à Shiny. Dans de nombreux cas, ce sera l’image rocker/geospatial, qui inclut R, Shiny et de nombreux outils préinstallés. L’utilisation d’une image de base préfabriquée nous évite de configurer manuellement R et Shiny dans le Dockerfile. Le Dockerfile commencera donc par FROM une version spécifique de rocker/geospatial (par exemple, rocker/geospatial:4.2.2 si vous utilisez R 4.2.2).
  • Le Dockerfile inclura ensuite des instructions pour installer les bibliothèques système et les packages R. Grâce au fichier renv.lock, {shiny2docker} sait exactement quels packages R (et quelles versions) installer dans le conteneur. Typiquement, le Dockerfile fera quelque chose comme :

    • Installer renv dans le conteneur (pour pouvoir l’utiliser pour restaurer les packages).
    • Copier le fichier renv.lock (et éventuellement vos fichiers de projet R) dans le conteneur.
    • Exécuter renv::restore() pour installer tous les packages R requis aux versions verrouillées.
    • Copier vos fichiers d’application Shiny dans le conteneur (par exemple, app.R ou ui.R/server.R et toutes les ressources de l’application).
    • Définir les permissions de fichiers appropriées et les variables d’environnement pour l’application Shiny (si nécessaire).
  • Enfin, le Dockerfile spécifiera une commande pour lancer l’application Shiny lorsque le conteneur s’exécute. En général, ceci se fait en appelant shiny::runApp() ou en utilisant la commande par défaut de Shiny Server. Par exemple, il pourrait utiliser un entrypoint qui exécute quelque chose comme :

    R -e "shiny::runApp('/srv/shiny-server', port=3838, host='0.0.0.0')"

Pour jeter un œil au Dockerfile généré, ouvrez-le dans un éditeur de texte. Il devrait ressembler à ceci (votre fichier exact peut différer légèrement) :

FROM rocker/geospatial:4.4.2
RUN apt-get update -y && apt-get install -y  make zlib1g-dev git && rm -rf /var/lib/apt/lists/*
RUN mkdir -p /usr/local/lib/R/etc/ /usr/lib/R/etc/
RUN echo "options(renv.config.pak.enabled = FALSE, repos = c(CRAN = 'https://cran.rstudio.com/'), download.file.method = 'libcurl', Ncpus = 4)" | tee /usr/local/lib/R/etc/Rprofile.site | tee /usr/lib/R/etc/Rprofile.site
RUN R -e 'install.packages("remotes")'
RUN R -e 'remotes::install_version("renv", version = "1.0.3")'
COPY renv.lock renv.lock
RUN --mount=type=cache,id=renv-cache,target=/root/.cache/R/renv R -e 'renv::restore()'
WORKDIR /srv/shiny-server/
COPY . /srv/shiny-server/
EXPOSE 3838
CMD R -e 'shiny::runApp("/srv/shiny-server",host="0.0.0.0",port=3838)'

L’exemple ci-dessus est une illustration de ce que le Dockerfile peut contenir. Les points clés sont qu’il utilise l’image de base rocker/geospatial (correspondant à votre version de R), copie le lockfile et l’application, installe les packages via renv::restore() et configure le conteneur pour exécuter Shiny Server afin de servir l’application. Notez que le port 3838 est exposé : c’est le port par défaut sur lequel Shiny Server sert les applications.
À cette étape, sans écrire vous-même la moindre instruction Docker, vous disposez d’un Dockerfile prêt à l’emploi. Passons maintenant à la construction de l’image Docker.

Étape 3 : Construire l’image Docker

Avec le Dockerfile en place, l’étape suivante consiste à construire l’image Docker pour votre application Shiny. Cette étape se fait dans le terminal (et non dans R). Ouvrez un terminal (ou utilisez l’onglet Terminal de RStudio) et placez-vous dans le répertoire contenant le Dockerfile (notre dossier myapp). Puis exécutez :

# Make sure you are in the directory with the Dockerfile
# Replace 'myshinyapp' with the name you want for your Docker image.
docker build -t myshinyapp .

Décomposons cette commande :

  • docker build est la commande pour construire une image Docker.
  • -t myshinyapp assigne le tag « myshinyapp » à l’image (vous pouvez choisir n’importe quel nom ; c’est ce que vous utiliserez pour exécuter le conteneur).
  • . (point) à la fin indique à Docker d’utiliser le répertoire courant comme contexte de build, en cherchant le Dockerfile à cet endroit.

Docker va ensuite exécuter les instructions du Dockerfile. La première fois que vous lancez cette commande, il peut télécharger l’image de base (rocker/geospatial), qui pèse plsu de 1,5 Go. Ensuite, il installera les packages R nécessaires. Ce processus peut prendre un certain temps, surtout si votre application comporte de nombreux packages, mais pour notre application simple (qui ne nécessite que R de base et shiny), cela devrait être assez rapide.
Surveillez la sortie dans le terminal. Vous devriez voir Docker télécharger des couches, installer des packages, etc., et finalement afficher un message indiquant que l’image a été construite et taguée « myshinyapp:latest ». Si quelque chose ne va pas (par exemple, un package qui échoue à s’installer), les logs d’erreur apparaîtront. Dans notre cas, la construction devrait se terminer avec succès.
Astuce : Si vous modifiez votre application ou ajoutez des packages et que vous souhaitez reconstruire, vous devrez exécuter à nouveau docker build. Le cache Docker accélérera les reconstructions (les étapes non modifiées sont mises en cache), mais si vous ajoutez de nouveaux packages R, l’étape renv::restore() installera les nouveaux packages lors de la reconstruction.

Étape 4 : Exécuter l’application Shiny dans un conteneur Docker

Maintenant que l’image est construite, vous êtes prêt à exécuter votre application Shiny dans un conteneur. Exécuter le conteneur lancera un Shiny Server hébergeant votre application. Utilisez la commande docker run suivante :

docker run -d -p 80:3838 --name myshinyapp_container myshinyapp

Voici la signification de chaque partie :

  • docker run est la commande pour démarrer un nouveau conteneur à partir d’une image.
  • -d exécute le conteneur en mode « détaché » (en arrière-plan). Vous pouvez omettre -d si vous souhaitez l’exécuter au premier plan pour voir les logs, mais dans ce cas, vous aurez besoin d’un terminal séparé.
  • -p 80:3838 mappe le port 3838 à l’intérieur du conteneur vers le port 80 sur votre machine locale. C’est essentiel : l’application Shiny est servie sur le port 3838 du conteneur, et cette option expose ce port afin que vous puissiez y accéder via http://localhost:80 dans votre navigateur web.
  • --name myshinyapp_container donne un nom convivial au conteneur (optionnel mais utile pour gérer les conteneurs). Nous avons choisi « myshinyapp_container » ici.
  • myshinyapp à la fin est le nom de l’image à exécuter (celle que nous avons construite et taguée à l’étape 3).

Après exécution, Docker démarrera le conteneur. Vous pouvez vérifier qu’il est en cours d’exécution en exécutant docker ps (qui devrait lister le conteneur en cours). Ouvrez maintenant votre navigateur web et allez à http://localhost:80. Vous devriez voir votre application Shiny d’histogramme s’exécuter, tout comme en local ! 🎉 Ajustez le curseur et l’histogramme se mettra à jour, mais notez qu’il s’exécute maintenant dans un conteneur Docker.
Si vous ne voyez rien ou si vous obtenez une erreur, vérifiez quelques points :

  • Assurez-vous que Docker est effectivement en cours d’exécution et que le conteneur est opérationnel (docker ps devrait l’afficher).
  • Si vous êtes sur un serveur distant, assurez-vous de visiter le bon hôte ou d’avoir effectué la redirection de port nécessaire. L’exemple ci-dessus suppose un développement en local.
  • Consultez les logs du conteneur avec docker logs myshinyapp_container pour voir si l’application Shiny ou le serveur a imprimé des messages d’erreur au démarrage.

À ce stade, vous avez réussi à conteneuriser une application Shiny à l’aide de {shiny2docker}. L’application s’exécute dans un environnement isolé avec toutes ses dépendances gérées par renv et installées dans le conteneur.
Lorsque vous avez terminé, vous pouvez arrêter le conteneur avec docker stop myshinyapp_container et le supprimer avec docker rm myshinyapp_container. L’image Docker « myshinyapp » restera sur votre système (visible avec docker images), et vous pourrez la réexécuter à tout moment ou la pousser vers un registre de conteneurs si vous souhaitez la déployer ailleurs.

Conseils pour un processus de conteneurisation fluide

Conteneuriser les applications est beaucoup plus simple avec {shiny2docker}, mais voici quelques conseils et bonnes pratiques à prendre en compte :

  • Gardez votre répertoire d’application propre : Étant donné que {shiny2docker} empaquettera tout ce qui se trouve dans votre dossier d’application (à l’exception de ce qui est exclu par .dockerignore), assurez-vous de ne pas avoir de gros fichiers inutiles à l’intérieur. Utilisez le .dockerignore généré pour exclure des éléments tels que des jeux de données locaux, des caches ou des paramètres de projet R qui ne sont pas nécessaires dans le conteneur.
  • Utilisez {renv} pour gérer les dépendances : Même si {shiny2docker} peut créer un lockfile pour vous, il est préférable d’utiliser {renv} dans votre projet Shiny dès le début. En utilisant {renv} pendant le développement (en appelant renv::init() et en faisant régulièrement renv::snapshot()), vous suivez explicitement les versions des packages. Cela aide non seulement {shiny2docker}, mais documente également votre environnement pour toute personne collaborant sur votre projet.
  • Testez en local avant CI/CD : Si vous prévoyez de vous intégrer à une pipeline CI/CD (en utilisant les fonctions fournies set_gitlab_ci() ou set_github_action()), testez d’abord la construction et l’exécution de votre image Docker localement (comme nous l’avons fait ci-dessus). Cela permet de détecter les problèmes dès le départ. Une fois que cela fonctionne localement, vous pouvez ajouter en toute confiance la configuration CI et laisser votre pipeline CI construire l’image à chaque fois.
  • Pensez à la sécurité : Lorsque votre application Shiny s’exécute dans Docker, traitez le conteneur comme vous le feriez pour un serveur. Exposer le port 3838 est acceptable pour le développement, mais en production, vous pourriez placer un proxy devant ou utiliser une authentification si nécessaire. De plus, évitez d’inclure des informations sensibles dans l’image. Si votre application nécessite des clés API ou des mots de passe, utilisez des variables d’environnement ou une configuration externe plutôt que de les coder en dur dans l’application ou le Dockerfile.
  • Apprenez les bases de Docker : Bien que {shiny2docker} masque les détails du Dockerfile, avoir une compréhension de base de Docker est bénéfique. Savoir comment construire, exécuter, arrêter des conteneurs et comment fonctionnent les couches Docker vous aidera à dépanner et à optimiser vos applications conteneurisées. Le Dockerfile généré est une excellente ressource pédagogique : lisez-le pour voir comment les packages R sont installés et comment l’application est lancée. Avec le temps, vous pourriez le personnaliser (en utilisant l’objet dockerfiler retourné par shiny2docker() si nécessaire).

Conclusion

La conteneurisation d’une application Shiny peut sembler intimidante au premier abord, surtout si vous n’êtes pas familier avec Docker. Le package {shiny2docker} comble le fossé entre les développeurs Shiny et DevOps, offrant un moyen simple de créer des images Docker pour les applications Shiny sans écrire manuellement de Dockerfiles. Dans notre exemple, nous avons vu comment une application Shiny simple peut être conteneurisée en seulement quelques étapes : générer un Dockerfile, construire l’image et exécuter un conteneur. Le résultat est une application portable qui fonctionne de la même façon partout, ce qui est incroyablement utile pour le déploiement et le partage.
Les développeurs Shiny et les ingénieurs DevOps peuvent tous deux apprécier ce workflow : les développeurs n’ont pas besoin de devenir des experts Docker pour déployer leurs applications, et les professionnels DevOps obtiennent un environnement reproductible défini par du code. Avec la conteneurisation, la montée en charge et la gestion des applications Shiny en production (à l’aide d’outils tels que Kubernetes, ShinyProxy ou des services cloud) deviennent beaucoup plus simples, car chaque application est encapsulée dans sa propre image.
Nous vous encourageons à essayer {shiny2docker} sur l’un de vos propres projets Shiny. Vous gagnerez du temps et éviterez les pièges liés à la configuration de l’environnement. Pour aller plus loin, consultez la documentation et la vignette de {shiny2docker} pour un usage plus avancé, y compris la personnalisation du Dockerfile ou l’intégration des pipelines CI/CD. Bonne conteneurisation !


À propos de l'auteur

Vincent Guyader

Vincent Guyader

Codeur fou, formateur et expert logiciel R


Comments


Also read