Skip to content
Skip to the article
In NixOS: 13 articles
NixOS

Set up an Odoo and OCA development environment with odoo-nix

Scaffold an Odoo project with odoo-nix, start the devenv shell, and run the daily odoo CLI commands for databases, modules and upgrades.

Updated
Applies to
  • odoo-nix
  • Odoo 18.0
  • Odoo 19.0
  • devenv
  • Nix flakes
Tags
  • nixos
  • odoo
  • devenv
  • oca
Reading time
10 min

This document covers creating an Odoo project with odoo-nix, entering its development shell, and the commands you use every day. Use it when you want a reproducible Odoo Community (OCB) plus OCA setup that a new machine can reproduce with one checkout and one command.

What you get

odoo-nix keeps the project itself small. You own a thin flake.nix, a pyproject.toml, a modules.txt list of modules to install, and your own addons. odoo-nix supplies the rest:

  • A development shell (via devenv) running PostgreSQL, Odoo and Mailpit.

  • A Python environment built from your pyproject.toml and uv.lock.

  • An odoo.conf generated from Nix options, with addons_path derived from the folders on disk.

  • One odoo command for databases, modules and workspace updates.

Requirements

  • Nix with flakes enabled.

  • direnv for automatic shell entry. Without it, use nix develop --no-pure-eval instead.

  • git, since OCB and every OCA repository are added as git submodules.

Scaffold a project

Run the scaffolder from anywhere. It is an interactive gum prompt that asks for the Odoo series, then whether to add curated OCA bundles and individual OCA modules (both optional), then a project directory. It does not ask for a database name. The default is odoo, and --db changes it.

nix run github:Avunu/odoo-nix

For a scripted run, pass everything as flags. --series is required when there is no terminal. The last argument is the target directory, which must be empty or not exist.

nix run github:Avunu/odoo-nix -- \
  --series 18.0 \
  --bundles base \
  --modules account_financial_report,repair_order_group \
  --name myproject \
  --db myproject \
  myproject
FlagMeaning
--seriesOdoo series, 18.0 or 19.0. It fixes the Python version (3.11 for 18.0, 3.12 for 19.0).
--bundlesComma-separated curated OCA bundles to install.
--modulesComma-separated OCA module names to install. --apps is an alias.
--nameProject name. Defaults to the target directory name.
--dbDefault database name. Defaults to odoo.

OCA software is opt-in. If you decline both prompts, or pass neither --bundles nor --modules, you get plain Odoo core. If you pass both, the two selections are combined and de-duplicated.

When you pick a module, the scaffolder works out which OCA repositories it depends on, transitively, and adds only those as submodules. It then writes the wrapper flake, generates pyproject.toml and runs uv lock.

Note

18.0 is the default for the odooSeries option and has the richest OCA catalog. 19.0 is supported, but fewer OCA modules have been ported to it, so the picker offers fewer choices.

What the scaffolder creates

.
├── flake.nix          # thin wrapper around odoo-nix
├── pyproject.toml     # Odoo core deps plus the modules you chose, locked in uv.lock
├── uv.lock
├── modules.txt        # the OCA install set, one module per line
├── odoo/              # OCB source (git submodule, branch = series)
├── modules/           # OCA repositories (git submodules, one per repo)
├── custom/            # your own modules
└── odoo.conf          # symlink into the Nix store (generated on first shell entry)

.devenv/state/ holds the development PostgreSQL cluster and Odoo's filestore. Nothing mutable is tracked by git. The generated .gitignore also excludes the odoo.conf, .venv and odools.toml symlinks.

Enter the shell and start the stack

From the project directory:

direnv allow
devenv up

The scaffolded .envrc contains use flake . --no-pure-eval, so direnv allow builds the environment and loads it into your shell. devenv up then runs three processes:

  • postgres, with its data under .devenv/state.

  • odoo, running odoo-bin -c odoo.conf --dev=all as a single threaded process that serves HTTP and websockets.

  • mailpit, an SMTP sink with a web interface. odoo starts after postgres (and after mailpit while the mail catcher is enabled).

Leave devenv up running and open a second shell in the same directory for the commands below.

On shell entry, odoo-nix also checks out submodules that a fresh clone has never had, symlinks the generated odoo.conf into place, creates the filestore and custom/ directories, and prints a banner listing the main commands. That means a fresh git clone --recurse-submodules followed by direnv allow is a complete setup.

Ports

ServiceAddress
Odoohttp://localhost:8069
Mailpit web interfacehttp://localhost:8025
Mailpit SMTP127.0.0.1:1025

These are the defaults. You can change them through odooConf.httpPort and the mailcatch.* options in flake.nix.

Create the database

In the second shell:

odoo db provision

With no argument, provision uses db_name from the generated odoo.conf, which is the name you gave the scaffolder with --db. You can also pass a name, or set ODOO_DB.

The command is idempotent:

  • If the database does not exist, or exists without an Odoo schema, it installs base plus every module in modules.txt.

  • If the database is already initialized, it migrates it instead of reinstalling, after taking a zip snapshot first. Add --no-backup to skip that snapshot.

Then open http://localhost:8069.

Daily commands

Everything below is a subcommand of the single odoo command, available inside the dev shell.

TaskCommand
Create the database and install modules.txt, or migrate itodoo db provision [DB]
Upgrade specific modulesodoo db upgrade MODULE[,MODULE2] [DB]
Upgrade whatever changed since the last migrationodoo db migrate [DB]
Open an Odoo Python shellodoo shell [DB]
Add OCA modulesodoo module add [MODULE ...]
Add a curated OCA bundleodoo module add-bundle [NAME ...]
Pull submodules, refresh dependencies, re-lock, migrateodoo project update
Run a module's testsodoo test MODULE[,MODULE2] [DB]
List databasesodoo db list

Note

Earlier versions of odoo-nix shipped separate hyphenated scripts such as odoo-shell, odoo-add-module, odoo-update and odoo-test. The odoo subcommands above replace them (odoo shell, odoo module add, odoo project update, odoo test). If an older guide uses the hyphenated names, use the subcommands instead.

Upgrade modules after a code change

odoo db upgrade runs Odoo's -u for the modules you name and shows a live progress bar, followed by a per-module summary table:

odoo db upgrade my_module,other_module
odoo db upgrade my_module --all

--all here means every database matching dbfilter, not every module. To upgrade every module whose code or version changed, without naming them, use odoo db migrate. It compares a checksum of each module against the one stored at the last successful migration and prints a plan before it touches anything. Useful flags:

  • --full updates every installed module (-u all).

  • --also m1,m2 forces extra modules into the update.

  • --snapshot zip|dump|none controls the pre-migration snapshot (default zip).

  • --rollback restores the snapshot if the update fails.

Open a shell

odoo shell
odoo shell myproject

This starts Odoo's own shell command against the database, using the generated odoo.conf.

Add OCA modules

odoo module add
odoo module add account_financial_report
odoo module add-bundle base sales

With no arguments in a terminal, odoo module add asks whether to browse the OCA catalog or add from a Git URL. Either way, it adds any missing repositories as submodules, records the modules in modules.txt, regenerates the managed blocks in pyproject.toml and re-locks. When it finishes, reload the environment and install:

direnv reload
odoo db provision

direnv reload is required. It makes Nix re-derive addons_path and rebuild the Python environment.

You are not limited to OCA. Give odoo module add any Git URL, or owner/repo for GitHub, with an optional branch and submodule folder name:

odoo module add https://git.example.com/owner/repo.git
odoo module add owner/repo 18.0 my-repo

The branch defaults to your Odoo series. If the repository has no such branch, it offers the repository's default branch. A repository with several modules asks which ones to record in modules.txt.

add-bundle takes names from the bundled oca-bundles.json in odoo-nix, such as base and sales. Any member that has no module for your series is reported and dropped rather than left to fail at provision time.

Update the workspace

odoo project update

This pulls the submodules on their pinned branches, re-aggregates the OCA Python dependencies and runs uv lock. It then migrates every database matching dbfilter. Pass --no-migrate to skip the migration step.

Warning

odoo project update moves submodule pointers. Review git status, test, and commit the new pointers deliberately. Shell entry never advances submodules for you.

The generated configuration

addons_path

odoo-nix builds addons_path on every evaluation from the directories that are actually present, in this order:

  1. ./odoo/odoo/addons, then ./odoo/addons (OCB core).

  2. One entry per repository under modules/ that contains at least one module (an immediate child with __manifest__.py), sorted by name.

  3. ./custom.

  4. Any extra entries from layout.extraAddons.

Add or remove a submodule and the path changes with it. There is no list to keep in sync. Order matters because Odoo uses the first match.

odoo.conf

The odoo.conf at the project root is a symlink to a read-only file in the Nix store. It is generated from the odooConf.* options in your flake.nix, plus the derived addons_path. Never edit it. Change the options and reload instead:

odoo-nix = {
  enable = true;
  projectName = "myproject";
  workspaceRoot = ./.;
  odooSeries = "18.0";
  python = pkgs.python311;
  odooConf.dbName = "myproject";
};

Other options you may want include odooConf.httpPort, odooConf.workers, odooConf.withoutDemo and odooConf.extra for arbitrary extra keys. Keep workers at its default of 0 in development, because live code reload only works on the threaded server.

Mail in development

With mailcatch.enable on (the default), every outgoing email from Odoo is redirected to Mailpit, so nothing can reach a real recipient from your machine. Open http://localhost:8025 to read what Odoo sent.

This works through a small server-wide addon that odoo-nix ships and loads from the Nix store. It is not installed into any database and does not appear in custom/ or modules.txt. It applies to the web server, odoo shell and --stop-after-init runs alike. The catch-all exists only in the dev shell. Production deployments do not load it.

Live reload

The dev server runs with --dev=all. Edits to .py files on the addons_path restart the server in place. XML views, QWeb templates and assets need only a browser refresh. Odoo's framework code under odoo/odoo/ is not watched, so restart devenv up after changing it.

At startup, look for this line to confirm the watcher is active:

AutoReload watcher running with watchdog

If a module's change adds a field or changes data files, a restart is not enough. Run odoo db upgrade for that module.

Running other Odoo commands

db, module, project, shell and test are odoo-nix's own subcommands. Anything else passes straight through to the real odoo-bin, so odoo scaffold, odoo populate and the rest of Odoo's command set still work. With no subcommand, odoo starts the server. The options -c/--config, -d/--database and --db-host/--db-port/--db-user/--db-password are accepted before the subcommand name.

Troubleshooting

  • A module you just added is not found. Run direnv reload so addons_path is re-derived and the Python environment is rebuilt, then run odoo db provision.

  • Changed code but the schema did not follow. Odoo only creates columns and reloads data for modules being updated. Run odoo db upgrade <MODULE> or odoo db migrate.

  • uv lock fails during module add or scaffolding. The scaffolder reports this as usually a dependency version conflict. Adjust [project].dependencies in pyproject.toml and run uv lock again. Do not hand-edit the blocks between the >>> odoo-nix and <<< odoo-nix markers.

  • odoo test refuses to run. It checks that browser tours can actually run, since Odoo reports a skipped tour like a pass. It needs the websocket-client package and a headless browser on PATH. The scaffolded pyproject.toml already lists websocket-client in the dev group, so the browser is usually what is missing.

Sources

This article is in the public domain (CC0 1.0), code samples included. Use it however helps you.