Your agent request touches twelve containers. Only one of them runs a model.
SOIT Team

The most common reaction to our quickstart is that twelve containers is a lot for a demo. It is a fair reaction, and the honest answer is not a defence of the number. It is a walk through one agent run, service by service, saying what each container is actually responsible for and what guarantee you lose if you delete it. A previous post answered how few services you can cut down to. This one answers why the rest are there.
Start by correcting the number, because two different counts get argued past each other. The quickstart command names twelve services. The compose file defines fourteen. Thirteen actually start: minio-init is not in the command, but api depends on it completing successfully, so it is pulled in, creates the bucket, turns anonymous access off, and exits. The fourteenth is scheduler, which no service depends on and which the quickstart therefore never starts at all. How many services a compose file defines and how many you actually run are two different numbers, and separating them is a precondition for arguing about weight at all.
Three of the containers are one-shot tasks that exit before your first request arrives: migrate creates the tables, bootstrap creates the admin user and the tenant, and minio-init prepares the object bucket. They cost startup time, not steady-state footprint. Two more are simply the thing being demonstrated: api and web. Everything else is the ledger and the boundaries around it.
Postgres is the ledger, and it is not incidental. A run is recorded across five tables covering the run itself, its steps, its tool calls, its artifacts, and its cost. That is the physical basis for the two claims the platform makes about every execution: that it can be observed and that it can be replayed. MinIO is the other half of that, holding the large objects a run produces while the database keeps only a storage key and a SHA-256. These two are also the only hard readiness gates in the stack. If either is down, the platform reports itself unready rather than serving requests that it cannot record.
The remaining containers each buy exactly one boundary. Vault holds credentials in a KV v2 store so they never land in a process or in a dotenv file. Redis caches permission decisions with a five-minute TTL, backs the rate limiter, and carries the cross-instance event bus. Milvus provides vector retrieval, and etcd is worth naming precisely because it is a frequent source of confusion: it is the metadata store belonging to Milvus itself, not a dependency of the platform. It comes and goes with Milvus. Notably, a Milvus outage makes knowledge retrieval fail, but it does not lower readiness. That asymmetry is deliberate, and it tells you which capabilities the runtime considers essential to its promises and which it does not.
One container only becomes interesting after the response has already been returned. Events raised inside a run are committed to the database in the same transaction as the work itself. The outbox dispatcher is what then delivers them, with checkpointed idempotency. Remove it and nothing appears broken during the request, because the request already succeeded. The events simply stay pending forever and every downstream consumer receives nothing. Two of the background processes, the outbox dispatcher and the knowledge ingest worker, are the same codebase as api running a different entrypoint, which is worth knowing before you count them as separate systems to operate.
Regrouped by what they guarantee rather than by what they are called, the steady-state picture is small. Two containers are the product. Two are the physical carriers of the ledger and its artifacts. Two and a half are one capability, vector retrieval. One is secret isolation. One is cross-replica broadcast and caching. Two are background workers built from the same code. Exactly one process in the entire topology is running a model. The rest of the weight buys a single thing: that after the run finishes there is still evidence it happened, and that a crash midway does not leave half a state behind.
Whether that trade is worth it depends on what you are doing. If you want to find out whether an agent can complete a task at all, this stack is too heavy for you, and the earlier post shows how to cut it to four resident containers. If the agent is going into a process that someone has to reconcile afterwards, these containers are the things you would eventually write yourself. One caveat worth stating plainly: this walkthrough is a static reading of the code and the compose file, not a new end-to-end run. The scheduler gap in particular is established from the topology and the configuration defaults rather than by waiting for a scheduled task to not fire. The repository is at github.com/soit-ai/soit, the full topology is in docker/docker-compose.yml, and if you think one of these calls is wrong, the issue tracker is the right place to say so.