Deploying: by moving a name
tcm build notes.main # an immutable build in .tcm/apps/<app hash>/
tcm start notes.main --port 8080 # serve whatever notes.main names now, and follow it
tcm push prod # to a server running tcm start: that's a deploy
tcm start serves the build the app's name points to. When the name moves (tcm update, mv, reset, pull), it
builds the new version if needed and switches to it. Deploying and rolling back are both moving a name.
Builds
tcm build <app>writes an immutable, content-addressed build to.tcm/apps/<app hash>/(for an app with apwa, the hash of both). An existing build is reused, unless you pass--forceor a different tcm made it (tcm's runtime is part of the build, so a new tcm builds again).--static <dir>writes the build as a static site for any static host, every page rendered as it's built. Only for apps that call no server functions (static sites).- React component apps also get a server-rendering bundle (
server/, never served), whichtcm startuses for each page's first paint (React). - Concurrent builds don't clash. Two builds of one app at once (two verifies, a
tcm buildwhiletcm startdeploys) both succeed, and a server never reads a half-written build. Dev servers on one codebase, of one app or several, never replace each other's files.
What the browser downloads
- A few chunks, kept across deploys. React, other npm packages, tcm's runtime and the app's own code are each a
chunk, named by its content and served immutable under
/_app/assets/. A deploy that changes only the app's code re-downloads only its chunk; the others keep their URLs. Stylesheets<Stylesheet>links to (React) are named by hash the same way. - Compressed. Assets are served brotli or gzip, as the browser accepts.
- Updates as differences. Each chunk is offered as a
compression dictionary for its next version. A browser that has the
previous chunk gets the new one compressed against it (
dcb): after a small change, a few hundred bytes. Browsers without support, or proxies that drop the headers, get plain brotli. - Source maps are kept, not served. tcm reads them to say which definition a page's error was thrown in (traces).
No version skew
Earlier builds stay served, and their server functions stay callable. A tab opened before a deploy keeps loading its own assets and calling the code it was built against until it reloads.
tcm appslists builds (*is current).tcm apps prune --keep Nremoves all but the current build and the N newest others (default 3).tcm start --keep Nprunes so after every deploy. Builds in use are never pruned: the one a runningtcm startserves, and one being made.- A tab on a build that's gone (pruned, from another copy of the codebase, or whose code the codebase no
longer has) gets
410 Gonefrom its next server call, and the browser runtime reloads it onto the current version.tcm devdoes the same for a version a newer change replaced. A page reloads itself at most once in ten seconds; if it's still outdated, the call fails with "this page's build is no longer served, and reloading it didn't help: reload it yourself". - Data changes: old pages that read rows written by newer code also get a 410 and reload (data).
Serving
- Pushing is deploying.
tcm push prodto a server runningtcm startsends only the definitions it lacks. The server verifies them by re-hashing, and itstcm startsees the name move, builds and switches. No deploy script, no restart. - Jobs and HTTP routes.
tcm startalso runs the job worker and schedules, and serves HTTP routes with--http <namespace>[=/prefix]. - TLS.
--tlsserves HTTPS (self-signed unless you give--tls-certand--tls-key); cookies then default toSecure. - Errors. Unexpected errors are logged on the server and reach the browser only as "internal error" (server API). A failing call is that call's 500; the server keeps running.
- Traces, with nothing to set up. Every call (as the page and the server saw it), page, route, job and table
statement is a span, kept for
tcm tracesand sent over OTLP toOTEL_EXPORTER_OTLP_ENDPOINTif set. Each names the build that answered (traces). WithTCM_TRACES_TOKENset, the server hands them to that token's holder, such as the studio (traces from production). - Containers that cap processes (cgroups'
pids.max; Railway caps at 1000, threads included): tcm runs its TypeScript compiler processes on few threads (GOMAXPROCS2 for a cap of 1000, unless you set it), so many tcm processes stay under the cap. A compiler process that dies anyway is restarted on the next call.
Under a path of a site
tcm start <app> --base /examples/notes serves the app under that path, beside other things on one site (tcm's
docs site runs its examples this way, behind tcm serve --proxy).
- Requests outside the path get 404. The path itself redirects to it with a trailing slash.
- The page's assets, server function calls (
<base>/__tcm/…) and plain HTTP routes are under the path. - One build serves any path: the page is told its base as it's sent, and the chunks don't change.
- Cookies are scoped to the path unless they say otherwise (
setCookie(name, value, { path })), so apps on one site keep their sessions apart. - The react library's paths are relative to the base:
useLocation().pathis/at the base,navigate("/about")goes to<base>/about, andhref("/about")gives a link's address (<a href>, or React Aria'sRouterProvider useHref). Server rendering sees the same, so links in the first paint are right. - A plain HTTP route's
Requesthas the address the caller used: under the path, with thehttps:and host a proxy in front reports (x-forwarded-proto,x-forwarded-host).