<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Peter Solymos</title>
    <description>The latest articles on Forem by Peter Solymos (@psolymos).</description>
    <link>https://forem.com/psolymos</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F401373%2F00db8a49-7553-4178-bc40-d9c6d65c2ecc.jpg</url>
      <title>Forem: Peter Solymos</title>
      <link>https://forem.com/psolymos</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/psolymos"/>
    <language>en</language>
    <item>
      <title>Manage Dependencies with the deps R Package for Docker Containers</title>
      <dc:creator>Peter Solymos</dc:creator>
      <pubDate>Fri, 11 Nov 2022 13:00:00 +0000</pubDate>
      <link>https://forem.com/analythium/manage-dependencies-with-the-deps-r-package-for-docker-containers-1hg8</link>
      <guid>https://forem.com/analythium/manage-dependencies-with-the-deps-r-package-for-docker-containers-1hg8</guid>
      <description>&lt;p&gt;&lt;em&gt;By: &lt;a href="https://hosting.analythium.io/author/peter/" rel="noopener noreferrer"&gt;Peter Solymos&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The deps package gives you a lightweight option to manage package dependencies and you can install these inside containers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When building Docker images for your R-based applications, the biggest hurdle is knowing exactly which packages and system libraries your package depends on. Luckily, the tools have evolved quite a bit over the past few years. In this post, I show you where the deps package fits in and how this can be a great choice for dependency management for Docker-based workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproducibility
&lt;/h2&gt;

&lt;p&gt;Tools like &lt;a href="https://cran.r-project.org/package=packrat" rel="nofollow noopener noreferrer"&gt;packrat&lt;/a&gt;, &lt;a href="https://cran.r-project.org/package=renv" rel="nofollow noopener noreferrer"&gt;renv&lt;/a&gt;, and &lt;a href="https://github.com/MilesMcBain/capsule" rel="noopener noreferrer"&gt;capsule&lt;/a&gt; let you go to great lengths to make your R projects perfectly reproducible. This requires knowing the exact package versions and the source where it was installed from (CRAN, remotes, local files). This information is registered in a lock file, which serves as the manifest for recreating the exact replica of the environment.&lt;/p&gt;

&lt;p&gt;Full reproducibility is often required for reports, markdown-based documents, and scripts. A loosely defined project that is combined with strict versioning requirements, often erring on the side of “more dependencies are safer”.&lt;/p&gt;

&lt;p&gt;In our previous post we covered how to manage dependencies with the renv package:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fassets%2Ficon-192x192.png%3Fv%3D16e0fd50da" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fassets%2Ficon-192x192.png%3Fv%3D16e0fd50da"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Package-based development
&lt;/h2&gt;

&lt;p&gt;On the other end of the spectrum, we have package-based development. This is the main use case for dependency management-oriented packages, such as &lt;a href="https://cran.r-project.org/package=remotes" rel="nofollow noopener noreferrer"&gt;remotes&lt;/a&gt; and &lt;a href="https://cran.r-project.org/package=pak" rel="nofollow noopener noreferrer"&gt;pak&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this case, exact versions are managed only to the extent of avoiding breaking changes (given that testing can surface these). So what we have is a package-based workflow combined with a “no breaking changes” philosophy to version requirements. This approach often leads to leaner installation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The middle ground
&lt;/h2&gt;

&lt;p&gt;What if we are not writing an R package and wanted to combine the best of both approaches? – A loosely defined project with just strict-enough versioning requirements. All this without having to write a &lt;code&gt;DESCRIPTION&lt;/code&gt; file by hand. Because why would you need a &lt;code&gt;DESCRIPTION&lt;/code&gt; file when you have no package? Also, a &lt;code&gt;DESCRIPTION&lt;/code&gt; file won’t let you pin an exact package version or specify alternative CRAN-like repositories.&lt;/p&gt;

&lt;p&gt;What if you could manage dependencies by decorating your existing R code with special, roxygen-style comments? Just like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight r"&gt;&lt;code&gt;&lt;span class="cd"&gt;#' @remote analythium/rconfig@CRAN-v0.1.3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;rconfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="cd"&gt;#' @repo sf https://r-spatial.r-universe.dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="cd"&gt;#' @ver rgl 0.108.3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rgl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is exactly what deps does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;helps to find all dependencies from our files,&lt;/li&gt;
&lt;li&gt;writes these into a &lt;code&gt;dependencies.json&lt;/code&gt; file,&lt;/li&gt;
&lt;li&gt;performs package installs according to the decorators.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The decorators make our intent explicit, just like if we were writing an R package. But we do not need to manually write these into a file and keep it up-to-date. We can just rerun &lt;code&gt;create&lt;/code&gt; to update the JSON manifest file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tags
&lt;/h2&gt;

&lt;p&gt;There are many different tags that you can use as part of your roxygen-style comments:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tag&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@sys&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;System requirement(s)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@sys req1,req2,...&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@remote&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remote source(s)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@remote remote1,remote2,...&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@local&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Local source(s)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@local path1,path2,...&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@ver&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Versioned package&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@ver pkg version&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@dev&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Development package(s)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@dev pkg1,pkg2,...&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@repo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CRAN-like source&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@repo pkg repo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@repos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Global CRAN-like repo(s)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@repos repo1,repo2,...&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@rver&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;R version&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@rver 4.1.3&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These tags are listed and explained in the package's GitHub repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ffluidicon.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ffluidicon.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the deps package
&lt;/h2&gt;

&lt;p&gt;The deps package has 2 main functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;create()&lt;/code&gt; crawls the project directory for package dependencies. It will amend the dependency list and package sources based on the comments and query system requirements for the packages where those requirements are known for a particular platform; the summary is written into the &lt;code&gt;dependencies.json&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;install()&lt;/code&gt; looks for the &lt;code&gt;dependencies.json&lt;/code&gt; file in the root of the project directory (or runs &lt;code&gt;create()&lt;/code&gt; when the JSON file is not found) and performs dependency installation according to the instructions in the JSON file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the simplest case, one might have a project folder with some R code inside. Running &lt;code&gt;deps::install()&lt;/code&gt; will perform the package installation in one go. Additional arguments can be passed to &lt;code&gt;install()&lt;/code&gt; so that local libraries etc. can be specified.&lt;/p&gt;

&lt;p&gt;These arguments are passed to &lt;code&gt;install.packages()&lt;/code&gt;. This is a really important consideration when it comes to utilizing RSPM or BSPM repositories on Linux systems. &lt;a href="https://cran.r-project.org/package=rspm" rel="noopener noreferrer"&gt;RSPM&lt;/a&gt; (RStudio Package Manager) provides rebuild binaries, &lt;a href="https://cran.r-project.org/package=bspm" rel="noopener noreferrer"&gt;BSPM&lt;/a&gt; (Bridge to System Package Manager) provides full system dependency resolution and integration with &lt;code&gt;apt&lt;/code&gt; on top of binary packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker workflow
&lt;/h2&gt;

&lt;p&gt;The following example is part of the &lt;a href="https://github.com/analythium/deps/tree/main/inst/examples/99-docker" rel="noopener noreferrer"&gt;deps package examples&lt;/a&gt;. We will use a Shiny app that we have used before to draw a &lt;a href="https://hosting.analythium.io/update-existing-shiny-apps-in-shinyproxy/" rel="noopener noreferrer"&gt;3D surface for a bivariate Normal distribution&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2022%2F10%2FScreen-Shot-2022-10-14-at-11.28.57-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2022%2F10%2FScreen-Shot-2022-10-14-at-11.28.57-PM.png" alt="3D surface of a bivariate Normal distribution."&gt;&lt;/a&gt;&lt;/p&gt;
3D surface of a bivariate Normal distribution.



&lt;p&gt;Let's say that we have a single file &lt;code&gt;app/app.R&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight r"&gt;&lt;code&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shiny&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MASS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rgl.useNULL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rgl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fluidPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;titlePanel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Correlated variables"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;sidebarLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;sidebarPanel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;sliderInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sample size"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;sliderInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Correlation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0.05&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;mainPanel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;rglwidgetOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"plot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"500px"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"500px"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Sigma&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reactive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reactive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;mvrnorm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Sigma&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;renderRglwidget&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kde2d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[,&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[,&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;try&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;close3d&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;persp3d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;ann&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;axes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;xlab&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ylab&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zlab&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lightblue"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;rglwidget&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;shinyApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fassets%2Ficon-192x192.png%3Fv%3D16e0fd50da" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fassets%2Ficon-192x192.png%3Fv%3D16e0fd50da"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can start with the following &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; eddelbuettel/r2u:22.04&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;installGithub.r analythium/deps
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; jq

&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--ingroup&lt;/span&gt; app app
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /home/app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; app .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;R &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"deps::create()"&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="si"&gt;$(&lt;/span&gt; jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.sysreqs | join(" ")'&lt;/span&gt; dependencies.json &lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;R &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"deps::install()"&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;app:app &lt;span class="nt"&gt;-R&lt;/span&gt; /home/app
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; app&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["R", "-e", "shiny::runApp(port = 8080, host = '0.0.0.0')"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, we just copy over the contents of the &lt;code&gt;app&lt;/code&gt; folder, &lt;code&gt;create&lt;/code&gt; the &lt;code&gt;dependencies.json&lt;/code&gt; file inside the Docker image. We use the &lt;code&gt;jq&lt;/code&gt; command line utility to parse the JSON file and pull out the system requirements that we want to install. Then &lt;code&gt;install&lt;/code&gt; the R packages.&lt;/p&gt;

&lt;p&gt;We are using the &lt;a href="https://github.com/eddelbuettel/r2u#r2u-cran-as-ubuntu-binaries" rel="noopener noreferrer"&gt;&lt;code&gt;eddelbuettel/r2u:22.04&lt;/code&gt;&lt;/a&gt; parent image, so we do not have to worry too much about the installation times. But copying the files and then performing the installation is not the best approach. This way we cannot use the Docker image layer catching very effectively. When the files change, the cache gets invalidated and we'll have to wait for the install step to complete.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fassets%2Ficon-192x192.png%3Fv%3D16e0fd50da" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fassets%2Ficon-192x192.png%3Fv%3D16e0fd50da"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can do much better if we keep an up-to-date &lt;code&gt;dependencies.json&lt;/code&gt; file as part of the project that we want to containerize. The JSON file has the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.2.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"repos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sysreqs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"libgl1-mesa-dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"libglu1-mesa-dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"libpng-dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pandoc pandoc-citeproc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zlib1g-dev"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"packages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MASS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"installed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rgl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"installed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cran"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shiny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"installed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cran"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;dependencies.json&lt;/p&gt;

&lt;p&gt;This JSON file can be copied over on its own, ensuring that installation happens only when the dependencies are updated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; eddelbuettel/r2u:22.04&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;installGithub.r analythium/deps
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; dependencies.json .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; jq
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="si"&gt;$(&lt;/span&gt; jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.sysreqs | join(" ")'&lt;/span&gt; dependencies.json &lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;R &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"deps::install()"&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--ingroup&lt;/span&gt; app app
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /home/app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; app .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;app:app &lt;span class="nt"&gt;-R&lt;/span&gt; /home/app
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; app&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["R", "-e", "shiny::runApp(port = 8080, host = '0.0.0.0')"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are the steps from this &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pull the r2u parent image&lt;/li&gt;
&lt;li&gt;install the deps package using the &lt;a href="https://dirk.eddelbuettel.com/code/littler.html" rel="noopener noreferrer"&gt;littler&lt;/a&gt; command line utility&lt;/li&gt;
&lt;li&gt;copy the &lt;code&gt;dependencies.json&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;install the &lt;code&gt;jq&lt;/code&gt; tool for manipulating the JSON file&lt;/li&gt;
&lt;li&gt;install system requirements from the &lt;code&gt;"sysreqs"&lt;/code&gt; property of &lt;code&gt;dependencies.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;install R packages listed in &lt;code&gt;dependencies.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;create a user and group called &lt;code&gt;app&lt;/code&gt; to increase the security of the Docker image&lt;/li&gt;
&lt;li&gt;set the home folder of the &lt;code&gt;app&lt;/code&gt; user as the working directory&lt;/li&gt;
&lt;li&gt;copy the contents of the &lt;code&gt;app&lt;/code&gt; folder into the home folder&lt;/li&gt;
&lt;li&gt;set permission and change the user from &lt;code&gt;root&lt;/code&gt; to &lt;code&gt;app&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;expose port 8080&lt;/li&gt;
&lt;li&gt;set the command to start the Shiny app on port 8080&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's build and test the Docker image to see deps in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# change directory to the example&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;inst/examples/99-docker

&lt;span class="c"&gt;# change this as needed if you want to `docker push`&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;analythium/deps-shiny-example:v1

&lt;span class="c"&gt;# build the image&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$TAG&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# start the app&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="nv"&gt;$TAG&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;code&gt;http://localhost:8080&lt;/code&gt; and make sure the app is functioning as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;The deps package helps users be more intentional about the R package source and version requirements using text decorators in comments. This is similar to a package-based workflow without actually writing a package. But deps also lends itself to Dockerized development. It identifies system requirements for the R packages, which is a welcome addition to making the Docker experience for R as user-friendly and hands-off as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hosting.analythium.io/dockerizing-shiny-applications/" rel="noopener noreferrer"&gt;Dockerizing Shiny Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hosting.analythium.io/best-practices-for-r-with-docker/" rel="noopener noreferrer"&gt;Best Practices for R with Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hosting.analythium.io/dockerized-shiny-apps-with-dependencies/" rel="noopener noreferrer"&gt;Dockerized Shiny Apps with Dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/eddelbuettel/r2u#r2u-cran-as-ubuntu-binaries" rel="noopener noreferrer"&gt;r2u: CRAN as Ubuntu Binaries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/analythium/deps" rel="noopener noreferrer"&gt;deps: Dependency Management with roxygen-style Comments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rstats</category>
      <category>shiny</category>
      <category>docker</category>
    </item>
    <item>
      <title>Containerizing Shiny for Python and Shinylive Applications</title>
      <dc:creator>Peter Solymos</dc:creator>
      <pubDate>Thu, 10 Nov 2022 13:00:00 +0000</pubDate>
      <link>https://forem.com/analythium/containerizing-shiny-for-python-and-shinylive-applications-48gl</link>
      <guid>https://forem.com/analythium/containerizing-shiny-for-python-and-shinylive-applications-48gl</guid>
      <description>&lt;p&gt;&lt;em&gt;By: &lt;a href="https://hosting.analythium.io/author/peter/" rel="noopener noreferrer"&gt;Peter Solymos&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shiny for Python brings easy interactivity to web applications that build on the data and scientific stack of Python. Containerizing Py-Shiny apps is the next step towards deployment to a wide array of hosting options, including static hosting.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Shiny is a framework that makes it easy to build interactive web applications. Shiny was introduced 10 years ago as an R package. In his 10th anniversary keynote speech, Joe Cheng announced &lt;a href="https://shiny.rstudio.com/py/" rel="noopener noreferrer"&gt;Shiny for Python&lt;/a&gt; at the 2022 RStudio Conference. Python programmers can now try out Shiny to create interactive data-driven web applications. Shiny comes as an alternative to other frameworks, like &lt;a href="https://plotly.com/dash/" rel="noopener noreferrer"&gt;Dash&lt;/a&gt;, or &lt;a href="https://streamlit.io/" rel="noopener noreferrer"&gt;Streamlit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2022%2F10%2FScreen-Shot-2022-10-04-at-11.43.25-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2022%2F10%2FScreen-Shot-2022-10-04-at-11.43.25-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⚠️&lt;/p&gt;

&lt;p&gt;Shiny for Python is currently in Alpha. It may be unstable, and the API may change. We’re excited to hear your feedback, but please don’t use it for production applications just yet!&lt;/p&gt;



&lt;p&gt;Similarly to R Shiny applications, Shiny for Python can be &lt;a href="https://shiny.rstudio.com/py/docs/deploy.html" rel="nofollow noopener noreferrer"&gt;deployed&lt;/a&gt; using RStudio Connect, Shiny Server Open Source, and Shinyapps.io. Alternative hosting options – that the &lt;em&gt;Hosting Data Apps&lt;/em&gt; website is dedicated to – require the Python Shiny app to run inside a container. In this post, we review how to use Docker to containerize a Shiny for Python app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shiny app template
&lt;/h2&gt;

&lt;p&gt;We follow the &lt;a href="https://shiny.rstudio.com/py/docs/get-started.html" rel="nofollow noopener noreferrer"&gt;Get started&lt;/a&gt; guide (see also the &lt;a href="https://shiny.rstudio.com/py/docs/install.html" rel="noopener noreferrer"&gt;install guide&lt;/a&gt;). You can install Shiny with &lt;code&gt;pip&lt;/code&gt; or &lt;code&gt;conda&lt;/code&gt;. Here we will use &lt;code&gt;pip&lt;/code&gt;. The following commands generate the app file that we will use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;shiny
shiny create app
shiny run &lt;span class="nt"&gt;--reload&lt;/span&gt; app/app.py

&lt;span class="c"&gt;# INFO:     Will watch for changes in these directories: ['/Users/Username/app']&lt;/span&gt;
&lt;span class="c"&gt;# INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)&lt;/span&gt;
&lt;span class="c"&gt;# INFO:     Started reloader process [57404] using StatReload&lt;/span&gt;
&lt;span class="c"&gt;# INFO:     Started server process [57406]&lt;/span&gt;
&lt;span class="c"&gt;# INFO:     Waiting for application startup.&lt;/span&gt;
&lt;span class="c"&gt;# INFO:     Application startup complete.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to &lt;code&gt;http://127.0.0.1:8000&lt;/code&gt; in your browser to try the app displaying a slider and a text output returning double of the slider input value (&lt;code&gt;N=n&lt;/code&gt; of course):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2022%2F10%2FScreen-Shot-2022-10-04-at-11.18.24-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2022%2F10%2FScreen-Shot-2022-10-04-at-11.18.24-PM.png" alt="The simplest Shiny for Python example."&gt;&lt;/a&gt;&lt;/p&gt;
The simplest Shiny for Python example.



&lt;p&gt;Use Ctrl+C to quit the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example app with plot
&lt;/h2&gt;

&lt;p&gt;We will use the &lt;a href="https://shinylive.io/py/examples/#app-with-plot" rel="nofollow noopener noreferrer"&gt;app with plot&lt;/a&gt; example. Open the &lt;code&gt;app/app.py&lt;/code&gt; file in a text editor and copy the following contents into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;shiny&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ui&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;

&lt;span class="n"&gt;app_ui&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;page_fluid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Histogram with Shiny for Python!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;layout_sidebar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;panel_sidebar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;input_slider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;N&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;panel_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output_plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@output&lt;/span&gt;
    &lt;span class="nd"&gt;@render.plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A histogram&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;19680801&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;437&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;density&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the file &lt;code&gt;app/requirements.txt&lt;/code&gt; in the same directory as the &lt;code&gt;app.py&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shiny&amp;gt;=0.2.7
numpy&amp;gt;=1.23.3
matplotlib&amp;gt;=3.6.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now use &lt;code&gt;pip install --no-cache-dir --upgrade -r app/requirements.txt&lt;/code&gt; to install the remaining packages. Then load the app again with &lt;code&gt;shiny run --reload app/app.py&lt;/code&gt; and visit &lt;code&gt;http://127.0.0.1:8000&lt;/code&gt; in your browser again.&lt;/p&gt;

&lt;p&gt;You'll see the new app with a plot that looks very similar to the classical R &lt;a href="https://shiny.rstudio.com/gallery/example-01-hello.html" rel="noopener noreferrer"&gt;hello Shiny app&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2022%2F10%2FScreen-Shot-2022-10-04-at-11.33.26-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2022%2F10%2FScreen-Shot-2022-10-04-at-11.33.26-PM.png" alt="Shiny for Python app with the plot."&gt;&lt;/a&gt;&lt;/p&gt;
Shiny for Python app with the plot.



&lt;h2&gt;
  
  
  The Dockerfile
&lt;/h2&gt;

&lt;p&gt;If you look at the printout after launching the app, you'll notice that Shiny is using &lt;a href="https://www.uvicorn.org/" rel="noopener noreferrer"&gt;Uvicorn&lt;/a&gt; under the hood. This is a common way of containerizing apps using &lt;a href="https://fastapi.tiangolo.com/deployment/docker/" rel="nofollow noopener noreferrer"&gt;FastAPI deployments&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fassets%2Ficon-192x192.png%3Fv%3D123029de7e" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fassets%2Ficon-192x192.png%3Fv%3D123029de7e"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's see what goes into the &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use the &lt;a href="https://hub.docker.com/layers/library/python/3.9/images/sha256-a8bb865d30b5eb878f26d19479fe8ec258efe410a459271476e07eef854e9d66?context=explore" rel="noopener noreferrer"&gt;official &lt;code&gt;python:3.9&lt;/code&gt; parent image&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;create the &lt;code&gt;/home/app&lt;/code&gt; folder and set an &lt;code&gt;app&lt;/code&gt; user with appropriate non-root permissions&lt;/li&gt;
&lt;li&gt;install requirements before copying the app – this is to best utilize caching when still iterating on the app&lt;/li&gt;
&lt;li&gt;copy the rest of the &lt;code&gt;app&lt;/code&gt; folder, i.e. the app itself&lt;/li&gt;
&lt;li&gt;expose the 8080 port and define the &lt;code&gt;uvicorn&lt;/code&gt; command
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.9&lt;/span&gt;

&lt;span class="c"&gt;# Add user an change working directory and user&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--ingroup&lt;/span&gt; app app
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /home/app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;app:app &lt;span class="nt"&gt;-R&lt;/span&gt; /home/app
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; app&lt;/span&gt;

&lt;span class="c"&gt;# Install requirements&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; basic/requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Copy the app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; basic .&lt;/span&gt;

&lt;span class="c"&gt;# Run app on port 8080&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we can build the image. Define your own image name and tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;analythium/python-shiny:0.1

docker build &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$IMAGE&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the app running inside the container with &lt;code&gt;docker run&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="nv"&gt;$IMAGE&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to &lt;code&gt;http://127.0.0.1:8080&lt;/code&gt; in your browser and check.&lt;/p&gt;

&lt;p&gt;Push to the docker registry with &lt;code&gt;docker push $IMAGE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fassets%2Ficon-192x192.png%3Fv%3D123029de7e" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fassets%2Ficon-192x192.png%3Fv%3D123029de7e"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Shinylive
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://shiny.rstudio.com/py/docs/shinylive.html" rel="noopener noreferrer"&gt;Shinylive&lt;/a&gt; is an experimental feature (Shiny + &lt;a href="https://webassembly.org/" rel="noopener noreferrer"&gt;WebAssembly&lt;/a&gt;) that allows applications to run entirely in a web browser, without the need for a separate server running Python.&lt;/p&gt;

&lt;p&gt;⚠️&lt;/p&gt;

&lt;p&gt;Shinylive is more experimental than Shiny for Python itself. Not all Python packages are &lt;a href="https://shiny.rstudio.com/py/docs/shinylive.html#python-packages" rel="noopener noreferrer"&gt;supported by the underlying Pyodide project&lt;/a&gt;, so not every app would compile easily.&lt;/p&gt;



&lt;p&gt;The point of Shinylive is not really to be served via Docker, but rather as static assets. Still, there might be cases when containerizing Shinylive seems like a good idea. When for example all the rest of the stack is using Docker and we don't want a file server besides that.&lt;/p&gt;

&lt;p&gt;If this did not deter you, let's create the simplest Shiny app again inside the &lt;code&gt;live&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shiny create live
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a &lt;code&gt;live/requirements.txt&lt;/code&gt; file with the following contents&lt;/p&gt;

&lt;p&gt;Shinylive will be installed on its own, no need to include it, just use your requirements from a non-live app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# live/requirements.txt
shiny
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fassets%2Ficon-192x192.png%3Fv%3D123029de7e" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fassets%2Ficon-192x192.png%3Fv%3D123029de7e"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Dockerfile follows the pattern borrowed from the &lt;a href="https://hosting.analythium.io/containerizing-interactive-r-markdown-documents/#runtime-static" rel="nofollow noopener noreferrer"&gt;static R Markdown deployment&lt;/a&gt; using a multi-stage Docker build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;install requirements + Shinylive&lt;/li&gt;
&lt;li&gt;copy the app&lt;/li&gt;
&lt;li&gt;build the Shinylive assets in the &lt;code&gt;site&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;copy the &lt;code&gt;site&lt;/code&gt; folder into a minimal image alongside the &lt;a href="https://github.com/openfaas/of-watchdog#4-static-modestatic" rel="noopener noreferrer"&gt;OpenFaaS watchdog&lt;/a&gt; and serve
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /root&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; live/requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;shinylive
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; live app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;shinylive &lt;span class="nb"&gt;export &lt;/span&gt;app site

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ghcr.io/openfaas/of-watchdog:0.9.6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;watchdog&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:latest&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; /app
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /root/site /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=watchdog /fwatchdog .&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; mode="static"&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; static_path="/app"&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=3s CMD [ -e /tmp/.lock ] || exit 1&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["./fwatchdog"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should be able to build, test, and push the Docker image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;analythium/python-shiny-live:0.1

&lt;span class="c"&gt;# Build&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$IMAGE&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Test, visit http://127.0.0.1:8080&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="nv"&gt;$IMAGE&lt;/span&gt;

&lt;span class="c"&gt;# Push&lt;/span&gt;
docker push &lt;span class="nv"&gt;$IMAGE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app at &lt;code&gt;http://127.0.0.1:8080&lt;/code&gt; in your browser should look like the one we began with: a slider and a text output showing the double of the slider input value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We covered how to containerize Shiny for Python applications with dynamic or static Shinylive versions. This newly gained Docker power opens the door for &lt;a href="https://hosting.analythium.io/how-to-pick-the-right-hosting-option-for-your-shiny-app/#options-at-a-glance" rel="noopener noreferrer"&gt;deploying the app to various platforms via the Docker image&lt;/a&gt;. These options include Heroku, the DigitalOcean App Platform, Fly.io, Docker Compose, or ShinyProxy. And for the experimental Shinylive apps, just host it anywhere (GitHub pages, Netlify, etc.) as static files.&lt;/p&gt;

&lt;p&gt;Deploying a single instance of a Shiny app, however, is not the same as deploying multiple instances. &lt;a href="https://shiny.rstudio.com/py/docs/deploy.html#other-hosting-options" rel="noopener noreferrer"&gt;Load balancing between these instances of the same app could prove difficult.&lt;/a&gt; We'll revisit the pitfalls of scaling Shiny apps in a subsequent post. Get notified about new posts by &lt;a href="https://hosting.analythium.io/signup/" rel="noopener noreferrer"&gt;signing up for the newsletter&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further readings
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rstudio.com/blog/get-started-with-shiny-for-python/" rel="noopener noreferrer"&gt;Get started with Shiny for Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shiny.rstudio.com/py/" rel="noopener noreferrer"&gt;Shiny for Python docs and examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=R0vu3zSdvgM&amp;amp;list=PL9HYL-VRX0oTJtI1dWaT9T827fe7OqFhC" rel="noopener noreferrer"&gt;Shiny for Python YouTube playlist&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docker images referenced in this post that you can &lt;code&gt;docker pull&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;analythium/python-shiny:0.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;analythium/python-shiny-live:0.1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>shiny</category>
      <category>docker</category>
    </item>
    <item>
      <title>We Need to Talk About Docker Registries</title>
      <dc:creator>Peter Solymos</dc:creator>
      <pubDate>Wed, 09 Nov 2022 07:22:11 +0000</pubDate>
      <link>https://forem.com/analythium/we-need-to-talk-about-docker-registries-18em</link>
      <guid>https://forem.com/analythium/we-need-to-talk-about-docker-registries-18em</guid>
      <description>&lt;p&gt;&lt;em&gt;By: &lt;a href="https://hosting.analythium.io/author/peter/"&gt;Peter Solymos&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Docker registry stores Docker images, this is where we push images to and pull images from. Why do we need a registry? What registries are out there? How can we work with them? Read on to find out.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many Docker tutorials begin with &lt;code&gt;docker pull&lt;/code&gt; as the first command to cover. The &lt;code&gt;docker pull&lt;/code&gt; command is followed by the "tag" of a Docker image that we want to be pulled. Say that we wanted the latest Alpine Linux image (I picked this due to its minimal size):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull alpine:latest

&lt;span class="c"&gt;# latest: Pulling from library/alpine&lt;/span&gt;
&lt;span class="c"&gt;# 530afca65e2e: Pull complete&lt;/span&gt;
&lt;span class="c"&gt;# Digest: sha256:7580ece7963bfa...&lt;/span&gt;
&lt;span class="c"&gt;# Status: Downloaded newer image for alpine:latest&lt;/span&gt;
&lt;span class="c"&gt;# docker.io/library/alpine:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But where does this image come from?&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Docker registry?
&lt;/h2&gt;

&lt;p&gt;Where does the Docker command line interface (CLI for short) pulls the images from? The answer is on the last line of the output. &lt;code&gt;docker.io&lt;/code&gt; is the URL for a so called &lt;strong&gt;Docker registry&lt;/strong&gt;. In this case, it refers to the default registry, &lt;a href="https://hub.docker.com/"&gt;Docker Hub&lt;/a&gt;, applied when you don't specify a registry URL as part of the pull command.&lt;/p&gt;

&lt;p&gt;Docker Hub is a service provided by Docker for finding and sharing container images.&lt;/p&gt;

&lt;p&gt;The registry URL is usually followed by the user or organization ID, the &lt;a href="https://docs.docker.com/docker-hub/repos/"&gt;repository&lt;/a&gt; name, the image name and the tag: &lt;code&gt;&amp;lt;registry&amp;gt;/&amp;lt;user&amp;gt;/&amp;lt;repository&amp;gt;/&amp;lt;image&amp;gt;:&amp;lt;tag&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In short, a &lt;a href="https://docs.docker.com/registry/"&gt;Docker registry&lt;/a&gt; stores Docker &lt;strong&gt;images&lt;/strong&gt;. This is where we push images to and pull images from:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GmSr-7Gn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/04/architecture.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GmSr-7Gn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/04/architecture.png" alt="Docker architecture / © Analythium" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;
Docker architecture / © Analythium



&lt;p&gt;Read more about how the different parts of the Docker system relate to each other in this introductory post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vn_qlHge--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/assets/icon-192x192.png%3Fv%3D53de193d60" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vn_qlHge--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/assets/icon-192x192.png%3Fv%3D53de193d60" width="192" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do you need a registry?
&lt;/h2&gt;

&lt;p&gt;You might think that you only need a registry because you need to pull your parent images (like Ubuntu, Alpine, &lt;code&gt;r-base&lt;/code&gt; from the &lt;a href="https://rocker-project.org/"&gt;Rocker project&lt;/a&gt; etc.) from somewhere. Then you can build your Docker images locally using a Dockerfile, and run them. Why bother with a registry at all?&lt;/p&gt;

&lt;p&gt;You can even use the &lt;code&gt;docker save&lt;/code&gt; and &lt;code&gt;docker load&lt;/code&gt; commands to save to a compressed tar file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull alpine:latest

docker save &lt;span class="nt"&gt;-o&lt;/span&gt; alpine-latest.tar alpine:latest

&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; alpine-latest.tar
&lt;span class="c"&gt;# 11368 alpine-latest.tar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then take this tar file, copy it to another server and load it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker load &lt;span class="nt"&gt;--input&lt;/span&gt; alpine-latest.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now imagine that you are managing more than two servers, or you want to share the Docker image with others (to use it or to serve as a parent image). The save/copy/load workflow becomes cumbersome.&lt;/p&gt;

&lt;p&gt;In this case, using a registry might be a much better idea. Luckily, there are many options to choose from, and you can even host your own registry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roll your own registry
&lt;/h2&gt;

&lt;p&gt;If you want a &lt;a href="https://docs.docker.com/registry/deploying/"&gt;registry hosted on your machine&lt;/a&gt;, just pull the registry image. The next command will pull the &lt;code&gt;registry&lt;/code&gt; image, and run the similarly named container in the background on port 5000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 5000:5000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; registry registry:2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tag the Alpine image with the URL of your local registry, &lt;code&gt;localhost:5000&lt;/code&gt;, and push the image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker tag alpine:latest localhost:5000/my-alpine

docker push localhost:5000/my-alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove the images so the &lt;code&gt;docker images&lt;/code&gt; will not list these anymore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker image remove alpine:latest
docker image remove localhost:5000/my-alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pull the image from the local registry and list it with &lt;code&gt;docker images&lt;/code&gt; to verify that it is there again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull localhost:5000/my-alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stop and remove the registry container. The &lt;code&gt;-v&lt;/code&gt; option makes sure to remove anonymous volumes associated with the container which is often used to mount a volume from your hard drive into the container where the images are stored:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker container stop registry &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  docker container &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; registry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want your own registry to be accessed over a network, then you need to think about security and access control.&lt;/p&gt;

&lt;p&gt;Setting up &lt;a href="https://blog.alexellis.io/get-a-tls-enabled-docker-registry-in-5-minutes/"&gt;transport layer security&lt;/a&gt; (TLS) for HTTPS and &lt;a href="https://docs.docker.com/registry/deploying/#restricting-access"&gt;user authentication&lt;/a&gt; are all advanced topics, so implement those at your own risk.&lt;/p&gt;

&lt;p&gt;For now, we will continue with a hosted registry, as most people do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Log into a registry
&lt;/h2&gt;

&lt;p&gt;When you work with private registries or private images, you need to log in with the &lt;code&gt;docker login&lt;/code&gt; command. For Docker Hub, just type &lt;code&gt;docker login&lt;/code&gt;. For  all other registries, type in the registry URL as well, e.g. &lt;code&gt;docker login ghcr.io&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Docker CLI then will prompt you for your username and password (or access token).&lt;/p&gt;

&lt;p&gt;You can log in programmatically by providing your username and the password through standard input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CR_PAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_TOKEN
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$CR_PAT&lt;/span&gt; | docker login ghcr.io &lt;span class="nt"&gt;-u&lt;/span&gt; USER &lt;span class="nt"&gt;--password-stdin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As an alternative, the password can be provided via a file, this approach won't leave trace of your token in your history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/my_password.txt | docker login &lt;span class="nt"&gt;-u&lt;/span&gt; USER &lt;span class="nt"&gt;--password-stdin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use one of these approaches to log into any public or private repository for which you have credentials. The credentials will be &lt;a href="https://docs.docker.com/engine/reference/commandline/login/#privileged-user-requirement"&gt;stored locally&lt;/a&gt;: in &lt;code&gt;$HOME/.docker/config.json&lt;/code&gt; on Linux or &lt;code&gt;%USERPROFILE%/.docker/config.json&lt;/code&gt; on Windows. After login, there is no need to re-authenticate until you log out with &lt;code&gt;docker logout&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Note that &lt;code&gt;docker login&lt;/code&gt; requires users to use &lt;code&gt;sudo&lt;/code&gt; or be &lt;code&gt;root&lt;/code&gt; in most cases.&lt;/p&gt;

&lt;p&gt;It is always a good idea to use a token instead of your password. Tokens can have limited scope (i.e. only for pulling images), and can be revoked at any time without it impacting other areas of your life.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commonly used registries
&lt;/h2&gt;

&lt;p&gt;There are many registries out there besides Docker Hub. Here is a non-exhaustive list of options.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry"&gt;GitHub container registry&lt;/a&gt; (the &lt;code&gt;ghcr.io&lt;/code&gt; URL from above) is available as part of GitHub Packages for free and paid plans, even for private repositories under the free plan. This registry requires authentication using your GitHub token.&lt;/p&gt;

&lt;p&gt;An alternative to GitHub is &lt;a href="https://about.gitlab.com/"&gt;GitLab&lt;/a&gt; (&lt;code&gt;registry.gitlab.com&lt;/code&gt;), which has provided &lt;a href="https://about.gitlab.com/blog/2016/05/23/gitlab-container-registry/"&gt;registry support&lt;/a&gt; for its free (public and private) repositories long before GitHub. The docs are also much better in my opinion, and the whole experience is tightly integrated with GitLab's CICD pipelines. This registry also needs login with a token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://devcenter.heroku.com/articles/container-registry-and-runtime"&gt;Heroku comes with a Docker registry&lt;/a&gt; (&lt;code&gt;registry.heroku.com&lt;/code&gt;) where the Docker-based deploys push the images to.&lt;/p&gt;

&lt;p&gt;Of course, every major cloud provider offers a Docker container registry that is often integrated with their other offerings. Latency should be minimal due to network proximity to the servers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/ecr/"&gt;Amazon Elastic Container Registry (Amazon ECR)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/en-us/services/container-registry/"&gt;Azure Container Registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/container-registry"&gt;Google Container Registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/products/container-registry"&gt;DigitalOcean Container Registry&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other common alternatives for container registries, Helm charts, etc., include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jfrog.com/container-registry/"&gt;JFrog Container Registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://goharbor.io/"&gt;Harbor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.scaleway.com/en/container-registry/"&gt;Scaleway&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although these services are called "container registry", but strictly speaking they store container images.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;We reviewed what container registries are, how to access them, and what are the commonly used services out there that you can leverage for your workflow.&lt;/p&gt;

&lt;p&gt;You have now a good understanding of where the images go after push and where the images come from after pull.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further readings
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/registry/"&gt;The official Docker docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jfrog.com/knowledge-base/docker-hub-and-docker-registries-a-beginners-guide/"&gt;Docker Hub and Docker registries: A beginner’s guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hosting.analythium.io/cicd-patterns-with-github-actions-and-docker/"&gt;CICD patterns with GitHub Actions and Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-private-docker-registry-on-ubuntu-20-04"&gt;How to set up a private docker registry on Ubuntu 20.04&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.alexellis.io/get-a-tls-enabled-docker-registry-in-5-minutes/"&gt;Get a TLS-enabled Docker registry in 5 minutes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>cicd</category>
    </item>
    <item>
      <title>CICD Patterns with GitHub Actions and Docker</title>
      <dc:creator>Peter Solymos</dc:creator>
      <pubDate>Sat, 04 Dec 2021 04:28:48 +0000</pubDate>
      <link>https://forem.com/analythium/cicd-patterns-with-github-actions-and-docker-b1n</link>
      <guid>https://forem.com/analythium/cicd-patterns-with-github-actions-and-docker-b1n</guid>
      <description>&lt;p&gt;&lt;em&gt;By: &lt;a href="https://hosting.analythium.io/author/peter/"&gt;Peter Solymos&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start with easy integrations and add more serious testing to your CICD pipeline as your application matures.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;CICD is a shorthand to refer to &lt;a href="https://en.wikipedia.org/wiki/CI/CD"&gt;continuous integration and continuous delivery/deployment&lt;/a&gt;. CICD includes automated building, testing and deployment of applications. It connects development and operation activities and teams responsible for these stages in application life cycles.&lt;/p&gt;

&lt;p&gt;CICD often builds on version control (i.e. GitHub, GitLab, Bitbucket), and hosted CICD services (&lt;a href="https://github.com/features/actions"&gt;GitHub actions&lt;/a&gt;, &lt;a href="https://docs.gitlab.com/ee/ci/pipelines/"&gt;GitLab pipelines&lt;/a&gt;, &lt;a href="https://bitbucket.org/product/features/pipelines"&gt;Bitbucket pipelines&lt;/a&gt;, and many others like Travis, CircleCI, etc.). The workflow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Commit your changes to the git remote&lt;/li&gt;
&lt;li&gt; Changes on the remote trigger the CICD pipeline&lt;/li&gt;
&lt;li&gt; If the steps in the pipeline are successful a new delivery or deployment is initiated&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The difference between continuous delivery and continuous deployment is that delivery usually means that all the artifacts are delivered that are required for the deployment, but deployment happens in a separate step. Continuous deployment means that a new version is deployed without manual intervention. Think like your Shiny app is included in a Docker image (delivered) vs. the new image is deployed as a running container.&lt;/p&gt;

&lt;p&gt;However, when it comes to Docker-based delivery and deployments, there are multiple ways how CICD can be set up. In this post, I review some of the common patterns I have seen in Docker-related CICD workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should you care?
&lt;/h2&gt;

&lt;p&gt;Why is this whole CICD thing important? On one side, it just makes your life easier by automating some of the steps that would otherwise require you to run tests, copy files, push/pull images, etc. Making sure that no human error occurred along the way. By removing the human element and relying on great tooling, your chances of making silly mistakes are hugely eliminated.&lt;/p&gt;

&lt;p&gt;On the other side, platform-as-a-service (PaaS) offerings often include some form of integration with major hosted git provides, such as GitHub and GitLab (this latter can be self-hosted too). Therefore, it is a good idea to take advantage of these integrations to reap the benefits of automation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sIwQyFNv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/11/1-PaaS-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sIwQyFNv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/11/1-PaaS-1.png" alt="Four CICD patterns to work with Docker and PaaS" width="880" height="495"&gt;&lt;/a&gt;Four CICD patterns to work with Docker and PaaS&lt;/p&gt;

&lt;h2&gt;
  
  
  Non-git-based delivery and deployment
&lt;/h2&gt;

&lt;p&gt;By non-git based I mean that the source code management and the delivery/deployment ideas of your activities are independent. You use git to manage your codebase (versioning). From the same code base, you make sure that the right artifacts(files, images) are built, tested, delivered, and deployed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local image build
&lt;/h3&gt;

&lt;p&gt;In this most basic scenario, you version your codebase. You build Docker images locally. You push the images to a Docker registry. When you deploy the image, you trigger the pull yourself (via command line or user interface) which then initiates a new deployment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NVggmyHy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/10/2-PaaS-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NVggmyHy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/10/2-PaaS-2.png" alt="Local build with independent source code management" width="880" height="495"&gt;&lt;/a&gt;Local build with independent source code management&lt;/p&gt;

&lt;p&gt;This is not a very common pattern in production. It is most likely that you start with something like this when you are just experimenting.&lt;/p&gt;

&lt;p&gt;An example would be how to deploy Docker container images to virtual machines or Kubernetes clusters using OpenFaaS. In this case, the &lt;code&gt;faas-cli&lt;/code&gt; is used to build images locally, push these into a registry, then instruct the servers to pull the updated images and deploy them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kmcXDPoJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.openfaas.com/touch-icon.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kmcXDPoJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.openfaas.com/touch-icon.png" width="32" height="32"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  PaaS-based image build
&lt;/h3&gt;

&lt;p&gt;In this scenario, the version control is still independent of the deployment process in the sense that GitHub won't instruct your PaaS provider about what to do. It is your responsibility. Once you push a new version of your app, PaaS takes care of the build and deployment processes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M0W89vtz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/10/1-PaaS-1-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M0W89vtz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/10/1-PaaS-1-1.png" alt="Build and deployment taken care of by the PaaS" width="880" height="495"&gt;&lt;/a&gt;Build and deployment taken care of by the PaaS&lt;/p&gt;

&lt;p&gt;An example of this scenario is &lt;a href="https://hosting.analythium.io/deploying-shiny-apps-to-heroku-with-docker-from-the-command-line/"&gt;deploying apps to Heroku from the command line interface&lt;/a&gt; (CLI). The twist here is Heroku CLI sets up a new remote on Heroku's servers. So in a way, git instructs the PaaS about the deployment. But you also keep your other remote (GitHub, etc.) to version your code. When you push a new git commit (the event), the Heroku remote will kick off the Docker build process and the deployment of the new version for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter GitOps
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;GitOps is an operational framework that takes DevOps best practices used for application development such as version control, collaboration, compliance, and CI/CD, and applies them to infrastructure automation. – &lt;a href="https://about.gitlab.com/topics/gitops/"&gt;GitLab Docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means that the remote git platform is responsible for all downstream events. Secure communication between different service provides (GitHub and your PaaS) is set up using access tokens (revocable "secrets").&lt;/p&gt;

&lt;p&gt;The advantage of this setup is that a lot more steps can be automated within the same workflow, like testing etc. This will make the process more robust.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git-triggered and PaaS-based image build
&lt;/h3&gt;

&lt;p&gt;This setup is very similar to the previous, PaaS-based image build. The difference is that the build process is triggered by the code hosting platform, GitHub for example. Pushing the new commit, often to a certain pre-configured branch, will result in a webhook alerting the PaaS provider. The PaaS servers then launch the deployment process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2Kv8F1hy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/10/3-PaaS-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2Kv8F1hy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/10/3-PaaS-3.png" alt="Git commit triggers the build process in the PaaS" width="880" height="495"&gt;&lt;/a&gt;Git commit triggers the build process in the PaaS&lt;/p&gt;

&lt;p&gt;GitHub will only deliver the news to the PaaS, but no GitHub actions are involved here. Examples for this scenario include the &lt;a href="https://hosting.analythium.io/auto-deploy-shiny-app-changes-to-the-digitalocean-app-platform/"&gt;GitHub integration with the DigitalOcean App Platform&lt;/a&gt;. Setup is usually super simple, but the ease of setup is at the expense of full control over how exactly the process is taking place. If you need more control then see the next option.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git-triggered and GitHub-based image build
&lt;/h3&gt;

&lt;p&gt;The last scenario circles back to the 1st option, but instead of everything happening locally, the whole build/deployment process is happening on remote servers. The git push event kicks off the GitHub action (or similar) CICD process. The Docker image build happens inside the &lt;a href="https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners"&gt;GitHub actions runners&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rmP7OAq9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/10/4-PaaS-4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rmP7OAq9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/10/4-PaaS-4.png" alt="Build and deploy with GitHub actions" width="880" height="495"&gt;&lt;/a&gt;Build and deploy with GitHub actions&lt;/p&gt;

&lt;p&gt;This setup and the required GitHub actions steps give you the most control of how you want things built and delivered. For example, you can specify if you want to &lt;a href="https://hosting.analythium.io/how-to-use-github-actions-with-the-digitalocean-app-platform-for-shiny-apps/#3-docker-build-and-push-with-cache"&gt;cache the Docker image layers&lt;/a&gt;. This is also ideal to make sure new deployment only happens if all steps are successful. E.g. testing your code might find errors in unit tests. You don't want to release a new version until the issues are dealt with.&lt;/p&gt;

&lt;p&gt;Here are a few examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://hosting.analythium.io/deploying-shiny-apps-to-heroku-with-docker-and-github-actions/"&gt;Heroku deployment with GitGub actions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://hosting.analythium.io/how-to-use-github-actions-with-the-digitalocean-app-platform-for-shiny-apps/"&gt;DigitalOcean app platform with image caching example&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/"&gt;CICD with the Fly.io&lt;/a&gt; platform with a &lt;a href="https://github.com/analythium/fly-shiny/blob/main/.github/workflows/main.yml"&gt;Shiny example workflow&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Functions and microservices with &lt;a href="https://www.openfaas.com/blog/openfaas-functions-with-github-actions/"&gt;OpenFaaS and GitHub actions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When there is a command-line tool to interact with the PaaS, you can most likely set up a GitHub actions workflow with that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;CICD practices are important for a robust and automated developer experience. Knowing that the workflows are in order gives you peace of mind. Deploying containerized applications adds a few extra steps to your workflows. As a result, there are multiple ways of approaching it.&lt;/p&gt;

&lt;p&gt;Some workflows are simpler, others are more complex. You can start simple and add complexity as your needs evolve. Try easy integrations when prototyping, add more serious testing and GitHub actions as your application matures. Once you get the hang of it, CICD becomes second nature. Tooling is getting better every day.&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>docker</category>
    </item>
    <item>
      <title>How to Pick the Right Hosting Option for Your Shiny App</title>
      <dc:creator>Peter Solymos</dc:creator>
      <pubDate>Mon, 15 Nov 2021 06:29:39 +0000</pubDate>
      <link>https://forem.com/analythium/how-to-pick-the-right-hosting-option-for-your-shiny-app-2ph7</link>
      <guid>https://forem.com/analythium/how-to-pick-the-right-hosting-option-for-your-shiny-app-2ph7</guid>
      <description>&lt;p&gt;&lt;em&gt;By: &lt;a href="https://hosting.analythium.io/author/peter/"&gt;Peter Solymos&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You mastered Shiny, your app is production-ready. Here are the different ways of hosting it. This post helps you pick one that best suits your needs!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Hosting Data Apps website recently celebrated its &lt;a href="https://hosting.analythium.io/hosting-data-apps-6-months-and-40-posts-later/"&gt;6-months anniversary&lt;/a&gt;. During this time I have written 40 posts, almost all about Shiny hosting options. Some of these posts reviewed particular hosting options, such as Shinyapps.io, Shiny Server, Heroku, and ShinyProxy.&lt;/p&gt;

&lt;p&gt;A lot has been said about the hosting options themselves, but what about the needs of the developers and the users? The goal of this post is to address this shortcoming by &lt;strong&gt;summarizing the&lt;/strong&gt; &lt;a href="https://hosting.analythium.io/tag/review/"&gt;&lt;strong&gt;reviews&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How not to do a review
&lt;/h2&gt;

&lt;p&gt;You might have seen various websites listing comparisons titled "&lt;strong&gt;&lt;em&gt;A vs. B&lt;/em&gt;&lt;/strong&gt;". I was wondering if doing something like that would make sense for the Hosting Data site. Think like "&lt;em&gt;Which is better: Shinyapps or Heroku?&lt;/em&gt;".&lt;/p&gt;

&lt;p&gt;This would instantly give me 36 more articles to write. Plenty of ammunition for the next 9 months by recycling the previous content. Easy-peasy, if all I wanted was to boost the page ranking.&lt;/p&gt;

&lt;p&gt;Instead, I decided that these &lt;strong&gt;A vs. B posts are useless&lt;/strong&gt;. They fail to ask the most important question: "&lt;em&gt;which option is better **for what&lt;/em&gt;&lt;strong&gt;"? This is where developers and the users of the app come into the picture with their **specific needs and constraints&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You could say, for example, that "&lt;em&gt;I want to host my portfolio for free and I don't care about a custom domain name&lt;/em&gt;". Or a nonprofit might say, "&lt;em&gt;we want to host our apps at low cost, we want custom domains, and we want to be able to handle surge traffic, but we don't want to maintain any servers&lt;/em&gt;".&lt;/p&gt;

&lt;p&gt;These are really specific criteria, and if you ask me, I might say Shinyapps.io is best for you and Heroku with a Docker-based deployment is best for this organization. But how would you or I make such a decision?&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3 step process to make the decision
&lt;/h2&gt;

&lt;p&gt;If you have been developing Shiny apps, you might already have your preferred way of deployment. But as your needs evolve, you will identify additional requirements and might find that your go-to option is not the best anymore. Then you'll do some research and find the next option.&lt;/p&gt;

&lt;p&gt;If you are not yet familiar with Shiny hosting options, you still need to make an informed choice at some point, so that you are not wasting your time and effort on something that will not serve you well over the long run. Here is the 3 step process that you can follow to help you with this decision.&lt;/p&gt;

&lt;p&gt;Before you begin take a piece of paper.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Start with the why
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why do you want the app or apps deployed?&lt;/strong&gt; Are you building a portfolio to boost your career? Are you deploying useful apps for stakeholders or clients of your organization? Are you trying to sell an app as a software-as-a-service (SaaS) offering?&lt;/p&gt;

&lt;p&gt;Write down your answer.&lt;/p&gt;

&lt;p&gt;Getting clear on the why is the most important question. You might even realize that &lt;strong&gt;you don't need to host&lt;/strong&gt; your Shiny app. For example, your app might be used on a laptop as a GUI to analyze data by non-specialists in the field without internet or cell coverage. In this case, no need to move on to step 2, because all you need is to &lt;a href="https://hosting.analythium.io/run-shiny-apps-locally/"&gt;run Shiny locally&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, if your answer makes it clear to you that your users will be accessing the app over the Internet, move on to step 2.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. List your requirements
&lt;/h3&gt;

&lt;p&gt;Answering the Why question will probably reveal important details about your motivations, your audience, the number of apps you are going to host, etc. The answer will also bring you closer to identifying the &lt;strong&gt;requirements&lt;/strong&gt; that you'll need.&lt;/p&gt;

&lt;p&gt;For example, do you need a custom domain, how many users are you expecting, do you need authentication or app-level authorization? Do you want to host a single app or do you need to host many apps? Will you host non-Shiny apps?&lt;/p&gt;

&lt;p&gt;Write these down too.&lt;/p&gt;

&lt;p&gt;The following table lists the important features for many different &lt;strong&gt;Shiny hosting options&lt;/strong&gt;. The table lists tiers offered by the same company as a separate option. Use this table to find the options that meet your requirements. For now, just ignore the columns inside the blue rectangle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Pemr0Fur--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/10/Screen-Shot-2021-10-31-at-1.22.51-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Pemr0Fur--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/10/Screen-Shot-2021-10-31-at-1.22.51-PM.png" alt="Feature matrix comparing Shiny hosting options" width="880" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you crave a more interactive experience, I made a filterable version:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/iGKQMKuz-ww"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hosting.analythium.io/assets/files/shiny-hosting-options.html"&gt;Here is the link&lt;/a&gt; to the page where you can filter the options.&lt;/p&gt;

&lt;p&gt;Once you filter the table according to your requirements, you'll see a list of your &lt;strong&gt;ideal hosting options&lt;/strong&gt;. Put these in the file or onto the paper too.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Identify your constraints
&lt;/h3&gt;

&lt;p&gt;The last step involves identifying your &lt;strong&gt;constraints&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  What is your &lt;strong&gt;budget&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;  What is your current &lt;strong&gt;skill level&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;  How much time do you have &lt;strong&gt;time&lt;/strong&gt;?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Recognizing these constraints will guide you toward an &lt;strong&gt;optimal solution&lt;/strong&gt;. This is the point where the columns inside the blue rectangle come in.&lt;/p&gt;

&lt;p&gt;The "&lt;strong&gt;Total Cost&lt;/strong&gt;" of ownership (USD/year) covers licensing fees and operating costs for the "Number of Apps" listed in the table. Prices range quite a bit from free to the tens of thousands. Price increases with performance and with the availability of enterprise features, such as custom domains and authentication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt; means platform-as-a-service, i.e. it is a fully managed system without you having to worry about the underlying infrastructure. This also means less control over the infrastructure, i.e. when it comes to choosing the &lt;strong&gt;data region&lt;/strong&gt; where your app is served from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unlimited app hours&lt;/strong&gt; are more common for self-hosted options or paid PaaS plans including a single app. The need to host &lt;strong&gt;multiple apps&lt;/strong&gt; will involve some compromises. The ability to host &lt;strong&gt;non-Shiny apps&lt;/strong&gt; (Dahs, Streamlit, etc.) is a feature for RStudio Connect and the Docker-based options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time&lt;/strong&gt; as a constraint will depend on how far your current skill level is from the level needed for a specific hosting option. You also have to consider that some options are fully managed PaaS offerings, others you have to manage, or learn how to use Docker.&lt;/p&gt;

&lt;p&gt;If you have to develop &lt;strong&gt;new skills&lt;/strong&gt;, it might take longer. If you have to manage your servers, it will take more time to get started and then you are on the hook for maintaining your setup.&lt;/p&gt;

&lt;p&gt;Make your selections inside the columns within the blue area.&lt;/p&gt;

&lt;h3&gt;
  
  
  You are done!
&lt;/h3&gt;

&lt;p&gt;After the 3-step process, you should see only a few or a single option left. Click on the name of the hosting option and the link will take you to the relevant tag page on the Hosting Data Apps website:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Follow the instructions in the &lt;strong&gt;tutorials&lt;/strong&gt; to get started&lt;/li&gt;
&lt;li&gt;  At the end of each post, you'll find a &lt;em&gt;Further reading&lt;/em&gt; section listing &lt;strong&gt;additional resources&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If there is no option left in the table, then you might need to be more realistic about your expectations or relax some of your constraints. For example, to keep costs low, you could spend more time and invest in skill development. But if you have more room in your budget, you might choose a different path. You can also revise your requirements until you find an acceptable solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Options at a glance
&lt;/h2&gt;

&lt;p&gt;The following diagram gives an intuitive overview of the different options. The vertical axis represents the &lt;strong&gt;total cost&lt;/strong&gt; from the table above: free, low, and high cost. The horizontal axis shows a &lt;strong&gt;range of skills&lt;/strong&gt; you need to &lt;a href="https://hosting.analythium.io/the-taxonomy-of-shiny-hosting-options/"&gt;set up and manage your hosting solution&lt;/a&gt;. It can be as simple as pushing a button, or as complex as managing servers or cloud clusters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JqrQLMBI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/10/shiny-hosting-options-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JqrQLMBI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/10/shiny-hosting-options-1.png" alt="Costs and complexity associated with different Shiny hosting options" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The hosting options in this diagram are not separated by tiers but rather shown as spanning over a range. The fill colours identify Docker and non-Docker-based options, the stroke styling indicates the PaaS solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is next
&lt;/h2&gt;

&lt;p&gt;If you followed the 3-step process to collect all the information you need, it is likely that you have found an option that is best for your needs. Now go ahead and learn more about that option, deploy, and start hosting your app.&lt;/p&gt;

&lt;p&gt;Shiny is a very popular interactive data application framework. As a result, &lt;strong&gt;new hosting options&lt;/strong&gt; are popping up every time. As the number of these hosting options grows in the future, I might update this post by adding the new contenders to the table.&lt;/p&gt;

</description>
      <category>shiny</category>
      <category>rstats</category>
    </item>
    <item>
      <title>Auto deploy Shiny app changes to the DigitalOcean App Platform</title>
      <dc:creator>Peter Solymos</dc:creator>
      <pubDate>Sun, 24 Oct 2021 18:06:13 +0000</pubDate>
      <link>https://forem.com/analythium/auto-deploy-shiny-app-changes-to-the-digitalocean-app-platform-47de</link>
      <guid>https://forem.com/analythium/auto-deploy-shiny-app-changes-to-the-digitalocean-app-platform-47de</guid>
      <description>&lt;p&gt;&lt;em&gt;By: &lt;a href="https://hosting.analythium.io/author/peter/" rel="noopener noreferrer"&gt;Peter Solymos&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integrate the DigitalOcean App Platform with GitHub for automated deployment of your Shiny app.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I introduced the DigitalOcean App Platform in a previous post and described how to &lt;a href="https://hosting.analythium.io/how-to-host-shiny-apps-on-the-digitalocean-app-platform/" rel="noopener noreferrer"&gt;deploy Shiny apps from existing Docker images&lt;/a&gt; using the control panel or the command line.&lt;/p&gt;

&lt;p&gt;This post continues the App Platform discussion by looking at how to integrate the App Platform with GitHub and set up continuous integration and deployment (CICD).&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;If you have not done so before, go to the &lt;a href="https://www.digitalocean.com/products/app-platform/" rel="noopener noreferrer"&gt;App Platform&lt;/a&gt; landing page and sign up for a DigitalOcean account. Log into your account and go to your dashboard. In the left drawer, click on 'Apps'.&lt;/p&gt;

&lt;p&gt;For the &lt;a href="https://github.com/analythium/app-platform-shiny" rel="noopener noreferrer"&gt;analythium/app-platform-shiny&lt;/a&gt; repository because you will have to give permissions to access the repo.&lt;/p&gt;

&lt;p&gt;Here is what's inside the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
├── .do
│   ├── app.yaml
│   └── deploy.template.yaml
├── app
│   ├── global.R
│   ├── server.R
│   └── ui.R
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
└── renv.lock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.do&lt;/code&gt; folder contains a template file that is needed if you want to deploy the Shiny app template from the button in the &lt;code&gt;README&lt;/code&gt; file. The &lt;a href="https://hosting.analythium.io/how-to-host-shiny-apps-on-the-digitalocean-app-platform/" rel="noopener noreferrer"&gt;app specification&lt;/a&gt; (&lt;code&gt;app.yaml&lt;/code&gt;) that was introduced before, but it is largely simplified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-platform-shiny&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;dockerfile_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;
  &lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
    &lt;span class="na"&gt;deploy_on_push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;analythium/app-platform-shiny&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-platform-shiny&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;app&lt;/code&gt; folder contains &lt;a href="https://hosting.analythium.io/run-shiny-apps-locally/#multiple-files" rel="noopener noreferrer"&gt;multiple files for the Shiny app&lt;/a&gt;. The &lt;code&gt;Dockerfile&lt;/code&gt; uses the &lt;a href="https://hosting.analythium.io/dockerized-shiny-apps-with-dependencies/#use-the-renv-r-package" rel="noopener noreferrer"&gt;renv package to handle the dependencies&lt;/a&gt; specified in the &lt;code&gt;renv.lock&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;The app itself, as you will see, is a relatively simple example with some range slider, a dependency that does not require building from source. It also has a file upload button – I use the upload functionality to test when a large file upload is required. Large file upload is often restricted, &lt;a href="https://mastering-shiny.org/action-transfer.html" rel="noopener noreferrer"&gt;Shiny itself has a 5 Mb file size limit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub integration
&lt;/h2&gt;

&lt;p&gt;Let's set up the GitHub integration. Go to the DigitalOcean control panel In your control panel. Click on 'Launch New App' under the 'App' item in the left side menu:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.41.14-PM-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.41.14-PM-1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you are asked to choose a source, click on the GitHub icon:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.41.27-PM-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.41.27-PM-2.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow the prompts and install the GitHub app for your personal account or the organization of your choice. Select 'All repositories' or 'Only select repositories' as appropriate. Finally, click 'Save':&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-8.31.03-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-8.31.03-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy from GitHub repository
&lt;/h2&gt;

&lt;p&gt;Now go back to the Apps control panel. Select the repository from the dropdown menu, specify the branch you wish to deploy. If you want to deploy both a development and a production version of the same repository, you can create multiple apps with the same repo but using different branches.&lt;/p&gt;

&lt;p&gt;Leave 'autodeploy' checked if you want to trigger new deployments when the code changes, then click 'Next':&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-8.33.35-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-8.33.35-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Review the app settings inferred from the &lt;code&gt;Dockerfile&lt;/code&gt;, i.e. the HTTP port, etc.:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-8.37.49-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-8.37.49-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Type in the name of the service, select the data region, then click 'Next:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-8.38.09-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-8.38.09-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final step lets you define the performance of the app and the corresponding pricing. Here I use the smallest size under the 'Basic' plan for $5 US per month:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-8.38.32-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-8.38.32-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a few minutes, you should see the green checks besides the build and deployment log entries. Building the image might take some time, depending on the number of dependencies in your app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-9.48.00-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-9.48.00-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow the app URL from the control panel to see the app online served over HTTPS:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-9.50.51-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-9.50.51-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a custom domain
&lt;/h2&gt;

&lt;p&gt;Adding a custom domain is done via the control panel. Click 'Add Domain':&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-9.55.41-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-9.55.41-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow the prompts and copy the app URL:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-9.57.59-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-9.57.59-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add a CNAME record in your DNS settings on your DNS providers page (Google Domains shown here), make it point to a domain or subdomain of yours:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-9.57.03-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-9.57.03-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will see the custom domain listed in the app settings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-10.01.47-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-10.01.47-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on your new custom domain link and check. You might have to wait a bit if your record has not propagated through the name servers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-10.01.17-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-10.01.17-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto deploy updates
&lt;/h2&gt;

&lt;p&gt;Next, we will make some changes to the Shiny app and see the app magically update via git. Do it in your forged repo. The small change I made was to edit the app title. Here is the change:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-10.05.00-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-10.05.00-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you commit the changes and push to GitHub, it triggers the web-flow pipeline. The new deployment is then listed in the deployment history of your app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-10.09.35-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-10.09.35-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Visit the custom URL and see the new version deployed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-10.10.00-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-10.10.00-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don't forget to delete the app and the CNAME record if you don't need them anymore to avoid accruing hosting charges. Dynamic app hosting is not free on DigitalOcean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing before deployment
&lt;/h2&gt;

&lt;p&gt;One potential issue with this GitHub integration is it &lt;a href="https://www.digitalocean.com/community/questions/app-platform-wait-for-tests-to-pass-before-deploying" rel="noopener noreferrer"&gt;does not depend on passing tests before deployment&lt;/a&gt;. If you want to test your changes before deployment, here are the steps to follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; push changes to the development branch,&lt;/li&gt;
&lt;li&gt; run automated tests for the development branch, e.g. using GitHub actions,&lt;/li&gt;
&lt;li&gt; set up the production branch as a protected branch, so that &lt;a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests" rel="noopener noreferrer"&gt;merging pull requests require passing tests&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;When we deployed an exiting Docker image, the image was pulled to the App Platform from Docker Hub. In this case, an immutable image was pushed and pulled.&lt;/p&gt;

&lt;p&gt;The GitHub integration described here takes a different approach. It builds the Docker image after changes are pushed to a pre-defined branch in the GitHub repository. The image is built and stored in the &lt;a href="https://www.digitalocean.com/products/container-registry/" rel="noopener noreferrer"&gt;DigitalOcean Container Registry&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With this approach, you get a new deployment on every git push event. You can debate how much this automated deployment (CD) can be considered continuous integration and deployment (CICD) without actually having the CI part.&lt;/p&gt;

&lt;p&gt;Testing the app before deployment requires additional steps as part of your git workflow. To achieve full CICD experience, you can use the &lt;code&gt;doctl&lt;/code&gt; &lt;a href="https://hosting.analythium.io/how-to-host-shiny-apps-on-the-digitalocean-app-platform/#programmatic-image-deployment" rel="noopener noreferrer"&gt;command-line utility&lt;/a&gt; in GitHub Actions. As it turns out, this route is a bit more involved and there are several gotchas to address. Thus, I will explain it in a follow-up post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://docs.digitalocean.com/products/app-platform/" rel="noopener noreferrer"&gt;App Platform documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.digitalocean.com/products/app-platform/how-to/deploy-from-monorepo/" rel="noopener noreferrer"&gt;Deploy app from a monorepo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.digitalocean.com/products/app-platform/how-to/troubleshoot-app/" rel="noopener noreferrer"&gt;Troubleshoot your app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>shiny</category>
      <category>docker</category>
      <category>cicd</category>
      <category>rstats</category>
    </item>
    <item>
      <title>How to Host Shiny Apps on the DigitalOcean App Platform</title>
      <dc:creator>Peter Solymos</dc:creator>
      <pubDate>Sat, 02 Oct 2021 17:08:38 +0000</pubDate>
      <link>https://forem.com/analythium/how-to-host-shiny-apps-on-the-digitalocean-app-platform-43h4</link>
      <guid>https://forem.com/analythium/how-to-host-shiny-apps-on-the-digitalocean-app-platform-43h4</guid>
      <description>&lt;p&gt;&lt;em&gt;By: &lt;a href="https://hosting.analythium.io/author/peter/" rel="noopener noreferrer"&gt;Peter Solymos&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The DigitalOcean App Platform is another PaaS offering that allows you to publish Shiny apps without worrying about infrastructure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;DigitalOcean announced the App Platform a &lt;a href="https://www.digitalocean.com/press/releases/digitalocean-launches-app-platform/" rel="noopener noreferrer"&gt;year ago&lt;/a&gt; as the platform-as-a-service(PaaS) offering that automates infrastructure management. Developers can deploy their code to production in just a few clicks.&lt;/p&gt;

&lt;p&gt;The App Platform can publish applications from your GitHub, GitLab or public Git repositories, or publish a container image you have already uploaded to a Docker registry. The platform supports GitOps (git push-based deployment), and vertical and horizontal scaling.&lt;/p&gt;

&lt;p&gt;In spirit and most practical aspects, the platform is very similar to &lt;a href="https://hosting.analythium.io/tag/heroku/" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt;. You can use &lt;a href="https://docs.digitalocean.com/products/app-platform/build-system/cloud-native-buildpacks/" rel="noopener noreferrer"&gt;Buildpacks&lt;/a&gt; or &lt;a href="https://docs.digitalocean.com/products/app-platform/build-system/dockerfile-build/" rel="noopener noreferrer"&gt;Dockerfiles&lt;/a&gt; to build applications. There is no native Buildpack for R. But as we saw before, Shiny apps can be containerized. This gives us a way to deploy to the App Platform seamlessly.&lt;/p&gt;

&lt;p&gt;This post focuses on how to deploy a Shiny app from an &lt;a href="https://docs.digitalocean.com/products/app-platform/how-to/deploy-from-registry/" rel="noopener noreferrer"&gt;existing Docker image&lt;/a&gt; to the App Platform. This is a fully no-code experience because we already built and pushed the image. You will use the &lt;code&gt;r-minimal&lt;/code&gt; Alpine Linux-based &lt;a href="https://hub.docker.com/r/analythium/covidapp-shiny/tags?page=1&amp;amp;ordering=last_updated" rel="noopener noreferrer"&gt;COVID19 app&lt;/a&gt; that we build before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Visit the &lt;a href="https://www.digitalocean.com/products/app-platform/" rel="noopener noreferrer"&gt;App Platform&lt;/a&gt; landing page and sign up for a DigitalOcean account:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.39.28-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.39.28-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once have an account, log in and go to your dashboard. In the left drawer, click on 'Apps':&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.40.52-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.40.52-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Launch your Shiny app from the dashboard
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Deploy
&lt;/h3&gt;

&lt;p&gt;Click on the 'Launch Your App' button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.41.14-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.41.14-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next screen will present your options. Select Docker Hub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.41.43-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.41.43-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, type in the repository name (&lt;code&gt;analythium/covidapp-shiny&lt;/code&gt;) and the tag (&lt;code&gt;minimal&lt;/code&gt;), then click 'Next':&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.44.40-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.44.40-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next screen, leave the Type as &lt;code&gt;Web Services&lt;/code&gt;, set the Run command to the &lt;code&gt;CMD&lt;/code&gt; command of the &lt;a href="https://github.com/analythium/covidapp-shiny/blob/main/99-images/Dockerfile.minimal" rel="noopener noreferrer"&gt;Dockerfile&lt;/a&gt; that you are deploying (without the square brackets), and change the port to the one exposed by Docker (3838):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.54.01-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.54.01-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next screen, you can edit the app's name your data region:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.47.26-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.47.26-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last step is to select the plan. Non-static apps require a paid plan ($5 US/month and above):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.48.14-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.48.14-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where you can set the number of replicates (horizontal scaling). Finally, click the 'Launch' button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.48.25-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.48.25-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the deployment
&lt;/h3&gt;

&lt;p&gt;After a few seconds, you'll see the app deployed on a subdomain: &lt;code&gt;app-name-hash.ondigitalocean.app&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.55.02-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.55.02-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Visit the link to check the app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.55.15-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.55.15-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look around in the dashboard to inspect the deployment and runtime logs. You can even access the container through the console. Under the insights tab, you can check CPU and memory consumption:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-12.06.34-AM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-12.06.34-AM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a custom domain
&lt;/h3&gt;

&lt;p&gt;You can add a &lt;a href="https://docs.digitalocean.com/products/app-platform/quickstart/#register-a-custom-domain" rel="noopener noreferrer"&gt;custom domain&lt;/a&gt; using a CNAME record with your DNS provider. Go to your app's settings tab, find the 'Add Domain' button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.57.30-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.57.30-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow the prompts and copy the app's current URL:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.58.57-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.58.57-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to your DNS provider (Google Domains shown here) and add a CNAME record, and save:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-12.01.39-AM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-12.01.39-AM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After some time the custom domain is live and listed under settings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-12.03.29-AM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-12.03.29-AM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Visit the new link, note that it is served over secure HTTPS protocol:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-12.03.03-AM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-12.03.03-AM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Update the app image
&lt;/h3&gt;

&lt;p&gt;You can edit the source image tag in your app's settings. Once the new version is tagged and pushed to the Docker registry, just edit the tag and the app will be &lt;a href="https://docs.digitalocean.com/products/app-platform/how-to/deploy-from-registry/#change-the-tag-of-the-container-source-after-deployment" rel="noopener noreferrer"&gt;redeployed&lt;/a&gt; to the App Platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Programmatic image deployment
&lt;/h2&gt;

&lt;p&gt;Of course, you can use the &lt;code&gt;doctl&lt;/code&gt; &lt;a href="https://docs.digitalocean.com/products/app-platform/references/command-line/" rel="noopener noreferrer"&gt;command-line tool&lt;/a&gt; and a YAML-based &lt;a href="https://docs.digitalocean.com/products/app-platform/references/app-specification-reference/" rel="noopener noreferrer"&gt;app specification&lt;/a&gt; file format for deployment. You can find the app specification in the control panel, look under the Settings tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.56.51-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-17-at-11.56.51-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most of the info in the spec file is self-explanatory and relates to all the settings we made manually. Let's grab the YAML file and use it as a starter.&lt;/p&gt;

&lt;p&gt;To use the app spec from the command line you have to &lt;a href="https://docs.digitalocean.com/reference/doctl/how-to/install/" rel="noopener noreferrer"&gt;install the&lt;/a&gt; &lt;code&gt;doctl&lt;/code&gt; &lt;a href="https://docs.digitalocean.com/reference/doctl/how-to/install/" rel="noopener noreferrer"&gt;command-line tool&lt;/a&gt; and follow the steps described in the article to use the API token to grant &lt;code&gt;doctl&lt;/code&gt; access to your DigitalOcean account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy an app
&lt;/h3&gt;

&lt;p&gt;We'll make some changes to the app specs and deploy a different app, the one that &lt;a href="https://hosting.analythium.io/the-quickest-way-to-add-new-apps-to-shinyproxy/" rel="noopener noreferrer"&gt;visualizes the correlation&lt;/a&gt; between 2 variables. This will use the &lt;code&gt;analythium/correlation:v1&lt;/code&gt; &lt;a href="https://hub.docker.com/repository/docker/analythium/correlation" rel="noopener noreferrer"&gt;Docker image&lt;/a&gt;. Create an empty file called &lt;code&gt;shiny-app.yml&lt;/code&gt; and add the following content to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;alerts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEPLOYMENT_FAILED&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DOMAIN_FAILED&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;analythium-correlation-shiny&lt;/span&gt;
&lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nyc&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http_port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3838&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;analythium&lt;/span&gt;
    &lt;span class="na"&gt;registry_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DOCKER_HUB&lt;/span&gt;
    &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;correlation&lt;/span&gt;
    &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
  &lt;span class="na"&gt;instance_count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;instance_size_slug&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;basic-xxs&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shiny-app&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
  &lt;span class="na"&gt;run_command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;R -e "shiny::runApp('/home/app',port=3838,host='0.0.0.0')"&lt;/span&gt;
  &lt;span class="na"&gt;source_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's use some &lt;code&gt;doctl apps&lt;/code&gt; commands now (these are the commands specific to the App Platform). First, we validate the YAML specification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doctl apps spec validate shiny-app.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We either get an error message or the file content. Once the app spec passed inspection, we can use it to create an app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doctl apps create &lt;span class="nt"&gt;--spec&lt;/span&gt; shiny-app.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll see the newly created app info. Take note of the &lt;code&gt;ID&lt;/code&gt;. You can get the &lt;code&gt;ID&lt;/code&gt; later using the &lt;code&gt;doctl apps list&lt;/code&gt; command to list all the apps. This will give the &lt;code&gt;ID&lt;/code&gt;, and the Default Ingress, which is the assigned subdomain (&lt;a href="https://analythium-correlation-shiny-vil3a.ondigitalocean.app" rel="noopener noreferrer"&gt;https://analythium-correlation-shiny-vil3a.ondigitalocean.app&lt;/a&gt; in this case). If the ingress field is empty, it means the deployment is in progress or it has failed.&lt;/p&gt;

&lt;p&gt;Use the app ID to see the deployment and runtime logs (edit the &lt;code&gt;APP_ID&lt;/code&gt; value according to your console output):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;APP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"5c187c9a-5d1a-4988-8848-82905d0f0302"&lt;/span&gt;

doctl apps logs &lt;span class="nv"&gt;$APP_ID&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt; deploy
doctl apps logs &lt;span class="nv"&gt;$APP_ID&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt; run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all went well, you should see the app after visiting the ingress URL:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-2.09.53-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-2.09.53-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Update the app
&lt;/h3&gt;

&lt;p&gt;You can change the YAML spec file, e.g. change the &lt;code&gt;services.image.tag&lt;/code&gt; property to &lt;code&gt;tag: v2&lt;/code&gt; and save the file. Then run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doctl apps update &lt;span class="nv"&gt;$APP_ID&lt;/span&gt; &lt;span class="nt"&gt;--spec&lt;/span&gt; shiny-app.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will update the app in a few minutes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-2.15.46-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F09%2FScreen-Shot-2021-09-18-at-2.15.46-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Delete the apps
&lt;/h3&gt;

&lt;p&gt;Once you are done playing with the new deployment and you don't need it anymore, just click the 'Actions' button, select 'Destroy' from the drop-down menu. Or use the app ID and &lt;code&gt;doctl apps delete $APP_ID&lt;/code&gt; to get rid of the apps. Also, remove the CNAME record from your DNS if you have set up a custom domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;The DigitalOcean App Platform makes it straightforward to deploy Shiny apps without ever opening your terminal. But if you prefer, the command-line option only needs a YAML file and a single command.&lt;/p&gt;

&lt;p&gt;Docker once again proved to be a really useful piece of technology. We saw how immutable container images can be moved around the internet without issues. We'll cover the GitHub and App Platform integration in some of the next posts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://docs.digitalocean.com/products/app-platform/" rel="noopener noreferrer"&gt;App Platform documentation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>shiny</category>
      <category>docker</category>
      <category>rstats</category>
    </item>
    <item>
      <title>ShinyProxy 1-click App</title>
      <dc:creator>Peter Solymos</dc:creator>
      <pubDate>Thu, 02 Sep 2021 20:28:10 +0000</pubDate>
      <link>https://forem.com/analythium/shinyproxy-1-click-app-2omn</link>
      <guid>https://forem.com/analythium/shinyproxy-1-click-app-2omn</guid>
      <description>&lt;p&gt;&lt;em&gt;By: &lt;a href="https://hosting.analythium.io/author/peter/"&gt;Peter Solymos&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ShinyProxy is a great way to deploy containerized Shiny apps to production and it is only 1 click away.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ShinyProxy is one of the most popular options to host any type of containerized web application, including but not limited to Shiny apps. It provides authentication, authorization, application isolation, scalability, and a lot more with &lt;a href="https://shinyproxy.io/"&gt;extensive documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;ShinyProxy is free and open-source. Although free here means that you are free to do anything with the software within the broad limits of the Apache 2 license. But you will have to manage your server and pay for hosting it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is there a simple setup process?
&lt;/h2&gt;

&lt;p&gt;Setting up ShinyProxy can be a bit intimidating if this is the first time you are venturing into this realm. &lt;a href="https://hosting.analythium.io/tag/shinyproxy/"&gt;Tutorials&lt;/a&gt; about the installation process, configuration, and custom domains are great. But you might wonder if there is an easier way to fast forward the process.&lt;/p&gt;

&lt;p&gt;This is the reason &lt;a href="https://analythium.io/"&gt;Analythium Solutions&lt;/a&gt; published the &lt;a href="https://marketplace.digitalocean.com/apps/shinyproxy?refcode=a8041699739d"&gt;ShinyProxy  1-click app in the DigitalOcean marketplace&lt;/a&gt;. We have been successfully using the setup reflected in the tutorials and the 1-click app for some years with our consulting clients. Automating the system setup is beneficial for our own purposes. Releasing it as a marketplace app, however, means that others can benefit from our learnings too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ob2cPA2Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.28.19-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ob2cPA2Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.28.19-PM.png" alt="The ShinyProxy 1-click app info page"&gt;&lt;/a&gt;The ShinyProxy 1-click app info page&lt;/p&gt;

&lt;p&gt;This post is a screenshot-based demo of the setup process explaining how you can reclaim an hour of your day. There is also a video version:&lt;/p&gt;
&lt;h1&gt;
  
  
  An error occurred.
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=aoIlaOYRpQs"&gt;Try watching this video on www.youtube.com&lt;/a&gt;, or enable JavaScript if it is disabled in your browser.&lt;/p&gt;





&lt;p&gt;ShinyProxy 1-click app tutorial video&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Marketplace?
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://marketplace.digitalocean.com/"&gt;DigitalOcean Marketplace&lt;/a&gt; is a collection of so-called 1-click apps, which are pre-configured &lt;a href="https://docs.digitalocean.com/products/images/custom-images/"&gt;machine images&lt;/a&gt; that can be used to spin up virtual machines in minutes.&lt;/p&gt;

&lt;p&gt;The apps are maintained by DigitalOcean or different vendors. Apps include WordPress for blogging, &lt;a href="https://marketplace.digitalocean.com/apps/rstudio"&gt;RStudio&lt;/a&gt; image for computing applications, our &lt;a href="https://marketplace.digitalocean.com/apps/shinyproxy?refcode=a8041699739d"&gt;ShinyProxy image&lt;/a&gt;, and a lot more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.digitalocean.com/"&gt;DigitalOcean&lt;/a&gt; has a simple and intuitive browser-based dashboard and prices are predictable pricing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Signup
&lt;/h3&gt;

&lt;p&gt;When you create a DigitalOcean account, you can choose to sign up with an email address or with Google or GitHub single sign-on (SSO) following the prompts after clicking the 'Sign Up' button. Once you sign up, you can set up &lt;a href="https://www.digitalocean.com/docs/accounts/security/2fa/"&gt;two-factor authentication&lt;/a&gt; to protect your account. You can consider using this &lt;a href="https://m.do.co/c/a8041699739d"&gt;referral link&lt;/a&gt; and get $100 free credits on sign-up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Login
&lt;/h3&gt;

&lt;p&gt;To log into your account, click on the 'Sign In' button in the top right corner. This should take you to a form where you can sign in using email and password, or use Google or GitHub SSO. Once you pass login, you'll be prompted to type in a 6-digit verification code that you receive to the email address that you have used for signup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add your SSH key
&lt;/h3&gt;

&lt;p&gt;Ideally, you should add your SSH key to your DigitalOcean account before creating the creating resources (virtual machines, etc.). This way your SSH key can be deployed to the new servers and accessing those through the command line will be straightforward.&lt;/p&gt;

&lt;p&gt;To set up an SSH key, follow &lt;a href="https://phoenixnap.com/kb/generate-ssh-key-windows-10"&gt;this&lt;/a&gt; guide. Once your SSH key is generated you will see two files (these fine names might be different depending on how you generated them), one public, one private. These two files are cryptographically matched, like a key and a lock.&lt;/p&gt;

&lt;p&gt;Open the public key file (e.g. &lt;code&gt;.ssh/id_rsa.pub&lt;/code&gt;) in a text editor, copy the content. Now go to your DigitalOcean Dashboard, under Settings find Security. Click 'Add SSH Key'. Paste here the contents of the public key file. Add a name, that will remind you which one is yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spin up a "droplet"
&lt;/h2&gt;

&lt;p&gt;Now you signed up and logged in to your account and see your dashboard. The top navigation has a search bar to quickly find your resources, a 'Create' button with a dropdown listing the most common actions. This is where you can find an option to create a new Droplet. What is a "droplet"? A droplet is a small part of the ocean, and this is how DigitalOcean refers to its &lt;a href="https://docs.digitalocean.com/products/droplets/"&gt;virtual machines&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eyJ9nJci--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.16.59-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eyJ9nJci--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.16.59-PM.png" alt="The user dashboard with the Create button"&gt;&lt;/a&gt;The user dashboard with the Create button&lt;/p&gt;

&lt;p&gt;Click the 'Create' button and select the 'Droplets - create cloud servers' option. This will take you to a page where you can set the specifications for the new Droplet. You can create the new Droplet as a fresh Linux server (you can select many different Linux distributions, including Ubuntu, Debian, etc., and different versions), or you can use one of your saved images to replicate a given setting.&lt;/p&gt;

&lt;p&gt;Search and select the ShinyProxy 1-Click app. This is a pre-configured image on Ubuntu 20.04.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---NhIo9e6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.17.21-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---NhIo9e6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.17.21-PM.png" alt="Select ShinyProxy from the Marketplace as your image"&gt;&lt;/a&gt;Select ShinyProxy from the Marketplace as your image&lt;/p&gt;

&lt;p&gt;After the image is selected, you can choose a plan based on hardware configuration and pricing (see Droplet pricing &lt;a href="https://www.digitalocean.com/docs/droplets/resources/choose-plan/"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tchN16Hk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.17.43-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tchN16Hk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.17.43-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, you can pick your data region, I picked Toronto (Canada) here, feel free to choose what's closest to you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8-emfsis--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.18.01-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8-emfsis--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.18.01-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, you can add additional monitoring (recommender: this allows collecting more refined monitoring data and is required for alerts).&lt;/p&gt;

&lt;p&gt;Authentication can be based on a root password or using SSH keys. If you select SSH key-based authentication, password-based authentication will be turned off by default.&lt;/p&gt;

&lt;p&gt;Finally, you can specify how many Droplets you wish to create with the same specifications, you can edit the hostname (this is how the new Droplet will show up in the dashboard), or add backup for 20% of the Droplet price.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ekW9134j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.18.33-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ekW9134j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.18.33-PM.png" alt="Set monitoring, security, and replication levels"&gt;&lt;/a&gt;Set monitoring, security, and replication levels&lt;/p&gt;

&lt;p&gt;If everything looks good, you can click the 'Create Droplet' button at the bottom and wait for your Droplet to be provisioned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b4-aDWs1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.19.09-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b4-aDWs1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.19.09-PM.png" alt="Click 'Create Droplet'"&gt;&lt;/a&gt;Click 'Create Droplet'&lt;/p&gt;

&lt;p&gt;Once the progress bar reaches 100% you can copy the IP address and visit the droplet in the browser or in the terminal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hS0-tFjP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.19.28-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hS0-tFjP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.19.28-PM.png" alt="Droplet creation in progress"&gt;&lt;/a&gt;Droplet creation in progress&lt;/p&gt;

&lt;p&gt;In the meantime you can study the 1-click app information on what to do next once the virtual machine is up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qtIS3ktg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.19.56-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qtIS3ktg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.19.56-PM.png" alt="ShinyProxy 1-click app description"&gt;&lt;/a&gt;ShinyProxy 1-click app description&lt;/p&gt;

&lt;p&gt;Click on the droplet name to see the relevant dashboard. The green dot indicates a healthy instance, but you might have to wait another minute before you see CPU and memory usage info. Click on the 'ipv4' address, it will copy the address to your clipboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_FOMcPgc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.21.06-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_FOMcPgc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.21.06-PM.png" alt="Droplet about the new instance"&gt;&lt;/a&gt;Droplet about the new instance&lt;/p&gt;

&lt;h2&gt;
  
  
  API Creation
&lt;/h2&gt;

&lt;p&gt;In addition to creating a Droplet from the ShinyProxy 1-Click App via the control panel, you can also use the &lt;a href="https://digitalocean.com/docs/api"&gt;DigitalOcean API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As an example, to create a 4GB ShinyProxy Droplet in the SFO2 region, you can use the following curl command. You’ll need to either save your &lt;a href="https://docs.digitalocean.com/reference/api/create-personal-access-token/"&gt;API access token&lt;/a&gt; to an environment variable or substitute it into the command below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer '&lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s1"&gt;'{"name":"choose_a_name","region":"sfo2","size":"s-2vcpu-4gb","image":"analythium-shinyproxy-20-04"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"https://api.digitalocean.com/v2/droplets"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Log in using the ShinyProxy UI
&lt;/h2&gt;

&lt;p&gt;It takes some time for all the services, including Docker and ShinyProxy to come online. Once your new droplet with the ShinyProxy 1-Click app is up and running, you can visit your droplet's IP address. Use &lt;code&gt;admin&lt;/code&gt;/&lt;code&gt;password&lt;/code&gt; or &lt;code&gt;user&lt;/code&gt;/&lt;code&gt;password&lt;/code&gt; as user name and password to log into your ShinyProxy instance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T09jG1Z---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.23.28-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T09jG1Z---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.23.28-PM.png" alt="ShinyProxy login page"&gt;&lt;/a&gt;ShinyProxy login page&lt;/p&gt;

&lt;p&gt;Once you are in, you'll see two demo R Shiny applications. Try them to make sure Docker is running fine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xm1WySci--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.23.41-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xm1WySci--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.23.41-PM.png" alt="ShinyProxy listing the available applications"&gt;&lt;/a&gt;ShinyProxy listing the available applications&lt;/p&gt;

&lt;p&gt;Now you can modify the configuration to deploy your own apps. This will require you to access the droplet through a secure shell (SSH).&lt;/p&gt;

&lt;h2&gt;
  
  
  Log in through SSH
&lt;/h2&gt;

&lt;p&gt;Secure shell or &lt;code&gt;ssh&lt;/code&gt; is a command-line tool and also a cryptographic &lt;a href="https://en.wikipedia.org/wiki/Secure_Shell_Protocol"&gt;network protocol&lt;/a&gt; for operating network services securely over an unsecured network.&lt;/p&gt;

&lt;p&gt;SSH provides a secure channel connecting an SSH client application with an SSH server (the standard port for SSH is 22).&lt;/p&gt;

&lt;p&gt;You'll be able to log into a new Droplet if your SSH key was added on droplet creation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; c:/.ssh/id_rsa root@142.93.156.135
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will use the SSH private key (&lt;code&gt;c:/.ssh/id_rsa&lt;/code&gt;) to let you into the server as the &lt;code&gt;root&lt;/code&gt; user (superuser).&lt;/p&gt;

&lt;p&gt;I used the full path to tell &lt;code&gt;ssh&lt;/code&gt; where to find the private key (using the &lt;code&gt;-i&lt;/code&gt; flag) that will be matched with the public key already on the server. You'll be asked to add the server to your list of trusted hosts on the first login, just answer yes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ObmlGIqb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.25.27-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ObmlGIqb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-16-at-10.25.27-PM.png" alt="Message of the day after log in through SSH"&gt;&lt;/a&gt;Message of the day after log in through SSH&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;/etc/shinyproxy/application.yml&lt;/code&gt; to configure your instance. Pay special attention to authentication: it is set to &lt;code&gt;simple&lt;/code&gt;. You should change user names and passwords, possibly the authentication type.&lt;/p&gt;

&lt;p&gt;Pull Docker images and add those to the configuration file to deploy your Shiny apps.&lt;/p&gt;

&lt;p&gt;Then restart ShinyProxy for the changes to take effect using &lt;code&gt;sudo service shinyproxy restart&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All this is explained in the following posts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  ⚙️ &lt;a href="https://hosting.analythium.io/advanced-configuration-for-shinyproxy/?utm_source=as-digest&amp;amp;utm_medium=email&amp;amp;utm_campaign=aug-2021"&gt;Advanced Configuration for ShinyProxy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  🔄 &lt;a href="https://hosting.analythium.io/update-existing-shiny-apps-in-shinyproxy/?utm_source=as-digest&amp;amp;utm_medium=email&amp;amp;utm_campaign=aug-2021"&gt;Update Existing Shiny Apps in ShinyProxy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  🚀 &lt;a href="https://hosting.analythium.io/the-quickest-way-to-add-new-apps-to-shinyproxy/?utm_source=as-digest&amp;amp;utm_medium=email&amp;amp;utm_campaign=aug-2021"&gt;The Quickest Way to Add New Apps to ShinyProxy&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also go ahead and set up a custom domain name and HTTPS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  🔒 &lt;a href="https://hosting.analythium.io/custom-domain-and-security-for-shinyproxy-with-nginx/?utm_source=as-digest&amp;amp;utm_medium=email&amp;amp;utm_campaign=aug-2021"&gt;Custom Domain and Security for ShinyProxy with Nginx&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  🔒 &lt;a href="https://hosting.analythium.io/securing-shinyproxy-with-caddy-server/?utm_source=as-digest&amp;amp;utm_medium=email&amp;amp;utm_campaign=aug-2021"&gt;Securing ShinyProxy with Caddy Server&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't forget to destroy (and not just power off) your droplet if you don't need it any longer to avoid any unwanted changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;The ShinyProxy 1-click app is a machine image that helps you get your production server up and running in no time. The usual setup process can take anywhere between 30 minutes to an hour. With the 1-click app, it takes only a few minutes. It is still valuable to understand the details, but the app can be a real-time saver in the long run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further readings
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://shinyproxy.io/"&gt;ShinyProxy website&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://support.openanalytics.eu/"&gt;ShinyProxy forum&lt;/a&gt;: Q&amp;amp;A website under the ShinyProxy category&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://github.com/analythium/shinyproxy-1-click/issues"&gt;1-Click App Support&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.digitalocean.com/docs/"&gt;DigitalOcean product documentation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>shinyproxy</category>
      <category>shiny</category>
      <category>rstats</category>
      <category>docker</category>
    </item>
    <item>
      <title>Securing ShinyProxy with Caddy Server</title>
      <dc:creator>Peter Solymos</dc:creator>
      <pubDate>Mon, 30 Aug 2021 14:13:44 +0000</pubDate>
      <link>https://forem.com/analythium/securing-shinyproxy-with-caddy-server-c7b</link>
      <guid>https://forem.com/analythium/securing-shinyproxy-with-caddy-server-c7b</guid>
      <description>&lt;p&gt;&lt;em&gt;By: &lt;a href="https://hosting.analythium.io/author/peter/" rel="noopener noreferrer"&gt;Peter Solymos&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Caddy server to obtain TLS certificates for your custom domain and to serve Shiny apps securely with ShinyProxy.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In a previous post, I explained how to &lt;a href="https://hosting.analythium.io/custom-domain-and-security-for-shinyproxy-with-nginx/" rel="noopener noreferrer"&gt;add a custom domain and HTTPS to your ShinyProxy server using Nginx&lt;/a&gt;, and how to set up certificate auto-renewals with Certbot. That was a really long post and the setup included many steps.&lt;/p&gt;

&lt;p&gt;A while back, I also used &lt;a href="https://hosting.analythium.io/securing-shiny-server-with-caddy/" rel="noopener noreferrer"&gt;Caddy server to secure a Shiny Server instance&lt;/a&gt;. That process was quite straightforward with a lot fewer moving parts. Let's see if we can do the same for ShinyProxy, as it is pictured below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F08%2FSPCaddy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F08%2FSPCaddy.png" alt="ShinyProxy setup with Caddy server."&gt;&lt;/a&gt;ShinyProxy setup with Caddy server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Start a Ubuntu 20.04 virtual machine and follow instructions from the &lt;a href="https://hosting.analythium.io/how-to-set-up-shinyproxy-to-host-shiny-apps/" rel="noopener noreferrer"&gt;introductory ShinyProxy post&lt;/a&gt; to have the server available on &lt;code&gt;http://$HOST:8080&lt;/code&gt; with the two demo applications.&lt;/p&gt;

&lt;p&gt;For a &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;Let's Encrypt certificate&lt;/a&gt;, you need a fully registered domain name and an email address. I use the &lt;code&gt;example.com&lt;/code&gt; domain here, you have to substitute your domain name. Add an &lt;code&gt;A&lt;/code&gt; record with &lt;code&gt;example.com&lt;/code&gt; pointing to your server's public IP address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Caddy
&lt;/h2&gt;

&lt;p&gt;Add some keys and update the &lt;code&gt;apt&lt;/code&gt; sources, then install Caddy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; debian-keyring debian-archive-keyring apt-transport-https
curl &lt;span class="nt"&gt;-1sLf&lt;/span&gt; &lt;span class="s1"&gt;'https://dl.cloudsmith.io/public/caddy/stable/gpg.key'&lt;/span&gt; | &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-key add -
curl &lt;span class="nt"&gt;-1sLf&lt;/span&gt; &lt;span class="s1"&gt;'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/caddy-stable.list

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;caddy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add Caddyfile &lt;code&gt;/etc/caddy/Caddyfile&lt;/code&gt; with the following content, replace your email in the global configuration block (some challenges and Let's Encrypt notifications require the email, but this block is optional):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;
        email your.name@example.com
&lt;span class="o"&gt;}&lt;/span&gt;

example.com &lt;span class="o"&gt;{&lt;/span&gt;
        reverse_proxy 127.0.0.1:8080
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Caddy with &lt;code&gt;systemctl reload caddy&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set the firewall
&lt;/h2&gt;

&lt;p&gt;You can firewall off everything except for the SSH, HTTP, and HTTPS ports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow http
ufw allow https
ufw &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. Now you can visit &lt;code&gt;https://$HOST&lt;/code&gt; to see the ShinyProxy login page with the secure lock icon:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F08%2FScreen-Shot-2021-08-14-at-12.24.41-AM-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhosting.analythium.io%2Fcontent%2Fimages%2F2021%2F08%2FScreen-Shot-2021-08-14-at-12.24.41-AM-1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you decide to destroy your virtual machine, do not forget to remove the DNS record for your custom domain to prevent a &lt;a href="https://reposify.com/blog/how-to-avoid-hostile-subdomain-takeovers/" rel="noopener noreferrer"&gt;hostile subdomain takeover&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;The brevity of this post should be convincing enough that adding a custom domain and HTTPS with Caddy is easy as a breeze. Caddy will take care of the certificate renewals.&lt;/p&gt;

&lt;p&gt;Remember that it is your job to make sure your app is not exposing sensitive information and that you follow &lt;a href="https://hosting.analythium.io/best-practices-for-r-with-docker/" rel="noopener noreferrer"&gt;Docker best practices&lt;/a&gt; to minimize risk to your users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://www.shinyproxy.io/documentation/security/" rel="noopener noreferrer"&gt;ShinyProxy documentation: security&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://github.com/openanalytics/shinyproxy-config-examples" rel="noopener noreferrer"&gt;ShinyProxy configuration examples&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://caddyserver.com/docs/" rel="noopener noreferrer"&gt;Caddy server documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://doesmysiteneedhttps.com/" rel="noopener noreferrer"&gt;Should you use HTTPS?&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>shinyproxy</category>
      <category>shiny</category>
      <category>docker</category>
      <category>rstats</category>
    </item>
    <item>
      <title>Custom Domain and Security for ShinyProxy with Nginx</title>
      <dc:creator>Peter Solymos</dc:creator>
      <pubDate>Mon, 23 Aug 2021 15:44:53 +0000</pubDate>
      <link>https://forem.com/analythium/custom-domain-and-security-for-shinyproxy-with-nginx-54bb</link>
      <guid>https://forem.com/analythium/custom-domain-and-security-for-shinyproxy-with-nginx-54bb</guid>
      <description>&lt;p&gt;&lt;em&gt;By: &lt;a href="https://hosting.analythium.io/author/peter/"&gt;Peter Solymos&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here are the final steps to make your ShinyProxy instance secure and production-ready.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the last post in this ShinyProxy series where I describe how to configure ShinyProxy with Nginx using HTTPS, set up certificate renewals, and a custom domain name.&lt;/p&gt;

&lt;p&gt;Configuring ShinyProxy is a complex topic, that is the reason I have devoted quite a few posts to it including installation, configuration, and maintenance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://hosting.analythium.io/how-to-set-up-shinyproxy-to-host-shiny-apps/"&gt;How to Set Up ShinyProxy to Host Shiny Apps&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://hosting.analythium.io/advanced-configuration-for-shinyproxy/"&gt;Advanced Configuration for ShinyProxy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://hosting.analythium.io/update-existing-shiny-apps-in-shinyproxy/"&gt;Update Existing Shiny Apps in ShinyProxy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://hosting.analythium.io/the-quickest-way-to-add-new-apps-to-shinyproxy/"&gt;The Quickest Way to Add New Apps to ShinyProxy&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's see what else is to be done to have your very own, production-ready, and secure ShinyProxy instance, just like as pictured below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZFX06k76--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/SPNginx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZFX06k76--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/SPNginx.png" alt="ShinyProxy setup with Nginx."&gt;&lt;/a&gt;ShinyProxy setup with Nginx.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Start a Ubuntu 20.04 virtual machine and follow instructions from the &lt;a href="https://hosting.analythium.io/how-to-set-up-shinyproxy-to-host-shiny-apps/"&gt;introductory ShinyProxy post&lt;/a&gt; to have the server available on &lt;code&gt;http://$HOST:8080&lt;/code&gt; with the two demo applications.&lt;/p&gt;

&lt;p&gt;If you are short on time and need a virtual machine with ShinyProxy already installed, try the &lt;a href="https://marketplace.digitalocean.com/apps/shinyproxy?refcode=a8041699739d"&gt;ShinyProxy 1-click app from the Digitalocean Marketplace&lt;/a&gt; for $5 a month. I explain the setup in &lt;a href="https://youtu.be/aoIlaOYRpQs"&gt;this short video&lt;/a&gt;. The 1-click machine image has Nginx configured, so feel free to jump ahead to the &lt;em&gt;Firewall settings&lt;/em&gt; section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run ShinyProxy on port 80
&lt;/h2&gt;

&lt;p&gt;Your ShinyProxy instance is running on port 8080 (&lt;code&gt;http://$HOST:8080&lt;/code&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y3qHKty6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-13-at-11.51.00-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y3qHKty6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-13-at-11.51.00-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is common practice in development mode. 8080 is an alternative to port 80 (HTTP). When you type &lt;code&gt;$HOST&lt;/code&gt; in the browser, it is going to take you to &lt;code&gt;http://$HOST:80&lt;/code&gt; implicitly. Unlike port 80, this 8080 needs an explicit port override to request in the browser.&lt;/p&gt;

&lt;p&gt;Port 8080 is there to remind you of the fact that you need to decide how you want to host ShinyProxy. I explain how to use Nginx as a reverse proxy to serve ShinyProxy on port 80.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Nginx
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://nginx.org/en/"&gt;Nginx&lt;/a&gt; (pronounced "engine X") is a web server that can also be used as a &lt;a href="https://en.wikipedia.org/wiki/Nginx#HTTP_proxy_and_Web_server_features"&gt;reverse proxy or load balancer&lt;/a&gt;. A large portion of the Internet is running on Nginx, so it is a good idea to get some exposure. Let's install Nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Set up a reverse proxy
&lt;/h3&gt;

&lt;p&gt;A reverse proxy is a server that is placed in front of other servers and forwards browser requests to those servers. In our case, Nginx forwards the request made to port 80 to ShinyProxy listening on port 8080.&lt;/p&gt;

&lt;p&gt;You can read more about the difference between a &lt;a href="https://www.cloudflare.com/en-ca/learning/cdn/glossary/reverse-proxy/"&gt;(forward) proxy and a reverse proxy setup here&lt;/a&gt;. For our purposes, the important security consideration is that Nginx as a reverse proxy ensures that no client ever communicates directly with the ShinyProxy server.&lt;/p&gt;

&lt;p&gt;Edit the config file (&lt;code&gt;/etc/nginx/sites-enabled/default&lt;/code&gt;) with &lt;code&gt;vim&lt;/code&gt; or &lt;code&gt;nano&lt;/code&gt; to have the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;server &lt;span class="o"&gt;{&lt;/span&gt;
        listen 80 default_server&lt;span class="p"&gt;;&lt;/span&gt;
        listen &lt;span class="o"&gt;[&lt;/span&gt;::]:80 default_server&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c"&gt;## Add here your custom domain&lt;/span&gt;
    &lt;span class="c"&gt;# server_name example.com www.example.com;&lt;/span&gt;
    server_name _&lt;span class="p"&gt;;&lt;/span&gt;

    location / &lt;span class="o"&gt;{&lt;/span&gt;
        proxy_pass          http://127.0.0.1:8080/&lt;span class="p"&gt;;&lt;/span&gt;

        proxy_http_version 1.1&lt;span class="p"&gt;;&lt;/span&gt;
        proxy_set_header Upgrade &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        proxy_set_header Connection &lt;span class="s2"&gt;"upgrade"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        proxy_read_timeout 600s&lt;span class="p"&gt;;&lt;/span&gt;

            proxy_redirect    off&lt;span class="p"&gt;;&lt;/span&gt;
            proxy_set_header  Host             &lt;span class="nv"&gt;$http_host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            proxy_set_header  X-Real-IP        &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            proxy_set_header  X-Forwarded-For  &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            proxy_set_header  X-Forwarded-Protocol &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file and restart Nginx with &lt;code&gt;service nginx restart&lt;/code&gt;. After this, Nginx will send all traffic to ShinyProxy which is running on port 8080. You can visit &lt;code&gt;http://$HOST/&lt;/code&gt; to access ShinyProxy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TM-WE3Mr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-13-at-11.55.48-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TM-WE3Mr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-13-at-11.55.48-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Firewall settings
&lt;/h2&gt;

&lt;p&gt;We will use a firewall to filter all the ports except for the SSH (22), HTTP (80) and HTTPS (443) ports.&lt;/p&gt;

&lt;p&gt;The server behind the firewall will use other ports. For example, ShinyProxy itself will connect to the ports on the Docker host that are mapped to the individual containers. It is not recommended to make these ports accessible to the outside world.&lt;/p&gt;

&lt;p&gt;For this reason, we install &lt;code&gt;ufw&lt;/code&gt;, the &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-firewall-with-ufw-on-ubuntu-20-04"&gt;Uncomplicated Firewall&lt;/a&gt;, to allow incoming traffic only on the above-mentioned ports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;ufw

ufw default deny incoming
ufw default allow outgoing

ufw allow ssh
ufw allow http
ufw allow https

ufw &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="c"&gt;# answer y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now check &lt;code&gt;ufw status&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Status: active

To                         Action      From
&lt;span class="nt"&gt;--&lt;/span&gt;                         &lt;span class="nt"&gt;------&lt;/span&gt;      &lt;span class="nt"&gt;----&lt;/span&gt;
22/tcp                     ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere
22/tcp &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;                ALLOW       Anywhere &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;
80/tcp &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;                ALLOW       Anywhere &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;
443/tcp &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;               ALLOW       Anywhere &lt;span class="o"&gt;(&lt;/span&gt;v6&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure your custom domain
&lt;/h2&gt;

&lt;p&gt;For a &lt;a href="https://letsencrypt.org/"&gt;Let's Encrypt certificate&lt;/a&gt;, you need a fully registered domain name. I use the &lt;code&gt;example.com&lt;/code&gt; domain. You have to substitute your domain name. If you want your server hosted on an &lt;a href="https://stackoverflow.com/questions/55461299/what-is-the-correct-term-for-apex-naked-bare-root-domain-names"&gt;apex domain name&lt;/a&gt; (&lt;code&gt;example.com&lt;/code&gt;), it is recommended to set up a &lt;code&gt;www&lt;/code&gt; subdomain as well. Both of the following &lt;a href="https://en.wikipedia.org/wiki/Domain_Name_System"&gt;domain name system&lt;/a&gt; (DNS) records need to be set up for your server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  an &lt;code&gt;A&lt;/code&gt; record with &lt;code&gt;example.com&lt;/code&gt; pointing to your server's public IP address,&lt;/li&gt;
&lt;li&gt;  an &lt;code&gt;A&lt;/code&gt; record with &lt;code&gt;www.example.com&lt;/code&gt; pointing to your server's public IP address.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the &lt;code&gt;/etc/nginx/sites-available/default&lt;/code&gt; file, find the line &lt;code&gt;server_name _;&lt;/code&gt; and change it to &lt;code&gt;server_name example.com www.example.com;&lt;/code&gt; (see comments in the snippet above).&lt;/p&gt;

&lt;p&gt;If you are using a subdomain, e.g. &lt;code&gt;shinyproxy.example.com&lt;/code&gt;, you only need a single DNS record set up and the single subdomain added to the Nginx default configuration. This is what I am doing in my example using Google Domains:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cZVfofXj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-13-at-11.59.40-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cZVfofXj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-13-at-11.59.40-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Restart Nginx to enable your changes by &lt;code&gt;systemctl restart nginx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Visit your domain to see the login page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OSi8LA-D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-14-at-12.06.24-AM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OSi8LA-D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-14-at-12.06.24-AM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up HTTPS
&lt;/h2&gt;

&lt;p&gt;The image above showed the lock icon crossed which indicates we are accessing the custom domain over a non-secure HTTP connection. To have a secure connection over HTTPS, we need to set up &lt;a href="https://en.wikipedia.org/wiki/Transport_Layer_Security"&gt;Transport Layer Security&lt;/a&gt; (TLS).&lt;/p&gt;

&lt;p&gt;TLS is a protocol that encrypts traffic between the client and the server. It relies on a set of trusted third-party Certificate Authorities (CA) to establish the authenticity of certificates. One such non-profit Certificate Authority is called &lt;a href="https://letsencrypt.org/"&gt;Let's Encrypt&lt;/a&gt;, which is what we used in the example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Certbot
&lt;/h3&gt;

&lt;p&gt;Let’s Encrypt certificates expire after 90 days, so it is a good idea to use software tools for auto-renewal. We use Certbot to keep certificates up-to-date.&lt;/p&gt;

&lt;p&gt;Add repository for up to date Certbot version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;add-apt-repository ppa:certbot/certbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need to press ENTER to accept.&lt;/p&gt;

&lt;p&gt;Install Certbot's Nginx package with apt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;python3-certbot-nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Certbot is now ready to use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Obtaining a TLS certificate
&lt;/h3&gt;

&lt;p&gt;Run this command after changing the domain names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; example.com &lt;span class="nt"&gt;-d&lt;/span&gt; www.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Be careful with capitalization. Browsers might not be case sensitive but Nginx and Certbot want things nice and clean and matching DNS settings.&lt;/p&gt;

&lt;p&gt;If this is your first time running Certbot, you will be prompted to enter an email address and agree to the terms of service. Providing a valid email address is encouraged so that you can get notified about expiring certificates or any other issues that might arise.&lt;/p&gt;

&lt;p&gt;After doing so, Certbot will communicate with the Let's Encrypt server, then run a challenge to verify that you control the domain that you are requesting a certificate for.&lt;/p&gt;

&lt;p&gt;When prompted whether or not to redirect HTTP traffic to HTTPS (option 1) or remove HTTP access (option 2), I usually pick the second option. This is why there is no need to firewall off port 80, see &lt;a href="https://letsencrypt.org/docs/allow-port-80/"&gt;https://letsencrypt.org/docs/allow-port-80/&lt;/a&gt; for an explanation.&lt;/p&gt;

&lt;p&gt;If you pick option 1, make sure to read the ShinyProxy security documentation and &lt;a href="https://www.shinyproxy.io/documentation/security/#forward-headers"&gt;forward request headers&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verifying auto-renewal
&lt;/h3&gt;

&lt;p&gt;Run this command for a dry run: &lt;code&gt;sudo certbot renew --dry-run&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you reload the custom domain in your browser, it should redirect to the HTTPS, notice the lock icon is not crossed anymore:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jzE9elTB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-14-at-12.24.41-AM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jzE9elTB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-14-at-12.24.41-AM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Remember that it is your job to follow &lt;a href="https://hosting.analythium.io/best-practices-for-r-with-docker/"&gt;Docker best practices&lt;/a&gt; and not use admin privileges inside the containers. You should also be careful that your app is not exposing sensitive information.&lt;/p&gt;

&lt;p&gt;If you want to stop and destroy your virtual server, do not forget to remove the DNS record for your custom domain and subdomain to prevent a &lt;a href="https://reposify.com/blog/how-to-avoid-hostile-subdomain-takeovers/"&gt;hostile subdomain takeover&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;You are in full control when it comes to ShinyProxy. You can do whatever you like, but you also need to understand how to configure your server and be willing to expand your knowledge base as needed.&lt;/p&gt;

&lt;p&gt;Well done! You just learned how to add your custom domain to the ShinyProxy server. This will certainly help your personal or corporate branding.&lt;/p&gt;

&lt;p&gt;But your users want also their data to be safe. That's why HTTPS is so important. Security measures for your site can always be improved, and security should always be top of your mind starting from your Shiny app to your firewall settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://www.shinyproxy.io/documentation/security/"&gt;ShinyProxy documentation: security&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://github.com/openanalytics/shinyproxy-config-examples"&gt;ShinyProxy configuration examples&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="http://nginx.org/en/docs/"&gt;Nginx documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.nginx.com/resources/wiki/start/topics/examples/full/"&gt;Nginx example configurations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://doesmysiteneedhttps.com/"&gt;Should you use HTTPS?&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>shinyproxy</category>
      <category>shiny</category>
      <category>rstats</category>
      <category>docker</category>
    </item>
    <item>
      <title>The Quickest Way to Add New Apps to ShinyProxy</title>
      <dc:creator>Peter Solymos</dc:creator>
      <pubDate>Mon, 16 Aug 2021 14:33:21 +0000</pubDate>
      <link>https://forem.com/analythium/the-quickest-way-to-add-new-apps-to-shinyproxy-4lki</link>
      <guid>https://forem.com/analythium/the-quickest-way-to-add-new-apps-to-shinyproxy-4lki</guid>
      <description>&lt;p&gt;&lt;em&gt;By: &lt;a href="https://hosting.analythium.io/author/peter/"&gt;Peter Solymos&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Once your ShinyProxy server is up and running, you need to maintain and update it. Last time we looked at how to update existing apps. This section explains how to add new ones hassle-free.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ShinyProxy serves containerized web applications, like Shiny apps, without limiting the number of concurrent users or application hours. It is free, open-source, and comes with free enterprise features, such as authentication and app-level authorization.&lt;/p&gt;

&lt;p&gt;In the previous posts, I &lt;a href="https://hosting.analythium.io/how-to-set-up-shinyproxy-to-host-shiny-apps/"&gt;introduced ShinyProxy&lt;/a&gt; and its &lt;a href="https://hosting.analythium.io/advanced-configuration-for-shinyproxy/"&gt;configuration&lt;/a&gt;, then reviewed how to &lt;a href="https://hosting.analythium.io/update-existing-shiny-apps-in-shinyproxy/"&gt;update existing apps&lt;/a&gt; without disrupting the users. But once in a while, you want to add new apps to ShinyProxy. This is when you have to update the configuration and do a few more steps. Let's see what are these steps and how to make the process more efficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You can find the configuration file &lt;code&gt;application.yml&lt;/code&gt; for this tutorial in the &lt;a href="https://github.com/analythium"&gt;analythium&lt;/a&gt;/&lt;a href="https://github.com/analythium/shiny-correlation"&gt;shiny-correlation&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;p&gt;To follow along, set up a virtual machine running Docker and ShinyProxy following &lt;a href="https://hosting.analythium.io/how-to-set-up-shinyproxy-to-host-shiny-apps/"&gt;this post&lt;/a&gt; on any cloud provider using Ubuntu 20.04. Or you can use the &lt;a href="https://marketplace.digitalocean.com/apps/shinyproxy?refcode=a8041699739d"&gt;ShinyProxy 1-click app from the Digitalocean Marketplace&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ANw6rVP8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-06-at-11.00.24-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ANw6rVP8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-06-at-11.00.24-PM.png" alt="Create a droplet (virtual machine) using the ShinyProxy marketplace image"&gt;&lt;/a&gt;Create a droplet (virtual machine) using the ShinyProxy marketplace image&lt;/p&gt;

&lt;p&gt;Once you know the host URL or IPv4 address, set the &lt;code&gt;$HOST&lt;/code&gt; environment variable and log in as root user using &lt;code&gt;ssh&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"178.128.235.125"&lt;/span&gt;

ssh root@&lt;span class="nv"&gt;$HOST&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Steps to add new apps
&lt;/h2&gt;

&lt;p&gt;The ShinyProxy configuration is defined by the &lt;code&gt;application.yml&lt;/code&gt; file in the &lt;code&gt;/etc/shinyproxy&lt;/code&gt; folder. This file lists the apps, therefore adding new apps involve the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; update the &lt;code&gt;application.yml&lt;/code&gt; to include the new apps&lt;/li&gt;
&lt;li&gt; pull the new Docker images to the host with &lt;code&gt;docker pull&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; restart the ShinyProxy service with &lt;code&gt;service shinyproxy restart&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 2 and 3 happen on the server. But step 1 can be achieved in a few different ways. Let's see what those are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edit configuration on the server  
&lt;/h2&gt;

&lt;p&gt;The simplest approach is to edit the YAML file directly on the server. Open the file with &lt;code&gt;nano&lt;/code&gt; or &lt;code&gt;vim&lt;/code&gt;, remove unwanted applications (I removed both demo apps from the &lt;code&gt;/etc/shinyproxy/application.yml&lt;/code&gt;), and add the new apps. I show the final &lt;code&gt;application.yml&lt;/code&gt; file here for reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Shiny Proxy&lt;/span&gt;
  &lt;span class="na"&gt;logo-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://hub.analythium.io/assets/logo/logo.png&lt;/span&gt;
  &lt;span class="na"&gt;landing-page&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
  &lt;span class="na"&gt;favicon-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;favicon.ico&lt;/span&gt;
  &lt;span class="na"&gt;heartbeat-rate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10000&lt;/span&gt;
  &lt;span class="na"&gt;heartbeat-timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60000&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;authentication&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;simple&lt;/span&gt;
  &lt;span class="na"&gt;admin-groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admins&lt;/span&gt;
  &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
    &lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admins&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
    &lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;users&lt;/span&gt;
  &lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:2375&lt;/span&gt;
    &lt;span class="na"&gt;port-range-start&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20000&lt;/span&gt;
  &lt;span class="na"&gt;specs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cor2d&lt;/span&gt;
    &lt;span class="na"&gt;display-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Correlation in 2D&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;App with 2D kernel density estimate&lt;/span&gt;
    &lt;span class="na"&gt;container-cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-e"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shiny::runApp('/home/app')"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;container-image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;analythium/correlation:v1&lt;/span&gt;
    &lt;span class="na"&gt;logo-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://hub.analythium.io/assets/apps/cor2d.png&lt;/span&gt;
    &lt;span class="na"&gt;access-groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cor3d&lt;/span&gt;
    &lt;span class="na"&gt;display-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Correlation in 3D&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;App with kernel density estimate with 3D RGL plot&lt;/span&gt;
    &lt;span class="na"&gt;container-cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-e"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shiny::runApp('/home/app')"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;container-image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;analythium/correlation:v2&lt;/span&gt;
    &lt;span class="na"&gt;logo-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://hub.analythium.io/assets/apps/cor3d.png&lt;/span&gt;
    &lt;span class="na"&gt;access-groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;admins&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shinyproxy.log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two new apps are the same as what you saw when discussing how to &lt;a href="https://hosting.analythium.io/update-existing-shiny-apps-in-shinyproxy/"&gt;update existing apps&lt;/a&gt;. The &lt;code&gt;access-groups: []&lt;/code&gt; value means that all authenticated users can access the 2D version of the apps.&lt;/p&gt;

&lt;p&gt;Pull the new Docker images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull analythium/correlation:v1
docker pull analythium/correlation:v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using a private Docker registry, you have to log in. It is best to generate a token with restricted scopes (i.e. pull-only privileges). You can use this token instead of your password, and it is easy to revoke the token any time without having to change your password at all the places you have used it. This is a one-time thing, the username and token will be saved and used later when login is required. Save the token into a file (the double space at the beginning of the line prevents the token to end up in bash history):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;echo &lt;/span&gt;your_token &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./token.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can pass the token via standard input, use your &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;registryname&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ./token.txt | docker login &lt;span class="nt"&gt;--username&lt;/span&gt; username &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; registryname
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, restart ShinyProxy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;service shinyproxy restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you were logged into the ShinyProxy server, now you should see &lt;em&gt;502 Bad Gateway&lt;/em&gt; until ShinyProxy restarts, then you'll be asked to log in again.&lt;/p&gt;

&lt;p&gt;This is what will happen with any of the logged-in users on the server. To avoid such disruptions, you can schedule the updates to times when there are no users logged in, or when the disruption is minimal and advertised to users in advance. You can also use &lt;a href="https://shinyproxy.io/documentation/configuration/#session-persistence"&gt;Redis in-memory database for session persistence&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The approach described here is a perfectly functional way of updating ShinyProxy configuration. But once the file is changed, it is harder going back unless you saved a backup copy somewhere. This is why using version control could be helpful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use git to edit and update the configuration
&lt;/h2&gt;

&lt;p&gt;Edit the &lt;code&gt;application.yml&lt;/code&gt; on your local machine and git push the changes to a remote repository. When you first update the configuration on the server, git clone the project you have under version control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/analythium/shiny-correlation.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next time, you can just &lt;code&gt;cd&lt;/code&gt; into the directory and &lt;code&gt;git pull&lt;/code&gt; the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;shiny-correlation
git pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the YAML configuration file into the &lt;code&gt;/etc/shinyproxy&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;application.yml /etc/shinyproxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pull the new images and restart ShinyProxy as in the previous section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull analythium/correlation:v1
docker pull analythium/correlation:v2

service shinyproxy restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach gets around the versioning issue we had before. But typing the new image names one by one seems tedious. Let's see if we can find some efficiencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Push configuration changes over SSH
&lt;/h2&gt;

&lt;p&gt;I am going to explain a workflow similar to the previous one but instead of pulling changes, I am going to use &lt;code&gt;ssh&lt;/code&gt; and &lt;code&gt;scp&lt;/code&gt; to transfer the file to the server and run the docker pull and ShinyProxy restart commands.&lt;/p&gt;

&lt;p&gt;Edit the &lt;code&gt;application.yml&lt;/code&gt; and commit the changes so you have the full history. Once your &lt;code&gt;application.yml&lt;/code&gt; is ready, &lt;code&gt;cd&lt;/code&gt; into the local directory where the configuration file is located and download the following &lt;a href="https://github.com/analythium/shinyproxy-1-click/blob/master/digitalocean/setup.sh"&gt;script file&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://raw.githubusercontent.com/analythium/shinyproxy-1-click/master/digitalocean/setup.sh &lt;span class="nt"&gt;-o&lt;/span&gt; setup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The general usage of the freshly downloaded &lt;code&gt;setup.sh&lt;/code&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash setup.sh &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/id_rsa &lt;span class="nt"&gt;-s&lt;/span&gt; root@ip_address &lt;span class="nt"&gt;-f&lt;/span&gt; application.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following command-line arguments need to be passed to the &lt;code&gt;setup.sh&lt;/code&gt; script:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;-i&lt;/code&gt;: path to your &lt;code&gt;ssh&lt;/code&gt; key&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;-s&lt;/code&gt;: user name (&lt;code&gt;root\&lt;/code&gt;) and the IP address, e.g. &lt;code&gt;user@178.128.235.125&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;-f&lt;/code&gt;: path and file name of the ShinyProxy config, e.g. &lt;code&gt;/path/to/application-new.yml&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The script then takes care of the steps in the following order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Copies the updated YAML to the server into the &lt;code&gt;/etc/shinyproxy&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt; pulls the Docker images listed in the YAML file: updates the ones already pulled before, and the ones newly added too&lt;/li&gt;
&lt;li&gt; restarts the ShinyProxy service&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the command for our actual YAML file inside the git repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash setup.sh &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/id_rsa &lt;span class="nt"&gt;-s&lt;/span&gt; root@&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; application.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see an output similar to this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;INFO] Copying application.yml to host
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Updating docker images according to application.yaml
v1: Pulling from analythium/correlation
420047682034: Pulling fs layer

...

7bdb658c82cf: Pull &lt;span class="nb"&gt;complete
&lt;/span&gt;Digest: sha256:175663b0be8c97743f7a5bd1960c59d3f09536cd762a0f6f0a5744ee55ddb7ce
Status: Downloaded newer image &lt;span class="k"&gt;for &lt;/span&gt;analythium/correlation:v1
docker.io/analythium/correlation:v1
v2: Pulling from analythium/correlation
420047682034: Already exists

...

b65c57a60c68: Pull &lt;span class="nb"&gt;complete
&lt;/span&gt;Digest: sha256:4eaa98afe32b88d4cce7ce5436956ecb38e94c776bee97244a8fc28cde8e415f
Status: Downloaded newer image &lt;span class="k"&gt;for &lt;/span&gt;analythium/correlation:v2
docker.io/analythium/correlation:v2
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Restarting ShinyProxy
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script parses the YAML configuration and finds the &lt;code&gt;container-image&lt;/code&gt; entries. This is done by the following script in the background:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'container-image:'&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/[^:]*://'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^[[:space:]]*//g'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; line&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;docker pull &lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$db&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check your ShinyProxy server, you should see the two new apps added:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_MN3b6ra--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-07-at-1.04.59-AM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_MN3b6ra--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-07-at-1.04.59-AM.png" alt="ShinyProxy listing the 2 new images after the updates"&gt;&lt;/a&gt;ShinyProxy listing the 2 new images after the updates&lt;/p&gt;

&lt;p&gt;Don't forget to shut down the server if you don't need it anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;We reviewed 3 ways of updating ShinyProxy. The 3rd script-based update of the &lt;code&gt;application.yml&lt;/code&gt; file is quite efficient if you have all the command line tools handy. Changes can be version-controlled, and you are one line away from an updated ShinyProxy server without navigating files and directories on the host machine. The script even scans the YAML file for all the Docker images that need to be pulled. The only thing to remember is to perform these updates at a time when it causes minimal disruption to users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://www.shinyproxy.io/documentation/configuration/"&gt;ShinyProxy documentation: configuration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.docker.com/docker-hub/access-tokens/"&gt;Managing access tokens on Docker Hub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://marketplace.digitalocean.com/apps/shinyproxy?refcode=a8041699739d"&gt;ShinyProxy 1-click app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://shinyproxy.io/documentation/configuration/#session-persistence"&gt;Session persistence with Redis&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>shinyproxy</category>
      <category>shiny</category>
      <category>docker</category>
      <category>rstats</category>
    </item>
    <item>
      <title>Update Existing Shiny Apps in ShinyProxy</title>
      <dc:creator>Peter Solymos</dc:creator>
      <pubDate>Mon, 09 Aug 2021 18:50:54 +0000</pubDate>
      <link>https://forem.com/analythium/update-existing-shiny-apps-in-shinyproxy-5aah</link>
      <guid>https://forem.com/analythium/update-existing-shiny-apps-in-shinyproxy-5aah</guid>
      <description>&lt;p&gt;&lt;em&gt;By: &lt;a href="https://hosting.analythium.io/author/peter/"&gt;Peter Solymos&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ShinyProxy runs containerized applications while providing enterprise features for free. Learn how to update apps without disrupting your users.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hosting.analythium.io/how-to-set-up-shinyproxy-to-host-shiny-apps/"&gt;ShinyProxy is one of the best self-hosting options&lt;/a&gt; out there when you need enterprise features, like authentication and app-level authorization, for your app. In this post, I explain how to manage existing applications. All this without restarting your ShinyProxy server and thus keeping all your users connected. There are a few important considerations to remember when working with Docker images.&lt;/p&gt;

&lt;p&gt;You can find the code for this tutorial in the &lt;a href="https://github.com/analythium"&gt;analythium&lt;/a&gt;/&lt;a href="https://github.com/analythium/shiny-correlation"&gt;shiny-correlation&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to consider when updating apps
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hosting.analythium.io/advanced-configuration-for-shinyproxy/"&gt;Remember how the apps are listed&lt;/a&gt; in the &lt;code&gt;application.yml&lt;/code&gt; file in the &lt;code&gt;/etc/shinyproxy&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;02_hello&lt;/span&gt;
    &lt;span class="na"&gt;display-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Demo Shiny App&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;App with sliders and file upload&lt;/span&gt;
    &lt;span class="na"&gt;container-cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-e"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shiny::runApp('/home/app')"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;container-image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;analythium/shinyproxy-demo:latest&lt;/span&gt;
    &lt;span class="na"&gt;access-groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;admins&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two ways of updating the apps in ShinyProxy. You can &lt;strong&gt;update only the Docker image&lt;/strong&gt;. This means that the &lt;code&gt;container-image&lt;/code&gt; tag and everything else in the config YAML file stays the same. Because the &lt;code&gt;application.yml&lt;/code&gt; file remains the same, there is &lt;strong&gt;no need to restart the ShinyProxy&lt;/strong&gt; service on the server.&lt;/p&gt;

&lt;p&gt;If you change anything in the &lt;code&gt;application.yml&lt;/code&gt; configuration file, you'll have to &lt;strong&gt;restart the ShinyProxy service&lt;/strong&gt; for these changes to take effect. This might include replacing a &lt;code&gt;container-image&lt;/code&gt; tag, adding new apps, or touching any of the general configurations.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deploying and managing ShinyProxy can get complex when many apps are used, especially when the configuration of ShinyProxy is often updated. When restarting a running ShinyProxy instance (in order to update its configuration), users will face a disconnect from their running applications. – &lt;a href="https://shinyproxy.io/documentation/deployment/"&gt;ShinyProxy Docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Restarting ShinyProxy would mean that all current users would lose their Shiny sessions and logged-in users would be logged out. This is a disruption one can avoid by managing the Docker images carefully. You can also schedule the updates to times when there are no users logged in, or when the disruption is minimal and advertised to users in advance.&lt;/p&gt;

&lt;p&gt;Let's focus on the first scenario. The rest of the post explains how you can manage your image tags to minimize disruption to your users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add an app to ShinyProxy
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/analythium"&gt;analythium&lt;/a&gt;/&lt;a href="https://github.com/analythium/shiny-correlation"&gt;shiny-correlation&lt;/a&gt; GitHub repository contains a simple Shiny app. This app is a bivariate version of the Hello Shiny histogram example. We will build this app locally, then deploy it to the ShinyProxy server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build the Docker image
&lt;/h3&gt;

&lt;p&gt;Let's build the Docker image without a tag, using the &lt;code&gt;-f&lt;/code&gt; flag to specify which Dockerfile to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile-v1 &lt;span class="nt"&gt;-t&lt;/span&gt; analythium/correlation &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The build process should be pretty fast because I specified a parent image that already contains all the dependencies. Here is the &lt;code&gt;Dockerfile-v1&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; analythium/shinyproxy-demo:latest&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /home/app/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./app-v1.R ./app.R&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you list the Docker images with &lt;code&gt;docker image ls&lt;/code&gt;, you see something interesting. The image has a tag called &lt;code&gt;:latest&lt;/code&gt;, we'll come back to this in a bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;REPOSITORY                   TAG                 IMAGE ID
analythium/correlation       latest              90732201668c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can test the image by running the &lt;code&gt;docker run -p 4000:3838 analythium/correlation&lt;/code&gt; command and visiting &lt;code&gt;http://localhost:4000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Change the sample size and the correlation and watch how the 2-dimensional estimates of the intensity are changing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z1V4vMpN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-03-at-1.51.08-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z1V4vMpN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-03-at-1.51.08-PM.png" alt="Shiny app: scatterplot and 2D density of 2 correlated variables"&gt;&lt;/a&gt;Shiny app: scatterplot and 2D density of 2 correlated variables&lt;/p&gt;

&lt;h3&gt;
  
  
  Add the app to ShinyProxy
&lt;/h3&gt;

&lt;p&gt;Follow the &lt;a href="https://hosting.analythium.io/how-to-set-up-shinyproxy-to-host-shiny-apps/"&gt;ShinyProxy setup instructions&lt;/a&gt; or use the &lt;a href="https://marketplace.digitalocean.com/apps/shinyproxy?refcode=a8041699739d"&gt;ShinyProxy 1-click app from the Digitalocean Marketplace&lt;/a&gt; to set up your ShinyProxy server.&lt;/p&gt;

&lt;p&gt;Push the image to the registry (&lt;code&gt;docker push analythium/correlation:latest&lt;/code&gt;), add it to the &lt;a href="https://hosting.analythium.io/advanced-configuration-for-shinyproxy/"&gt;ShinyProxy configuration&lt;/a&gt;, pull the image on the host machine, and restart the ShinyProxy service with &lt;code&gt;service shinyproxy restart&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update an existing app
&lt;/h2&gt;

&lt;p&gt;When the Docker image itself changes, but its name (including the tag) stays the same, you won't have to restart ShinyProxy. This can happen when you push a new version of the Docker image to your Docker registry, e.g. after updating the &lt;code&gt;analythium/correlation&lt;/code&gt; image.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update the Docker image
&lt;/h3&gt;

&lt;p&gt;The version 2 update of the Shiny app includes a 3D representation of the distribution using the wonderful &lt;a href="https://CRAN.R-project.org/package=rgl"&gt;rgl&lt;/a&gt; R package. Say, we want to tag the new image as &lt;code&gt;:v2&lt;/code&gt; because we'd like to keep both versions. We use &lt;code&gt;Dockerfile-v2&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; analythium/shinyproxy-demo:latest&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;install2.r &lt;span class="nt"&gt;-r&lt;/span&gt; http://cran.rstudio.com/ rgl
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /home/app/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./app-v2.R ./app.R&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Dockerfile copies version 2 of the Shiny app into the image and installs the rgl package.&lt;/p&gt;

&lt;p&gt;Let's build the new image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile-v2 &lt;span class="nt"&gt;-t&lt;/span&gt; analythium/correlation:v2 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now print out the list of Docker images with &lt;code&gt;docker image ls&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;REPOSITORY                   TAG                 IMAGE ID
analythium/correlation       v2                  9d98df8ea853
analythium/correlation       latest              90732201668c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have a new image tagged as &lt;code&gt;:v2&lt;/code&gt; but the previous image stayed the &lt;code&gt;:latest&lt;/code&gt;. This is counter-intuitive behaviour because we would think that the &lt;code&gt;:latest&lt;/code&gt; tag would point to the image that was built last. Instead, it means &lt;em&gt;"&lt;a href="https://medium.com/@mccode/the-misunderstood-docker-tag-latest-af3babfd6375"&gt;the last build/tag that ran without a specific tag/version specified&lt;/a&gt;".&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you push a new image with a tag which is neither empty nor ‘latest’, &lt;code&gt;:latest&lt;/code&gt; will not be affected or created. – &lt;a href="https://vsupalov.com/docker-latest-tag/"&gt;Vladislav Supalov&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Image tagging and versioning
&lt;/h3&gt;

&lt;p&gt;You need to be careful when omitting the image tag. Here is what you can do to version the images and to also keep the &lt;code&gt;:latest&lt;/code&gt; tag consistent with the intuitive notion of the latest image.&lt;/p&gt;

&lt;p&gt;Let's tag the previously created (version 1) image as &lt;code&gt;:v1&lt;/code&gt; using the &lt;a href="https://docs.docker.com/engine/reference/commandline/tag/"&gt;&lt;code&gt;docker tag&lt;/code&gt; command&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker tag analythium/correlation analythium/correlation:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;List and verify, &lt;code&gt;:latest&lt;/code&gt; is now an alias for &lt;code&gt;:v1&lt;/code&gt; because the image IDs match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;REPOSITORY                   TAG                 IMAGE ID
analythium/correlation       v2                  9d98df8ea853
analythium/correlation       latest              90732201668c
analythium/correlation       v1                  90732201668c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now rebuild the image without the tag (this should be quick due to caching), this will make the new image the &lt;code&gt;:latest&lt;/code&gt;. Then tag the latest image as the new version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile-v2 &lt;span class="nt"&gt;-t&lt;/span&gt; analythium/correlation &lt;span class="nb"&gt;.&lt;/span&gt;
docker tag analythium/correlation analythium/correlation:v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;List and verify again, the &lt;code&gt;:latest&lt;/code&gt; image ID matches &lt;code&gt;:v2&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;REPOSITORY                   TAG                 IMAGE ID
analythium/correlation       latest              9d98df8ea853
analythium/correlation       v2                  9d98df8ea853
analythium/correlation       v1                  90732201668c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the new image with &lt;code&gt;docker run -p 4000:3838 analythium/correlation&lt;/code&gt; and visit &lt;a href="http://localhost:4000/"&gt;&lt;code&gt;http://localhost:4000/&lt;/code&gt;&lt;/a&gt; to play with the 3D rendering of the correlated variables:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---jc6-P3Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-03-at-2.28.59-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---jc6-P3Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hosting.analythium.io/content/images/2021/08/Screen-Shot-2021-08-03-at-2.28.59-PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Push these images to the registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker push analythium/correlation
docker push analythium/correlation:v1
docker push analythium/correlation:v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update the app on the ShinyProxy server
&lt;/h3&gt;

&lt;p&gt;After this little Docker detour, we are on track to update the app in ShinyProxy because the latest tag will mean version 2:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;ssh&lt;/code&gt; into the ShinyProxy server as the root user&lt;/li&gt;
&lt;li&gt; pull the new version of the image with &lt;code&gt;docker pull analythium/correlation:latest&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you update the image, there is &lt;a href="https://support.openanalytics.eu/t/how-to-update-docker-image-for-shinyproxy/140/3"&gt;no need to restart the Docker or ShinyProxy services&lt;/a&gt;. The next time a user starts the application on your ShinyProxy installation, the new image will be used to launch a container.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Versioning your images is considered good practice&lt;/strong&gt;. It makes a lot of sense so that you can roll back changes when something unexpected happens.&lt;/p&gt;

&lt;p&gt;If you tag your images carefully and test the apps locally, you can use the &lt;code&gt;:latest&lt;/code&gt; image tag in your ShinyProxy configuration without much trouble. You can also &lt;strong&gt;roll back&lt;/strong&gt; to &lt;code&gt;:v1&lt;/code&gt; if anything unexpected comes up – you'll have to change the config and restart the service in this case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update multiple existing apps
&lt;/h2&gt;

&lt;p&gt;If you have multiple apps, you will have to pull the latest version for each. You can use a script for that, or use the following 1-liner to &lt;strong&gt;update all the Docker images&lt;/strong&gt; on your host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker images |grep &lt;span class="nt"&gt;-v&lt;/span&gt; REPOSITORY|awk &lt;span class="s1"&gt;'{print $1":"$2}'&lt;/span&gt;|xargs &lt;span class="nt"&gt;-L1&lt;/span&gt; docker pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes, you might want to &lt;a href="https://docs.docker.com/config/pruning/"&gt;&lt;strong&gt;remove dangling images&lt;/strong&gt;&lt;/a&gt; with &lt;code&gt;docker image prune -f&lt;/code&gt;. A dangling image is one that is not tagged and is not referenced by any container, i.e. intermediate layers, etc., which might not be used by the latest images. These can accumulate over time and take up space.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a Cron job
&lt;/h2&gt;

&lt;p&gt;You can set up a Cron job to periodically update the images on the server. Run &lt;code&gt;crontab -e&lt;/code&gt; as root to have access to the Cron utility. Pick an editor (e.g. &lt;code&gt;nano&lt;/code&gt;) if you haven't done so already and then add these lines to the bottom of the file and save it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Cleanup at 3:00am every Sunday&lt;/span&gt;
0 3 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 0 docker image prune &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Update all images at 1:00am every day&lt;/span&gt;
0 1 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; docker images |grep &lt;span class="nt"&gt;-v&lt;/span&gt; REPOSITORY|awk &lt;span class="s1"&gt;'{print $1":"$2}'&lt;/span&gt;|xargs &lt;span class="nt"&gt;-L1&lt;/span&gt; docker pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the settings using &lt;code&gt;crontab -l&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Cron jobs represent a polling type of update, which means we are regularly checking for updates. On one hand, if changes to the images are infrequent, there is no need for constant polling. On the other hand, setting Cron intervals too large might lead to missing important updates.&lt;/p&gt;

&lt;p&gt;Pick the frequency of updates with this in mind. Webhooks for existing apps and images are &lt;a href="https://blog.cloud-elements.com/webhooks-vs-polling-youre-better-than-this"&gt;considered a better alternative to polling&lt;/a&gt;, although webhooks require a bit more work that I am not covering here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Updating existing apps for your ShinyProxy server is straightforward. You are only one command away from updating all the already deployed apps at once – no disruption to your users. The next user who opens the app will see the updated version. However, be careful with tagging and versioning your images to avoid unwanted surprises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://medium.com/@mccode/the-misunderstood-docker-tag-latest-af3babfd6375"&gt;The misunderstood Docker tag: latest&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.docker.com/config/pruning/"&gt;Pruning unused Docker images&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-use-cron-to-automate-tasks-ubuntu-1804"&gt;Cron guide&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>shinyproxy</category>
      <category>shiny</category>
      <category>docker</category>
      <category>rstats</category>
    </item>
  </channel>
</rss>
