Architecture overview

Local execution

In local execution mode, Posit Connect executes content using local processes running on the same Linux host as the Connect server process.

%%{init: {"look": "handDrawn" } }%%
graph LR
  subgraph host["Linux Host / Container"]
    connect["Posit Connect"]
    p1["Shiny App"]
    p2["Plumber API"]
    p3["Quarto Doc"]
    connect --> p1
    connect --> p2
    connect --> p3
  end

This style of deployment can happen with bare-metal servers, with virtual machines, or inside a container. Connect can be deployed within Kubernetes using this model, but all of its managed processes run within the same container.

Off-host execution

Advanced

Connect can use Kubernetes to execute Python, R, and other content types. These jobs are external to the server or container that is running Connect.

%%{init: {"look": "handDrawn" } }%%
graph TB
  subgraph cluster["Kubernetes Cluster"]
    connect["Posit Connect"]
    j1["Job: Shiny App"]
    j2["Job: Plumber API"]
    j3["Job: Quarto Doc"]
    connect --> j1
    connect --> j2
    connect --> j3
  end
  db[("PostgreSQL")]
  connect --- db
  nfs[("Shared Storage<br>(NFS / PVC)")]
  j1 & j2 & j3 --- nfs
  connect --- nfs

In this scenario, Connect launches a job to service requests for a Shiny application. That job uses an image appropriate for that content. For our Shiny example, Connect identifies an image that contains a version of R that is compatible with the version used during development. When the job is launched, the container is augmented with a handful of required runtime components using an init-container. The use of an init-container to prepare the runtime components means that the content image does not need to be rebuilt for a particular version of Connect. For more details about the init-container, see the Runtime Init-Container section below.

%%{init: {"look": "handDrawn", "wrap": false} }%%
graph LR
  subgraph cluster["Kubernetes"]
    connect@{ shape: processes, label: "<div style='white-space:nowrap; padding: 4px 40px 4px 8px'><b>Posit Connect (HA)</b><br><code>ghcr.io/posit-dev/connect</code></div>" }
    connectmounts["<div style='white-space:nowrap'>Volume Mounts<br><code>DataDir: /var/lib/rstudio-connect</code></div>"]
    pvc["Persistent Volume Claim (RWX)"]
    podmounts["<div style='white-space:nowrap'>Volume Mounts<br>
      AppDir: <code>/opt/rstudio-connect/mnt/app</code><br>
      JobDir: <code>/opt/rstudio-connect/mnt/job</code><br>
      Runtimes:
        <code>/opt/rstudio-connect/mnt/R</code><br>
        <code>/opt/rstudio-connect/mnt/python</code></div>"
    ]
    subgraph pod["Content Pod"]
      direction TB
      initimg["<div style='white-space:nowrap; padding: 0 12px'>Init Container<br><code>ghcr.io/posit-dev/connect-content-init</code></div>"]
      initimg -- "copy runtime binaries" --> contentimg
      contentimg["<div style='white-space:nowrap; padding: 0 12px'>Content Container<br><code>ghcr.io/posit-dev/connect-content:...</code></div>"]
    end
    connect --> connectmounts --> pvc --> podmounts --> pod
    db[("PostgreSQL<br><em>inside or outside cluster</em>")]
    nfs[("NFS<br><em>inside or outside cluster</em>")]
    connect --- db
    pvc --- nfs
  end

Using an approach very similar to process sandboxing, Connect instructs Kubernetes to mount storage locations into the containers that run your content. These directories contain the variable data associated with your content, such as the Python and R packages, the source code for your content, and directories for rendering output. These locations are normally mounted using NFS, but can also use host mounts. This approach lets you use smaller content images and share execution environments across multiple content items.

Containerized operation

When Connect is running in off-host execution mode, the change is transparent to users. They continue to deploy, view, and interact with content the same as before. However, when Connect launches a process to prepare a build environment (installing Python and R packages), render a document, or run an interactive application, it uses a Kubernetes container instead of a local process.

Connect automatically selects a compatible execution environment from the available set of environments. See Content Execution Environments to learn about the selection process and how content execution environments are defined.

Connect respects Runtime settings as usual, but each process runs in a separate container. For example, an application with Max processes set to 3 might result in up to 3 separate Kubernetes Jobs, each running one content process. Process logs are available in the Connect dashboard as usual.

The Processes tab in the System section of the Connect dashboard shows processes running across the cluster.

Note

Content process startup takes longer with off-host execution than with local processes. Image size and Kubernetes scheduler overhead contribute to this. This is most noticeable with interactive applications.

Runtime init-container

To make the maintenance of content images as seamless as possible for users, Posit Connect uses an init-container when running content on Kubernetes. The purpose of this initialization is to copy the runtime artifacts required by Connect into your container just before your content starts to execute. This means that the image being used to execute your content does not need to contain any Connect components, and therefore it does not need to be rebuilt after a Connect upgrade.

Posit maintains an init-container image which is 1:1 with the Connect version. When a content execution Pod is created in Kubernetes, Connect also creates an EmptyDir volume. This empty directory is used as a staging area for the runtime components. The init-container is then responsible for copying the runtime artifacts into the staging area so that they are also available to the content container.

%%{init: {"look": "handDrawn", "wrap": false} }%%
graph LR
  subgraph k8s["Kubernetes"]
    volumes["<div style='white-space:nowrap'><b>Volumes</b><br>
      <code>mount0:</code><br>
      &nbsp;&nbsp;Type: PersistentVolumeClaim<br>
      &nbsp;&nbsp;ClaimName: connect-data-pvc<br>
      &nbsp;&nbsp;ReadOnly: false<br>
      <code>connect-volume:</code><br>
      &nbsp;&nbsp;Type: EmptyDir</div>"]

    subgraph pod["Content Pod"]

      initc["<div style='white-space:nowrap'><b>Init Container (Connect managed)</b><br><code>ghcr.io/posit-dev/connect-content-init</code></div>"]
      initmounts["<div style='white-space:nowrap'><b>Init Container Mounts</b><br><code>/mnt/rstudio-connect-runtime/</code> from connect-volume (rw)</div>"]

      contentc["<div style='white-space:nowrap'><b>Content Container (Customer managed)</b><br><code>ghcr.io/posit-dev/connect-content:...</code></div>"]
      contentmounts["<div style='white-space:nowrap'><b>Content Container Mounts</b><br>
        <code>/opt/rstudio-connect</code> from connect-volume (rw)<br>
        <code>/opt/rstudio-connect/mnt/R</code> from mount0 (rw)<br>
        <code>/opt/rstudio-connect/mnt/app</code> from mount0 (rw)<br>
        <code>/opt/rstudio-connect/mnt/job</code> from mount0 (rw)<br>
        <code>/opt/rstudio-connect/mnt/python</code> from mount0 (rw)</div>"]

      execop["<div style='white-space:nowrap; text-align:left'><b>Execute Content</b><br>
        <code>/opt/rstudio-connect/ext/rsc-session</code><br>
        <code>&nbsp;&nbsp;-d /opt/rstudio-connect/mnt/job</code><br>
        <code>&nbsp;&nbsp;/opt/rstudio-connect/ext/env-manager</code><br>
        <code>&nbsp;&nbsp;/opt/R/4.4.3/bin/R</code><br>
        <code>&nbsp;&nbsp;/opt/rstudio-connect/R/run_app.R</code></div>"]
    end

    volumes --> initc
    initc -- "<div style='white-space:nowrap'><b>Copy Runtime Binaries</b><br><code>cp -r /opt/rstudio-connect-runtime /mnt/rstudio-connect-runtime</code></div>" --> contentc
    contentc --> execop
    initc -.- initmounts
    contentc -.- contentmounts
  end