Skip to content

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:

  1. Scanner — Recursively walks every Git repo in addons/ looking for requirements.txt
  2. Synthesis — Merges all requirements into a single requirements_all.txt, generates a Dockerfile via Jinja2
  3. Baking — Triggers docker build to install system deps + Python packages
  4. Run — Launches the environment with the custom-built image