Skip to main content

Standard and Scale

ITSM Engine can be deployed in two ways. Standard runs the whole desk on one server. Scale runs the same application on several servers for desks with more agents working at the same time than one server can handle.

Both use the same container image, the same database schema and the same features. Scale is not an edition: nothing is unlocked by it, and nothing is paid for. The difference is only how many copies of the application run and which supporting services sit around them.

[!NOTE] Standard is what the installation guide sets up, and where to start. Scale is deploy/compose.scale.yaml, laid over the Standard file.

SetupMeasured
One Standard serverHandles up to about 3,000 agents working at the same time (see system requirements).
Scale, two application serversCarried 3,000 agents with 95% of requests within 62 ms (load testing).

Standard​

Internet
Caddy
TLS certificates, reverse proxy
ITSM Engine
one container
PostgreSQL 17
S3-compatible storage

Four containers from deploy/compose.yaml:

ServiceWhat it does
CaddyTerminates HTTPS, gets and renews certificates, and forwards requests to the desk. A portal on its own domain is one more site in its configuration (portals).
ITSM EngineThe application. One Node.js process serves the agent app, portals, hosted forms, the REST API and the MCP server, and runs the background jobs (email, webhooks, automations, service-level timers, housekeeping).
PostgreSQL 17All data, and the job queue (pg-boss, in its own pgboss schema).
StorageAttachments, logos and exports, in an S3-compatible bucket. The bundled MinIO can be replaced by any S3-compatible service.

How Standard holds together​

  • The database holds the state. Sessions, rate-limit counters, the job queue and every record are in Postgres; files are in storage. The application container writes nothing to its own disk and runs with a read-only file system.
  • Background work runs in the same process, taken from the queue in Postgres. Scheduled jobs run once, whichever process picks them up.
  • Some data is held in memory and refreshed. Installation settings and plugin state are reloaded from the database every 30 seconds. Who is viewing or typing on a case is kept in the application's memory.
  • Email goes out through the provider you configure (SMTP or Resend). The desk runs no mail server of its own.

[!TIP] Standard needs no Redis, no message broker and no load balancer. Backups are one database dump and one copy of the storage bucket (backups).

Scale​

Internet
Caddy
TLS, load balancing
ITSM Engine× N
app servers
Worker
background jobs
Redis
presence, messages between servers
PgBouncer
database connections
S3-compatible storage
PostgreSQL

Scale adds an override file, deploy/compose.scale.yaml, on top of the Standard compose file. From deploy/:

docker compose -f compose.yaml -f compose.scale.yaml up -d --build
ServiceWhat changes
CaddySpreads requests over the application servers (Caddyfile.scale). It finds them by name every 10 seconds and stops sending to one that fails its health check.
ITSM Engine (desk)APP_SERVERS copies of the same image (2 by default). They answer requests and queue background jobs, but run none, and don't apply migrations. None keeps anything another one needs.
WorkerOne more copy of the same image. It applies migrations when it starts, before the application servers start, and runs every background job and schedule, so request handling and jobs don't compete for the same CPU. Caddy sends it no requests.
PgBouncerPools database connections in transaction mode, so many application servers share PGBOUNCER_POOL_SIZE Postgres connections (40 by default).
RedisShort-lived state the application servers share: who is viewing a case, and messages between servers. Nothing is saved to disk, and nothing in Redis needs a backup.
PostgreSQL, storageUnchanged: one database and one bucket, as in Standard.

What to set​

The override sets the addresses for you (configuration):

VariablePoints at
DATABASE_URLPgBouncer
DATABASE_DIRECT_URLPostgres, straight
REDIS_URLRedis

In .env, set APP_SERVERS and, if you need, PGBOUNCER_POOL_SIZE; everything else is as for Standard.

Redis can be any Redis-compatible server, such as Valkey or a managed one: point REDIS_URL at it and remove the redis service.

What makes several servers work​

  • Presence goes through Redis. With one process, the desk keeps who is viewing or writing on a case in memory. With several, each server keeps it in Redis, so an agent on one server sees a colleague on another.
  • Database connections are pooled twice. Each application server keeps a pool of up to DATABASE_POOL_SIZE connections (20 by default) to PgBouncer, which shares PGBOUNCER_POOL_SIZE connections to Postgres among them. Migrations and the job queue use a direct connection (DATABASE_DIRECT_URL), since PgBouncer's transaction mode can't hold the locks they take.
  • The application decides from its configuration. With REDIS_URL set it uses Redis; without it, memory, as Standard does. With JOBS_WORKER=false it queues jobs but leaves running them to the worker. No code or feature differs between the two setups.
  • Settings stay in step. Each server reloads settings from the database, so a change made on one server reaches all of them within 30 seconds, as it does in Standard.
  • Access changes count at once, everywhere. Each server keeps people's roles and units in memory, but checks one number in the database on every request; the database raises it whenever roles, teams or units change. Taking someone's access away works on every server from their next click, in both setups.
  • Sidebar counts are shared. The open, unassigned, due-soon and held counts are counted once for everyone and kept for 10 seconds, instead of for each person each minute. When a case changes, the server it changed on tells the others through Redis, and they all count again.
  • Live updates reach every browser. Each app server listens to Postgres for changes to cases (a direct connection, since PgBouncer can't pass that on) and pushes them to the browsers connected to it, so a reply written on one server shows at once to a colleague on another. Who is on a case goes through Redis.
  • Jobs run once. The job queue is in Postgres, and only the worker takes jobs from it, so an email or a scheduled task runs once however many application servers there are.

[!IMPORTANT] Keep PGBOUNCER_POOL_SIZE, plus 10 for the job queue and a few for migrations and backups, under Postgres's max_connections (100 by default).

Choosing between them​

StandardScale
ServersOneSeveral application servers, plus the database (often managed)
Containers44, plus N application servers, a worker, PgBouncer and Redis
Agents working at the same timeUp to about 3,000Beyond that, by adding application servers
If the application server failsThe desk is down until it restartsThe other servers carry on
BackupsDatabase and storageThe same: Redis holds nothing to back up
UpgradesRestart one containerThe worker migrates, then the application servers restart together (upgrades)
FeaturesAllAll

Start with Standard. It is simpler to run, back up and upgrade, and one server goes a long way: the load test measured 3,000 agents online with 95% of requests answered within 124 ms.

Consider Scale when:

  • more than about 2,500 agents work at the same time, and the number is growing;
  • the desk must stay up while one server is restarted or fails;
  • the application container regularly uses more than about 0.7 of a CPU core. One Node.js process uses one core for requests, and that is the first limit Standard reaches.

Moving from Standard to Scale​

The data stays where it is: the same database and the same bucket.

Back up → Set APP_SERVERS → Start with both compose files → Worker applies migrations → Application servers start
Application servers start → Standard: move back with compose.yaml alone
  1. Back up as usual (backups).

  2. In .env, set APP_SERVERS (2 to start with).

  3. Start the stack with both compose files:

    docker compose -f compose.yaml -f compose.scale.yaml up -d --build

    The worker starts first and applies any migrations, then the application servers start. Nothing in the data changes.

Moving back​

Moving back works the same way in reverse: start the Standard stack with compose.yaml alone:

docker compose up -d --remove-orphans

The worker, PgBouncer and Redis stop, and the one desk container runs jobs again.