How Antelao is put together
A handful of ideas explain most of the product's behaviour: what a server, an instance and a database are, which part decides and which does the work, why every change is a background job, and where the state lives.
Server, instance, database
Three nouns, nested.
A server is one Linux host running the managed stack. An instance is one Odoo deployment on
it: its own container, its own Odoo version and edition, its own addons and its own host names. A
database is an Odoo database inside an instance. All databases from all instances live in the
one shared ohm-postgres container.
Each instance has a twelve-character id, and its container is named ohm- followed by it.
The agent, the API and who does the work
The ports the console, the API and the agent answer on:
| Port | Protocol | Bound to | Firewall script | Service | What it is |
|---|---|---|---|---|---|
| 5201 | HTTP | Loopback | Nothing | ohm-agent | The host agent, for the command line and the server's own parts |
| 5202 | HTTPS | Every interface | Your addresses | ohm-agent | The host agent's page and API. Protected by its key and by who may reach the agent |
| 5311 | HTTP | Loopback | Nothing | ohm-api | The management API, for the command line and the console on the server itself |
| 5312 | HTTPS | Every interface | Your addresses | ohm-api | The management API |
| 5321 | HTTP | Loopback | Nothing | ohm-web | The console, for the server itself and an SSH tunnel |
| 5322 | HTTPS | Every interface | Your addresses | ohm-web | The console |
| 5323 | HTTPS | Every interface | Your addresses | ohm-web | The browser editor an instance's Code tab opens, with the console's certificate. Allow it wherever you allow 5322 |
Four parts, and the split between them is the single most useful thing to know.
The API decides. It owns the job queue, the scheduler and the master database. The console holds
no Docker socket and no domain code at all: it is an HTTP client of the API and nothing else. ohm
is the same by default, and only touches the host directly in local mode.
The agent does. It is a container the host's own systemd unit runs, and it is what actually
creates containers, writes files and installs the server - including recreating ohm-api and
ohm-web themselves. That is why it is a separate thing: something has to be able to rebuild the
management plane, and it cannot be the management plane.
The split is absolute rather than conventional. The API holds no Docker socket and mounts no data tree - not the instance trees, not the backups, not the keys. Anything that touches the daemon or the disk, it asks the agent to do. Two things follow that are worth knowing before you debug anything:
- A container that has only the API in it cannot be used to inspect an instance's files. There is nothing there to inspect.
- The license's hardware identity is read by the agent, on the host, and handed to the API. The API is not the thing being identified, which is why replacing the API container does not disturb a license.
Two practical consequences:
- If the API container is down, the console can show you nothing and change nothing. The agent's own
page on 5202 still works, and
ohm --localstill works, which is why the repair commands are local-only. - The agent and the API must be the same build. A mismatch refuses every dispatch rather than letting a stale agent act on a model it no longer shares. Core services shows both builds, in red when they disagree.
Everything is a background job
Every state-changing operation is queued as a job rather than performed inline. The API accepts your request, writes a job row, returns immediately and works through the queue.
This is why the console shows queued -> running -> completed rather than success straight away, and
why a remote ohm command streams a log until it finishes.
Jobs are persisted and resumable. If the API restarts mid-queue, queued jobs are picked up
again; jobs that were running are marked Interrupted, because half-finished work may be
destructive and re-running it blindly is worse than stopping.
Concurrency is bounded by job concurrency under Configuration, which defaults to 1, meaning jobs run one at a time. Changing it takes effect without a restart. Two rules hold whatever you set it to:
- Two jobs never run against the same instance at once.
- A server-level job (one with no instance) drains the queue and runs alone, because it mutates Docker, PostgreSQL or the filesystem that everything else shares.
Secrets never reach a job. A password you submit is kept out of the job and used once; a password the server generates is shown to you once, and can be read for ten minutes only. Neither appears in the job, its result or its log.
Where state lives
| What | Where |
|---|---|
| The host's half of the server configuration | /etc/antelao/agent.env and /var/lib/ohm-agent/host-state.json, served by the agent |
| The policy half | The master database |
| Instance configuration | The master database |
| Users, jobs, schedules, statistics, the license register | The master database |
| Instance data trees, backups, secrets | The filesystem under /var/lib/odoo |
| Licenses | *.lic files under the application root's license/ directory |
There is no server configuration file. The two halves and their owners are set out in Configuration and state.
The master database is PostgreSQL in the bundled ohm-postgres container by default, and can be
an external SQL Server. Its schema is owned by the application and upgraded forward. That upgrade is
forward-only: an older binary refuses to start against a newer schema, so a rollback means
restoring the database from before the upgrade too.
Saved, running, and what still differs
An instance has two configurations: the one you have edited, and the one the running container was actually built with. Antelao keeps a snapshot of the second.
Saving a configuration applies itself: the apply is queued behind the save and converges the
running system - the odoo.conf, the nginx site, a restart when the change needs one. You do not
have to remember a second step.
What still differs afterwards is on the Pending changes page, each entry with its reason and the action that goes with it: an apply on its way, a failed apply with its error and a retry, or something the apply deliberately leaves to you - the image rebuild, the certificate, the modules, the server install. The badge beside your account counts them.
Some settings only take effect when the container is recreated rather than restarted. Anything that changes a bind mount is in that category, addons paths above all. The apply picks the right action; doing it by hand with a restart may silently do nothing.
Events
The API publishes one event stream that carries signals, not data: "this job changed", "this instance changed state", "there are new statistics". Clients then re-read the authoritative state through the normal endpoints, which are role-scoped.
A signal that says "something changed" therefore shows nothing to a viewer who is not allowed to see what changed.