Apps
Apps are a customizable, interactive front end for your Tines Stories workflows, and are fully supported on both cloud and self-hosted environments. This page focuses on the latter, so if you wish to learn more about Apps in general, see this page.
In self-hosted environments, the build process differs from Tines Stories Cloud and introduces a small number of infrastructure requirements: an up-to-date tines-command-runner image, and somewhere for a build to get its npm packages from. That can be the public npm registry, a registry on your own network, or the packages baked into the command runner image, so Apps can also be built in a fully air-gapped deployment.
How app builds work on self-hosted
Every time an App's source files change, Tines Stories bundles the app on the server: it resolves the App's npm dependencies, compiles the TypeScript and React source with esbuild, and stores the resulting JavaScript bundle for the app to render.
On Tines Stories Cloud, that bundling runs in AWS Lambda. On self-hosted there is no Lambda, so bundling runs in tines-command-runner, the same container that already powers Run Script actions. The web server and background workers call tines-command-runner over HTTP on port 4400, the same connection already required for Run Script.
Each app gets its own build workspace on the tines-command-runner container's disk, holding its node_modules directory. The first build for an App is a cold build and runs a full npm install, which can take quite a bit of time depending on the number of dependencies. Later builds reuse the cached dependencies and typically complete in a few seconds. When an app is deleted, Tines Stories removes its workspace.
The Tines app runtime libraries are sent to the command runner as part of the build itself, so they are never fetched from a registry.
Requirements
Keep tines-command-runner aligned with your Tines Stories version
The app build toolchain, Node and esbuild, ships inside the tines-command-runner image. The Tines Stories UI exposes the app builder as soon as Apps are enabled for your tenant, based on the entitlement alone. It does not check whether your tines-command-runner image can actually build. If you enable apps or upgrade tines-app without also re-pulling tines-command-runner, users will see the app builder and every build will fail.
Always upgrade tines-command-runner to the same version as tines-app. This is the same guidance that already applies to other command runner features, see Upgrading Tines.
No configuration is needed to turn the toolchain on; it is present in all official Tines Stories images. If you build your own custom tines-command-runner images, you must pass the ENABLE_APPS_BUILD=1 Docker build argument, which defaults to off.
Version alignment matters most in air-gapped deployments. An offline build can only resolve the package versions baked into the image it is running on, so upgrading tines-app without re-pulling tines-command-runner can leave builds unable to find a dependency version they expect.
Connectivity
App builds add one outbound requirement to tines-command-runner, and only when they install from the public npm registry.
No other connectivity changes are needed. Builds travel over the existing web server and background worker connection to the command runner on port 4400, and published apps are served from your existing tenant domain through your load balancer. For the full picture, see Connectivity requirements.
Resources
Plan for:
Disk headroom on the command runner. Each App keeps its own
node_modulesdirectory, plus a shared npm cache. Budget a few hundred megabytes per actively built app.Ephemeral storage. Build workspaces and the npm cache live on the container's local disk and are not persisted across restarts. Losing them is safe: the command runner re-seeds its npm cache from the packages baked into its image every time it starts, and the next build simply repeats the cold
npm install.Cold builds after a restart or scale-out. If you run more than one tines-command-runner container, each keeps its own dependency cache, so the same app may build cold more than once. No session stickiness configuration is required for Apps.
Setting up Apps on self-hosted
Contact us. Contact your Account Executive or Customer Success Manager, see Before you begin. Apps are gated by a tenant entitlement that cannot be self-enabled.
Confirm your tines-command-runner image matches your Tines Stories version. See "Keep tines-command-runner aligned with your Tines Stories version" above.
Decide where app builds will get their npm packages. The public npm registry needs no configuration. If your command runner has no route to it, see "Air-gapped and restricted-egress deployments" below.
Turn apps on for your tenant. As a tenant owner, go to
https://your-tenant-domain/settings/apps. You can enable apps tenant-wide or for specific teams. See Admin settings for Apps.
Configuring the npm registry
By default, app builds fetch dependencies from the public npm registry. To install from a registry on your own network instead, or to build with no registry at all, set these environment variables on the tines-command-runner container. These sit alongside the Python package index settings documented in Run Script (tines-command-runner).
NPM_CONFIG_REGISTRY- URL of the npm registry used when building apps, for example an internal mirror or the bundled registry described below. When unset, the public npm registry is used. Example:https://npm.your-company.com/NPM_TOKEN- Auth token for a private npm registry. Has no effect unlessNPM_CONFIG_REGISTRYis also set. The token is resolved from the environment at install time and is never written to disk.NPM_CONFIG_STRICT_SSL- Set tofalseto disable TLS certificate verification for the npm registry. Prefer adding your registry's CA to the container usingNODE_EXTRA_CA_CERTS; use this only as a last resort when the registry is behind a self-signed certificate you cannot otherwise trust.NO_NPM_REGISTRY- Set totrueto build apps with no registry access at all, resolving dependencies only from the packages baked into the image. Takes precedence overNPM_CONFIG_REGISTRY, which is ignored while it is set. Set it on thetines-appandtines-sidekiqcontainers too, so that the app builder knows which packages it can use.NO_PROXY- If tines-command-runner runs behind an egress proxy configured withHTTP_PROXYorHTTPS_PROXY, addnpm-registryhere when you install from the bundled registry. npm honors proxy settings, and the proxy cannot resolve a hostname on the internal Docker network.
You can also tune build behavior, though the defaults suit most deployments.
TCR_APPS_BUILD_TIMEOUT_SECONDS- Wall-clock limit for a single app build, in seconds. Default:120TCR_APPS_WORKSPACE_DIR- Directory holding per-app build workspaces. Default:/run/tcr/appsTCR_APPS_NPM_CACHE_DIR- Shared npm cache directory, seeded from the packages baked into the image every time the container starts. Default:/run/tcr/apps-npm-cache
Persisting the workspace or cache directories on a volume is optional, and only saves the first build after a restart. If you do mount a volume over the npm cache, it must be writable by the container's app user, uid 2000, because a newly created named volume is owned by root. An unwritable cache is logged as a warning at startup and the container keeps running, but with NO_NPM_REGISTRY=true app builds then fail, because there is nothing else to resolve from. A persisted cache is re-seeded automatically when a newer image changes the packages it ships.
Air-gapped and restricted-egress deployments
Deployments with no route to the public npm registry have two options for app builds. They are alternatives rather than complements: NO_NPM_REGISTRY takes precedence, and NPM_CONFIG_REGISTRY is ignored while it is set.
Option 1: build offline from the packages in the image
Set NO_NPM_REGISTRY=true on tines-command-runner, and on the tines-app and tines-sidekiq containers.
Builds then install offline. Nothing is fetched, and dependencies resolve entirely from an npm cache baked into the tines-command-runner image, which the container copies into its cache directory every time it starts.
Setting the variable on all three containers is important. The command runner's copy decides how npm install runs. Setting it on tines-app and tines-sidekiq is what tells the app builder what packages it can use, so it can avoid trying to use dependencies which will never resolve.
Which packages are preloaded
The image ships the dependencies every app needs, which are React, React Router, and @tines/apps . It also includes some commonly used packages:
@tanstack/react-tableclsxdate-fnsfuse.jslodashnanoidpapaparserechartsuuidzod
Their own dependencies are included too. Because an offline install cannot resolve a version range, only the exact versions in the image can be installed. You can see the list at /opt/apps-build/preloaded-packages.json inside the container, and it is also shown in the Tines UI, see "Checking your configuration" below.
When an App needs a package that is not preloaded
The build fails with an error that names the package, lists what is available, and points at the alternatives, for example:
Package chart.js@4.4.0 is not available in this air-gapped Tines deployment, which can only install packages preloaded into the tines-command-runner image. Available packages: ... Remove it from package.json, or ask your administrator to configure an internal npm registry (NPM_CONFIG_REGISTRY).
The app builder reads that error and corrects itself, so there is nothing to allowlist or maintain on the Tines Stories side.
Option 2: install from an npm registry
Point tines-command-runner at a registry with NPM_CONFIG_REGISTRY, adding NPM_TOKEN if it requires authentication. Every package available in the registry will be available to Apps. This is a good option if you already run Artifactory, Nexus, or another mirror, or if you want to use the mirror bundled with Tines.
The bundled npm registry
Self-hosted Tines Stories ships a verdaccio↗ registry as the npm-registry service, the npm counterpart of the pypi-server service used by Run Script. It listens on port 4873 on the internal Docker network only, is never published to the host, and stores packages in the npm-packages volume. To install from it, uncomment this line on the tines-command-runner service in your compose or stack file:
- NPM_CONFIG_REGISTRY=http://npm-registry:4873/Installing and publishing are both anonymous, so no NPM_TOKEN is needed.
It is seeded for you. The registry is pre-seeded with the same set of default dependencies that are baked into the image, mentioned above. The registry is seeded when the container is setup, or upgraded. You can manually run, or re-run, the script which seeds the registry:
docker compose run --rm --no-deps tines-command-runner /usr/local/bin/publish_npm_seed http://npm-registry:4873/Publishing other packages. Apps can only access the packages that are available on the registry. The build of an App will fail if it requests a package which does not exist on the registry. Additional packages can be added by following the steps below.
Note that publishing a package means publishing its dependencies too: npm pack packs a single package, and npm resolves the rest from the registry at install time.
mkdir npm-sideload && cd npm-sideload
npm init -y
npm install <package>@<version>
mkdir tarballs
npm ls --all --parseable | tail -n +2 | sort -u | xargs -n1 npm pack --pack-destination ./tarballsTransfer the tarballs directory to your Tines Stories host, then publish each tarball using the tines-command-runner image, which carries npm and can reach the registry on the internal network:
docker compose run --rm --no-deps -v "$PWD/tarballs:/tarballs:ro" tines-command-runner sh -c 'export PATH=/opt/apps-build/bin:$PATH; echo "//npm-registry:4873/:_authToken=publish" > /tmp/npmrc; for tarball in /tarballs/*.tgz; do npm publish "$tarball" --registry http://npm-registry:4873/ --userconfig /tmp/npmrc --cache /tmp/npm-cache --provenance=false --tag latest || echo "not published: $tarball"; done'npm refuses to publish without credentials for the target host, even though the bundled registry accepts anonymous publishes, which is what the placeholder token is for; its value is never checked. --provenance=false and --tag latest override settings that some public packages ship, which would otherwise fail the publish or leave the package without a latest version. A version already on the registry is refused rather than replaced, so re-running the command after adding a tarball is safe.
On Docker Swarm there is no compose file to run against. Replace docker compose run --rm --no-deps in the commands above with docker run --rm --network tines_tines-net against the tines-command-runner image, on a manager node, substituting your stack name if it is not tines.
Things to note about the bundled registry:
The
npm-packagesvolume grows with everything published to it, including the versions each upgrade adds. Likepython-packages, it has no size limit.verdaccio-config.yamlships in the release ZIP and is mounted into the service. Keep it alongside your compose or stack file: if it is missing when the service starts, Docker creates a directory in its place, andupgrade.shstops with an error explaining how to restore it.The service runs whether or not you install from it. Setting
NPM_CONFIG_REGISTRYis what opts a deployment in.
Checking your configuration
You can see information on your configuration in the tenant health section of the Tines UI. For Apps, you can see whether NO_NPM_REGISTRY is set, the registry it installs from when that is not the public one, along with that registry's reachability and latency, and how many packages are baked into the image. Hover over the package count to see their names and versions.
How published apps are served
On Tines Stories Cloud, published apps are served from a dedicated *.tines.app domain. On self-hosted they are served from your own tenant domain, at https://your-tenant-domain/apps/<app>.
Nothing extra is needed to support this. Apps reach your users over the same load balancer, TLS certificate, and hostname as the rest of Tines Stories, so no additional DNS records or certificates are required. Viewers must be signed in to your Tines Stories tenant, and access follows the app's visibility setting.