From c82e88a80d7914e92220180c2fea397f606f5535 Mon Sep 17 00:00:00 2001 From: Milan Malfait <38256462+milanmlft@users.noreply.github.com> Date: Wed, 28 Aug 2024 15:15:54 +0100 Subject: [PATCH] Add Docker (#44) * Run `golem::add_dockerfile_with_renv()` * Rename files * Clean up base Dockerfile * Clean up main Dockerfile * Remove dev dependencies from `renv.lock.prod` * Update deployment instructions * Update README; add overview and Deployment section --- .Rbuildignore | 1 + README.md | 14 + deploy/Dockerfile | 16 + deploy/Dockerfile.base | 19 + deploy/README.md | 41 + deploy/calypso_0.0.0.9000.tar.gz | Bin 0 -> 16539 bytes deploy/renv.lock.prod | 1214 ++++++++++++++++++++++++++++++ 7 files changed, 1305 insertions(+) create mode 100644 deploy/Dockerfile create mode 100644 deploy/Dockerfile.base create mode 100644 deploy/README.md create mode 100644 deploy/calypso_0.0.0.9000.tar.gz create mode 100644 deploy/renv.lock.prod diff --git a/.Rbuildignore b/.Rbuildignore index 188d009..82378b1 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -11,3 +11,4 @@ $run_dev.* ^\.github$ ^\.lintr$ ^\.renvignore$ +^deploy$ diff --git a/README.md b/README.md index 6e4427c..56c9944 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,16 @@ The goal of calypso is to provide a summary of OMOP data and display it in a public data catalogue +## Overview + +1. [Installation](#installation) +2. [Development](#development) + - [Set up](#set-up) + - [Updating the `renv` lockfile](#updating-the-renv-lockfile) + - [Design](#design) + - [Coding style](#coding-style) +3. [Deployment](./deploy/README.md) + ## Installation You can install the development version of calypso from within R like so: @@ -120,3 +130,7 @@ lintr::lint_package() ``` (or have it [run automatically in your IDE](https://lintr.r-lib.org/articles/editors.html)). + +## Deployment + +See the [deployment docs](./deploy/README.md). diff --git a/deploy/Dockerfile b/deploy/Dockerfile new file mode 100644 index 0000000..a21a28f --- /dev/null +++ b/deploy/Dockerfile @@ -0,0 +1,16 @@ +FROM calypso_base:4.4.1 + +WORKDIR /app +COPY renv.lock.prod renv.lock + +RUN R -e 'renv::restore()' + +COPY calypso_*.tar.gz /app.tar.gz +RUN R -e 'remotes::install_local("/app.tar.gz", upgrade="never")' && \ + rm /app.tar.gz + +EXPOSE 3838 +CMD ["R", "-e", "options('shiny.port'=3838,shiny.host='0.0.0.0');library(calypso);calypso::run_app()"] + +# to build: docker build -t calypso . +# to run: docker run -p 3838:3838 calypso diff --git a/deploy/Dockerfile.base b/deploy/Dockerfile.base new file mode 100644 index 0000000..ab5990c --- /dev/null +++ b/deploy/Dockerfile.base @@ -0,0 +1,19 @@ +FROM rocker/shiny-verse:4.4.1 +SHELL ["/bin/bash", "-o", "pipefail", "-e", "-u", "-x", "-c"] + +RUN mkdir -p /usr/local/lib/R/etc/ /usr/lib/R/etc/ + +# Set R options +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 + +# Install renv +RUN R -e 'install.packages("remotes")' && \ + R -e 'remotes::install_version("renv", version = "1.0.7")' + +# Restore renv environment +RUN mkdir /app +WORKDIR /app +COPY renv.lock.prod renv.lock +RUN R -e 'renv::restore()' diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..288dfef --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,41 @@ +# Deploy + +## Build the package + +From the package root directory, run from `R`: + +```r +pkgbuild::build(path = ".", dest_path = "deploy") +``` + +## Create `renv.lock.prod` + +From the package root directory, run from `R`: + +```r +renv::snapshot(project = ".", lockfile = "./deploy/renv.lock.prod", type = "explicit") +``` + +## Build Docker images + +In the `deploy/` directory, run: + +```shell +# Assuming R version 4.4.1 +docker build -f Dockerfile.base --platform=linux/amd64 -t calypso_base:4.4.1 . +docker build -f Dockerfile --platform=linux/amd64 -t calypso:latest . +``` + +The `calypso_base` image acts as a cached image with most of the necessary dependencies installed, +to speed up the build process of the `calypso` image. The base image is not intended to be run and +is only expected to be rebuilt in case of major dependency changes. + +Note that the `calypso` image also includes a `renv::restore()` step, so any dependencies not present +in the base image will still be installed. + +## Run Docker container + +```shell +docker run -p 3838:3838 calypso:latest +# then go to 127.0.0.1:80 +``` diff --git a/deploy/calypso_0.0.0.9000.tar.gz b/deploy/calypso_0.0.0.9000.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..0e404c38037001cacea0c2e29ec8e53cac6045bc GIT binary patch literal 16539 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