Deploying to Posit Connect
This skill deploys both Python and R content to Posit Connect. Work through the stages in order.
Two toolchains are involved:
- Python —
rsconnect-python, which
provides the
rsconnectCLI and is published on PyPI. - R — the R
rsconnectpackage, targeting the Connect server (not Connect Cloud).
As you go, record every decision, install, fallback, and assumption so you can report them at the end — this is what makes the run auditable and self-healing rather than silent.
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 (disambiguate by imports, below) |
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 directly, no framework guess needed) |
Disambiguate app.py by its imports:
grep -Eo 'import (shiny|streamlit|dash|gradio|panel|bokeh)|from (shiny|streamlit|dash|gradio|panel|bokeh)' app.py
shiny→ shiny (Python) ·streamlit→ streamlit ·dash→ dashgradio→ gradio ·panel→ panel ·bokeh→ bokeh- A bare ASGI/WSGI object (
fastapi/flask) →fastapi/flask
Dependency-file signals confirm the language:
- Python:
requirements.txt,pyproject.toml - R:
DESCRIPTION,renv.lock, or a spread of.Rfiles
If the content is ambiguous (e.g. both Python and R files, or an app.py
with no recognizable import): if you have an ask-user / prompt tool available,
ask the user which framework to deploy. Otherwise, pick the strongest signal
(a framework-specific import beats a generic dep file) and record the
assumption for your report.
Stage 2 — Inventory your tools
Probe the environment and build a capability set — don't assume anything is installed.
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
Note which of these are present: rsconnect (or uv, which can run it without
installing), Rscript + R rsconnect, quarto, git.
With uv available you never need an install step for Python content —
uv tool run --from rsconnect-python rsconnect ... fetches and runs it on
demand.
Stage 3 — Pick a route
Cross the detected content (Stage 1) with your capabilities (Stage 2):
Python content
Use rsconnect-python.
rsconnectalready onPATH:rsconnect deploy <framework> ./my-app- Not on
PATHbutuvis — run it without installing anything:uv tool run --from rsconnect-python rsconnect deploy <framework> ./my-app
Both forms take identical arguments; the rest of this skill writes the bare
rsconnect ... form, so prefix it with uv tool run --from rsconnect-python
if you're on route 2.
<framework> is one of api, bokeh, bundle, dash, fastapi, flask,
git, gradio, html, manifest, nodejs, notebook, panel, pyproject,
quarto, shiny, streamlit, tensorflow, voila
(rsconnect deploy other-content prints guidance for anything not in that
list).
The available frameworks and flags depend on the installed version — that
list is from 1.30.0, and older versions have fewer (e.g. bundle, git,
and pyproject are absent in 1.29.0). Always confirm against
rsconnect deploy --help rather than trusting 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
Prefer the R rsconnect package targeting the Connect server. The flow is:
register the server, register your API user, then deploy.
library(rsconnect)
# 1. Register the Connect server (once per server; name is a local nickname)
rsconnect::addServer(url = "https://connect.example.com", name = "myserver")
# 2. Register your API user against that server (Connect SERVER auth)
rsconnect::connectApiUser(
server = "myserver",
account = "your-username",
apiKey = Sys.getenv("CONNECT_API_KEY")
)
# 3. Deploy, choosing the function that matches the content:
rsconnect::deployApp(appDir = ".", appTitle = "My App") # Shiny R, Plumber, dirs
rsconnect::deployDoc("report.Rmd") # single Rmd / qmd
rsconnect::deploySite(siteDir = ".") # Rmd/Quarto website
Critical — this is Connect server, not Connect Cloud. Use
rsconnect::connectApiUser()(orconnectUser()), neverconnectCloudUser(). The Cloud functions authenticate against a different service and will not work here.
Which deploy function to use:
- Shiny for R / Plumber API / any app directory →
deployApp() - A single R Markdown or Quarto document →
deployDoc() - A full R Markdown / Quarto site →
deploySite()
If R is absent (no Rscript): deploy the R content through rsconnect-python
using a manifest.json.
- If a
manifest.jsonalready exists, deploy it directly:rsconnect deploy manifest ./manifest.json - If there's no manifest and R is available elsewhere, generate one first with
rsconnect::writeManifest()(see Stage 4). - If there's neither R nor a manifest, you cannot produce a valid R bundle. Surface this as a blocker: ask the user (if you have an ask-user tool) or report it clearly. Don't fake a deploy.
Quarto content
Use the rsconnect quarto route:
rsconnect deploy quarto ./report
Note: R-flavored Quarto (documents with R code chunks) needs R available to
render. If the .qmd has R chunks and R is absent, treat it like R content
(manifest route) or surface the gap.
Stage 4 — Resolve discrepancies (self-heal)
When Stage 3 finds a gap, close it and record the action:
rsconnectnot onPATH→ don't install anything ifuvis present; just run it on demand:
If the user wants it installed persistently (oruv tool run --from rsconnect-python rsconnect deploy <framework> ./my-appuv tool runisn't viable):
Note the package/command mismatch: the PyPI package isuv tool install rsconnect-python # or: pip install rsconnect-pythonrsconnect-python, the command it provides isrsconnect. That's whyuv tool runneeds--from rsconnect-python. To update later:uv tool upgrade rsconnect-python.- R
rsconnectpackage missing (butRscriptpresent) → install it from Posit Package Manager (P3M), which serves precompiled Linux binaries — far faster than a source build and with no-devsystem libraries to apt-get. Two things are required to actually get binaries: the__linux__/<codename>repo URL and a platform-identifyingHTTPUserAgent(without it P3M serves source):
P3M binaries exist for x86_64 on common distros; on arm64 or an unsupported distro P3M transparently falls back to source (still correct, just slower — make sure the usualexport P3M="https://packagemanager.posit.co/cran/__linux__/$(. /etc/os-release && echo "$VERSION_CODENAME")/latest" Rscript -e ' options(HTTPUserAgent = sprintf("R/%s R (%s)", getRversion(), paste(getRversion(), R.version["platform"], R.version["arch"], R.version["os"]))) install.packages("rsconnect", repos = Sys.getenv("P3M")) '-devlibraries and a compiler are present). Only reach forhttps://cloud.r-project.org(CRAN source) if P3M is unreachable. manifest.jsonmissing for R content (R present) → generate it:
Alternatively, rsconnect-python can write one for Python content:Rscript -e 'rsconnect::writeManifest()'
Then deploy the manifest via rsconnect-python if R can't deploy directly.rsconnect write-manifest <framework> ./my-app- Dependencies → you generally do not hand-list them. rsconnect and
rsconnect-python scan the code and snapshot required package versions
automatically (Python from
requirements.txt/imports, R from your.Rcode). Make sure a Pythonrequirements.txtexists when deploying Python content. For R, the content's own packages must be installed locally for rsconnect to detect and snapshot them (e.g.plumberfor a Plumber API,shinyfor a Shiny app) — install any that are missing from the same P3M repo shown above, not from CRAN source.
Stage 5 — Authenticate
Deploying needs credentials for the Connect server.
Python (rsconnect-python)
In order of preference:
- OAuth login (interactive). Requires rsconnect-python 1.30.0+ — check
with
rsconnect versionfirst. One browser flow per server; tokens land in the OS keyring (falling back to a local credential store) and refresh automatically:
In CI, skip the browser entirely by exchanging an OIDC identity token (e.g. a GitHub Actions token) for a short-lived Connect API key — prefer thersconnect login https://connect.example.com rsconnect login https://connect.example.com --use-device-code # headless rsconnect logout https://connect.example.com # drop the tokens-fileform so the token never lands in process args or logs:rsconnect login https://connect.example.com --identity-token-file "$TOKEN_FILE" - Env vars. Best for headless/automated runs — no state to manage, and
works on any version:
export CONNECT_SERVER=https://connect.example.com export CONNECT_API_KEY=... # honored across the whole `rsconnect` surface - Saved API-key nickname. Save once, select later with
-n/--name:rsconnect add -n myserver -s https://connect.example.com -k <api-key> rsconnect list # confirm what's saved - Ad hoc flags on the deploy command itself:
-s <url> -k <api-key>.
Shared credential flags: -n/--name (saved server), -s/--server (env
CONNECT_SERVER), -k/--api-key (env CONNECT_API_KEY), -i/--insecure (env
CONNECT_INSECURE, for self-signed TLS), -c/--cacert <file> (env
CONNECT_CA_CERTIFICATE).
Pick ONE auth path — never mix
-nwith env-var credentials. rsconnect rejects a command that combines a saved-server name (-n/--name) withCONNECT_SERVER/CONNECT_API_KEYset in the environment, with an error like-n/--name (from COMMANDLINE) cannot be specified in conjunction with options -s/--server (from ENVIRONMENT). Choose by what you have:
CONNECT_SERVERandCONNECT_API_KEYare set (typical headless/automated run) → do not pass-n; let the env vars supply the target and key. Deploy with justrsconnect deploy <framework> <dir>.- The request names a specific saved server (e.g. "deploy to dogfood") → use
-n dogfood, and make sureCONNECT_SERVER/CONNECT_API_KEYare not also exported for that command (unsetthem, or don't runrsconnect addfrom a shell that has them set).If you have env-var creds but the request also names a server, prefer the env vars (drop
-n) — mixing is what triggers the rejection.
R (rsconnect)
Register the server and API user as shown in Stage 3
(addServer() + connectApiUser()), pulling the key from CONNECT_API_KEY.
Check for already-linked accounts first:
rsconnect::accounts() # lists linked servers/accounts; empty => authenticate
If credentials are missing
- If you have an ask-user / prompt tool, ask the user for the server URL and API key.
- Otherwise, rely on the
CONNECT_SERVER/CONNECT_API_KEYenv vars and, if they're absent, report the missing credentials rather than guessing.
Stage 6 — Deploy and handle failure
Discover the live command surface (Python)
rsconnect-python's frameworks and flags change between releases, so read the help text — it's the source of truth:
rsconnect version # which version you're actually running
rsconnect deploy --help # every framework you can deploy
rsconnect deploy <framework> --help # flags for one framework
Deploy
rsconnect deploy streamlit ./my-app
rsconnect deploy shiny ./my-shiny-app
rsconnect deploy fastapi ./my-api
rsconnect deploy quarto ./report
rsconnect deploy manifest ./manifest.json # a prepared bundle
Useful flags on any deploy command: -t/--title, -N/--new (force a new
deployment instead of updating the recorded one), -a/--app-id <id> (target an
existing item explicitly — mutually exclusive with --new), -E NAME=VALUE
(set an environment variable, repeatable), --draft (keep serving the previous
bundle until published).
For R, run the deployApp() / deployDoc() / deploySite() call from Stage 3.
Resolving rsconnect not found
rsconnect-python may be installed but not on PATH in the current shell (common
in IDE-spawned terminals or when a virtualenv is active). Check
uv tool list | grep rsconnect; either way, uv tool run works:
uv tool run --from rsconnect-python rsconnect deploy shiny ./my-app -n myserver
Critical: always pass --from rsconnect-python — the package name and the
command name differ, so a bare uv tool run rsconnect won't resolve.
Pre-flight check (optional)
Before deploying, verify CLI access:
rsconnect list # saved servers
rsconnect details -n myserver # reachability + auth for one server
When a deploy fails
Python:
- Auth errors: confirm the target with
rsconnect list, re-runrsconnect login(1.30.0+), or pass-s/-k(or setCONNECT_SERVER/CONNECT_API_KEY). -n/--name ... cannot be specified in conjunction with ... ENVIRONMENT: you mixed a saved nickname with env-var credentials — see the Stage 5 warning. Drop-norunset CONNECT_SERVER CONNECT_API_KEY.The requirements file 'requirements.txt' does not exist: Python content needs one. Create it, point at another file with--requirements-file, or generate it with--force-generate(apip freeze, so it may over-pin).- Self-signed TLS: use
-i/--insecure(or-c/--cacert <file>); setCONNECT_INSECURE/CONNECT_CA_CERTIFICATEto apply it everywhere. - Rejected flag or unknown framework: re-check
rsconnect versionand re-readrsconnect deploy <framework> --help— this usually means the installed version is older than the flag you used.
R:
- "No account" / auth errors: run
rsconnect::accounts(); if empty, re-runrsconnect::addServer()+rsconnect::connectApiUser(). Double-check you usedconnectApiUser(server), notconnectCloudUser(Cloud). - Wrong deploy function: use
deployApp()for directories/apps,deployDoc()for a single document,deploySite()for a site. - Self-signed TLS: pass the CA bundle via the
RETICULATE/curloptions or add the server with the appropriate certificate; for quick tests setoptions(rsconnect.check.certificate = FALSE)(use sparingly). - Absolute-path warnings: files with hard-coded absolute paths won't block the deploy but should be made relative to the project directory.
R rsconnect functions reference (Connect server)
| Function | Purpose |
|---|---|
addServer(url, name) |
Registers a Connect server under a local nickname |
connectApiUser(server, account, apiKey) |
Authenticates an API user against a Connect server (use this, not connectCloudUser) |
accounts() |
Lists linked servers/accounts |
deployApp(appDir, appTitle) |
Deploys a directory app — Shiny for R, Plumber, etc. |
deployDoc(doc) |
Deploys a single document (Rmd, qmd) |
deploySite(siteDir) |
Deploys a full R Markdown / Quarto site |
writeManifest() |
Generates manifest.json (for the rsconnect-python / no-R route) |
removeAccount(name) |
Removes a stored account from the local machine |