@@ -9,7 +9,7 @@ description: >-
99 or commands.
1010metadata :
1111 author : posit-pbc
12- version : " 1 .0"
12+ version : " 2 .0"
1313---
1414
1515# Deploying to Posit Connect
@@ -120,41 +120,15 @@ if you're on route 2.
120120(` rsconnect deploy other-content ` prints guidance for anything not in that
121121list).
122122
123- ** The available frameworks and flags depend on the installed version** — that
124- list is from ** 1.30.0** , and older versions have fewer (e.g. ` bundle ` , ` git ` ,
125- and ` pyproject ` are absent in 1.29.0). Always confirm against
126- ` rsconnect deploy --help ` rather than trusting this list. If ` uv tool run `
127- resolves a stale cached version, pin it:
123+ ** The available frameworks and flags depend on the installed version** .
124+ Always confirm against ` rsconnect deploy --help ` rather than trusting this list.
125+ If ` uv tool run ` resolves a stale cached version, pin it:
128126` uv tool run --from 'rsconnect-python==1.30.0' rsconnect ... ` .
129127
130128### R content
131129
132- Prefer the R ` rsconnect ` package targeting the Connect ** server** . The flow is:
133- register the server, register your API user, then deploy.
134-
135- ``` r
136- library(rsconnect )
137-
138- # 1. Register the Connect server (once per server; name is a local nickname)
139- rsconnect :: addServer(url = " https://connect.example.com" , name = " myserver" )
140-
141- # 2. Register your API user against that server (Connect SERVER auth)
142- rsconnect :: connectApiUser(
143- server = " myserver" ,
144- account = " your-username" ,
145- apiKey = Sys.getenv(" CONNECT_API_KEY" )
146- )
147-
148- # 3. Deploy, choosing the function that matches the content:
149- rsconnect :: deployApp(appDir = " ." , appTitle = " My App" ) # Shiny R, Plumber, dirs
150- rsconnect :: deployDoc(" report.Rmd" ) # single Rmd / qmd
151- rsconnect :: deploySite(siteDir = " ." ) # Rmd/Quarto website
152- ```
153-
154- > ** Critical — this is Connect * server* , not Connect Cloud.** Use
155- > ` rsconnect::connectApiUser() ` (or ` connectUser() ` ), ** never**
156- > ` connectCloudUser() ` . The Cloud functions authenticate against a different
157- > service and will not work here.
130+ Prefer the R ` rsconnect ` package targeting the Connect ** server** , run through
131+ ` Rscript -e '...' ` or an R session.
158132
159133Which deploy function to use:
160134
@@ -170,7 +144,7 @@ using a `manifest.json`.
170144 rsconnect deploy manifest ./manifest.json
171145 ```
172146- If there's no manifest and R * is* available elsewhere, generate one first with
173- ` rsconnect::writeManifest() ` (see Stage 4 ).
147+ ` rsconnect::writeManifest() ` (see Stage 5 ).
174148- If there's ** neither R nor a manifest** , you cannot produce a valid R bundle.
175149 Surface this as a blocker: ask the user (if you have an ask-user tool) or
176150 report it clearly. Don't fake a deploy.
@@ -189,9 +163,45 @@ render. If the `.qmd` has R chunks and R is absent, treat it like R content
189163
190164---
191165
192- ## Stage 4 — Resolve discrepancies (self-heal)
166+ ## Stage 4 — Check credentials for that tool
167+
168+ Now that you know which tool you're using, check whether it can already reach the
169+ server. ** This is a check, not a login** — being authenticated already is the
170+ common case (env vars set by CI, an account linked in an earlier session). If a
171+ path is live, do nothing here: don't run a login, don't run ` rsconnect add ` .
172+
173+ Both tools read ` CONNECT_SERVER ` / ` CONNECT_API_KEY ` from the environment, so
174+ start there either way:
175+
176+ ``` console
177+ env | grep -E '^CONNECT_(SERVER|API_KEY)=' | sed 's/=.*/=<set>/' # don't print the key
178+ ```
193179
194- When Stage 3 finds a gap, close it and ** record the action** :
180+ ** Python (rsconnect-python)** — saved servers and stored tokens:
181+
182+ ``` console
183+ rsconnect list
184+ ```
185+
186+ ** R (` rsconnect ` )** — linked servers and accounts:
187+
188+ ``` console
189+ Rscript -e 'print(rsconnect::accounts())'
190+ ```
191+
192+ If the tool itself isn't installed yet, the env-var check still tells you what
193+ you need; close the install gap in Stage 5, then run the tool-specific check.
194+
195+ Record the verdict: either a credential path is live (continue to Stage 6 once
196+ any other gaps are closed) or there is none, which is a discrepancy for Stage 5.
197+ Beware of having * two* paths live at once for Python — see the mixing warning in
198+ the [ credentials reference] ( #credentials-reference ) .
199+
200+ ---
201+
202+ ## Stage 5 — Resolve discrepancies (self-heal)
203+
204+ When Stages 3 and 4 find a gap, close it and ** record the action** :
195205
196206- ** ` rsconnect ` not on ` PATH ` ** → don't install anything if ` uv ` is present;
197207 just run it on demand:
@@ -233,6 +243,11 @@ When Stage 3 finds a gap, close it and **record the action**:
233243 rsconnect write-manifest <framework> ./my-app
234244 ```
235245 Then deploy the manifest via rsconnect-python if R can't deploy directly.
246+ - ** No credentials** (Stage 4 found no env vars, no saved server, no linked
247+ account) → authenticate now, following
248+ [ Credentials reference] ( #credentials-reference ) for the ordering, the
249+ pitfalls, and what to do when nothing can supply them. Prefer a browser login
250+ (` rsconnect login ` , ` rsconnect::connectUser() ` ) when one is available.
236251- ** Dependencies** → you generally do ** not** hand-list them. rsconnect and
237252 rsconnect-python scan the code and snapshot required package versions
238253 automatically (Python from ` requirements.txt ` /imports, R from your ` .R `
@@ -244,83 +259,6 @@ When Stage 3 finds a gap, close it and **record the action**:
244259
245260---
246261
247- ## Stage 5 — Authenticate
248-
249- Deploying needs credentials for the Connect server.
250-
251- ### Python (rsconnect-python)
252-
253- In order of preference:
254-
255- 1 . ** OAuth login (interactive).** Requires ** rsconnect-python 1.30.0+** — check
256- with ` rsconnect version ` first. One browser flow per server; tokens land in
257- the OS keyring (falling back to a local credential store) and refresh
258- automatically:
259- ``` console
260- rsconnect login https://connect.example.com
261- rsconnect login https://connect.example.com --use-device-code # headless
262- rsconnect logout https://connect.example.com # drop the tokens
263- ```
264- In CI, skip the browser entirely by exchanging an OIDC identity token (e.g. a
265- GitHub Actions token) for a short-lived Connect API key — prefer the
266- ` -file ` form so the token never lands in process args or logs:
267- ``` console
268- rsconnect login https://connect.example.com --identity-token-file "$TOKEN_FILE"
269- ```
270- 2 . ** Env vars.** Best for headless/automated runs — no state to manage, and
271- works on any version:
272- ``` console
273- export CONNECT_SERVER=https://connect.example.com
274- export CONNECT_API_KEY=... # honored across the whole `rsconnect` surface
275- ```
276- 3 . ** Saved API-key nickname.** Save once, select later with ` -n/--name ` :
277- ``` console
278- rsconnect add -n myserver -s https://connect.example.com -k <api-key>
279- rsconnect list # confirm what's saved
280- ```
281- 4 . ** Ad hoc flags** on the deploy command itself: ` -s <url> -k <api-key> ` .
282-
283- ** Shared credential flags:** ` -n/--name ` (saved server), ` -s/--server ` (env
284- ` CONNECT_SERVER ` ), ` -k/--api-key ` (env ` CONNECT_API_KEY ` ), ` -i/--insecure ` (env
285- ` CONNECT_INSECURE ` , for self-signed TLS), ` -c/--cacert <file> ` (env
286- ` CONNECT_CA_CERTIFICATE ` ).
287-
288- > ** Pick ONE auth path — never mix ` -n ` with env-var credentials.** rsconnect
289- > rejects a command that combines a saved-server name (` -n/--name ` ) with
290- > ` CONNECT_SERVER ` /` CONNECT_API_KEY ` set in the environment, with an error like
291- > `-n/--name (from COMMANDLINE) cannot be specified in conjunction with options
292- > -s/--server (from ENVIRONMENT)`. Choose by what you have:
293- >
294- > - ** ` CONNECT_SERVER ` and ` CONNECT_API_KEY ` are set** (typical headless/automated
295- > run) → do ** not** pass ` -n ` ; let the env vars supply the target and key.
296- > Deploy with just ` rsconnect deploy <framework> <dir> ` .
297- > - ** The request names a specific saved server** (e.g. "deploy to dogfood") →
298- > use ` -n dogfood ` , and make sure ` CONNECT_SERVER ` /` CONNECT_API_KEY ` are ** not**
299- > also exported for that command (` unset ` them, or don't run ` rsconnect add `
300- > from a shell that has them set).
301- >
302- > If you have env-var creds but the request also names a server, prefer the env
303- > vars (drop ` -n ` ) — mixing is what triggers the rejection.
304-
305- ### R (` rsconnect ` )
306-
307- Register the server and API user as shown in Stage 3
308- (` addServer() ` + ` connectApiUser() ` ), pulling the key from ` CONNECT_API_KEY ` .
309- Check for already-linked accounts first:
310-
311- ``` r
312- rsconnect :: accounts() # lists linked servers/accounts; empty => authenticate
313- ```
314-
315- ### If credentials are missing
316-
317- - If you have an ** ask-user / prompt tool** , ask the user for the server URL and
318- API key.
319- - Otherwise, rely on the ` CONNECT_SERVER ` / ` CONNECT_API_KEY ` env vars and, if
320- they're absent, ** report the missing credentials** rather than guessing.
321-
322- ---
323-
324262## Stage 6 — Deploy and handle failure
325263
326264### Discover the live command surface (Python)
@@ -336,41 +274,30 @@ rsconnect deploy <framework> --help # flags for one framework
336274
337275### Deploy
338276
339- ``` console
340- rsconnect deploy streamlit ./my-app
341- rsconnect deploy shiny ./my-shiny-app
342- rsconnect deploy fastapi ./my-api
343- rsconnect deploy quarto ./report
344- rsconnect deploy manifest ./manifest.json # a prepared bundle
345- ```
346-
347- Useful flags on any deploy command: ` -t/--title ` , ` -N/--new ` (force a new
348- deployment instead of updating the recorded one), ` -a/--app-id <id> ` (target an
349- existing item explicitly — mutually exclusive with ` --new ` ), ` -E NAME=VALUE `
350- (set an environment variable, repeatable), ` --draft ` (keep serving the previous
351- bundle until published).
352-
353- For R, run the ` deployApp() ` / ` deployDoc() ` / ` deploySite() ` call from Stage 3.
277+ For Python, ` rsconnect deploy <framework> <dir> ` with the framework Stage 3
278+ picked (` manifest ` takes the manifest file rather than a directory).
354279
355- ### Resolving ` rsconnect ` not found
280+ Non-obvious flags: ` -t/--title ` , ` -N/--new ` (force a new deployment instead of
281+ updating the recorded one), ` -a/--app-id <id> ` (target an existing item
282+ explicitly — mutually exclusive with ` --new ` ), ` -E NAME=VALUE ` (set an
283+ environment variable, repeatable), ` --draft ` (keep serving the previous bundle
284+ until published).
356285
357- rsconnect-python may be installed but not on ` PATH ` in the current shell (common
358- in IDE-spawned terminals or when a virtualenv is active). Check
359- ` uv tool list | grep rsconnect ` ; either way, ` uv tool run ` works:
286+ For R, call the function Stage 3 selected, passing ` appTitle ` so the content
287+ isn't named after the directory.
360288
361- ``` console
362- uv tool run --from rsconnect-python rsconnect deploy shiny ./my-app -n myserver
363- ```
289+ ### If ` rsconnect ` isn't found at deploy time
364290
365- ** Critical:** always pass ` --from rsconnect-python ` — the package name and the
366- command name differ, so a bare ` uv tool run rsconnect ` won't resolve.
291+ It may be installed but off ` PATH ` in this shell (common in IDE-spawned
292+ terminals or with a virtualenv active). Fall back to ` uv tool run ` as described
293+ in Stage 5 — remembering ` --from rsconnect-python ` , since the package and
294+ command names differ.
367295
368296### Pre-flight check (optional)
369297
370- Before deploying, verify CLI access :
298+ To confirm the target is reachable and the credentials work before deploying :
371299
372300``` console
373- rsconnect list # saved servers
374301rsconnect details -n myserver # reachability + auth for one server
375302```
376303
@@ -382,8 +309,9 @@ rsconnect details -n myserver # reachability + auth for one se
382309 ` rsconnect login ` (1.30.0+), or pass ` -s ` /` -k ` (or set
383310 ` CONNECT_SERVER ` /` CONNECT_API_KEY ` ).
384311- ` -n/--name ... cannot be specified in conjunction with ... ENVIRONMENT ` : you
385- mixed a saved nickname with env-var credentials — see the Stage 5 warning.
386- Drop ` -n ` or ` unset CONNECT_SERVER CONNECT_API_KEY ` .
312+ mixed a saved nickname with env-var credentials — see the mixing warning in
313+ the credentials reference. Drop ` -n ` or
314+ ` unset CONNECT_SERVER CONNECT_API_KEY ` .
387315- ` The requirements file 'requirements.txt' does not exist ` : Python content
388316 needs one. Create it, point at another file with ` --requirements-file ` , or
389317 generate it with ` --force-generate ` (a ` pip freeze ` , so it may over-pin).
@@ -396,8 +324,10 @@ rsconnect details -n myserver # reachability + auth for one se
396324** R:**
397325
398326- "No account" / auth errors: run ` rsconnect::accounts() ` ; if empty, re-run
399- ` rsconnect::addServer() ` + ` rsconnect::connectApiUser() ` . Double-check you used
400- ` connectApiUser ` (server), not ` connectCloudUser ` (Cloud).
327+ ` rsconnect::addServer() ` + ` connectUser() ` /` connectApiUser() ` . Double-check you
328+ used a server function, not ` connectCloudUser() ` (Cloud).
329+ - Deploy hangs at an account prompt: more than one account is linked. Pass
330+ ` account = ` (and ` server = ` ) explicitly to the deploy call.
401331- Wrong deploy function: use ` deployApp() ` for directories/apps, ` deployDoc() `
402332 for a single document, ` deploySite() ` for a site.
403333- Self-signed TLS: pass the CA bundle via the ` RETICULATE ` /` curl ` options or
@@ -408,15 +338,86 @@ rsconnect details -n myserver # reachability + auth for one se
408338
409339---
410340
411- ## R ` rsconnect ` functions reference (Connect server)
341+ ## Credentials reference
412342
413- | Function | Purpose |
414- | --- | --- |
415- | ` addServer(url, name) ` | Registers a Connect ** server** under a local nickname |
416- | ` connectApiUser(server, account, apiKey) ` | Authenticates an API user against a Connect ** server** (use this, ** not** ` connectCloudUser ` ) |
417- | ` accounts() ` | Lists linked servers/accounts |
418- | ` deployApp(appDir, appTitle) ` | Deploys a directory app — Shiny for R, Plumber, etc. |
419- | ` deployDoc(doc) ` | Deploys a single document (Rmd, qmd) |
420- | ` deploySite(siteDir) ` | Deploys a full R Markdown / Quarto site |
421- | ` writeManifest() ` | Generates ` manifest.json ` (for the rsconnect-python / no-R route) |
422- | ` removeAccount(name) ` | Removes a stored account from the local machine |
343+ How to authenticate when Stage 4 found no credentials and Stage 5 sent you here.
344+ If a credential path is already live, you don't need any of this.
345+
346+ ### Python (rsconnect-python)
347+
348+ In order of preference:
349+
350+ 1 . ** OAuth login (interactive).** Requires ** rsconnect-python 1.30.0+** — check
351+ with ` rsconnect version ` first. One browser flow per server; tokens land in
352+ the OS keyring (falling back to a local credential store) and refresh
353+ automatically:
354+ ``` console
355+ rsconnect login https://connect.example.com
356+ rsconnect login https://connect.example.com --use-device-code # headless
357+ ```
358+ 2 . ** Saved API-key nickname.** Save once, select later with ` -n/--name ` :
359+ ``` console
360+ rsconnect add -n myserver -s https://connect.example.com -k <api-key>
361+ rsconnect list # confirm what's saved
362+ ```
363+ 3 . ** Env vars.** Best for headless/automated runs — no state to manage, and
364+ works on any version:
365+ ``` console
366+ export CONNECT_SERVER=https://connect.example.com
367+ export CONNECT_API_KEY=... # honored across the whole `rsconnect` surface
368+ ```
369+ 4 . ** Ad hoc flags** on the deploy command itself: ` -s <url> -k <api-key> ` .
370+
371+ ** Shared credential flags:** ` -n/--name ` (saved server), ` -s/--server ` (env
372+ ` CONNECT_SERVER ` ), ` -k/--api-key ` (env ` CONNECT_API_KEY ` ), ` -i/--insecure ` (env
373+ ` CONNECT_INSECURE ` , for self-signed TLS), ` -c/--cacert <file> ` (env
374+ ` CONNECT_CA_CERTIFICATE ` ).
375+
376+ > ** Pick ONE auth path — never mix ` -n ` with env-var credentials.** rsconnect
377+ > rejects a command that combines a saved-server name (` -n/--name ` ) with
378+ > ` CONNECT_SERVER ` /` CONNECT_API_KEY ` set in the environment, with an error like
379+ > `-n/--name (from COMMANDLINE) cannot be specified in conjunction with options
380+ > -s/--server (from ENVIRONMENT)`. Choose by what you have:
381+ >
382+ > - ** ` CONNECT_SERVER ` and ` CONNECT_API_KEY ` are set** (typical headless/automated
383+ > run) → do ** not** pass ` -n ` ; let the env vars supply the target and key.
384+ > Deploy with just ` rsconnect deploy <framework> <dir> ` .
385+ > - ** The request names a specific saved server** (e.g. "deploy to dogfood") →
386+ > use ` -n dogfood ` , and make sure ` CONNECT_SERVER ` /` CONNECT_API_KEY ` are ** not**
387+ > also exported for that command (` unset ` them, or don't run ` rsconnect add `
388+ > from a shell that has them set).
389+ >
390+ > If you have env-var creds but the request also names a server, prefer the env
391+ > vars (drop ` -n ` ) — mixing is what triggers the rejection.
392+
393+ ### R (` rsconnect ` )
394+
395+ Register the server under a local nickname, then register your user against it:
396+
397+ ``` r
398+ library(rsconnect )
399+
400+ # 1. The server (once per server; the name is a local nickname)
401+ rsconnect :: addServer(url = " https://connect.example.com" , name = " myserver" )
402+
403+ # 2a. Interactive — approve in a browser, no key to handle
404+ rsconnect :: connectUser(server = " myserver" )
405+
406+ # 2b. Or non-interactively (CI) — connectApiUser() requires an apiKey
407+ rsconnect :: connectApiUser(
408+ server = " myserver" ,
409+ account = " your-username" ,
410+ apiKey = Sys.getenv(" CONNECT_API_KEY" )
411+ )
412+ ```
413+
414+ > ** Critical — this is Connect * server* , not Connect Cloud.** Use
415+ > ` rsconnect::connectUser() ` or ` rsconnect::connectApiUser() ` , ** never**
416+ > ` connectCloudUser() ` . The Cloud functions authenticate against a different
417+ > service and will not work here.
418+
419+ ### If no credentials can be found
420+
421+ - Rely on the ` CONNECT_SERVER ` / ` CONNECT_API_KEY ` env vars and, if
422+ they're absent, ** report the missing credentials** rather than guessing
423+ or asking the user to paste an API key into the context.
0 commit comments