Skip to content

Working with several servers

The CLI keeps one credential per server. Connecting to a second ShinyHub adds it alongside the first instead of replacing it, so a laptop can hold a local hub, a staging server, and production at the same time and switch between them without re-authenticating.

Connect to a server

For a workstation, connect is the recommended first-time flow:

shinyhub connect https://shiny.example.com --name prod

It verifies the server and its runtimes. When that URL already has a saved credential that still authenticates, connect reports current and performs no browser authorization or key rotation. Otherwise it opens the browser for password or SSO sign-in, asks you to approve a matching verification code, then saves a private 90-day credential. The raw credential is generated in the CLI and never passes through the browser. On SSH or a workstation without a local browser, pass --no-browser and open the printed pairing URL on any signed-in device. A headless service can use --token-file; CI can use SHINYHUB_HOST and SHINYHUB_TOKEN without writing a credentials file.

If a saved CLI credential expires or is revoked, the next authenticated command explains what happened and points back to shinyhub connect. Reconnecting replaces only that server's credential and preserves every other saved host. Failures other than a credential rejection—such as rate limiting, an unavailable server, or a network error—are reported directly and never trigger rotation.

Refresh a still-working credential before it expires:

shinyhub connect --refresh
shinyhub connect --refresh --host prod

Refresh always uses browser approval, even when SHINYHUB_TOKEN is set. It preserves the server alias and all other saved hosts. The old local value is not changed until the new credential has authenticated successfully; after the atomic save, ShinyHub revokes the previous API key. If that last cleanup cannot be completed, the new credential remains usable and the command prints the exact tokens revoke command to finish it manually. Add --no-browser to open the pairing URL on another device.

Run shinyhub login when you want a short-lived username/password session or need to refresh an existing session.

Sign in to more than one server

shinyhub connect https://shiny.example.com --name prod
shinyhub connect http://localhost:8080 --name dev

--name is optional and gives the server a short alias. It has to be unique and must not look like a URL, so shinyhub use <name> can never be a coin flip between two servers. Reconnecting without --name keeps the alias the entry already had. Reusing a valid credential still applies --name and makes that server current, but leaves the token and its saved_at value unchanged.

The server you just connected to becomes the current one. The completion summary names the authenticated identity, role, deploy permission, available runtimes, credentials path, and previous server when the current selection changed.

Omit the URL to reconnect with the current server. Likewise, omit --host from login to refresh a short-lived session for the current server.

See what is saved, and switch

shinyhub hosts          # every saved server; * marks the current one
shinyhub use prod       # switch by name
shinyhub use https://shiny.example.com   # or by URL

Both commands read the local credentials file and contact no server, so they still work when every server in the list is down - which is when "where am I pointed?" is usually asked. hosts never prints a token, in any output format.

shinyhub whoami is the counterpart that does make a request: it asks the current server who the saved credential authenticates as and reports the credential type, name, creation time, prior last use, expiry, and lifecycle status. JSON output puts those fields under credential without exposing the token or hash.

Installed shell completion suggests saved aliases and URLs for shinyhub use and --host entirely from this local store. It never contacts a server or puts tokens into the completion stream. Install it with shinyhub completion install; see CLI completion and compatibility.

shinyhub doctor --remote goes further: it verifies the selected credential, warns when it expires within 14 days, and checks transport, server, identity, create-app permission, and reported runtimes. Add --slug sales to check whether the identity can update that exact existing app or create it if it is new. See Doctor.

Target one server for a single command

The global --host flag overrides the current server for one command:

shinyhub apps list --host prod
shinyhub deploy ./my-app --slug demo --host https://staging.example.com

The value is a saved server's name or a URL. hosts and use reject --host rather than ignoring it: neither contacts a server, so a flag that appears to scope them would be a lie.

Signing out

shinyhub logout                 # the current server
shinyhub logout --host prod     # a specific saved server
shinyhub logout --all           # every saved server

Signing out of one server promotes another saved server to current and leaves its credential alone. Each logout also asks that server to revoke the session, best-effort: an unreachable server produces a warning, not a failure, and the local credential is removed either way. --all attempts every revocation before removing the file, so one unreachable server does not strand the others.

Which credential a command uses

The server is chosen first, then that server's own token:

  1. --host (a saved name or a URL)
  2. SHINYHUB_HOST
  3. the current host from the credentials file

The token comes from the entry for the resolved server. A token is never carried over from a different server, so overriding the host cannot send production's credential to whatever address a typo or an inherited environment variable happens to name.

SHINYHUB_TOKEN is the way to say "use this credential, whatever the host":

SHINYHUB_HOST=https://shiny.example.com SHINYHUB_TOKEN=shk_... shinyhub apps list

That is the CI path - host and token from the environment, no credentials file at all - and it is also how you deliberately reuse one credential against a second URL for the same server (a direct IP behind a proxy, say).

The credentials file

Written by connect or login, owner-readable only (0600), at ~/.config/shinyhub/config.json unless --config, SHINYHUB_CREDENTIALS, or SHINYHUB_CONFIG points elsewhere.

{
  "host": "https://shiny.example.com",
  "token": "…",
  "current_host": "https://shiny.example.com",
  "hosts": {
    "https://shiny.example.com": {
      "name": "prod",
      "token": "…",
      "user": "alice",
      "saved_at": "2026-01-05T09:12:44Z"
    },
    "http://localhost:8080": {
      "name": "dev",
      "token": "…",
      "user": "admin",
      "saved_at": "2026-01-05T09:14:02Z"
    }
  }
}

Server URLs are normalized before they become keys: the scheme and host are lowercased and a trailing slash is dropped, so HTTPS://Shiny.Example.com/ and https://shiny.example.com are one entry rather than two. Any path keeps its case, because a reverse-proxy subpath is case-sensitive.

The top-level host and token mirror the current entry. They exist so a CLI older than multi-host support still finds a usable credential in this file. The reverse is not recoverable: an old binary's login rewrites the file with only those two fields and the other entries are gone, so upgrade the CLI everywhere that shares a credentials file.

A file that cannot be parsed is an error rather than an empty store. "Never logged in" and "the file was damaged" call for different responses, and only one of them is fixed by logging in again.