{"id":"deploy-to-connect","name":"deploy-to-connect","summary":"rsconnect-pythonまたはR rsconnectパッケージを使って、Posit ConnectサーバーにPythonおよびRコンテンツをデプロイまたは公開してください。","body":"<!--\nMaintainer note: edit this skill in posit-dev/connect only.\nDownstream copies are overwritten by the sync workflow.\n-->\n\n# Deploying to Posit Connect\n\nThis guide covers Python and R content on a Posit Connect server. Work through the stages in order.\n\nTwo toolchains do the work:\n\n- Python — [rsconnect-python](https://github.com/posit-dev/rsconnect-python), which provides the `rsconnect` CLI and is published on PyPI.\n- R — the R [`rsconnect`](https://rstudio.github.io/rsconnect/) package, pointed at a Connect server.\n\nIf the user asks a question (\"how do I…\", \"what is the command…\") rather than asking for a deploy, answer from this guide and stop.\n\nAt the end, report which server you deployed to, which content type you picked, any tool you installed, and any assumption you made.\n\n---\n\n## Stage 1 — Detect the content\n\nInfer the language and framework from the files in the project directory. Common signals:\n\n| Signal in project dir | Likely content |\n| --- | --- |\n| `app.py` | Python web app — Shiny for Python, Streamlit, Dash, Gradio, Panel, or Bokeh |\n| `app.R`, or `ui.R` + `server.R` | Shiny for R |\n| `plumber.R` / `entrypoint.R` containing `plumb()` | Plumber API (R) |\n| `*.qmd` | Quarto document |\n| `*.Rmd` | R Markdown |\n| `*.ipynb` | Jupyter notebook / Voila |\n| `manifest.json` | Prebuilt bundle — deploy it directly, no framework guess needed |\n\nThe imports in `app.py` name the framework:\n\n```console\ngrep -Eo 'import (shiny|streamlit|dash|gradio|panel|bokeh)|from (shiny|streamlit|dash|gradio|panel|bokeh)' app.py\n```\n\nA bare ASGI or WSGI object means `fastapi` or `flask`.\n\nDependency files confirm the language: `requirements.txt` and `pyproject.toml` for Python, `DESCRIPTION` and `renv.lock` for R.\n\nIf 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.\n\n---\n\n## Stage 2 — Inventory your tools\n\nProbe the environment and build a capability set:\n\n```console\ncommand -v rsconnect                                 # rsconnect-python on PATH\ncommand -v uv                                        # uv (installs and runs Python tools)\nuv tool list 2>/dev/null | grep rsconnect            # rsconnect-python installed via uv\ncommand -v Rscript                                   # R present\nRscript -e 'cat(requireNamespace(\"rsconnect\", quietly=TRUE))' 2>/dev/null   # R rsconnect package\ncommand -v quarto                                     # quarto CLI\ncommand -v git                                        # git\n```\n\nWith `uv` present, Python content needs no install step. `uv tool run --from rsconnect-python rsconnect ...` fetches and runs the CLI on demand.\n\n---\n\n## Stage 3 — Pick a route\n\nCross the detected content (Stage 1) with your capabilities (Stage 2).\n\n### Python content\n\nUse rsconnect-python. With `rsconnect` on `PATH`:\n\n```console\nrsconnect deploy <framework> ./my-app\n```\n\nOff `PATH` but with `uv` present:\n\n```console\nuv tool run --from rsconnect-python rsconnect deploy <framework> ./my-app\n```\n\nBoth 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.\n\n`<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.\n\nThe 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 ...`.\n\n### R content\n\nUse the R `rsconnect` package, through `Rscript -e '...'` or an R session:\n\n- Shiny for R, Plumber API, or any app directory → `deployApp()`\n- A single R Markdown or Quarto document → `deployDoc()`\n- A full R Markdown or Quarto site → `deploySite()`\n\nIf `Rscript` is absent, deploy the R content through rsconnect-python with a `manifest.json`:\n\n- A `manifest.json` already exists — deploy it directly:\n  ```console\n  rsconnect deploy manifest ./manifest.json\n  ```\n- No manifest, but R is available elsewhere — generate one first with `rsconnect::writeManifest()` (see Stage 5).\n- Neither R nor a manifest — a valid R bundle is not possible. Surface this as a blocker: ask the user or report it clearly.\n\n### Quarto content\n\n```console\nrsconnect deploy quarto ./report\n```\n\nR-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.\n\n---\n\n## Stage 4 — Find the target and check its credentials\n\nNow 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.\n\n**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.\n\nList the accounts the tool already has. This is the only credential check you need.\n\n```console\nrsconnect list                                   # Python: saved servers, stored tokens, and the default server on 1.30.0+\nRscript -e 'print(rsconnect::accounts())'        # R: registered accounts\n```\n\nIf the tool is not installed yet, close that gap in Stage 5 first. Then run the check.\n\nCompare the result with the target the user named. Three outcomes:\n\n- **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.\n- **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. Do not pick one for them, and do not deploy to the only saved server because it is the only one.\n- **The target is new, or no account matches it.** This is a gap for Stage 5. Register it with a browser login.\n\nA browser login is the way to register a new target:\n\n```console\nrsconnect login https://connect.example.com      # Python\n```\n\n```r\nrsconnect::addServer(url = \"https://connect.example.com\", name = \"myserver\")   # R\nrsconnect::connectUser(server = \"myserver\")\n```\n\nBoth forms open a browser flow, so the user approves the login and no key passes through the conversation. The [credentials reference](#credentials-reference) has the details and the pitfalls.\n\n---\n\n## Stage 5 — Resolve gaps\n\nWhen Stages 3 and 4 find a gap, close it, then include the action in your report.\n\n**`rsconnect` not on `PATH`.** With `uv` present, no install is needed:\n\n```console\nuv tool run --from rsconnect-python rsconnect deploy <framework> ./my-app\n```\n\nIf the user wants it installed persistently, or `uv tool run` is not viable:\n\n```console\nuv tool install rsconnect-python     # or: pip install rsconnect-python\n```\n\nThe package name and the command name differ: the PyPI package is `rsconnect-python`, and the command it provides is `rsconnect`. That is why `uv tool run` needs `--from rsconnect-python`. To update later, run `uv tool upgrade rsconnect-python`.\n\n**R `rsconnect` package missing, `Rscript` present.** Install it from Posit Package Manager (P3M), which serves precompiled Linux binaries. A binary install is much faster than a source build and needs no `-dev` system libraries. Binaries need two things: the `__linux__/<codename>` repo URL and a platform-identifying `HTTPUserAgent`. Without the user agent, P3M serves source.\n\n```console\nexport P3M=\"https://packagemanager.posit.co/cran/__linux__/$(. /etc/os-release && echo \"$VERSION_CODENAME\")/latest\"\nRscript -e '\n  options(HTTPUserAgent = sprintf(\"R/%s R (%s)\", getRversion(),\n    paste(getRversion(), R.version[\"platform\"], R.version[\"arch\"], R.version[\"os\"])))\n  install.packages(\"rsconnect\", repos = Sys.getenv(\"P3M\"))\n'\n```\n\nP3M binaries exist for x86_64 on common distros. On arm64 or an unsupported distro, P3M falls back to source. That result is still correct, only slower, and it needs the usual `-dev` libraries and a compiler. Use `https://cloud.r-project.org` (CRAN source) only when P3M is unreachable.\n\n**`manifest.json` missing for R content, R present.** Generate it:\n\n```console\nRscript -e 'rsconnect::writeManifest()'\n```\n\nrsconnect-python writes one for Python content:\n\n```console\nrsconnect write-manifest <framework> ./my-app\n```\n\nThen deploy the manifest with rsconnect-python if R cannot deploy directly.\n\n**No account for the target.** Register it now with a browser login: `rsconnect login` for Python, or `rsconnect::addServer()` and `rsconnect::connectUser()` for R. The [credentials reference](#credentials-reference) has the details and the pitfalls. Do not fall back to an API key from the environment. If the browser flow is not available, report that and stop.\n\n**Dependencies.** rsconnect and rsconnect-python scan the code and snapshot the required package versions for you, so hand-listing them is rarely necessary. Python content needs a `requirements.txt`. For R, the content's own packages must be installed locally for rsconnect to detect them — `plumber` for a Plumber API, `shiny` for a Shiny app. Install any that are missing from the same P3M repo shown above.\n\n---\n\n## Stage 6 — Deploy and handle failure\n\n### Discover the live command surface (Python)\n\nThe frameworks and flags in rsconnect-python change between releases, and the help text is the source of truth:\n\n```console\nrsconnect version                  # which version you are actually running\nrsconnect deploy --help            # every framework you can deploy\nrsconnect deploy <framework> --help  # flags for one framework\n```\n\n### Deploy\n\nFor Python, run `rsconnect deploy <framework> <dir>` with the framework Stage 3 picked. The `manifest` framework takes the manifest file rather than a directory.\n\nNon-obvious flags: `-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).\n\nFor R, call the function Stage 3 selected. Pass `appTitle` so the content is not named after the directory.\n\n### If `rsconnect` is not found at deploy time\n\nIt can be installed but off `PATH` in this shell. IDE-spawned terminals and active virtualenvs both cause this. Fall back to `uv tool run` as described in Stage 5, with `--from rsconnect-python`.\n\n### Pre-flight check (optional)\n\nTo confirm that the target is reachable and the credentials work before you deploy:\n\n```console\nrsconnect details -n myserver\n```\n\n### When a deploy fails\n\nPython:\n\n- Auth errors — confirm the target with `rsconnect list`, then re-run `rsconnect login` (1.30.0+). Pass `-s`/`-k` only when the user told you to use an existing key.\n- `-n/--name ... cannot be specified in conjunction with ... -s/--server (from ENVIRONMENT)` — `CONNECT_SERVER` is set and you also passed `-n`. Run `unset CONNECT_SERVER` and keep `-n`. The credentials reference explains why that direction. `CONNECT_API_KEY` can stay.\n- `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`. The last option runs a `pip freeze`, so it can over-pin.\n- Self-signed TLS — use `-i/--insecure` or `-c/--cacert <file>`. Set `CONNECT_INSECURE` or `CONNECT_CA_CERTIFICATE` to apply it everywhere.\n- Rejected flag or unknown framework — re-check `rsconnect version` and re-read `rsconnect deploy <framework> --help`. The installed version is usually older than the flag you used.\n\nR:\n\n- \"No account\" or auth errors — run `rsconnect::accounts()`. If it is empty, re-run `rsconnect::addServer()`, then `connectUser()` or `connectApiUser()`. Make sure that you used a server function and not `connectCloudUser()`.\n- `Found multiple accounts. Please disambiguate by setting server and/or account` — more than one account is linked. Pass `account =` and `server =` explicitly to the deploy call. An interactive R session shows a menu instead, which hangs a headless run.\n- Wrong deploy function — `deployApp()` for directories and apps, `deployDoc()` for a single document, `deploySite()` for a site.\n- Self-signed TLS — pass the CA bundle through the `curl` options, or add the server with the certificate. For a quick test, set `options(rsconnect.check.certificate = FALSE)`.\n- Absolute-path warnings — files with hard-coded absolute paths do not block the deploy, but they are better made relative to the project directory.\n\n---\n\n## Credentials reference\n\nHow to register a target that Stage 4 found no account for. If an account already matches the target, none of this is needed.\n\nA browser login is the route. The API-key routes below it are there for one case only: the user explicitly tells you to use a key that already exists, in an environment variable or a credential store. Do not go looking for a key on your own, and never ask the user to give you one. An API key does not belong in the conversation.\n\n### Python (rsconnect-python)\n\n1. **OAuth login (interactive).** The default route. Needs rsconnect-python 1.30.0+, so check `rsconnect version` first. One browser flow per server. Tokens land in the OS keyring, or a local credential store, and refresh automatically.\n   ```console\n   rsconnect login https://connect.example.com\n   rsconnect login https://connect.example.com --use-device-code   # headless\n   ```\n2. **Saved API-key nickname.** Only when the user asked for a key route. Save once, select later with `-n/--name`.\n   ```console\n   rsconnect add -n myserver -s https://connect.example.com -k <api-key>\n   rsconnect list                    # confirm what is saved\n   ```\n   On 1.30.0+ a server can be the default, used when a command passes neither `-n` nor `-s`. `add` sets the default only with `--set-default`. `login` sets it unless you pass `--no-set-default`. `rsconnect server set-default -n <name>` changes it later. `CONNECT_SERVER` still takes precedence over the default.\n3. **Environment variables.** Only when the user asked you to use them. rsconnect-python reads them directly, which suits a headless or automated run with no state to manage.\n   ```console\n   export CONNECT_SERVER=https://connect.example.com\n   export CONNECT_API_KEY=...        # honored across the whole `rsconnect` surface\n   ```\n4. **Ad hoc flags** on the deploy command: `-s <url> -k <api-key>`. Same condition, and read the key from the variable the user named rather than writing it out.\n\nShared 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`).\n\n> `-n` and `CONNECT_SERVER` cannot both be in play. rsconnect rejects a command that combines a saved-server name (`-n/--name`) with a server URL, including a URL that came from the environment: `-n/--name (from COMMANDLINE) cannot be specified in conjunction with options -s/--server (from ENVIRONMENT)`.\n>\n> Only the server conflicts. `CONNECT_API_KEY`, `CONNECT_INSECURE`, and `CONNECT_CA_CERTIFICATE` sit alongside `-n` without complaint, because the key is not part of the exclusion. `-n dogfood` with `CONNECT_API_KEY` exported is a valid command. It is `CONNECT_SERVER` that has to go.\n>\n> Choose by what the request names, not by what happens to be exported:\n>\n> - The request names a saved server (\"deploy to dogfood\") — use `-n dogfood` and `unset CONNECT_SERVER` for that command. Resolving the conflict the other way is worse: `CONNECT_SERVER` can point somewhere else entirely, so dropping `-n` to keep it would deploy to a server the user did not ask for.\n> - The request names no server, the typical headless run — let `CONNECT_SERVER` and `CONNECT_API_KEY` supply the target, and deploy with `rsconnect deploy <framework> <dir>`.\n>\n> `CONNECT_SERVER` is not secret. Print it if you are unsure which server it points at, and name the server you deployed to in your report.\n\n### R (`rsconnect`)\n\nRegister the server under a local nickname, then register your user against it:\n\n```r\nlibrary(rsconnect)\n\n# 1. The server (once per server; the name is a local nickname)\nrsconnect::addServer(url = \"https://connect.example.com\", name = \"myserver\")\n\n# 2a. Interactive — approve in a browser, no key to handle\nrsconnect::connectUser(server = \"myserver\")\n\n# 2b. Or non-interactively (CI), only when the user asked for a key route\nrsconnect::connectApiUser(\n  server  = \"myserver\",\n  account = \"your-username\",\n  apiKey  = Sys.getenv(\"CONNECT_API_KEY\")\n)\n```\n\n`connectCloudUser()` authenticates against Connect Cloud, a different service, so it does not work for a Connect server. Use `connectUser()` or `connectApiUser()` here.\n\n### If the login route is not available\n\nReport it and stop. Name the server you tried to register and say which login command failed. Do not search the environment, a `.env` file, or a credential store for a key to fill the gap, and do not ask the user for a key. The next step is theirs to choose.","author":"@posit-dev","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/posit-dev/skills/tree/main/connect/deploy-to-connect","license":"MIT","category":"document","lang":"en","tokens":4314,"stars":0,"calls30d":2,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":["cloud.r-project.org","connect.example.com","packagemanager.posit.co","rstudio.github.io"]}}