Skip to content

How Odoo Servers Are Built

TL;DR

Your Odizy Odoo server is assembled by ikb from a single recipe file: .muppy/buildit.jsonc. To add an addon (your own, or one from a git repo), tune Odoo, or pull a Python package, you edit that file, then run ikb install in a terminal. Commit the file when you are happy — that is what makes your build reproducible.

What builds your server: buildit / ikb

An Odizy Odoo server is not a hand-installed Odoo. It is assembled from a declarative recipe by a tool called Inouk Buildit, whose command-line is ikb.

ikb takes care, in one command, of everything that a working Odoo needs:

  • cloning Odoo itself at the right version,
  • cloning your addons from any number of git repositories,
  • installing all the Python dependencies,
  • generating the Odoo configuration file and a ready-to-use launcher.

It is heavily inspired by zc.buildout, reworked around a single JSON recipe. The engine lives at gitlab.com/inouk/buildit and its Odoo plugin at gitlab.com/inouk/ikb.

Your interface is the file

On Odizy you do not have a buildit GUI. Your single control surface for the build is buildit.jsonc. Everything on this page is something you express by editing that file and re-running ikb install.

The build loop

Everything happens from a terminal on your App Server — either the Code Server web IDE (a bash terminal in your browser) or an SSH session.

# from the repository root of your App Server
ikb install      # (re)build: clone/update Odoo + addons, install deps, generate config + launcher

ikb install is idempotent: run it as often as you like. On an existing build it updates the Odoo and addon clones (git fetch / checkout / pull) and reinstalls dependencies.

To start Odoo after a build, use the generated launcher (it accepts all standard Odoo arguments and options):

odoo-ikb                       # start Odoo
odoo-ikb -u my_addon           # upgrade a module, etc.

Resetting

ikb reset        # remove the generated config (etc/) and the launcher

ikb reset does not delete everything — on purpose

ikb reset only removes the generated parts (the etc/ config and the launcher). It deliberately keeps the cloned Odoo (parts/odoo19) and your cloned addons, because re-cloning them on every reset would be far too slow.

If you really want a clean-slate rebuild (a fresh Odoo or a fresh addon clone), delete the corresponding folder by hand and run ikb install again:

rm -rf parts/odoo19          # then: ikb install   (re-clones Odoo)

Reproducibility

When your build is the way you want it, commit buildit.jsonc. The committed recipe is the single source of truth: any future build — on this server, on a teammate's server, or in the Kubernetes / CI-CD pipeline — reproduces exactly the same Odoo from it.

Where is buildit.jsonc?

On your Odizy server the recipe is at .muppy/buildit.jsonc, and the environment variable IKB_BUILDIT_JSONC_PATH points ikb to it:

IKB_BUILDIT_JSONC_PATH=/opt/<your-app>/.muppy/buildit.jsonc

Default location

If IKB_BUILDIT_JSONC_PATH is not set, ikb looks, from the directory it is launched in, for (in order): buildit.json, buildit.jsonc, .ikb/buildit.json, .ikb/buildit.jsonc. On Odizy the variable is set for you, so the recipe is always .muppy/buildit.jsonc.

The file is JSONC — JSON with // comments allowed.

Anatomy of the recipe

buildit.jsonc describes a set of parts, each driven by a plugin. The one you care about is the odoo part. Here is a trimmed, representative version of a delivered Odoo 19 recipe:

{
    "buildit": {
        // ── infrastructure parts: delivered, leave them alone (see "Delivered — don't touch") ──
        "buildit-plugins": { "plugin": "inouk.buildit.git_checkout", "repositories": { /* … */ } },
        "ENV":  { "plugin": "ikb.environ" },
        "py3x": { "plugin": "ikb.virtualenv", "name": "py3x", "interpreter": "python3" },

        // ── the Odoo part: this is what you tune ──
        "odoo": {
            "plugin": "ikb.odoo",
            "python": "${py3x}",
            "major_version": "19",
            "version": {
                "type": "git",
                "repository": "https://github.com/odoo/odoo.git",
                "directory": "parts/odoo19",
                "branch": "19.0", "refspec": "19.0", "depth": 5
            },
            "requirements": {
                "requirements_file": "user_addons/requirements.txt",
                "options": { "no-deps": false }
            },
            "addons": {
                "project_addons": {
                    "install": true,
                    "type": "local",
                    "root_folder": "repository",
                    "path": "./user_addons"
                }
                // … more addons here …
            },
            "config": {
                "options": {
                    "http_port": 8069,
                    "workers": 1,
                    "proxy_mode": true
                    // … more Odoo options here …
                }
            }
        },

        "running-env-root-path": "$MPY_APP_ABS_PATH"
    }
}

The three things you will edit are inside the odoo part: addons, requirements, and config. Everything else is delivered and should be left untouched.

Adding addons

Addons are declared under buildit.odoo.addons, keyed by addon name. Each entry has:

  • install — a boolean. Set it to false to keep the declaration but skip the addon. (true/false is all you normally need.)
  • server_wide — optional boolean. When true and install is true, the addon is added to Odoo's server_wide_modules (loaded for the whole server, e.g. a session store).
  • type"local" or "git" (an advanced "http_file" type also exists).

Pre-wired Inouk addons

The delivered recipe already lists a set of inouk_* addons (session store, message queue, …). Some are shipped install: true, others install: false — flip the boolean to enable or disable one. You do not need to add them yourself.

Your own addons — type: local

Your project code lives in the user_addons/ folder of your repository. The delivered recipe already maps it:

"project_addons": {
    "install": true,
    "type": "local",
    "root_folder": "repository",   // path is relative to the repo root
    "path": "./user_addons"
}

A starter example_addon/ ships inside user_addons/. To add your own module, just create or copy it into user_addons/ (next to example_addon) and run ikb install. No recipe change is needed — the whole folder is already on Odoo's addons path.

Addons from a git repo — type: git

To pull an addon (or a whole collection) from a repository:

"my_addon": {
    "install": true,
    "type": "git",
    "repository": "https://github.com/me/my_addon.git",
    "directory": "my_addon",
    "group": "parts/my_addons",   // parent folder; this folder becomes the addons-path entry
    "refspec": "19.0"             // required: branch, tag or commit to checkout
}

Optional keys: branch + depth for a faster shallow clone.

The group vs directory distinction decides what lands on Odoo's addons path:

  • A repo that is a single addon (the module sits at the repo root): set group to a shared parent folder and directory to the addon's name. The repo is cloned into {group}/{directory}, and {group} is added to the addons path. Several such repos sharing the same group produce one addons-path entry.
  • A "collection" repo containing many addons (e.g. an OCA repository): omit group and point directory at the clone folder. The clone folder itself becomes the addons-path entry, and Odoo discovers every module inside it.
// OCA collection: the whole parts/oca_web folder is added to the addons path
"oca_web": {
    "install": true,
    "type": "git",
    "repository": "https://github.com/OCA/web.git",
    "directory": "parts/oca_web",
    "refspec": "19.0"
}

Private repositories

To clone a private repo, inject a token into the URL with an environment-variable reference, ${ENV:VAR_NAME}. The delivered enterprise addon shows the pattern — the token is in the URL and the same variable gates whether the addon installs at all:

"enterprise": {
    "install": "${ENV:MPY_GITHUB_ODOO_TOKEN_URL_AUTH}",
    "type": "git",
    "repository": "https://${ENV:MPY_GITHUB_ODOO_TOKEN_URL_AUTH}github.com/odoo/enterprise.git",
    "directory": "parts/enterprise",
    "refspec": "19.0"
}

Here MPY_GITHUB_ODOO_TOKEN_URL_AUTH carries the URL auth prefix (the user:token@ part). Environment variables come from your server's environment (/etc/muppy.env, managed on the Manganese side) and are exposed to the recipe through the ENV part.

install can be conditional

Because install is evaluated, a value like "${ENV:SOME_TOKEN}" means "install only when that variable is set" — empty/unset resolves to false. Handy for optional, credential-gated addons.

Python dependencies

Your project's pip packages go in user_addons/requirements.txt — a standard requirements.txt. The recipe already points at it:

"requirements": {
    "requirements_file": "user_addons/requirements.txt",
    "options": { "no-deps": false }
}

Add your packages to that file and run ikb install. Odoo's own requirements.txt is always installed as well. Set options.no-deps to true to pass --no-deps to pip.

Odoo parameters

Anything you would normally put in odoo.conf goes under buildit.odoo.config.options. These override the generated etc/odoo.buildit.cfg (the [options] section). Values support ${ENV:VAR} interpolation.

"config": {
    "options": {
        "admin_passwd": "${ENV:IKB_ODOO_ADMIN_PASSWORD}",
        "http_port": 8069,
        "gevent_port": 8072,
        "workers": 1,
        "proxy_mode": true,
        "limit_time_cpu": 360,
        "limit_time_real": 720,
        "db_maxconn": 256,
        "list_db": false
    }
}

You can also add extra addons-path folders explicitly with buildit.odoo.addons_path (a list of paths), appended after the cloned addons.

Advanced

Recipe inheritance and overloads

A buildit.jsonc can extend a base file with __$extends, then layer changes on top. When merging lists, three commands (used as a suffix on the key) give you fine control:

  • key__$let — replace the list outright.
  • key__$merge — merge list elements (dicts are matched by their name key).
  • key__$delete — remove elements; the value "__$delete" removes a key entirely.

Addon archives — type: http_file

Pull an addon pack from a URL (.zip only). The target folder is wiped on every install:

"theme_pack": {
    "install": true,
    "type": "http_file",
    "uri": "https://example.com/addon_pack.zip",
    "directory": "parts/downloaded_addons"
}

The launcher

launcher_name (here odoo-ikb) and install_into (the /usr/local/bin/ symlink) control the generated launcher. They are delivered with sensible values — change them only if you know why.

requirements.eggs — avoid

The recipe also supports an eggs dict under requirements for pip packages. Prefer requirements.txt (above): eggs is being phased out in favour of it.

Delivered — don't touch

These keys are set when your server is delivered and configured to work together. Changing them will likely break the build:

  • buildit.odoo.version (the Odoo repo / branch) and major_version
  • buildit.odoo.python (${py3x}) and running-env-root-path ($MPY_APP_ABS_PATH)
  • the ENV and py3x parts
  • buildit-plugins (the inouk.buildit.git_checkout plugin that fetches the ikb plugins)
  • do_not_generate_config, requirements.do_not_install

Appendix: installing buildit locally (advanced)

You normally never need this

Manganese always delivers a Dev Server with ikb already installed (via uv tool). On Odizy you have nothing to do. This appendix only applies if you want to run buildit on your own machine — for example to build or test a repository outside Odizy.

ikb is the inouk.buildit Python package; installing it puts the ikb command on your PATH. Use an isolated tool installer — uv (recommended, the same method Odizy uses) or pipx:

# with uv  (https://docs.astral.sh/uv/)
uv tool install git+https://gitlab.com/inouk/buildit.git

# or with pipx
pipx install git+https://gitlab.com/inouk/buildit.git

Check it:

ikb --help

Requirements

Python 3.8+. buildit pulls its own dependencies (invoke, pyyaml, jsonc-parser). For SSH-based private repos, use git+ssh://git@gitlab.com/inouk/buildit.git instead.