How Odoo's dbfilter picks which databases a request can reach, the regex traps to avoid, and how OCA's dbfilter_from_header lets Nginx map custom domains to databases.

Odoo is a multi-tenant system: a single Odoo server can run and serve several databases. The dbfilter option decides which of those databases a given request can reach.

The official deployment documentation has a good introduction to database filtering. This article goes into how it works in practice, the traps to avoid, and what to do when domain names and database names do not line up.

How dbfilter Works

dbfilter is a regular expression built dynamically for each request. You can use the variables %d and %h to adapt it to the Host header of the HTTP request:

  • %d is replaced by the first subdomain after www:
    • https://demo.example.com: %d = demo
    • https://www.demo.example.com: %d = demo
  • %h is replaced by the full domain, without the port if there is one:
    • https://demo.example.com: %h = demo.example.com
    • https://demo.example.com:8080: %h = demo.example.com

So if you define dbfilter = ^%d.*\Z, a user visiting https://demo.example.com can access the databases matching ^demo.*\Z, for example:

  • demo
  • demo_backup_20180404

Beware of Permissive Patterns

Odoo filters the database list with re.match:

dbs = [i for i in dbs if re.match(r, i)]

re.match anchors the pattern at the start of the name but not at the end, so it is easy to write a regex that is more permissive than intended. If you define dbfilter = demo_*, for example, it also matches demoinefromage_prod.

When Domains and Database Names Do Not Match: dbfilter_from_header

You cannot always derive database names from domain names.

For example, a single Odoo instance might serve two databases:

  • customer1_prod
  • customer2_prod

But each customer wants its own custom domain:

  • https://back.customer1.com
  • https://app.customer2.com

To support this case, you can use OCA’s dbfilter_from_header module.

Installation

git clone https://github.com/OCA/server-tools -b 9.0 --no-checkout --single-branch --depth 1
cd server-tools
git config core.sparsecheckout true
echo "dbfilter_from_header" > .git/info/sparse-checkout
git read-tree -mu HEAD

Configuration

On the Nginx side, define a dedicated database filter for each virtual host. In our example, for https://back.customer1.com:

server_name back.customer1.com;

(...)

location / {

    (...)

    proxy_set_header X-Odoo-dbfilter ^customer1_.*\Z;
}

Two things to note:

  • before passing the request to Odoo, Nginx takes all HTTP headers, lowercases them and converts dashes to underscores;
  • we use \Z instead of $ to match the end of the string, because Nginx cannot escape $.

Two Levels of Filtering

dbfilter_from_header respects the dbfilter defined at Odoo level, so you can set up two levels of filtering. In the Odoo configuration:

dbfilter = ^.+prod.*$

And in Nginx:

proxy_set_header X-Odoo-dbfilter ^.*customer1.*\Z;