Diese Containerfile baut einen sicheren Shiny Server, ohne Root-Rechte auf dem Host.
Möglich macht das Podman. Container laufen darin ohne Daemon und rootless, unter der UID des startenden Benutzers.
Wer mehr über Podman wissen will: What is Podman.
Voraussetzungen
Wie man Podman installiert, steht in der Installationsdoku.
Der Betrieb ohne Root setzt Einträge in /etc/subuid und /etc/subgid für den eigenen Benutzer voraus. Die meisten Distributionen legen sie bei der Benutzeranlage an. Nachsehen lässt sich das mit:
grep "^$USER" /etc/subuid /etc/subgid# Output auf meinem PC/etc/subuid:stefan:100000:65536/etc/subgid:stefan:100000:65536Diese 65536 IDs bildet Podman im Container auf 0 bis 65535 ab. Deshalb kann darin ein Benutzer shiny existieren, ohne dass es ihn auf dem Host gibt. Bleibt die Ausgabe leer, fehlen die Einträge und der folgende Befehl legt sie für den gerade angemeldeten Benutzer an:
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USERContainerfile
Wer die Containerfile für Podman selbst schreibt, bestimmt, welche Pakete im Image landen und aus welcher Quelle sie kommen. Soll es schnell gehen, etwa zum Ausprobieren, ist ein fertiges Image wie rocker-org/shiny eine Alternative.
Diese Containerfile ist auf mein Twitter-Dashboard zugeschnitten, erkennbar an der Paketliste. highcharter zeichnet dort die Diagramme, igraph den Netzwerkgraphen. Für eigene Projekte können hier die jeweils benötigten Pakete eingetragen werden.
FROM mirror.gcr.io/library/debian:trixie-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get -y update \ && apt-get -y --no-install-recommends install gnupg2 ca-certificates \ && rm -rf /var/lib/apt/lists/*
RUN gpg --keyserver keyserver.ubuntu.com \ --recv-key '95C0FAF38DB3CCAD0C080A7BDC78B2DDEABC47B7' \ && gpg --armor --export '95C0FAF38DB3CCAD0C080A7BDC78B2DDEABC47B7' \ > /usr/share/keyrings/cran.asc \ && echo "deb [signed-by=/usr/share/keyrings/cran.asc]" \ "http://cloud.r-project.org/bin/linux/debian trixie-cran40/" \ > /etc/apt/sources.list.d/cran.list
RUN apt-get -y update && apt-get -y --no-install-recommends install \ r-base r-base-dev git wget cmake python3 locales \ libxml2-dev libcurl4-openssl-dev libuv1-dev \ && rm -rf /var/lib/apt/lists/*
RUN echo "de_DE.UTF-8 UTF-8" > /etc/locale.gen && locale-genENV LANG=de_DE.UTF-8
RUN R -q -e 'pkgs <- c("shiny", "rmarkdown", "data.table", "dplyr", \ "tidytext", "readr", "igraph", "shinydashboard", "XML", "curl", \ "rjson", "highcharter"); \ install.packages(pkgs, repos = "https://cran.rstudio.com/", \ Ncpus = parallel::detectCores()); \ fehlend <- setdiff(pkgs, rownames(installed.packages())); \ if (length(fehlend)) \ stop("Nicht installiert: ", paste(fehlend, collapse = ", "))'
COPY --chmod=0755 shiny-srv-build.sh /root/RUN /root/shiny-srv-build.sh
EXPOSE 3838USER shinyCMD ["shiny-server"]shiny-srv-build.sh
Das Skript baut Shiny Server aus dem Quelltext, legt den Benutzer shiny samt seiner Verzeichnisse an und entfernt nicht mehr benötigte Pakete wieder.
#!/bin/shset -eu
BUILD_DIR=/tmp/shiny-server-build
git clone --depth 1 https://github.com/rstudio/shiny-server.git "$BUILD_DIR"
cd "$BUILD_DIR"mkdir tmpcd tmp
# Private Node.js-Kopie, die Shiny Server mitbringt../external/node/install-node.shPATH="$BUILD_DIR/bin:$PATH"export PATH
cmake -DCMAKE_INSTALL_PREFIX=/usr/local ../makemkdir -p ../build(cd .. && ./bin/npm ci --omit-dev)make install
mkdir -p /etc/shiny-servercp ../config/default.config /etc/shiny-server/shiny-server.conf
ln -sf /usr/local/shiny-server/bin/shiny-server /usr/bin/shiny-server
useradd -r -m shiny
DIRS="/var/log/shiny-server /srv/shiny-server /var/lib/shiny-server"mkdir -p $DIRSchown -R shiny:shiny $DIRS
cd /rm -rf "$BUILD_DIR"
apt-get -y purge git cmake wget python3apt-get -y autoremoverm -rf /var/lib/apt/lists/*Beide Codeblöcke gehören in ein leeres Verzeichnis, unter den Namen Containerfile und shiny-srv-build.sh. Aus diesem Verzeichnis heraus baut podman build daraus ein Image namens shiny-server. podman run startet davon einen Container, der auf Port 3838 lauscht und das lokale Verzeichnis apps nach /srv/shiny-server im Container einbindet:
podman build -t shiny-server .podman run --rm -p 3838:3838 -v ./apps:/srv/shiny-server:Z shiny-serverAlles auf einmal
Wer sich das Anlegen der beiden Dateien ersparen will, zieht sie per git clone direkt aus einem Repo, das dieselbe Containerfile und dasselbe Skript enthält. Das Ergebnis ist identisch mit den beiden Dateien oben.
git clone https://github.com/hillingerst/shiny-server-podman.gitcd shiny-server-podmanpodman build -t shiny-server .podman run --rm -p 3838:3838 -v ./apps:/srv/shiny-server:Z shiny-serverDer Server antwortet auf http://localhost:3838/. Jedes weitere Unterverzeichnis von apps wird zu einer eigenen Anwendung unter dem gleichnamigen Pfad.