Docs/Timebase Components/Scaling and Deployment
AtlasConcept

Scaling and Deployment

This article is for the person who has to size the machine, plan the deployment, schedule the upgrade and own the backup.

Updated 13 Oct 2026Timebase Docs

Overview#

This article is for the person who has to size the machine, plan the deployment, schedule the upgrade and own the backup.

It answers four questions: how far Atlas scales, what actually consumes memory and disk, what happens when you restart it or upgrade it, and what you have to back up.

The one thing to take away: Atlas is a single-instance product today. One container, one process, one data volume. Atlas runs as one process per data volume, no load balancer in front of two Atlas instances. You scale it by giving the one machine more RAM and more CPU, and by controlling how many compiled models it holds at once.

For the component picture (what runs inside the process, the eight network surfaces, the ingress and egress data path), see Timebase Atlas Architecture. For how the model is stored, branched and committed, see How Atlas Uses Git. For installing it in the first place, see Start Here for Docker: How to Deploy and Secure Timebase 2.0. This article does not repeat those.

How far Atlas scales#

Important: Atlas runs as a single instance. Run one Atlas process per data volume, and scale it by giving that machine more memory and CPU rather than by adding instances.

Six pieces of state are held per process, which is what makes this the supported shape:

Held per process Why a second instance would diverge
Hub subscriptions are per process. SignalR runs without a backplane A subscription held on one instance is not visible to another, so a client connected to instance A does not receive a value that arrived at instance B
The connector registry is in memory. The connector registry is held in memory and keyed by live connection Binding paths resolve on whichever instance the connector happened to connect to, and nowhere else
Compiled models are in memory. They live in the process that compiled them Compiling on instance A leaves instance B still answering "not compiled"
The Atlas session id is per process. One identity is minted per process and stamped on every connector handshake, so connectors can detect an Atlas restart Two instances would hand connectors two identities, which reads to them as a restart loop
The write locks are process-local. The per-branch write lock and the per-repository Git lock are both held in the process, which is what serialises writes to one repository Two processes would be two uncoordinated writers on one Git repository
One server, one repository. The configured data root is authoritative, and one server serves one repository There is no per-request repository selection

So the growth levers you actually have are: more RAM, more CPU, faster disk, and fewer compiled models held at once.

What does scale, and what does not#

  • Reads scale with CPU and do not contend. The read path resolves a branch to a commit SHA once and reads blobs in process at that pinned SHA, taking neither the branch lock nor the repository lock. Any number of readers run in parallel, including in parallel with a writer.
  • Writes to the same branch are strictly serial. Every write takes the per-branch lock, and every Git-mutating call additionally takes the per-repository lock. Because there is one repository, that second lock is effectively a process-wide mutex on the write path. Authors working on different branches avoid the branch lock but still queue on the repository lock.
  • Queries run in process against the cached compiled model for that branch and domain. There is no placement to tune and no node to choose.
  • Split a very large model into separate domains. It compiles into one in-memory model. If one model is too large for the machine, the answer is to split the model into separate domains, not to spread one domain across servers.

The deployment shape#

One container, one process, one volume.

Item Value
Image timebase/atlas
Compose service atlas, one of the services in the platform Compose file from Start Here for Docker
HTTP port 4551
HTTPS port 4552
Data volume atlas, mounted at /atlas. The Git-backed store sits under it at Config=/atlas/config; settings, logs, certificates and machine state sit beside it at /atlas/settings, /atlas/logs, /atlas/certificates and /atlas/data
Required companion A reachable Timebase Pulse instance, by default on 4542

Connectors and Bridge are separate processes on any host. Both connect outward to Atlas over the network and never need to sit on the Atlas machine.

Important

publish both ports. The platform Compose file does; a file of your own that publishes only 4551 sends every request to HTTPS on 4552, which then has nowhere to land. Publish 4552 yourself or terminate TLS in front of Atlas. HTTPS is also a hard requirement for the dictation microphone in the Atlas interface.

Pulse is a prerequisite, not an optional extra: Pulse:Auth:Enabled is true in the shipped settings.config, every API and hub is bearer-gated, and the AI assistant's models are reached only through Pulse. A deployment plan without a reachable Pulse produces an Atlas that nobody can sign in to.

Configuring the deployment#

Atlas configuration is JSON in settings.config, inside the data volume. It is not YAML, and it is not environment variables.

Configuration precedence#

Sources are layered in this order:

appsettings.json -> appsettings.{Environment}.json -> settings.config -> environment variables -> command line

The settings file is added before the environment-variable provider, so the environment and the command line take precedence over the file for every key - Pulse:*, Kestrel:* and Serilog:* included. A value in settings.config is the one in effect only where nothing outranks it.

Important

settings.Docker.config is not a file in the volume and not a layer that is loaded. It is an overlay embedded in the image, applied exactly once: the first time Atlas starts into an empty volume it composes settings.config from its template patched with that overlay, which sets Pulse:Host to pulse, and never reads it again. To change a value afterwards, edit settings.config in the volume, or set the environment variable or command-line argument for the key, which outranks the file.

Steps#

  1. Keep both ports published. The platform Compose file publishes 4551 and 4552; keep the HTTPS host port as 4552, because the HTTPS redirect carries the container's own port number.
  2. Run Atlas in the same Compose project as Pulse, Connect and Bridge. They share one network and reach each other by service name, which is what the shipped settings assume.
  3. Point Atlas at Pulse. On the first start into an empty volume the embedded Docker overlay sets Pulse:Host to pulse in settings.config, which is right for the platform Compose file. To use another address, edit Pulse:Host and Pulse:Port in settings.config in the volume, or set Pulse__Host and Pulse__Port as environment variables, which outrank the file. In Docker that is a service name the container can resolve, never localhost.
  4. Set the ports, if the defaults clash. The ports come from Kestrel:Endpoints:Http:Url and Https:Url. ASPNETCORE_URLS does not set the Atlas listen addresses.
  5. Restart the container. Settings are read at startup only.
  6. Keep Config= on the persistent volume. It is the Git-backed store - every domain, library and branch. Atlas reaches its language model through Pulse Gage, so no API key goes into the Compose file.

The Config, Settings, Logs, Certificates and Data environment entries in the Compose file are read before the settings file is even located, which is what lets them say where it lives.

Sizing: what actually consumes memory#

The single most important capacity fact about Atlas is this:

Memory scales with branches multiplied by domains, not with domains.

Every compiled model is held per branch and domain, and stays resident for the life of the process. An entry is replaced when a newer compile of the same branch and domain lands, and the set is released when the process restarts.

Because a branch is a version in Atlas, compiling the same domain on ten branches holds ten complete copies of that model in memory at once. Size against the number of (branch, domain) pairs rather than the number of domains.

Each entry carries:

  • the compiled model itself: every type, every expanded instance, every relationship, plus the columnar value store,
  • a Cypher engine with its own persistent query cache that also never evicts,
  • the compiled function registry,
  • the compiled functions for that model, released together with it.

Practical sizing guidance#

Count (branch, domain) pairs, not domains. A team of five modellers each working on a feature branch of the same three domains, with main also compiled, is up to 18 models resident, not three.

A restart recompiles the same set, so tidy the branch list first. The process starts clean, but The startup recompile reads every pair recorded in compiled-worlds.json and recompiles it, so the resident set is restored rather than reclaimed. Restarting is a way to recover from a leak or to pick up new settings, not a capacity measure.

The one thing that does reduce resident memory is having fewer (branch, domain) pairs to compile. Delete branches you have finished with. A record is pruned when the domain or branch behind it no longer exists, so the reduction takes effect at the next start.

Important

watch memory as branch count grows, not as model size grows. A team that adopts a branch-per-change workflow can multiply Atlas's memory footprint without anyone adding a single entity type.

Size Atlas from a fixed floor plus a per-instance allowance, then multiply by how many models are resident at once.

Allow about 1 GB before any model, and then about 2 KB per instance, counting each instance together with its properties and its relationships. A 25,000-instance model is therefore around 50 MB and a 250,000-instance model around 500 MB.

RAM  =  1 GB  +  (2 KB x instances x resident (branch, domain) pairs)  +  compile headroom

4 GB is a sound starting point for a plant-scale model across a working number of branches.

Branch count is the driver, not entity count. At roughly 50 MB a copy, the branch multiplier reaches a gigabyte long before a realistic model does. Size on entity count only past roughly 100,000 instances.

The remaining drivers, which the arithmetic does not capture, are the number of compiled function assemblies held and the number of live value-hub subscriptions.

Restart behaviour and cold start#

Atlas recompiles at boot. The startup recompile is a background hosted service that reads every recorded (Branch, Domain) pair from compiled-worlds.json and recompiles them one at a time, sequentially.

What this means operationally:

  • Kestrel binds and accepts traffic immediately. Atlas is reachable long before it is fully ready.
  • Domains become queryable one by one, in whatever order the records are read. A query against a domain whose turn has not come yet reports the domain as not compiled.
  • Time to full availability grows roughly linearly with the number of compiled (branch, domain) pairs. This, not a slow disk, is the answer to "why is Atlas slow for the first few minutes after a restart".
  • Connector-dependent domains compile when their connector arrives. A domain whose instances come from a connector-driven source has its declared binding paths captured at boot, and compiles the moment a connector advertises paths that resolve them.

The knob is Compilation:AutoRecompileOnStartup, default true.

Note

you do not need to recompile manually after a restart. The one case that calls for it is a connector-dependent domain whose connector is not yet online.

Disk growth#

Disk on the data volume grows monotonically. Plan for it.

  • Git garbage collection is pinned off. Every boot sets gc.auto=0 and core.autocrlf=false on the repository, so that background repacking cannot run under a live read handle. Loose Git objects therefore accumulate: every save is a commit, and commits are packed only when you run git gc against a stopped instance.
  • Every draft session is a real checked-out worktree. Durable per-identity draft worktrees live under {data root}/.worktrees/.v2-draft/{hash}, one per identity and target branch. Each one is a full working copy of the model on disk.
  • Logs and certificates live alongside the repository in the same volume.
Important

leave automatic Git garbage collection off on the Atlas repository, and run git gc only against a stopped instance. If the repository has grown to the point where it needs packing, stop Atlas first.

Upgrading Atlas#

An upgrade is a normal image pull and restart, and your data in the atlas volume survives it. From 2.0 the image runs as an unprivileged user (uid 1654) rather than as root, so a volume written by an earlier image has to be handed to that user once; the platform Compose file's volume-init service does that on every start, and Timebase 2.0 Release Notes has the one-off command for any other deployment. Atlas is the component where this matters most, because its Config volume is a Git repository that Atlas must be able to write on every start.

One library is system-owned: atlas-core. On every start, upgrade or not, Atlas compares its own copy against yours in libraries/, copies it in if yours is missing, and restores the shipped version if they differ. That restore is committed to the repository as part of the startup auto-commit, and it is how a framework vocabulary upgrade reaches your model.

Important

put your own types in a library of your own rather than adding them to atlas-core. Your library is never rewritten. Libraries you copied in during commissioning, such as atlas-common and isa95-equipment, are yours in the same way, which also means a newer shipped version of one of them reaches you only when you import it again.

Before an upgrade:

  1. Back up the whole data volume (see below).
  2. Confirm there are no local edits in atlas-core or atlas-common. If there are, move them into your own library first.
  3. Pull the new image and restart.
  4. Expect the boot recompile to run. Allow time proportional to your compiled (branch, domain) count before declaring the upgrade complete.
  5. Check the startup log for "Upgraded stale seed library" entries so you know which vocabulary changed.

Backup and restore#

Important: back up the whole data volume, not just the Git repository. Several things live inside the data directory and outside Git history, so a git clone or git bundle does not carry them.

What a Git-only backup leaves behind:

Excluded from Git What you lose
compiled-worlds.json The record of which (branch, domain) pairs were compiled. After a restore, the boot recompile has nothing to recompile, and every domain reports as not compiled until someone compiles it by hand
/agents/ Every AI agent definition. These have no version history at all
/skills/ Every skill definition, same
/views/ Every saved view
draft-sessions.json and /.worktrees/ Every author's unpublished work. Because a draft session is created only when a save fails validation, these hold work that is in progress and not yet correct - exactly the work nobody has a copy of
The Data folder The assistant's chat state (sessions, threads and exports) and the licensing state file. You lose the chat history and uploads

Back up the whole Atlas directory or volume. In Docker that is the atlas named volume, which holds the Git-backed store under config/ and the settings, logs, certificates and machine state beside it. On Windows it is the Atlas folder under C:\ProgramData\Flow Software\Timebase\, covering Config, Data, Settings, Logs and Certificates.

You can copy the volume out while Atlas is running, but a copy taken mid-write can catch the repository between a commit and a ref update. For a restore-grade backup, stop the container first.

Troubleshooting#

Symptom Likely cause What to do
The container will not start, and the log mentions a native probe The read path depends on the native Git library, so Atlas stops at startup rather than coming up unable to serve reads Read the probe message in the startup log. Confirm the data volume is mounted, reachable and writable by the user Atlas runs as (uid 1654 from 2.0), and that the image is intact
The container exits at once and the log reads Config path is not writable, certificate could not be written or cannot open its repository The volume was written by an image that ran as root, and the 2.0 image runs as uid 1654 Let the platform Compose file's volume-init service run, or hand the volume to that user once as described in Timebase 2.0 Release Notes
The container will not start, and the log mentions a port already in use Another process holds 4551 or 4552 on the host Remap the HTTP port freely, for example "14551:4551". Keep the HTTPS host port as 4552, because the redirect carries the container's own port number
A browser is redirected to a port that does not answer Atlas redirects plain HTTP to HTTPS on 4552, and your Compose file publishes only 4551 Publish 4552, or terminate TLS in front of Atlas
Every call returns 401, or a sign-in page appears where JSON was expected Pulse:Auth:Enabled is true (the default) and either no bearer was sent or Pulse is unreachable Confirm Pulse is running and reachable on 4542 from inside the container. Send a Pulse bearer whose aud includes Atlas. Turn off redirect-following in API clients so you see the challenge instead of an HTML page
You changed a setting and it had no effect An environment variable or command-line argument for the same key outranks the file, or the change was made somewhere Atlas does not read: there is no settings.Docker.config in the volume, only settings.config Check the Compose file for a matching environment: entry, make the change in settings.config in the volume, and restart. See Configuration precedence
After a restart, domains report as not compiled for several minutes The boot recompile works through them in sequence Wait and retry. Domains become queryable one at a time, so no manual recompile is needed
After a restart, one domain stays uncompiled It is connector-dependent and its connector has not advertised the binding paths its instance maps declare Check that the connector is running and connected, and compare the bindingPath values in the instance map against what the connector advertises
Memory climbs as the team works, with no new modelling Compiled worlds are held per branch and domain for the life of the process Count the (branch, domain) pairs compiled on the instance rather than the domains, and delete finished branches. A restart recompiles the same set, so tidy the branch list first. See Sizing
Disk on the data volume keeps growing gc.auto=0 is pinned at every boot, so loose Git objects accumulate, and every active draft session holds a full worktree Plan for steady growth. Pack the repository with git gc against a stopped instance rather than enabling automatic garbage collection
A type added to atlas-core is restored to the shipped version That library is system-owned and is compared against the installation on every start Move the types into a library of your own. See Upgrading Atlas
A restore came back missing agents, skills, or every domain uncompiled The backup was of the Git repository, not the data directory Restore from a whole-volume backup. Agents, skills, compiled-world records and draft sessions are all git-ignored. See Backup and restore
Two Atlas instances behave inconsistently: one sees a connector or a compiled domain and the other does not Two instances are running against the same data volume This is unsupported and cannot be configured around. Stop one of them. See How far Atlas scales
Someone asks how to deploy a domain to a node Atlas has no node assignment and no deploy step A domain is compiled and the result stays in the same process
Writes feel slow when several people model at once All writes to one branch are strictly serial, and every Git-mutating call takes a process-wide repository lock Expected behaviour. Authors on different branches contend less but still meet the repository lock. Reads are unaffected: they take no lock at all
Article Owns
Timebase Atlas Architecture The component picture, the eight network surfaces, and the ingress and egress data path
How Atlas Uses Git The repository layout, branches as versions, read and write paths, and what Atlas does to the repository at startup
Start Here for Docker: How to Deploy and Secure Timebase 2.0 First install, Pulse setup, and first-run behaviour
Compiling and Publishing a Domain The compile lifecycle, staleness and draft publishing
Connector Diagnostics - Data Not Flowing Data that is configured but not arriving
esc
↑↓ navigate · ↵ open · ⌘K anywhere