Deployment
Scaling a self-hosted workspace
OneCamp is one machine to begin with, and for most teams it stays one. This
page is about what that machine needs, what grows first, how to add capacity
without touching the machine, and how to move when it is time.
What one machine runs
Every install is the same Docker Compose stack: Postgres, Dgraph, Redis, MinIO
for files, EMQX for real-time, OpenSearch for search, LiveKit and its egress
service for calls, the collaboration service for shared editing, ClamAV for
scanning uploads, Traefik in front, and the OneCamp API. Four more are optional
profiles you turn on when you want them: the Python sandbox for data analysis
(code-execution), transcription (transcription), a local model server
(local-ai), and the coding agent's runner (coding).
Everything persistent lives under ./data/ next to your compose file. That one
fact is what makes the rest of this page short.
Sizing the machine
The floor is 8 GB of RAM and 40 GB of disk. That is the same floor the
managed service checks a server against before it will use it, and below it
the stack does not settle: OpenSearch and the JVM services want headroom,
ClamAV holds its whole signature database in memory, and the databases need
room to breathe. Run make create-swap after install; it is idempotent and it
is the difference between a small server that works and one that falls over
during a reindex.
What grows first depends on how the workspace is used:
- Files grow the disk. MinIO holds every upload.
make storage-report
says how full you are and whether a mirrored disk has quietly lost its pair. - Search grows memory. OpenSearch's heap is the first thing to raise on a
workspace with a long history. - Calls cost CPU while they run. LiveKit is quiet until a meeting starts.
- Local AI costs whatever the model costs. A model served by Ollama lives in
RAM while it answers; a 7B model wants around 8 GB of its own. If you run
local models, budget the machine for the model first and the workspace
second, or point the workspace at a hosted provider and keep the machine
small.
docker stats shows you which of these it is on your machine.
Adding workers without touching the machine
The API process also runs every background loop: the AI teammates' task queue,
the scheduler, the workflow engine, the sync and email workers, the sweeps.
When the AI teammates need more capacity than one process gives them, or a
long agent run is what makes the app feel slow, split the two.
Set SERVICE_ROLE=api in .env so the API process serves requests only, and
start workers from the same image:
docker compose --profile workers up -d --scale go-worker=3
Every worker claims from the same queue with a database lease, so adding one
never runs a job twice. Each process runs up to AI_AGENT_TASK_CONCURRENCY
jobs at once (default 6), so three workers is three times that. Workers serve
nothing but a health endpoint and are not on the public network.
Settings an admin saves reach every worker within thirty seconds; nothing
needs a restart for that. Admin, then Settings, then System check has anagent-queue line that goes red if due work sits unclaimed for two minutes,
which is what a fleet with the API set to api and no worker running looks
like.
You can also leave SERVICE_ROLE alone and just add workers; the API process
keeps running the loops and the workers add capacity on top.
Scaling the AI itself
The model is usually the bottleneck, not the machine. Three levers, in the
order to try them:
- Caps. Admin, then AI. A daily token cap per workspace and per teammate,
and a per-minute rate limit, keep one busy agent from starving the rest. - Hosted models. A hosted provider scales without you. The machine then
only runs the workspace. - Remote agents. A teammate can reason at an endpoint you run elsewhere,
over AG-UI, and still work under this workspace's permissions and audit.
That moves the heaviest compute off the box entirely.
Moving to a bigger machine
When one machine is genuinely too small, move. The backup is a dump of
Postgres and Dgraph, and with BACKUP_FILES=1 a copy of the files too.
On the old machine:
make backup BACKUP_FILES=1
make backup-list
Copy the newest directory under ./data/backups/, your .env, yourcompose.yml and your LiveKit configuration to the new machine. Install there
as you did the first time, then:
make restore FROM=<that directory> CONFIRM=restore
Restore stops the stack, replaces what is there with the backup, migrates, and
runs make verify. Point DNS at the new machine last, after make verify is
green there. Expect the workspace to be unavailable for the length of the copy.
What to watch
make verifyafter any change. It runs every check the install knows.make enable-backupsonce. A backup nobody schedules is a backup nobody has.
The nightly backup lives on the same disk as the data, so it protects you
from a deletion, not from losing the machine; copy it somewhere else too.- Admin, then Settings, then System check, for the things that break quietly:
a database that stopped answering, search behind, a queue nobody is draining. make doctorbefore you change.env. It catches a placeholder left in
place before the services fail in five different ways.
What does not scale this way
One workspace is one Postgres and one Dgraph. There is no multi-node database
and no sharding of a workspace across machines. Workers scale the application;
the data tier scales by moving to a bigger machine. For almost every team that
is the right trade, and the ones it is not right for know who they are.