Architecture¶
One picture of how Oduflow is put together: a single server process that orchestrates everything over the Docker socket, two physically separate PostgreSQL clusters (dev and production), per-branch dev environments, production containers, shared auxiliary services, and an S3 bucket that holds both logical snapshots and the WAL-G physical backup stream.
System overview¶
flowchart TB
agents["AI coding agents<br/>MCP — stdio / Streamable HTTP"]
github["GitHub<br/>push webhooks"]
users["Users / browsers<br/>dashboard, Odoo, services"]
subgraph server["Oduflow server — one process, Docker-out-of-Docker"]
core["server.py — FastMCP + Starlette UI<br/>bearer / session auth<br/>per-branch / per-team / system locks"]
system_ops["system_ops<br/>infra + templates"]
env_ops["env_ops<br/>dev env lifecycle"]
odoo_ops["odoo_ops<br/>modules, tests, shell, SQL"]
production_ops["production_ops<br/>deploy / rollback"]
service_ops["service_ops<br/>auxiliary services"]
build_ops["build_ops<br/>custom Odoo image builds"]
backup_ops["backup_ops + WAL-G<br/>snapshots, retention, PITR"]
core --> system_ops & env_ops & production_ops & service_ops & build_ops
env_ops --> odoo_ops
production_ops --> backup_ops
end
traefik["Traefik (optional)<br/>auto-HTTPS via Let's Encrypt"]
subgraph dev["Dev — per team"]
devdb[("oduflow-db<br/>shared PostgreSQL cluster:<br/>template DBs + per-branch DBs")]
envodoo["oduflow-{team}-{branch}-odoo<br/>one container per git branch"]
overlay["fuse-overlayfs filestore<br/>copy-on-write over template"]
end
subgraph prod["Production — per team"]
proddb[("oduflow-prod-db<br/>dedicated, auto-tuned cluster<br/>physically separate from dev")]
prododoo["Production Odoo container(s)<br/>auto-tuned odoo.conf, cron on,<br/>rollback on failed deploy"]
end
svc["Auxiliary services<br/>Redis / Meilisearch / custom<br/>oduflow-{team}-svc-{name}"]
s3[("S3-compatible bucket<br/>AWS / MinIO / R2:<br/>snapshots + WAL-G stream")]
agents --> core
github --> core
users --> traefik
traefik --> envodoo & prododoo & svc
env_ops --> devdb & envodoo
envodoo --- overlay
production_ops --> prododoo
prododoo --> proddb
service_ops --> svc
envodoo -.same team network.- svc
prododoo -.same team network.- svc
backup_ops --> s3
proddb -->|WAL archiving| s3
s3 -.restore / PITR.-> proddb
How to read it, layer by layer:
- Clients. AI agents speak MCP to
server.py(stdio for a single local user, Streamable HTTP for remote/multi-user); humans use the same process through the web dashboard and REST API; GitHub push webhooks drive auto-deploys. Everything funnels through one FastMCP + Starlette process guarded by granular locks — operations on different branches run in parallel, same-branch operations are serialised. - Ops modules. The server never shells out to
docker; each*_opsmodule drives the Docker SDK directly (Docker-out-of-Docker when Oduflow itself runs in a container).build_opsproduces custom Odoo images used by templates and environments. - Dev. One shared PostgreSQL cluster (
oduflow-db) holds template databases plus a database per branch environment; each branch gets its own Odoo container, with large filestores shared copy-on-write via fuse-overlayfs. See Environment Management and Template Management. - Production. A dedicated, auto-tuned cluster (
oduflow-prod-db) — physically separate from dev — backing production Odoo containers with deploy/rollback handled byproduction_ops. See Production Hosting. - Auxiliary services. Redis, Meilisearch, or any custom image, attached to the team network so both dev and production Odoo can reach them. See Auxiliary Services.
- Routing. In Traefik mode, environments, productions, and services get automatic HTTPS hostnames; without it, Oduflow publishes stable per-branch ports. See Traefik Routing.
- Backups.
backup_opsand WAL-G push production data to an S3-compatible bucket on two independent paths — detailed in the next diagram.
Backup and recovery¶
Production data leaves the host on two independent paths, both landing in the same S3-compatible bucket (AWS S3, MinIO, Cloudflare R2, …):
flowchart LR
proddb[("oduflow-prod-db<br/>production cluster")]
filestore["Production filestores"]
subgraph s3["S3-compatible bucket"]
snaps["Snapshots<br/>pg_dump + deduplicated filestore<br/>daily / pre-deploy / on-demand"]
wal["WAL-G<br/>continuous WAL archive +<br/>daily base backups"]
end
proddb -->|pg_dump| snaps
filestore -->|chunkstore, content-defined dedup| snaps
proddb -->|WAL archiving| wal
snaps -.restore_production — one database.-> proddb
wal -.cluster PITR (operator-only) — whole cluster,<br/>any point in time.-> proddb
- Snapshots are logical, per-database backups: a
pg_dumpplus the filestore deduplicated by the built-in chunkstore engine. They restore one production at a time (restore_production), including to a brand-new host. - WAL-G continuously archives WAL and takes scheduled base backups of the
whole production cluster. Cluster PITR — an operator action in the
/adminconsole oroduflow restore-cluster, deliberately not an MCP tool — rewinds the entire cluster to an arbitrary point in time: the disaster-recovery path. - Retention, scheduling, and pruning run in the server's background scheduler. Details in Production Hosting.
For the module-level view of the same system — file-by-file layout, locking, error hierarchy — see Internals.