Deployment¶
Prerequisites¶
- A shinyapps.io account (free tier is fine).
- The artifacts produced by
./setup.sh(see Local development).
One-time: register your token¶
- Sign in at shinyapps.io and go to Account → Tokens → Show.
- Copy the
rsconnect::setAccountInfo(...)snippet. -
Run it in R:
-
Verify:
Deploying¶
What it does:
- Verifies
Rscript, thersconnectpackage, and your configured account. - Reads
dictionary.parquetout ofconfig.ymlso it knows which release ships. - Checks all 15 required deploy artifacts exist (app + config + Python backend + model + the active parquet + both embeddings/metadata pairs + manifest).
- Confirms
data/embeddings/manifest.txtmatchesconfig.yml— refuses to ship a bundle that would crash on boot due to drift. - Cleans
python/__pycache__/(rsconnect's hardcoded__pycache__/exclusion has a trailing-slash bug). - Calls
rsconnect::listDeploymentFiles(), filters out any non-active dictionary parquet and stray release CSVs (.rscignorecan't express "all except the active release" — no globs), and prints a bundle preview (top 10 files by size + total). Expected: ~16 files, ~125 MB for the 7.0 build. - Asks for confirmation (skipped if stdin isn't a TTY).
-
Calls:
Configurable via env vars:
APP_NAME=abcd-dict-staging APP_TITLE='ABCD (staging)' ./deploy.sh
SHINYAPPS_ACCOUNT=myaccount ./deploy.sh # multi-account case
How Python is provisioned on shinyapps.io¶
This is the part most reticulate users get stuck on. The model that works in 2026:
deployApp(python = ...)tellsrsconnectto write apythonsection intomanifest.jsonwith the local Python version + a pointer torequirements.txt.- shinyapps.io's Connect runtime ignores the manifest's Python info at build time (a quirk of the free tier) — so we don't rely on it.
-
At runtime,
app.Rcallsreticulate::py_require(readLines("requirements.txt"))beforesource_python(). This triggers reticulate'suv-based auto-installer, which:- Downloads
uv(~1 s) - Downloads a pre-built CPython 3.12 tarball from Posit's mirror (~2 s, 32.5 MB)
- Resolves and installs all packages in
requirements.txt(~5 s, ~30 MB total)
- Downloads
The whole boot — R packages, Python install, package install, ONNX model load — takes about 16 seconds on a fresh instance. Subsequent boots reuse the cached Python+packages.
Earlier failures
Before settling on this approach we tried two patterns that don't work on the shinyapps.io free tier:
reticulate::install_python(version = "3.12.7")— uses pyenv to build Python from source on the server. Takes 5–10 minutes; shinyapps.io kills the app after ~100 seconds. Avoid.- Pre-setting
RETICULATE_PYTHONin.Rprofileand creating the venv inapp.R(the ranikay shiny-reticulate-app pattern from ~2019) — works only if Connect haspython3onPATH, which the modern shinyapps.io does not.
What ships and what doesn't¶
Controlled by .rscignore (top level), data/.rscignore (per-subdir), and an in-deploy.sh filter that drops any non-active dd-abcd-*.parquet plus stray release CSVs (rsconnect uses exact-string setdiff() against directory listings, not gitignore-style globs — so trailing slashes, nested paths, and *.csv don't match). See Troubleshooting.
Top-level .rscignore excludes:
sanity-chks/,.claude/,.dockerignore,Dockerfile,fly.tomlsetup.sh,run.sh,deploy.sh,clean_artifacts.sh*.Rproj,.RData- Root-level duplicate / dummy CSVs
data/.rscignore excludes the source CSVs for the currently active build (build inputs only; the runtime uses Parquet and .npy/.npz).
deploy.sh additionally filters out:
- Any
dd-abcd-*.parquetother than the one named inconfig.yml(e.g. an old 6.0 parquet left indata/won't ship alongside 7.0). - Any stray
dd-abcd-*.csvat the repo root or underdata/.
Final deploy bundle: ~16 files, ~125 MB (7.0), with the largest items being the imaging-corpus embedding (~69 MB), the ONNX model (~23 MB), and the noimag embedding (~27 MB).
Watching the deploy¶
After ./deploy.sh finishes, tail server logs:
A healthy first boot looks like:
shinyapps[...]: Shiny application starting ...
shinyapps[...]: Attaching package: 'dplyr'
shinyapps[...]: Attaching package: 'bslib'
shinyapps[...]: Downloading uv...Done!
shinyapps[...]: Downloading cpython-3.12.13-linux-x86_64-gnu (32.5MiB)
shinyapps[...]: Downloaded cpython-3.12.13-linux-x86_64-gnu
shinyapps[...]: Downloading numpy (15.9MiB)
shinyapps[...]: Downloading tokenizers (3.2MiB)
shinyapps[...]: Downloading onnxruntime (17.3MiB)
shinyapps[...]: Installed 27 packages in 5.40s
shinyapps[...]: Listening on http://127.0.0.1:35131
If the log streams stop before "Listening on…", see Troubleshooting.
Sanity-checking the bundle before deploying¶
deploy.sh already prints a preview, but you can also run it standalone (this won't replicate the in-script "drop non-active parquet" filter — deploy.sh is the canonical view):
Rscript -e '
files <- rsconnect::listDeploymentFiles(".")
sizes <- file.info(files)$size
cat(sprintf("%d files, %.1f MB\n", length(files), sum(sizes)/1e6))
for (i in order(-sizes)) cat(sprintf(" %7.2f MB %s\n", sizes[i]/1e6, files[i]))
'
Confirm no raw CSVs, sanity-chks/, python/__pycache__/, or .RData appear, and only the active parquet is present. If something leaks, add the exact directory entry name (no trailing slash, no nested path) to .rscignore, or extend the filter inside deploy.sh.
Updating the deployed app¶
Deployments are versioned by shinyapps.io. Every ./deploy.sh invocation:
- Uploads a new bundle
- Builds a new container image (~1 min for unchanged R deps; longer if
renv.lockchanged) - Performs a rolling restart (
deploying: Starting instances→terminating: Stopping old instances)
Users connected to the old version stay on it until they reload.