diff --git a/.github/workflows/extending.yaml b/.github/workflows/extending.yaml index 923aaf5..0e58407 100644 --- a/.github/workflows/extending.yaml +++ b/.github/workflows/extending.yaml @@ -22,13 +22,20 @@ jobs: run: | set -euo pipefail - # Find directories under extending that contain Dockerfile or Containerfile - entries=$(find extending -type f -name "Containerfile*" -o -name "Dockerfile*" | while read -r f; do - dir=$(dirname "$f") - name="${dir#extending/}" - containerfile=$(basename "$f") - echo "$name,$dir,$containerfile" - done | jq -R -s -c 'split("\n")[:-1] | map(split(",")) | map({name: .[0], directory: .[1], file: .[2]})') + # Plain extending examples: one entry per Containerfile or Dockerfile. + # Skip directories with a bakery.yaml — those are handled by the + # `bakery` job below via the shared bakery-build-pr workflow. + entries=$( + for dir in extending/*/; do + example_root=${dir%/} + [ -f "${example_root}/bakery.yaml" ] && continue + base=$(basename "$dir") + while IFS= read -r f; do + containerfile_dir=$(dirname "$f") + echo "${base},${containerfile_dir},$(basename "$f")" + done < <(find "$example_root" -type f \( -name "Containerfile*" -o -name "Dockerfile*" \)) + done | jq -R -s -c 'split("\n")[:-1] | map(split(",")) | map({name: .[0], directory: .[1], file: .[2]})' + ) # Create the matrix JSON in one step to avoid shell quoting issues matrix=$(echo "$entries" | jq -c '{include: .}') @@ -70,3 +77,12 @@ jobs: push: false context: ${{ matrix.directory }} file: ${{ matrix.directory }}/${{ matrix.file }} + + bakery: + name: Build with Bakery + permissions: + contents: read + packages: write + uses: posit-dev/images-shared/.github/workflows/bakery-build-pr.yml@main + with: + context: extending/posit-team diff --git a/README.md b/README.md index f33de8f..d7babe3 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Are you customizing one image or managing many? - [Bakery](./bakery/): The Posit [templating system](https://github.com/posit-dev/images-shared/tree/main/posit-bakery) for managing matrices of container images across multiple R versions, Python versions, OS variants, and product versions. Use Bakery to maintain a fleet of custom images and rebuild them consistently. Posit uses the same tool to build the official product images. +- [Fleet extension example](./extending/posit-team/): Manage customized Workbench, Connect, and Package Manager images together in one Bakery project. + Looking for something else? | Goal | Where to go | diff --git a/bakery/README.md b/bakery/README.md index 29d17bc..a218026 100644 --- a/bakery/README.md +++ b/bakery/README.md @@ -13,6 +13,8 @@ Posit Bakery is the Jinja2-based templating system from Posit for managing conta | [05-images-with-managed-dependencies](./05-images-with-managed-dependencies/) | Automatic version management of R, Python, and Quarto using dependency constraints | | [06-matrix-images](./06-matrix-images/) | Build multiple image variants from dependency combinations using Docker build arguments | +For a recipe that uses Bakery to manage a fleet of customized Posit product images, see [`extending/posit-team`](../extending/posit-team/). + ## Additional resources ### Posit Bakery documentation and resources diff --git a/extending/README.md b/extending/README.md index 4485441..02472c3 100644 --- a/extending/README.md +++ b/extending/README.md @@ -2,37 +2,38 @@ Posit product images are published in two variants: -- **Standard** (`std`) — Includes pre-installed versions of Python, R, and Quarto -- **Minimal** (`min`) — A lightweight base image without pre-installed languages, intended to be extended +- **Standard** (`std`): Includes pre-installed versions of Python, R, and Quarto +- **Minimal** (`min`): A lightweight base image without pre-installed languages, intended to be extended Product images are available on [Docker Hub](https://hub.docker.com/u/posit): -- [`posit/connect`](https://hub.docker.com/r/posit/connect) — [Posit Connect](https://github.com/posit-dev/images-connect) -- [`posit/connect-content`](https://hub.docker.com/r/posit/connect-content) — [Posit Connect content runtime](https://github.com/posit-dev/images-connect) -- [`posit/package-manager`](https://hub.docker.com/r/posit/package-manager) — [Posit Package Manager](https://github.com/posit-dev/images-package-manager) -- [`posit/workbench`](https://hub.docker.com/r/posit/workbench) — [Posit Workbench](https://github.com/posit-dev/images-workbench) -- [`posit/workbench-session`](https://hub.docker.com/r/posit/workbench-session) — [Posit Workbench session runtime](https://github.com/posit-dev/images-workbench) +- [`posit/connect`](https://hub.docker.com/r/posit/connect): [Posit Connect](https://github.com/posit-dev/images-connect) +- [`posit/connect-content`](https://hub.docker.com/r/posit/connect-content): [Posit Connect content runtime](https://github.com/posit-dev/images-connect) +- [`posit/package-manager`](https://hub.docker.com/r/posit/package-manager): [Posit Package Manager](https://github.com/posit-dev/images-package-manager) +- [`posit/workbench`](https://hub.docker.com/r/posit/workbench): [Posit Workbench](https://github.com/posit-dev/images-workbench) +- [`posit/workbench-session`](https://hub.docker.com/r/posit/workbench-session): [Posit Workbench session runtime](https://github.com/posit-dev/images-workbench) -> [!NOTE] -> For an alternative approach that uses the Bakery CLI to manage extended images as a project, see the [bakery examples](../bakery/). +## Built with Bakery -## Examples - -Examples are organized by product. Within each product folder a `README.md` explains which image to customize for different goals. - -| Path | Contents | -|:-----|:---------| -| [common](./common/) | Patterns that apply to any Posit product image | -| [workbench](./workbench/) | Examples for Posit Workbench images — with a guide to choosing the right image | -| [connect](./connect/) | Examples for Posit Connect images — with a guide to choosing the right image | - -### Common examples +These use [Bakery](../bakery/) to template and manage image definitions, typically across several Posit products or several versions at once. The rendered Containerfiles are committed alongside the templates, so a build does not require Bakery: `docker build` against the rendered file works the same way the static examples do. | Path | Example | |:-----|:--------| -| [common/ca-certificates](./common/ca-certificates/) | Add a custom CA certificate to the system trust store | -| [common/R](./common/R/) | Install specific versions of R and R packages | -| [common/python](./common/python/) | Install specific versions of Python and Python packages | -| [common/system-dependencies](./common/system-dependencies/) | Install system libraries required by R or Python packages | +| [posit-team](./posit-team/) | Manage a fleet of customized Workbench, Connect, and Package Manager images with shared R and Python pins | + +## Static image definitions + +Examples are organized by product. Within each product folder, a `README.md` explains which image to customize for different goals. + +| Path | Base product | Example | +|:-----|:-------------|:--------| +| [common/ca-certificates](./common/ca-certificates/) | Any | Add a custom CA certificate to the system trust store | +| [pip-conf](./pip-conf/) | Posit Connect | Add a custom `pip.conf` file to specify global pip settings | +| [pro-drivers](./pro-drivers/) | Posit Workbench | Install the Posit Pro Drivers (ODBC drivers) on a minimal product image | +| [common/python](./common/python/) | Any | Install specific versions of Python on a minimal product image
Install a list of Python packages in each Python version | +| [quarto](./quarto/) | Posit Connect | Install Quarto and TinyTeX on a minimal product image | +| [common/R](./common/R/) | Any | Install specific versions of R on a minimal product image
Install a list of R packages in each R version | +| [common/system-dependencies](./common/system-dependencies/) | Any | Install system dependencies required for additional libraries | +| [vs-code-extensions](./vs-code-extensions/) | Posit Workbench (Standard) | Pre-install a list of VS Code extensions | To configure a Python package index, use product admin settings rather than baking a `pip.conf` into the image: [Workbench](https://docs.posit.co/ide/server-pro/admin/python/package_installation.html#setting-a-python-package-index-for-sessions) · [Connect](https://docs.posit.co/connect/admin/python/package-management/#python-package-repositories) diff --git a/extending/posit-team/README.md b/extending/posit-team/README.md new file mode 100644 index 0000000..e001370 --- /dev/null +++ b/extending/posit-team/README.md @@ -0,0 +1,275 @@ +# Extending a fleet of Posit product images + +This example shows how a team can manage a small fleet of Posit product images on top of the official [Minimal](https://github.com/posit-dev/images/blob/main/docs/products/standard-vs-minimal.md) (`-min`) bases. You can version, customize, and rebuild three images (Posit Workbench, Posit Connect, and Posit Package Manager) as a single project. This keeps your development environment, deployment runtime, and package server in lockstep. + +The sibling [`extending/`](..) examples each show one customization of a single Posit image in a standalone Containerfile. This example covers the same kind of customization at fleet scale (multiple Posit products in one project), using [Posit Bakery](https://posit-dev.github.io/images-shared/) to manage rendering, versioning, and tagging across the fleet. The Bakery [tutorial examples](../../bakery/) cover its features in isolation. This one applies them to a realistic team setup. + +Run all command examples with `extending/posit-team/` as the working directory. + +Bakery commands can also use the `--context PATH` option to specify the path to the example directory when running from a different location. + +### Bakery documentation + +- [Bakery guide](https://posit-dev.github.io/images-shared/): project workflow and CLI concepts +- [Configuration reference](https://posit-dev.github.io/images-shared/configuration.html): `bakery.yaml`, images, versions, OSes, and dependency constraints +- [Templating and macros](https://posit-dev.github.io/images-shared/templating.html): template variables and package-installation macros + +## Structure + +```text +posit-team/ +├── bakery.yaml # Project config: 3 images, shared R and Python constraints +├── workbench/ +│ ├── template/ # Source templates +│ │ ├── Containerfile.ubuntu2404.jinja2 +│ │ ├── deps/ubuntu-24.04_packages.txt.jinja2 +│ │ ├── deps/{r,python}-packages.txt.jinja2 +│ │ └── test/goss.yaml.jinja2 +│ └── 2026.09/ # Generated files +│ ├── Containerfile.ubuntu2404 +│ ├── deps/ubuntu-24.04_packages.txt +│ ├── deps/{r,python}-packages.txt +│ └── test/goss.yaml +├── connect/ +│ ├── template/ +│ └── 2026.09/ +└── package-manager/ + ├── template/ + │ ├── Containerfile.ubuntu2404.jinja2 + │ ├── ca/Example-RootCA.crt # Static asset (copied as-is) + │ └── test/goss.yaml.jinja2 + └── 2026.09/ +``` + +## What this example builds + +| Image | Base | Adds | +|:------|:-----|:-----| +| `workbench:2026.09.0-174.pro3` | `posit/workbench:2026.09.0-174.pro3-ubuntu-24.04-min` | R 4.6.1, Python 3.14.7, team R + Python packages, spatial system deps | +| `connect:2026.09.0` | `posit/connect:2026.09.0-ubuntu-24.04-min` | Same R, Python, and packages as Workbench | +| `package-manager:2026.09.0` | `posit/package-manager:2026.09.0-ubuntu-24.04-min` | Internal certificate authority (CA) certificate in the system trust store | + +Package Manager does not host user code, so it gets a much lighter customization than Workbench and Connect. + +> Why install R 4.6.1 and Python 3.14.7 when the 2026.09 Standard images already contain them? This example deliberately uses Minimal bases to show how a team can own the language layer. The team can keep the versions aligned with the current Posit release, as here, or roll R and Python forward or hold them back independently of Posit. + +## Concepts + +### Fleet versioning maps to Posit product versions + +Each image's [`Image.Version`](https://posit-dev.github.io/images-shared/templating.html) is the Posit product version it extends. The `versions` and `subpath` fields are defined by the [image-version configuration](https://posit-dev.github.io/images-shared/configuration.html#imageversion) in Bakery: + +```jinja2 +FROM docker.io/posit/workbench:{{ Image.Version | tagSafe }}-ubuntu-24.04-min +``` + +When Posit releases a new product version, the team appends a new entry under `versions:` and runs `bakery update files`. The Posit product version is not a Bakery-managed dependency, so the team owns when to roll it forward (typically tracked in their own change process). + +Different products release on different cadences and Workbench includes build metadata in its version. In this example, Workbench uses `2026.09.0+174.pro3`, while Connect and Package Manager use `2026.09.0`. Each image's `versions:` list is independent, and `subpath: "2026.09"` keeps the generated files organized by the product release line. + +### Shared R and Python pinning across Workbench and Connect + +Workbench and Connect share R and Python pins so that anything a developer builds in Workbench will run in Connect without dependency surprises. The constraint declaration is identical between the two images: + +```yaml +images: + - name: workbench + dependencyConstraints: + - dependency: R + constraint: + latest: true + - dependency: python + constraint: + latest: true + versions: + - name: 2026.09.0+174.pro3 + subpath: "2026.09" + dependencies: + - dependency: R + version: "4.6.1" + - dependency: python + version: "3.14.7" + - name: connect + dependencyConstraints: # same as workbench + - dependency: R + constraint: + latest: true + - dependency: python + constraint: + latest: true + versions: + - name: 2026.09.0 + subpath: "2026.09" + dependencies: # same as workbench + - dependency: R + version: "4.6.1" + - dependency: python + version: "3.14.7" +``` + +`bakery create version` resolves an image's [dependency constraints](https://posit-dev.github.io/images-shared/configuration.html#dependencyconstraint) once, then writes the resolved values into that version's [`dependencies`](https://posit-dev.github.io/images-shared/configuration.html#dependencyversions) block. From that point on, the version is pinned, and re-running the command on a different day will not change the existing entry. + +Bakery does not enforce sync across images. `dependencyConstraints` is per-image, and two images with identical `latest: true` constraints will diverge if you create your versions on different days. Keeping `workbench` and `connect` aligned is part of the team's workflow, not something Bakery guarantees: + +- Create both versions in the same command sequence so the resolved R and Python land on the same values, or +- Resolve once for `workbench`, then copy the resolved `dependencies:` block into the new version of `connect` by hand. + +If the team adds more images later (e.g., a content runtime), the same constraint block is the starting point. The same manual-sync discipline applies. + +### Per-image customization where it matters + +Each image's template carries the customizations specific to that product: + +- `workbench`: installs the team system-package delta, R 4.6.1, Python 3.14.7, and team R and Python packages. +- `connect`: installs the same system packages and language packages as Workbench so apps developed in Workbench deploy cleanly. +- `package-manager`: adds a single CA certificate to the trust store. It does not install R or Python because Package Manager does not run user code. + +The fleet system-package file is a small addition to the Minimal base, not a copy of the Standard image package inventory. It covers the native libraries needed by the example's spatial and graphics packages: + +| Category | Packages | +|:---------|:---------| +| Spatial | `libgdal-dev`, `libgeos-dev`, `libproj-dev`, `libudunits2-dev` | +| Database and XML | `libsqlite3-dev`, `libxml2-dev`, `libcurl4-openssl-dev`, `libssl-dev` | +| Fonts and graphics | `libfontconfig1-dev`, `libfreetype-dev`, `libharfbuzz-dev`, `libfribidi-dev`, `libpng-dev`, `libtiff-dev`, `libjpeg-dev` | + +The current Workbench Minimal base already supplies its compiler toolchain. Connect Minimal does not include one. Add a build toolchain to the shared list if the fleet expects packages to compile from source rather than use the Posit Public Package Manager (P3M) binaries and Python wheels used by this example. + +Workbench and Connect templates are nearly identical because the team enforces that their dev and deploy environments match. The templates differ in the following ways: + +1. The base image (`posit/workbench:...` vs `posit/connect:...`) +2. The goss tests (one checks for `/usr/lib/rstudio-server/bin/rserver`, the other checks for `/opt/rstudio-connect/bin/connect`) + +If a team needs them to diverge (say, larger R libraries on Workbench for interactive work), the template separation makes that easy to do without affecting the other. + +### Package lists are duplicated, not shared + +The `ubuntu-24.04_packages.txt`, `r-packages.txt`, and `python-packages.txt` files under `workbench/template/deps/` and `connect/template/deps/` contain identical content. The system package filename makes the supported base OS explicit, matching the organization used by the product image repositories. Package Manager has no dependency list because its customization is only a certificate. Bakery has no built-in mechanism to share a deps file across images, so the team maintains the Workbench and Connect lists by hand. + +In practice, the diff in `git review` catches drift: if someone edits one file and not the other, the PR shows two diffs in different image trees, or just one. Both are immediately visible. For a two-image fleet that is tolerable. For a larger fleet, consider: + +- A pre-commit hook that fails if the deps files diverge. +- A `_shared/` directory with the canonical lists, then per-image template files that just `{% include %}` them (verify your Bakery version supports template paths outside the image's own `template/`). +- A separate script that regenerates per-image deps files from a single source. + +The example deliberately uses the simplest form to keep the structure obvious. The duplication is the cost of that simplicity. + +### Why use Bakery instead of standalone Containerfiles? + +The other [extending](..) examples show the standalone Containerfile approach. That is the right starting point for a single customization. Bakery becomes valuable when: + +- Multiple images need consistent R and Python versions +- Multiple Posit product versions need to coexist (e.g., maintaining `2026.09.0` and `2025.09.2` for a phased rollout) +- The team wants reproducible, version-controlled package lists per release +- Goss tests need to assert on resolved versions + +This example demonstrates all four. + +## Creation of this example + +```bash +# Initialize a new Bakery project +bakery create project + +# Create each image +bakery create image workbench +bakery create image connect +bakery create image package-manager + +# Edit bakery.yaml to add dependencyConstraints and team-specific config +# Edit each image's template/Containerfile.ubuntu2404.jinja2 to FROM the Posit -min base + +# Add the first version of each image, matching the Posit product version it extends +bakery create version workbench '2026.09.0+174.pro3' +bakery create version connect 2026.09.0 +bakery create version package-manager 2026.09.0 +``` + +## Building with the Bakery CLI + +See the [Bakery build workflow](https://posit-dev.github.io/images-shared/#step-4-build-the-images) for the corresponding CLI lifecycle. + +```bash +# Rerender templates after changes +bakery update files + +# Build everything +bakery build + +# Build a single image +bakery build --image workbench + +# Run goss tests for every image +bakery run dgoss +``` + +## Building directly with Docker + +Once rendered, each Containerfile is a normal Docker build context. The build context must be the example root (`extending/posit-team/`) because the Containerfile `COPY` instructions reference paths relative to it. + +```bash +docker buildx build \ + --load \ + -f workbench/2026.09/Containerfile.ubuntu2404 \ + -t ghcr.io/example-org/workbench:2026.09.0-174.pro3 \ + -t ghcr.io/example-org/workbench:latest \ + . +``` + +## Updating to a new Posit product version + +When a new Workbench version ships: + +1. Add a new entry under `workbench.versions:` in `bakery.yaml`, with the Posit product version as the `name`. +2. Run `bakery create version workbench ` (or `bakery update files` if you wrote the version entry by hand). +3. Bakery resolves the current `latest` R and Python and writes them into the new version's `dependencies:` block. Existing versions stay pinned to their original R and Python values. +4. Repeat for `connect` with the same Posit product version (and matching R and Python pins). + +## Adding a fourth image to the fleet + +If the team wants to add, say, a content-runtime image: + +1. `bakery create image content-runtime` +2. Copy `dependencyConstraints` from `workbench` to keep R and Python aligned. +3. Decide on a base, likely `posit/connect-content:-min`, or extend from `connect` directly. +4. Add a version and customize the template. + +The pattern scales because each image is independently described but participates in the same `bakery update files` and `bakery build` lifecycle. + +## Production considerations + +This example is the starting point, not the destination. Before running this in production, decide on each of the following. + +### Pin the package repository to a date, not `latest` + +The rendered Containerfiles install R packages from `https://p3m.dev/cran/__linux__/noble/latest`. `latest` floats: every rebuild pulls whatever P3M serves that day. For reproducible images, swap to a [P3M snapshot URL](https://docs.posit.co/rspm/admin/serving-binaries/#package-binary-urls) with a fixed date (e.g., `https://p3m.dev/cran/__linux__/noble/2026-09-15`). The team chooses when to bump the snapshot, the same way they choose when to bump the Posit product version. + +The `r.run_install_packages` macro in Bakery takes the repo URL through its `_os` argument indirectly (it computes the URL from the OS codename). To pin to a snapshot, either bypass the macro and write the `install.packages` RUN command directly with the snapshot URL, or pass a custom `_os` dict whose `Codename` includes the date suffix. + +The Python install path has the same issue: pip resolves from PyPI's current state at build time. Pin via a constraints file or a private mirror. + +### `r.run_install_packages` with more than one R version + +The example resolves to a single R version because the `dependencyConstraints` block uses bare `latest: true`. If a team adds `count: 2` to install two R minor versions, the `r.run_install_packages` macro in Bakery will emit one `RUN install.packages ... && rm -f /tmp/r-packages.txt` per version. The second `RUN` has nothing to read because the first deleted the file. + +Workarounds: write the per-version install loop directly in the template (skip the macro), or open an issue against bakery to expose the `clean` parameter on `run_install_packages`. Python has the same caveat with `count: 2`, but `python.run_install_packages` does expose `clean`, so passing `clean=False` plus a manual `RUN rm` works there. + +### Tag your images with the team's own version, not just the Posit product version + +This example tags each image with the Posit product version it extends (`workbench:2026.09.0-174.pro3`). That is the cleanest mapping for a team that rebuilds when Posit ships and never customizes mid-cycle. If your team adds packages or system deps between Posit releases, append a team-side suffix (`workbench:2026.09.0-2`, or `workbench:2026.09.0-team-r3`). Bakery does not have an opinion here. Set `name:` under `versions:` to whatever the team's tagging scheme requires. + +## Template variables + +| Variable | Description | Example | +|:---------|:------------|:--------| +| `{{ Image.Version }}` | Posit product version this image extends | `"2026.09.0+174.pro3"` | +| `{{ Path.Version }}` | Path to the version directory | `"workbench/2026.09"` | +| `{{ Dependencies.R }}` | Resolved R versions | `["4.6.1"]` | +| `{{ Dependencies.python }}` | Resolved Python versions | `["3.14.7"]` | + +See the [Bakery templating and macros reference](https://posit-dev.github.io/images-shared/templating.html) for the full reference. + +## Related examples + +- [extending/](..): the standalone Containerfile siblings of this example. Start there if you only need to customize one image. +- [bakery/01-basic-image](../../bakery/01-basic-image/): the simplest possible Bakery project, on a stock OS base. Useful for understanding the templating mechanics this example builds on. +- [bakery/05-images-with-managed-dependencies](../../bakery/05-images-with-managed-dependencies/): the `dependencyConstraints` mechanism used here to resolve R and Python. diff --git a/extending/posit-team/bakery.yaml b/extending/posit-team/bakery.yaml new file mode 100644 index 0000000..2eda393 --- /dev/null +++ b/extending/posit-team/bakery.yaml @@ -0,0 +1,89 @@ +####################################################################################################################### +# BAKERY REPOSITORY CONFIG +# +# This file is used to define global properties for builds inside a repo managed by Posit's Bakery tool. +####################################################################################################################### + +# Define properties of the local repository. This information will be used when labeling image builds. +repository: + url: "github.com/posit-dev/images-examples" + vendor: "Posit Software, PBC" + maintainer: "Posit Docker Team " + +# Define image registries used by builds in this repository. Images will be tagged appropriately for each registry. +# Logins for private registries should be managed independently of Bakery. +# +# `example-org` is a placeholder. Replace it with your team's namespace, for example +# `ghcr.io/your-org` or `123456789012.dkr.ecr.us-east-1.amazonaws.com/your-team`. +registries: + - host: "ghcr.io" + namespace: "example-org" + +# Each image extends a Posit product Minimal (`-min`) image. The image version matches the Posit +# product version it extends, so `workbench:2026.09.0-174.pro3` is built FROM +# `posit/workbench:2026.09.0-174.pro3-ubuntu-24.04-min`. When Posit releases a new product +# version, add a new entry under `versions` and run `bakery update files` to render it. +# +# The Workbench and Connect images share the same R and Python dependency constraints so a team's +# development environment (Workbench) stays in lockstep with its deployment target (Connect). +# Version-specific OS declarations keep Bakery's tags and package metadata complete. +images: + - name: workbench + dependencyConstraints: + - dependency: R + constraint: + latest: true + - dependency: python + constraint: + latest: true + versions: + - name: 2026.09.0+174.pro3 + subpath: "2026.09" + latest: true + os: + - name: Ubuntu 24.04 + primary: true + platforms: + - linux/amd64 + - linux/arm64 + dependencies: + - dependency: R + version: "4.6.1" + - dependency: python + version: "3.14.7" + + - name: connect + dependencyConstraints: + - dependency: R + constraint: + latest: true + - dependency: python + constraint: + latest: true + versions: + - name: 2026.09.0 + subpath: "2026.09" + latest: true + os: + - name: Ubuntu 24.04 + primary: true + platforms: + - linux/amd64 + - linux/arm64 + dependencies: + - dependency: R + version: "4.6.1" + - dependency: python + version: "3.14.7" + + - name: package-manager + versions: + - name: 2026.09.0 + subpath: "2026.09" + latest: true + os: + - name: Ubuntu 24.04 + primary: true + platforms: + - linux/amd64 + - linux/arm64 diff --git a/extending/posit-team/connect/2026.09/Containerfile.ubuntu2404 b/extending/posit-team/connect/2026.09/Containerfile.ubuntu2404 new file mode 100644 index 0000000..75f4bf7 --- /dev/null +++ b/extending/posit-team/connect/2026.09/Containerfile.ubuntu2404 @@ -0,0 +1,49 @@ +# Build Python using uv in a separate stage so the final image only contains the +# managed CPython installs, not the uv toolchain. +FROM ghcr.io/astral-sh/uv:debian-slim AS python-builder + +ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy +ENV UV_PYTHON_INSTALL_DIR=/opt/python +ENV UV_PYTHON_PREFERENCE=only-managed +RUN uv python install 3.14.7 +RUN mv /opt/python/cpython-3.14.7-linux-*/ /opt/python/3.14.7 + + +FROM docker.io/posit/connect:2026.09.0-ubuntu-24.04-min +LABEL org.opencontainers.image.base.name="docker.io/posit/connect:2026.09.0-ubuntu-24.04-min" + +### ARG declarations ### +ARG DEBIAN_FRONTEND=noninteractive + +### Install team system dependencies ### +# The Posit Minimal images already have apt configured with the Posit Pro repo +# and ship curl, ca-certificates, gnupg, and tar. Only the team-specific system +# packages need to be installed here. +COPY connect/2026.09/deps/ubuntu-24.04_packages.txt /tmp/ubuntu-24.04_packages.txt +RUN apt-get update -yqq && \ + xargs -a /tmp/ubuntu-24.04_packages.txt apt-get install -yqq --no-install-recommends && \ + apt-get clean -yqq && \ + rm -rf /var/lib/apt/lists/* + +### Install Python from the build stage ### +COPY --from=python-builder /opt/python /opt/python + +### Install R ### +RUN apt-get update -yqq && \ + apt-get install -yqq --no-install-recommends \ + r-4.6.1 && \ + apt-get clean -yqq && \ + rm -rf /var/lib/apt/lists/* + +### Install team R packages ### +# These versions and packages match workbench so apps developed in +# Workbench deploy cleanly to Connect without dependency surprises. +COPY connect/2026.09/deps/r-packages.txt /tmp/r-packages.txt +RUN /opt/R/4.6.1/bin/R --vanilla -e 'install.packages(readLines("/tmp/r-packages.txt"), repos="https://p3m.dev/cran/__linux__/noble/latest", clean = TRUE)' && \ + rm -f /tmp/r-packages.txt + +### Install team Python packages ### +COPY connect/2026.09/deps/python-packages.txt /tmp/python-packages.txt +RUN /opt/python/3.14.7/bin/pip install --no-cache-dir --upgrade --break-system-packages \ + -r /tmp/python-packages.txt && \ + rm -f /tmp/python-packages.txt diff --git a/extending/posit-team/connect/2026.09/deps/python-packages.txt b/extending/posit-team/connect/2026.09/deps/python-packages.txt new file mode 100644 index 0000000..a2c5dd1 --- /dev/null +++ b/extending/posit-team/connect/2026.09/deps/python-packages.txt @@ -0,0 +1,9 @@ +fastapi +ipykernel +jupyter +pandas +plotly +polars +scikit-learn +shiny +uvicorn diff --git a/extending/posit-team/connect/2026.09/deps/r-packages.txt b/extending/posit-team/connect/2026.09/deps/r-packages.txt new file mode 100644 index 0000000..cf75678 --- /dev/null +++ b/extending/posit-team/connect/2026.09/deps/r-packages.txt @@ -0,0 +1,9 @@ +dplyr +ggplot2 +lubridate +plumber +readr +rmarkdown +sf +shiny +tidyr diff --git a/extending/posit-team/connect/2026.09/deps/ubuntu-24.04_packages.txt b/extending/posit-team/connect/2026.09/deps/ubuntu-24.04_packages.txt new file mode 100644 index 0000000..411523f --- /dev/null +++ b/extending/posit-team/connect/2026.09/deps/ubuntu-24.04_packages.txt @@ -0,0 +1,15 @@ +libudunits2-dev +libgdal-dev +libgeos-dev +libproj-dev +libsqlite3-dev +libxml2-dev +libcurl4-openssl-dev +libssl-dev +libfontconfig1-dev +libfreetype-dev +libharfbuzz-dev +libfribidi-dev +libpng-dev +libtiff-dev +libjpeg-dev diff --git a/extending/posit-team/connect/2026.09/test/goss.yaml b/extending/posit-team/connect/2026.09/test/goss.yaml new file mode 100644 index 0000000..6258ff1 --- /dev/null +++ b/extending/posit-team/connect/2026.09/test/goss.yaml @@ -0,0 +1,25 @@ +file: + /opt/R/4.6.1/bin/R: + exists: true + /opt/python/3.14.7/bin/python: + exists: true + /opt/rstudio-connect/bin/connect: + exists: true + +command: + "Verify R 4.6.1 reports the correct version": + exec: /opt/R/4.6.1/bin/R --version + exit-status: 0 + stdout: + - "R version 4.6.1" + "Verify shiny is installed for R 4.6.1": + exec: /opt/R/4.6.1/bin/R -e 'library(shiny); cat(as.character(packageVersion("shiny")))' + exit-status: 0 + "Verify Python 3.14.7 reports the correct version": + exec: /opt/python/3.14.7/bin/python --version + exit-status: 0 + stdout: + - "3.14.7" + "Verify pandas is installed for Python 3.14.7": + exec: /opt/python/3.14.7/bin/python -c "import pandas; print(pandas.__version__)" + exit-status: 0 diff --git a/extending/posit-team/connect/template/Containerfile.ubuntu2404.jinja2 b/extending/posit-team/connect/template/Containerfile.ubuntu2404.jinja2 new file mode 100644 index 0000000..9ed3ab6 --- /dev/null +++ b/extending/posit-team/connect/template/Containerfile.ubuntu2404.jinja2 @@ -0,0 +1,44 @@ +{#- +Below are imports for Bakery's macros. If any are unneeded, they can be removed. +Bakery handles bootstrapping the files into template rendering. +-#} +{%- import "apt.j2" as apt -%} +{%- import "python.j2" as python -%} +{%- import "r.j2" as r -%} + +# Build Python using uv in a separate stage so the final image only contains the +# managed CPython installs, not the uv toolchain. +{{ python.build_stage(Dependencies.python) }} + +FROM docker.io/posit/connect:{{ Image.Version | tagSafe }}-ubuntu-24.04-min +LABEL org.opencontainers.image.base.name="docker.io/posit/connect:{{ Image.Version | tagSafe }}-ubuntu-24.04-min" + +### ARG declarations ### +ARG DEBIAN_FRONTEND=noninteractive + +### Install team system dependencies ### +# The Posit Minimal images already have apt configured with the Posit Pro repo +# and ship curl, ca-certificates, gnupg, and tar. Only the team-specific system +# packages need to be installed here. +COPY {{ Path.Version }}/deps/ubuntu-24.04_packages.txt /tmp/ubuntu-24.04_packages.txt +{{ apt.run_install(files=["/tmp/ubuntu-24.04_packages.txt"]) }} + +### Install Python from the build stage ### +{{ python.copy_from_build_stage() }} + +### Install R ### +{{ r.run_install(Dependencies.R) }} + +### Install team R packages ### +# These versions and packages match workbench so apps developed in +# Workbench deploy cleanly to Connect without dependency surprises. +COPY {{ Path.Version }}/deps/r-packages.txt /tmp/r-packages.txt +{{ r.run_install_packages( + Dependencies.R, + package_list_files=["/tmp/r-packages.txt"], + _os={"Name": "ubuntu", "Codename": "noble", "Version": "24.04", "Family": "debian"} +) }} + +### Install team Python packages ### +COPY {{ Path.Version }}/deps/python-packages.txt /tmp/python-packages.txt +{{ python.run_install_packages(Dependencies.python, requirements_file="/tmp/python-packages.txt") }} diff --git a/extending/posit-team/connect/template/deps/python-packages.txt.jinja2 b/extending/posit-team/connect/template/deps/python-packages.txt.jinja2 new file mode 100644 index 0000000..43933cf --- /dev/null +++ b/extending/posit-team/connect/template/deps/python-packages.txt.jinja2 @@ -0,0 +1,11 @@ +{#- Must match workbench/deps/python-packages.txt so apps developed in -#} +{#- Workbench deploy cleanly to Connect. -#} +fastapi +ipykernel +jupyter +pandas +plotly +polars +scikit-learn +shiny +uvicorn diff --git a/extending/posit-team/connect/template/deps/r-packages.txt.jinja2 b/extending/posit-team/connect/template/deps/r-packages.txt.jinja2 new file mode 100644 index 0000000..2ed74ce --- /dev/null +++ b/extending/posit-team/connect/template/deps/r-packages.txt.jinja2 @@ -0,0 +1,11 @@ +{#- Must match workbench/deps/r-packages.txt so apps developed in Workbench -#} +{#- deploy cleanly to Connect. -#} +dplyr +ggplot2 +lubridate +plumber +readr +rmarkdown +sf +shiny +tidyr diff --git a/extending/posit-team/connect/template/deps/ubuntu-24.04_packages.txt.jinja2 b/extending/posit-team/connect/template/deps/ubuntu-24.04_packages.txt.jinja2 new file mode 100644 index 0000000..0069aa8 --- /dev/null +++ b/extending/posit-team/connect/template/deps/ubuntu-24.04_packages.txt.jinja2 @@ -0,0 +1,17 @@ +{#- System packages must match workbench so that any compiled R or Python -#} +{#- package built against these libraries works in both Workbench and Connect. -#} +libudunits2-dev +libgdal-dev +libgeos-dev +libproj-dev +libsqlite3-dev +libxml2-dev +libcurl4-openssl-dev +libssl-dev +libfontconfig1-dev +libfreetype-dev +libharfbuzz-dev +libfribidi-dev +libpng-dev +libtiff-dev +libjpeg-dev diff --git a/extending/posit-team/connect/template/test/goss.yaml.jinja2 b/extending/posit-team/connect/template/test/goss.yaml.jinja2 new file mode 100644 index 0000000..b2b9332 --- /dev/null +++ b/extending/posit-team/connect/template/test/goss.yaml.jinja2 @@ -0,0 +1,35 @@ +{%- import "python.j2" as python -%} +{%- import "r.j2" as r -%} +file: +{%- for r_version in Dependencies.R %} + /opt/R/{{ r_version }}/bin/R: + exists: true +{%- endfor %} +{%- for py_version in Dependencies.python %} + /opt/python/{{ py_version }}/bin/python: + exists: true +{%- endfor %} + /opt/rstudio-connect/bin/connect: + exists: true + +command: +{%- for r_version in Dependencies.R %} + "Verify R {{ r_version }} reports the correct version": + exec: {{ r.get_version_directory(r_version) }}/bin/R --version + exit-status: 0 + stdout: + - "R version {{ r_version }}" + "Verify shiny is installed for R {{ r_version }}": + exec: {{ r.get_version_directory(r_version) }}/bin/R -e 'library(shiny); cat(as.character(packageVersion("shiny")))' + exit-status: 0 +{%- endfor %} +{%- for py_version in Dependencies.python %} + "Verify Python {{ py_version }} reports the correct version": + exec: {{ python.get_version_directory(py_version) }}/bin/python --version + exit-status: 0 + stdout: + - "{{ py_version }}" + "Verify pandas is installed for Python {{ py_version }}": + exec: {{ python.get_version_directory(py_version) }}/bin/python -c "import pandas; print(pandas.__version__)" + exit-status: 0 +{%- endfor %} diff --git a/extending/posit-team/package-manager/2026.09/Containerfile.ubuntu2404 b/extending/posit-team/package-manager/2026.09/Containerfile.ubuntu2404 new file mode 100644 index 0000000..73a1b45 --- /dev/null +++ b/extending/posit-team/package-manager/2026.09/Containerfile.ubuntu2404 @@ -0,0 +1,10 @@ +FROM docker.io/posit/package-manager:2026.09.0-ubuntu-24.04-min +LABEL org.opencontainers.image.base.name="docker.io/posit/package-manager:2026.09.0-ubuntu-24.04-min" + +### Add the team's internal CA to the system trust store ### +# The Posit Package Manager image runs as the unprivileged `rstudio-pm` user. +# Switch to root only for the trust-store update, then back. +USER root +COPY package-manager/2026.09/ca/Example-RootCA.crt /usr/local/share/ca-certificates/Example-RootCA.crt +RUN update-ca-certificates +USER rstudio-pm diff --git a/extending/posit-team/package-manager/2026.09/ca/Example-RootCA.crt b/extending/posit-team/package-manager/2026.09/ca/Example-RootCA.crt new file mode 100644 index 0000000..302e2a2 --- /dev/null +++ b/extending/posit-team/package-manager/2026.09/ca/Example-RootCA.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +s3+J1kOvkQ== +-----END CERTIFICATE----- diff --git a/extending/posit-team/package-manager/2026.09/test/goss.yaml b/extending/posit-team/package-manager/2026.09/test/goss.yaml new file mode 100644 index 0000000..00497ec --- /dev/null +++ b/extending/posit-team/package-manager/2026.09/test/goss.yaml @@ -0,0 +1,17 @@ +file: + /usr/local/share/ca-certificates/Example-RootCA.crt: + exists: true + owner: root + group: root + mode: "0644" + /etc/ssl/certs/Example-RootCA.pem: + exists: true + /opt/rstudio-pm/bin/rstudio-pm: + exists: true + +command: + "Verify the team CA is in the system trust store": + exec: openssl x509 -in /etc/ssl/certs/Example-RootCA.pem -noout -subject + exit-status: 0 + stdout: + - "Example-RootCA" diff --git a/extending/posit-team/package-manager/template/Containerfile.ubuntu2404.jinja2 b/extending/posit-team/package-manager/template/Containerfile.ubuntu2404.jinja2 new file mode 100644 index 0000000..d720048 --- /dev/null +++ b/extending/posit-team/package-manager/template/Containerfile.ubuntu2404.jinja2 @@ -0,0 +1,17 @@ +{#- +The Package Manager image typically needs a different set of customizations than +Workbench or Connect. It does not host user R/Python code, so installing extra +language runtimes adds weight without benefit. The most common change is adding +internal CA certificates so Package Manager can pull upstream sources and reach +internal registries over TLS. +-#} +FROM docker.io/posit/package-manager:{{ Image.Version }}-ubuntu-24.04-min +LABEL org.opencontainers.image.base.name="docker.io/posit/package-manager:{{ Image.Version }}-ubuntu-24.04-min" + +### Add the team's internal CA to the system trust store ### +# The Posit Package Manager image runs as the unprivileged `rstudio-pm` user. +# Switch to root only for the trust-store update, then back. +USER root +COPY {{ Path.Version }}/ca/Example-RootCA.crt /usr/local/share/ca-certificates/Example-RootCA.crt +RUN update-ca-certificates +USER rstudio-pm diff --git a/extending/posit-team/package-manager/template/ca/Example-RootCA.crt b/extending/posit-team/package-manager/template/ca/Example-RootCA.crt new file mode 100644 index 0000000..302e2a2 --- /dev/null +++ b/extending/posit-team/package-manager/template/ca/Example-RootCA.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +s3+J1kOvkQ== +-----END CERTIFICATE----- diff --git a/extending/posit-team/package-manager/template/test/goss.yaml.jinja2 b/extending/posit-team/package-manager/template/test/goss.yaml.jinja2 new file mode 100644 index 0000000..00497ec --- /dev/null +++ b/extending/posit-team/package-manager/template/test/goss.yaml.jinja2 @@ -0,0 +1,17 @@ +file: + /usr/local/share/ca-certificates/Example-RootCA.crt: + exists: true + owner: root + group: root + mode: "0644" + /etc/ssl/certs/Example-RootCA.pem: + exists: true + /opt/rstudio-pm/bin/rstudio-pm: + exists: true + +command: + "Verify the team CA is in the system trust store": + exec: openssl x509 -in /etc/ssl/certs/Example-RootCA.pem -noout -subject + exit-status: 0 + stdout: + - "Example-RootCA" diff --git a/extending/posit-team/workbench/2026.09/Containerfile.ubuntu2404 b/extending/posit-team/workbench/2026.09/Containerfile.ubuntu2404 new file mode 100644 index 0000000..ca1dafb --- /dev/null +++ b/extending/posit-team/workbench/2026.09/Containerfile.ubuntu2404 @@ -0,0 +1,50 @@ +# Build Python using uv in a separate stage so the final image only contains the +# managed CPython installs, not the uv toolchain. +FROM ghcr.io/astral-sh/uv:debian-slim AS python-builder + +ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy +ENV UV_PYTHON_INSTALL_DIR=/opt/python +ENV UV_PYTHON_PREFERENCE=only-managed +RUN uv python install 3.14.7 +RUN mv /opt/python/cpython-3.14.7-linux-*/ /opt/python/3.14.7 + + +FROM docker.io/posit/workbench:2026.09.0-174.pro3-ubuntu-24.04-min +LABEL org.opencontainers.image.base.name="docker.io/posit/workbench:2026.09.0-174.pro3-ubuntu-24.04-min" + +### ARG declarations ### +ARG DEBIAN_FRONTEND=noninteractive + +### Install team system dependencies ### +# The Posit Minimal images already have apt configured with the Posit Pro repo +# and ship curl, ca-certificates, gnupg, and tar. Only the team-specific system +# packages need to be installed here. +COPY workbench/2026.09/deps/ubuntu-24.04_packages.txt /tmp/ubuntu-24.04_packages.txt +RUN apt-get update -yqq && \ + xargs -a /tmp/ubuntu-24.04_packages.txt apt-get install -yqq --no-install-recommends && \ + apt-get clean -yqq && \ + rm -rf /var/lib/apt/lists/* + +### Install Python from the build stage ### +COPY --from=python-builder /opt/python /opt/python + +### Install R ### +RUN apt-get update -yqq && \ + apt-get install -yqq --no-install-recommends \ + r-4.6.1 && \ + apt-get clean -yqq && \ + rm -rf /var/lib/apt/lists/* + +### Install team R packages ### +# Bakery's `r.run_install_packages` generates one `install.packages()` call per R +# version. The `_os` dict tells P3M to serve pre-built Ubuntu 24.04 (noble) binaries +# instead of compiling from source, which is much faster. +COPY workbench/2026.09/deps/r-packages.txt /tmp/r-packages.txt +RUN /opt/R/4.6.1/bin/R --vanilla -e 'install.packages(readLines("/tmp/r-packages.txt"), repos="https://p3m.dev/cran/__linux__/noble/latest", clean = TRUE)' && \ + rm -f /tmp/r-packages.txt + +### Install team Python packages ### +COPY workbench/2026.09/deps/python-packages.txt /tmp/python-packages.txt +RUN /opt/python/3.14.7/bin/pip install --no-cache-dir --upgrade --break-system-packages \ + -r /tmp/python-packages.txt && \ + rm -f /tmp/python-packages.txt diff --git a/extending/posit-team/workbench/2026.09/deps/python-packages.txt b/extending/posit-team/workbench/2026.09/deps/python-packages.txt new file mode 100644 index 0000000..a2c5dd1 --- /dev/null +++ b/extending/posit-team/workbench/2026.09/deps/python-packages.txt @@ -0,0 +1,9 @@ +fastapi +ipykernel +jupyter +pandas +plotly +polars +scikit-learn +shiny +uvicorn diff --git a/extending/posit-team/workbench/2026.09/deps/r-packages.txt b/extending/posit-team/workbench/2026.09/deps/r-packages.txt new file mode 100644 index 0000000..cf75678 --- /dev/null +++ b/extending/posit-team/workbench/2026.09/deps/r-packages.txt @@ -0,0 +1,9 @@ +dplyr +ggplot2 +lubridate +plumber +readr +rmarkdown +sf +shiny +tidyr diff --git a/extending/posit-team/workbench/2026.09/deps/ubuntu-24.04_packages.txt b/extending/posit-team/workbench/2026.09/deps/ubuntu-24.04_packages.txt new file mode 100644 index 0000000..411523f --- /dev/null +++ b/extending/posit-team/workbench/2026.09/deps/ubuntu-24.04_packages.txt @@ -0,0 +1,15 @@ +libudunits2-dev +libgdal-dev +libgeos-dev +libproj-dev +libsqlite3-dev +libxml2-dev +libcurl4-openssl-dev +libssl-dev +libfontconfig1-dev +libfreetype-dev +libharfbuzz-dev +libfribidi-dev +libpng-dev +libtiff-dev +libjpeg-dev diff --git a/extending/posit-team/workbench/2026.09/test/goss.yaml b/extending/posit-team/workbench/2026.09/test/goss.yaml new file mode 100644 index 0000000..0839f33 --- /dev/null +++ b/extending/posit-team/workbench/2026.09/test/goss.yaml @@ -0,0 +1,25 @@ +file: + /opt/R/4.6.1/bin/R: + exists: true + /opt/python/3.14.7/bin/python: + exists: true + /usr/lib/rstudio-server/bin/rserver: + exists: true + +command: + "Verify R 4.6.1 reports the correct version": + exec: /opt/R/4.6.1/bin/R --version + exit-status: 0 + stdout: + - "R version 4.6.1" + "Verify shiny is installed for R 4.6.1": + exec: /opt/R/4.6.1/bin/R -e 'library(shiny); cat(as.character(packageVersion("shiny")))' + exit-status: 0 + "Verify Python 3.14.7 reports the correct version": + exec: /opt/python/3.14.7/bin/python --version + exit-status: 0 + stdout: + - "3.14.7" + "Verify pandas is installed for Python 3.14.7": + exec: /opt/python/3.14.7/bin/python -c "import pandas; print(pandas.__version__)" + exit-status: 0 diff --git a/extending/posit-team/workbench/template/Containerfile.ubuntu2404.jinja2 b/extending/posit-team/workbench/template/Containerfile.ubuntu2404.jinja2 new file mode 100644 index 0000000..db52a2b --- /dev/null +++ b/extending/posit-team/workbench/template/Containerfile.ubuntu2404.jinja2 @@ -0,0 +1,45 @@ +{#- +Below are imports for Bakery's macros. If any are unneeded, they can be removed. +Bakery handles bootstrapping the files into template rendering. +-#} +{%- import "apt.j2" as apt -%} +{%- import "python.j2" as python -%} +{%- import "r.j2" as r -%} + +# Build Python using uv in a separate stage so the final image only contains the +# managed CPython installs, not the uv toolchain. +{{ python.build_stage(Dependencies.python) }} + +FROM docker.io/posit/workbench:{{ Image.Version | tagSafe }}-ubuntu-24.04-min +LABEL org.opencontainers.image.base.name="docker.io/posit/workbench:{{ Image.Version | tagSafe }}-ubuntu-24.04-min" + +### ARG declarations ### +ARG DEBIAN_FRONTEND=noninteractive + +### Install team system dependencies ### +# The Posit Minimal images already have apt configured with the Posit Pro repo +# and ship curl, ca-certificates, gnupg, and tar. Only the team-specific system +# packages need to be installed here. +COPY {{ Path.Version }}/deps/ubuntu-24.04_packages.txt /tmp/ubuntu-24.04_packages.txt +{{ apt.run_install(files=["/tmp/ubuntu-24.04_packages.txt"]) }} + +### Install Python from the build stage ### +{{ python.copy_from_build_stage() }} + +### Install R ### +{{ r.run_install(Dependencies.R) }} + +### Install team R packages ### +# Bakery's `r.run_install_packages` generates one `install.packages()` call per R +# version. The `_os` dict tells P3M to serve pre-built Ubuntu 24.04 (noble) binaries +# instead of compiling from source, which is much faster. +COPY {{ Path.Version }}/deps/r-packages.txt /tmp/r-packages.txt +{{ r.run_install_packages( + Dependencies.R, + package_list_files=["/tmp/r-packages.txt"], + _os={"Name": "ubuntu", "Codename": "noble", "Version": "24.04", "Family": "debian"} +) }} + +### Install team Python packages ### +COPY {{ Path.Version }}/deps/python-packages.txt /tmp/python-packages.txt +{{ python.run_install_packages(Dependencies.python, requirements_file="/tmp/python-packages.txt") }} diff --git a/extending/posit-team/workbench/template/deps/python-packages.txt.jinja2 b/extending/posit-team/workbench/template/deps/python-packages.txt.jinja2 new file mode 100644 index 0000000..8ad2c18 --- /dev/null +++ b/extending/posit-team/workbench/template/deps/python-packages.txt.jinja2 @@ -0,0 +1,12 @@ +{#- Python packages installed for every Python version in the image. Keep this list -#} +{#- in sync with connect/deps/python-packages.txt so apps developed in -#} +{#- Workbench deploy cleanly to Connect. -#} +fastapi +ipykernel +jupyter +pandas +plotly +polars +scikit-learn +shiny +uvicorn diff --git a/extending/posit-team/workbench/template/deps/r-packages.txt.jinja2 b/extending/posit-team/workbench/template/deps/r-packages.txt.jinja2 new file mode 100644 index 0000000..b84c272 --- /dev/null +++ b/extending/posit-team/workbench/template/deps/r-packages.txt.jinja2 @@ -0,0 +1,12 @@ +{#- R packages installed for every R version in the image. Keep this list in sync -#} +{#- with connect/deps/r-packages.txt so apps developed in Workbench deploy -#} +{#- cleanly to Connect. -#} +dplyr +ggplot2 +lubridate +plumber +readr +rmarkdown +sf +shiny +tidyr diff --git a/extending/posit-team/workbench/template/deps/ubuntu-24.04_packages.txt.jinja2 b/extending/posit-team/workbench/template/deps/ubuntu-24.04_packages.txt.jinja2 new file mode 100644 index 0000000..2c7ae34 --- /dev/null +++ b/extending/posit-team/workbench/template/deps/ubuntu-24.04_packages.txt.jinja2 @@ -0,0 +1,18 @@ +{#- System packages the team needs in addition to those already in the Posit Minimal image. -#} +{#- These cover common requirements for spatial R packages ({sf}, {terra}) and for content -#} +{#- authoring that touches large file formats and image processing. -#} +libudunits2-dev +libgdal-dev +libgeos-dev +libproj-dev +libsqlite3-dev +libxml2-dev +libcurl4-openssl-dev +libssl-dev +libfontconfig1-dev +libfreetype-dev +libharfbuzz-dev +libfribidi-dev +libpng-dev +libtiff-dev +libjpeg-dev diff --git a/extending/posit-team/workbench/template/test/goss.yaml.jinja2 b/extending/posit-team/workbench/template/test/goss.yaml.jinja2 new file mode 100644 index 0000000..12938e5 --- /dev/null +++ b/extending/posit-team/workbench/template/test/goss.yaml.jinja2 @@ -0,0 +1,35 @@ +{%- import "python.j2" as python -%} +{%- import "r.j2" as r -%} +file: +{%- for r_version in Dependencies.R %} + /opt/R/{{ r_version }}/bin/R: + exists: true +{%- endfor %} +{%- for py_version in Dependencies.python %} + /opt/python/{{ py_version }}/bin/python: + exists: true +{%- endfor %} + /usr/lib/rstudio-server/bin/rserver: + exists: true + +command: +{%- for r_version in Dependencies.R %} + "Verify R {{ r_version }} reports the correct version": + exec: {{ r.get_version_directory(r_version) }}/bin/R --version + exit-status: 0 + stdout: + - "R version {{ r_version }}" + "Verify shiny is installed for R {{ r_version }}": + exec: {{ r.get_version_directory(r_version) }}/bin/R -e 'library(shiny); cat(as.character(packageVersion("shiny")))' + exit-status: 0 +{%- endfor %} +{%- for py_version in Dependencies.python %} + "Verify Python {{ py_version }} reports the correct version": + exec: {{ python.get_version_directory(py_version) }}/bin/python --version + exit-status: 0 + stdout: + - "{{ py_version }}" + "Verify pandas is installed for Python {{ py_version }}": + exec: {{ python.get_version_directory(py_version) }}/bin/python -c "import pandas; print(pandas.__version__)" + exit-status: 0 +{%- endfor %}