Architecture¶
System Overview¶
flowchart TB
subgraph "Odoofly CLI"
CLI["of / odoofly"]
CONF["~/.config/odoofly/config.yaml"]
PROJ["project.yml (IaC Manifest)"]
end
subgraph "Core Engine"
TM["Template Manager\n(Jinja2)"]
GM["Git Manager\n(clone/update/submodules)"]
DM["Docker Manager\n(compose/build/volumes)"]
BM["Backup Manager\n(ZIP + SQL + filestore)"]
CM["Config Manager\n(odoo.conf tuner)"]
end
subgraph "Runtime Containers"
ODOO["Odoo Container\n(port 8069)"]
PG["PostgreSQL Container\n(port 5432)"]
TRAEFIK["Traefik Proxy\n(ports 80/443)"]
end
subgraph "Storage Layer"
VOL_DB["DB Volume\n(postgres data)"]
VOL_FS["Filestore Volume\n(Odoo data)"]
BIND_ADDONS["Bind: addons/\n(Git repos)"]
BIND_LOGS["Bind: logs/\n(Odoo logs)"]
BIND_ETC["Bind: etc/\n(odoo.conf)"]
end
CLI --> CONF
CLI --> PROJ
CLI --> TM
CLI --> GM
CLI --> DM
CLI --> BM
CLI --> CM
TM --> ODOO
GM --> BIND_ADDONS
DM --> ODOO
DM --> PG
DM --> TRAEFIK
BM --> VOL_FS
CM --> BIND_ETC
ODOO --> VOL_FS
ODOO --> BIND_LOGS
PG --> VOL_DB
ODOO --> BIND_ADDONS
ODOO --> BIND_ETC
TRAEFIK --> ODOO
Convention over Configuration¶
Every Odoofly project follows an identical directory contract:
my-project/
├── project.yml # Single source of truth (IaC manifest)
├── main/ # Primary environment (dev/prod)
│ ├── addons/ # Git repos cloned here
│ ├── etc/ # Auto-tuned odoo.conf
│ │ └── odoo.conf
│ ├── logs/ # Odoo runtime logs (bind mount)
│ ├── data/ # Docker volumes (db / filestore)
│ │ ├── db/ # PostgreSQL persistent data
│ │ └── filestore/ # Odoo attachments & data
│ ├── backups/ # .zip backups (DB dump + filestore)
│ └── Dockerfile # Auto-synthesized by build engine
├── staging_.../ # Ephemeral staging environments
│ └── ... (same structure)
└── production/ # Production environment (with Traefik)
└── ... (same structure)
The Build Engine¶
flowchart LR
A["of env init main"] --> B["Scan addons/ for\nrequirements.txt"]
B --> C{"Any found?"}
C -->|Yes| D["Merge into\nrequirements_all.txt"]
C -->|No| E["Skip pip install layer"]
D --> F["Generate Dockerfile\nvia Jinja2 template"]
E --> F
F --> G["docker build\n(bake deps into image)"]
G --> H["docker compose up\n(immutable environment)"]
When you run of env init or of env update --git:
- Scanner — Recursively walks every Git repo in
addons/looking forrequirements.txt - Synthesis — Merges all requirements into a single
requirements_all.txt, generates aDockerfilevia Jinja2 - Baking — Triggers
docker buildto install system deps + Python packages - Run — Launches the environment with the custom-built image