Skip to main content
ClaudeWave
Skill488 repo starsupdated 8d ago

deploy-to-connect

>-

Install in Claude Code
Copy
git clone --depth 1 https://github.com/posit-dev/skills /tmp/deploy-to-connect && cp -r /tmp/deploy-to-connect/connect/deploy-to-connect ~/.claude/skills/deploy-to-connect
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

<!--
Maintainer note: edit this skill in posit-dev/connect only.
Downstream copies are overwritten by the sync workflow.
-->

# Deploying to Posit Connect

This guide covers Python and R content on a Posit Connect server. Work through the stages in order.

Two toolchains do the work:

- Python — [rsconnect-python](https://github.com/posit-dev/rsconnect-python), which provides the `rsconnect` CLI and is published on PyPI.
- R — the R [`rsconnect`](https://rstudio.github.io/rsconnect/) package, pointed at a Connect server.

If the user asks a question ("how do I…", "what is the command…") rather than asking for a deploy, answer from this guide and stop.

At the end, report which server you deployed to, which content type you picked, any tool you installed, and any assumption you made.

---

## Stage 1 — Detect the content

Infer the language and framework from the files in the project directory. Common signals:

| Signal in project dir | Likely content |
| --- | --- |
| `app.py` | Python web app — Shiny for Python, Streamlit, Dash, Gradio, Panel, or Bokeh |
| `app.R`, or `ui.R` + `server.R` | Shiny for R |
| `plumber.R` / `entrypoint.R` containing `plumb()` | Plumber API (R) |
| `*.qmd` | Quarto document |
| `*.Rmd` | R Markdown |
| `*.ipynb` | Jupyter notebook / Voila |
| `manifest.json` | Prebuilt bundle — deploy it directly, no framework guess needed |

The imports in `app.py` name the framework:

```console
grep -Eo 'import (shiny|streamlit|dash|gradio|panel|bokeh)|from (shiny|streamlit|dash|gradio|panel|bokeh)' app.py
```

A bare ASGI or WSGI object means `fastapi` or `flask`.

Dependency files confirm the language: `requirements.txt` and `pyproject.toml` for Python, `DESCRIPTION` and `renv.lock` for R.

If the content is ambiguous (both Python and R files, or an `app.py` with no recognizable import), use your discretion, and report the assumption you made.

---

## Stage 2 — Inventory your tools

Probe the environment and build a capability set:

```console
command -v rsconnect                                 # rsconnect-python on PATH
command -v uv                                        # uv (installs and runs Python tools)
uv tool list 2>/dev/null | grep rsconnect            # rsconnect-python installed via uv
command -v Rscript                                   # R present
Rscript -e 'cat(requireNamespace("rsconnect", quietly=TRUE))' 2>/dev/null   # R rsconnect package
command -v quarto                                     # quarto CLI
command -v git                                        # git
```

With `uv` present, Python content needs no install step. `uv tool run --from rsconnect-python rsconnect ...` fetches and runs the CLI on demand.

---

## Stage 3 — Pick a route

Cross the detected content (Stage 1) with your capabilities (Stage 2).

### Python content

Use rsconnect-python. With `rsconnect` on `PATH`:

```console
rsconnect deploy <framework> ./my-app
```

Off `PATH` but with `uv` present:

```console
uv tool run --from rsconnect-python rsconnect deploy <framework> ./my-app
```

Both forms take identical arguments. The rest of this guide writes the bare `rsconnect ...` form. Prefix it with `uv tool run --from rsconnect-python` when you use the second route.

`<framework>` is one of `api`, `bokeh`, `bundle`, `dash`, `fastapi`, `flask`, `git`, `gradio`, `html`, `manifest`, `nodejs`, `notebook`, `panel`, `pyproject`, `quarto`, `shiny`, `streamlit`, `tensorflow`, `voila`. For anything outside that list, `rsconnect deploy other-content` prints guidance.

The frameworks and flags depend on the installed version, so confirm against `rsconnect deploy --help` rather than this list. If `uv tool run` resolves a stale cached version, pin it: `uv tool run --from 'rsconnect-python==1.30.0' rsconnect ...`.

### R content

Use the R `rsconnect` package, through `Rscript -e '...'` or an R session:

- Shiny for R, Plumber API, or any app directory → `deployApp()`
- A single R Markdown or Quarto document → `deployDoc()`
- A full R Markdown or Quarto site → `deploySite()`

If `Rscript` is absent, deploy the R content through rsconnect-python with a `manifest.json`:

- A `manifest.json` already exists — deploy it directly:
  ```console
  rsconnect deploy manifest ./manifest.json
  ```
- No manifest, but R is available elsewhere — generate one first with `rsconnect::writeManifest()` (see Stage 5).
- Neither R nor a manifest — a valid R bundle is not possible. Surface this as a blocker: ask the user or report it clearly.

### Quarto content

```console
rsconnect deploy quarto ./report
```

R-flavored Quarto (a `.qmd` with R code chunks) needs R to render. If R is absent, treat the document as R content and use the manifest route, or surface the gap.

---

## Stage 4 — Find the target and check its credentials

Now that the tool is known, find out which server to deploy to and whether the tool can already reach it. This is a check, not a login.

**Do not search the environment for API keys.** Do not read `CONNECT_API_KEY`, `CONNECT_SERVER`, a `.env` file, a keychain entry, or any other stored secret to pick a target or to register a server. Do this only when the user explicitly asks for it. An environment variable is not a request to use it.

List the accounts the tool already has. This is the only credential check you need.

```console
rsconnect list                                   # Python: saved servers, stored tokens, and the default server on 1.30.0+
Rscript -e 'print(rsconnect::accounts())'        # R: registered accounts
```

If the tool is not installed yet, close that gap in Stage 5 first. Then run the check.

Compare the result with the target the user named. Three outcomes:

- **An account matches the named target.** The credential path is live. Run no login and no `rsconnect add`. Continue to Stage 6 once the other gaps are closed.
- **The user named no target.** Ask them. List the servers the check found, and ask which one to deploy to, or whether they want a new target instead.
alt-textSkill

>

brand-ymlSkill

Create and use brand.yml files for consistent branding across Shiny apps and Quarto documents. Covers: (1) Creating new _brand.yml files, (2) Applying to Shiny (R and Python), (3) Using in Quarto, (4) Modifying existing files, and (5) Troubleshooting. Includes complete specifications and integration guides.

ggsqlSkill

Write ggsql queries — a grammar of graphics for SQL. Use when the user wants to create, modify, or understand a ggsql visualization query.

pr-createSkill

Creates a pull request from current changes, monitors GitHub CI, and debugs any failures until CI passes. Activate when the user says "create pr", "make a pr", "open pull request", "submit pr", "pr for these changes", or wants to get their current work into a reviewable PR. Assumes the project uses git, is hosted on GitHub, and has GitHub Actions CI with automated checks (lint, build, tests, etc.). Does NOT merge - stops when CI passes and provides the PR link.

pr-threads-addressSkill

Address PR review feedback by systematically working through every unresolved PR review thread on the current branch's PR - analyze each comment, make the requested code changes (with tests where useful), commit, and optionally reply and resolve.

pr-threads-resolveSkill

Bulk resolve unresolved PR review threads on the current branch’s PR — typically after threads have been addressed manually or via /pr-threads-address

create-release-checklistSkill

>

maintainer-declineSkill

Guide for drafting issue closure and decline responses as an open-source package maintainer. Use when helping compose a reply that says \"no\" to a feature request, closes an issue as won't-fix, redirects a user to a different package, explains why a design choice is intentional, or otherwise declines or closes a community contribution. Also use when the maintainer needs to explain a deprecation, point out a user misunderstanding, or communicate an effort/scope tradeoff to a contributor.