Skip to main content
Architecture Guide

Odoo Multi-Tenant Architecture — Hosting Multiple Clients

Host Odoo for multiple clients — or multiple business units — on the same infrastructure. Three approaches compared: multi-company, multi-database, and full container isolation. Code examples included for each.

18 min read
Updated February 2026
Odoo 17/18

Three Approaches to Multi-Tenancy in Odoo

Odoo supports multi-tenancy, but it does not give you a single “multi-tenant” switch to flip. Instead, you choose from three approaches, each with different tradeoffs in isolation, complexity, and cost.

Multi-CompanyMulti-DatabaseFull Isolation
ArchitectureOne database, one Odoo instanceMultiple databases, one Odoo instanceSeparate containers per tenant
Data isolationNone (shared tables, access rules only)Database-level (separate PostgreSQL databases)Full (separate containers + databases)
Version independenceNo — all tenants share one Odoo versionNo — all tenants share one Odoo binaryYes — each tenant can run different versions
Resource isolationNoneNone (shared CPU/RAM/workers)Full (Docker resource limits per tenant)
Custom modulesShared (all tenants see all modules)Shared binary, per-database module installFully independent per tenant
ScalingVertical onlyVertical onlyHorizontal — add servers per tenant
Setup complexityLowMediumHigh
Cost per tenantLowestLowModerate
Best forInternal departments, related companiesHosting provider with similar clientsMSP/SaaS with enterprise clients

The right choice depends on how much isolation your tenants need. Internal departments that trust each other? Multi-company is fine. External paying clients who expect their data cannot leak? Full isolation or nothing.

Multi-Company (Single Database) — When It Works

Odoo has built-in multi-company support. You create multiple companies within a single Odoo database and use record rules to control which users see which data.

How it works

  • One Odoo instance, one PostgreSQL database
  • Companies are created in Settings > Companies
  • Users can be assigned to one or multiple companies
  • Record rules filter data per company (sales orders, invoices, inventory)
  • Some data is shared: product templates, contacts (configurable), user accounts

When to use it

  • Holding companies with subsidiaries that share operations
  • A parent company with regional offices
  • Internal departments that need separate accounting but shared products
  • Any scenario where tenants trust each other and share a single Odoo admin

When NOT to use it

  • External clients who must not see each other's data
  • Tenants who need different Odoo versions or different module sets
  • Scenarios where one tenant's heavy load should not affect another
  • Compliance requirements that mandate database-level isolation

Configuration

No Docker or Nginx changes needed. Standard single-instance deployment:

docker-compose.ymlyaml
# Standard docker-compose.yml — nothing multi-tenant-specific
version: "3.8"

services:
  odoo:
    image: odoo:18.0
    ports:
      - "8069:8069"
    environment:
      - HOST=db
      - USER=odoo
      - PASSWORD=odoo_db_password
    volumes:
      - odoo-data:/var/lib/odoo
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=odoo
      - POSTGRES_PASSWORD=odoo_db_password
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  odoo-data:
  db-data:

Multi-company is configured entirely inside Odoo's admin interface. The infrastructure is a standard single-instance deployment.

Pros

Simplest to set up. Lowest resource usage. Shared user accounts across companies. Shared master data (products, contacts) reduces duplication.

Cons

Zero data isolation at the infrastructure level. A bug or misconfigured record rule can expose data across companies. One bad query slows everyone. Cannot run different Odoo versions per company.

Multi-Database (Single Odoo Instance) — The Middle Ground

One Odoo process serves multiple databases. Each database is a completely separate Odoo instance with its own modules, users, and data. Nginx routes requests to the correct database based on subdomain or URL.

How it works

  • One Odoo binary, one set of workers
  • Multiple PostgreSQL databases, each containing a full Odoo installation
  • db_filter in odoo.conf maps hostnames to databases
  • Users log in through different URLs and are routed to their database

odoo.conf for multi-database

odoo.confini
[options]
addons_path = /mnt/extra-addons
data_dir = /var/lib/odoo

db_host = db
db_port = 5432
db_user = odoo
db_password = odoo_db_password

; Route databases by subdomain
; client1.example.com → database "client1"
; client2.example.com → database "client2"
dbfilter = ^%d$

; Disable database listing and management from the web UI
list_db = False
db_name = False

proxy_mode = True
workers = 6
max_cron_threads = 2

; Important: limit memory per worker, not per database
limit_memory_hard = 2684354560
limit_memory_soft = 2147483648
limit_time_cpu = 600
limit_time_real = 1200

The dbfilter = ^%d$ directive extracts the subdomain from the request hostname and uses it as the database name. A request to client1.example.com maps to database client1.

Other common patterns:

dbfilter patternsini
; Match full hostname (acme.example.com → acme)
dbfilter = ^%d$

; Match first part of hostname with a prefix (odoo-acme → acme)
dbfilter = ^odoo-%d$

; Hardcode to a single database (disable multi-database)
dbfilter = ^production_db$

Nginx for subdomain routing

nginx.confnginx
# Wildcard SSL — covers *.example.com
server {
    listen 443 ssl http2;
    server_name ~^(?<tenant>.+)\.example\.com$;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Proxy headers — Odoo uses Host header for dbfilter
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # Odoo web client
    location / {
        proxy_pass http://127.0.0.1:8069;
        proxy_read_timeout 720s;
        proxy_connect_timeout 720s;
        proxy_send_timeout 720s;
        client_max_body_size 200m;
    }

    # Longpolling (live chat, discuss, notifications)
    location /websocket {
        proxy_pass http://127.0.0.1:8072;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 86400;
    }

    # Static files — serve directly for performance
    location ~* /web/static/ {
        proxy_pass http://127.0.0.1:8069;
        proxy_cache_valid 200 90m;
        proxy_buffering on;
        expires 7d;
    }
}

# HTTP → HTTPS redirect
server {
    listen 80;
    server_name *.example.com;
    return 301 https://$host$request_uri;
}

Wildcard SSL with Let's Encrypt

Subdomain routing requires a wildcard SSL certificate. Let's Encrypt supports this via DNS-01 challenge:

certbot wildcardbash
# Using certbot with Cloudflare DNS plugin
certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d "example.com" \
  -d "*.example.com"

Docker Compose for multi-database

docker-compose.ymlyaml
version: "3.8"

services:
  odoo:
    image: odoo:18.0
    container_name: odoo-multitenant
    depends_on:
      - db
    ports:
      - "127.0.0.1:8069:8069"
      - "127.0.0.1:8072:8072"
    volumes:
      - odoo-data:/var/lib/odoo
      - ./config/odoo.conf:/etc/odoo/odoo.conf:ro
      - ./custom-addons:/mnt/extra-addons
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    container_name: odoo-db
    environment:
      - POSTGRES_USER=odoo
      - POSTGRES_PASSWORD=odoo_db_password
    volumes:
      - db-data:/var/lib/postgresql/data
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    container_name: odoo-nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
    depends_on:
      - odoo
    restart: unless-stopped

volumes:
  odoo-data:
  db-data:

Creating new tenant databases

Create tenant databasebash
# Create a new database for a tenant via CLI
docker exec odoo-multitenant odoo -d client3 --init=base --stop-after-init

# Or use the Odoo database manager API (if enabled)
curl -X POST https://client3.example.com/web/database/create \
  -d "master_pwd=your_master_password&name=client3&login=admin&password=admin_pass&lang=en_US"

Pros

Real data isolation — each tenant has its own database with its own users and data. Per-tenant backups are straightforward (pg_dump per database). Moderate resource efficiency — shared Odoo binary and workers.

Cons

All tenants share the same Odoo version. All tenants share CPU and memory — one tenant's heavy report can starve others. Custom modules are shared across the binary. The dbfilter regex can be tricky to debug.

Full Isolation (Separate Containers per Client) — Production Multi-Tenant

Each tenant gets their own Odoo container, their own PostgreSQL container (or dedicated database user), and their own resource limits. This is what hosting providers and MSPs use for paying clients.

Architecture

Full isolation architecturetext
                    ┌─────────────────────────┐
                    │     Nginx / Traefik      │
                    │   (SSL + routing)        │
                    └──────┴──────┴──────┴─────┘
                           │      │      │
               ┌───────────┘      │      └───────────┐
               ▼                  ▼                   ▼
    ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
    │  client1-odoo    │ │  client2-odoo    │ │  client3-odoo    │
    │  (Odoo 18.0)     │ │  (Odoo 18.0)     │ │  (Odoo 17.0)     │
    │  2GB RAM, 1 CPU  │ │  4GB RAM, 2 CPU  │ │  2GB RAM, 1 CPU  │
    └────────┴─────────┘ └────────┴─────────┘ └────────┴─────────┘
             │                    │                     │
    ┌────────┴─────────┐ ┌───────┴──────────┐ ┌───────┴──────────┐
    │  client1-db      │ │  client2-db      │ │  client3-db      │
    │  (PostgreSQL 16) │ │  (PostgreSQL 16) │ │  (PostgreSQL 16) │
    └──────────────────┘ └──────────────────┘ └──────────────────┘

Docker Compose per tenant

Each tenant gets their own compose file. Here is client1/docker-compose.yml:

client1/docker-compose.ymlyaml
version: "3.8"

services:
  odoo:
    image: odoo:18.0
    container_name: client1-odoo
    depends_on:
      - db
    expose:
      - "8069"
      - "8072"
    volumes:
      - odoo-data:/var/lib/odoo
      - ./config/odoo.conf:/etc/odoo/odoo.conf:ro
      - ./custom-addons:/mnt/extra-addons
    environment:
      - HOST=db
      - USER=client1
      - PASSWORD=${DB_PASSWORD}
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: "1.0"
        reservations:
          memory: 1G
          cpus: "0.5"
    networks:
      - internal
      - proxy
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    container_name: client1-db
    environment:
      - POSTGRES_USER=client1
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - db-data:/var/lib/postgresql/data
    deploy:
      resources:
        limits:
          memory: 1G
          cpus: "0.5"
    networks:
      - internal
    restart: unless-stopped

volumes:
  odoo-data:
  db-data:

networks:
  internal:
    driver: bridge
  proxy:
    external: true
    name: proxy-network

Key details:

  • No published ports. Each tenant's Odoo is only accessible through the shared proxy network. Tenants cannot reach each other's containers.
  • Separate Docker networks. The internal network connects Odoo to its own PostgreSQL only. The proxy network connects Odoo to the Nginx/Traefik reverse proxy.
  • Resource limits. Each tenant has hard caps on memory and CPU. One tenant cannot consume another's resources.

Traefik for automatic routing

Traefik is generally a better fit than Nginx for full-isolation setups because it discovers containers automatically via Docker labels. No Nginx config file per tenant.

traefik/docker-compose.ymlyaml
version: "3.8"

services:
  traefik:
    image: traefik:v3.0
    container_name: traefik
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - letsencrypt:/letsencrypt
    networks:
      - proxy-network
    restart: unless-stopped

volumes:
  letsencrypt:

networks:
  proxy-network:
    name: proxy-network

Then add labels to each tenant's Odoo service:

client1/docker-compose.yml (Traefik labels)yaml
# In client1/docker-compose.yml, add to the odoo service:
services:
  odoo:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.client1.rule=Host(`client1.example.com`)"
      - "traefik.http.routers.client1.entrypoints=websecure"
      - "traefik.http.routers.client1.tls.certresolver=letsencrypt"
      - "traefik.http.services.client1.loadbalancer.server.port=8069"

Traefik sees the labels, routes client1.example.com to that container, and provisions SSL automatically. Adding a new tenant means deploying a new compose file with different labels. No proxy config to edit.

Database Configuration for Multi-Tenant

Separate PostgreSQL roles per tenant

Even when sharing a single PostgreSQL server (to save resources), create separate roles:

PostgreSQL tenant isolationsql
-- Create a role per tenant with strict isolation
CREATE ROLE client1 WITH LOGIN PASSWORD 'client1_secure_pass';
CREATE DATABASE client1_odoo OWNER client1;
REVOKE ALL ON DATABASE client1_odoo FROM PUBLIC;

CREATE ROLE client2 WITH LOGIN PASSWORD 'client2_secure_pass';
CREATE DATABASE client2_odoo OWNER client2;
REVOKE ALL ON DATABASE client2_odoo FROM PUBLIC;

Each tenant's Odoo connects with its own credentials and can only access its own database. Even if a SQL injection vulnerability existed, it could not reach another tenant's data.

Connection pooling with PgBouncer

With many tenants hitting one PostgreSQL server, connection pooling becomes necessary. Odoo opens a connection per worker per database — 10 tenants with 4 workers each means 40 persistent connections.

pgbouncer.iniini
[databases]
client1_odoo = host=127.0.0.1 port=5432 dbname=client1_odoo
client2_odoo = host=127.0.0.1 port=5432 dbname=client2_odoo
client3_odoo = host=127.0.0.1 port=5432 dbname=client3_odoo

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = session
default_pool_size = 20
max_client_conn = 200

Point each tenant's odoo.conf at PgBouncer (port 6432) instead of PostgreSQL directly.

Backup strategies per database

backup-all-tenants.shbash
#!/bin/bash
# backup-all-tenants.sh — run via cron daily

BACKUP_DIR="/backups/$(date +%Y-%m-%d)"
mkdir -p "$BACKUP_DIR"

# List of tenant databases
TENANTS="client1_odoo client2_odoo client3_odoo"

for DB in $TENANTS; do
  echo "Backing up $DB..."
  pg_dump -U postgres -Fc "$DB" > "$BACKUP_DIR/${DB}.dump"

  # Optional: encrypt per-tenant backups
  gpg --encrypt --recipient "$DB@example.com" "$BACKUP_DIR/${DB}.dump"
  rm "$BACKUP_DIR/${DB}.dump"
done

# Rotate: keep 30 days
find /backups -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \;

Resource Isolation and Limits

Docker resource constraints per tenant

In production, always set resource limits. Without them, one tenant's heavy payroll calculation can OOM-kill another tenant's container.

Docker resource limitsyaml
deploy:
  resources:
    limits:
      memory: 2G      # Hard cap — container is killed if exceeded
      cpus: "1.0"     # Limit to 1 CPU core
    reservations:
      memory: 512M    # Guaranteed minimum
      cpus: "0.25"    # Guaranteed minimum

Sizing guide

As a starting point for Odoo tenants:

Tenant sizeOdoo workersMemory limitCPU limitPostgreSQL memory
Small (1-10 users)21.5 GB0.5 CPU512 MB
Medium (10-50 users)43 GB1.0 CPU1 GB
Large (50-200 users)86 GB2.0 CPU2 GB
Enterprise (200+ users)12+10 GB+4.0 CPU4 GB+

These are minimums. Heavily customized instances with complex reports need more. The OEC.sh Server Calculator can estimate requirements based on user count and module selection.

PostgreSQL connection limits

In postgresql.conf, cap connections per tenant to prevent one tenant from exhausting the connection pool:

postgresql.confini
# Global PostgreSQL limits
max_connections = 200

Combined with PgBouncer's per-database pool limits, this prevents any single tenant from starving others of connections.

Monitoring with Prometheus and Grafana

For multi-tenant monitoring, export per-container metrics:

cAdvisor monitoringyaml
# Add cAdvisor to your monitoring stack
services:
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    ports:
      - "8080:8080"
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro

cAdvisor exposes per-container CPU, memory, network, and disk metrics. Grafana dashboards can show per-tenant resource usage, making it easy to identify tenants that need more resources — or tenants whose workload has outgrown the shared server.

Scaling Multi-Tenant Odoo

When to split to multiple servers

A single 8-core, 32 GB server comfortably runs 10-15 small tenants or 5-8 medium tenants. When you hit these limits:

  1. 1Separate the database server. Move PostgreSQL to a dedicated machine (or managed service like RDS/Cloud SQL). This is the single biggest scaling win — Odoo and PostgreSQL competing for the same CPU is the most common bottleneck.
  2. 2Add compute nodes. Run Odoo containers on multiple servers, all pointing at the same database server. Use Traefik or a load balancer to route tenants to the right compute node.
  3. 3
    Shared storage for filestore. When Odoo runs on multiple compute nodes, the filestore (uploaded documents, images) must be shared. Options:
    • NFS mount across compute nodes
    • S3-compatible object storage (MinIO or cloud S3)
    • Odoo's S3 filestore addon (available for Enterprise)

Architecture at scale

Multi-server architecturetext
                ┌─────────────┐
                │ Load Balancer│
                │ (Traefik)    │
                └──┴───────┴──┘
                   │       │
        ┌──────────┘       └──────────┐
        ▼                             ▼
┌───────────────┐            ┌───────────────┐
│ Compute Node 1│            │ Compute Node 2│
│ client1-odoo  │            │ client3-odoo  │
│ client2-odoo  │            │ client4-odoo  │
└───────┴───────┘            └───────┴───────┘
        │                            │
        └────────┴───────────────┘
                 ▼
        ┌───────────────┐
        │ Database Server│
        │ PostgreSQL +   │
        │ PgBouncer      │
        └───────────────┘
                 │
        ┌───────────────┐
        │ Shared Storage │
        │ (NFS / S3)     │
        └───────────────┘

Security Considerations

Multi-tenant hosting means you are responsible for preventing data leakage between clients. This is not optional.

Network isolation with Docker networks

Every tenant must be on its own Docker network. The only shared network is the proxy network that connects to Traefik:

Docker network isolationyaml
networks:
  client1-internal:
    driver: bridge
    internal: true  # No external access
  proxy:
    external: true

The internal: true flag means containers on client1-internal cannot reach the internet or any other network. They can only talk to each other (Odoo to its own PostgreSQL).

PostgreSQL isolation

  • Separate database users per tenant (no shared superuser)
  • REVOKE ALL ON DATABASE ... FROM PUBLIC on every tenant database
  • If sharing a PostgreSQL instance, disable CREATE DATABASE for tenant roles
  • Consider row-level security for shared tables in multi-database setups (advanced)

Filesystem isolation

  • Each tenant's filestore is a separate Docker volume
  • Never share filestore volumes between tenants
  • Set proper file permissions — the Odoo container runs as UID 101 (odoo user)

Backup encryption

Encrypt backups per tenant. If a backup leaks, only that tenant's data is exposed:

Per-tenant backup encryptionbash
# Per-tenant encryption key
gpg --encrypt --recipient client1@example.com client1_backup.dump

GDPR and data residency

For EU clients, you may need to guarantee that their data stays in a specific region. With Docker, this is straightforward: deploy that tenant's containers on a server in the required region. With OEC.sh, you select the cloud provider and region per project.

SaaS Platform on Odoo

If you are building a full SaaS product on Odoo — automated signup, billing, tenant lifecycle — this is a significantly larger project. A brief overview of the components:

Automated provisioning

When a customer signs up:

  1. 1Create a new Docker Compose deployment (template with variables)
  2. 2Create a new PostgreSQL database with isolated credentials
  3. 3Initialize Odoo with base modules
  4. 4Configure Traefik labels for routing
  5. 5Provision SSL automatically

This can be orchestrated with a simple Python/Bash script for small scale, or Kubernetes operators for large scale.

Billing integration

  • Track per-tenant resource usage (CPU hours, storage, users)
  • Integrate with Stripe or a billing platform
  • Implement usage-based or per-user pricing
  • Handle trial periods, upgrades, downgrades

Tenant lifecycle management

  • Suspend (stop containers, retain data)
  • Reactivate (start containers)
  • Delete (destroy containers, database, backups after retention period)
  • Migrate (move tenant to a different server)
  • Upgrade (change Odoo version per tenant)

Self-service portal

A web application (separate from Odoo) where tenants can:

  • View their instance status and resource usage
  • Download backups
  • Install/uninstall modules
  • Upgrade their plan
  • Manage their custom domain

Building a full SaaS platform is a 3-6 month project depending on your automation level. Most teams start with manual provisioning (a script that takes 5 minutes per tenant) and automate incrementally.

Multi-Tenant with OEC.sh

Everything in this guide — Docker containers, PostgreSQL isolation, Nginx/Traefik routing, SSL, resource limits, backups, monitoring — is infrastructure work that does not directly serve your clients.

OEC.sh handles the infrastructure layer so you can focus on the Odoo deployment itself:

  • Each project is a fully isolated environment — separate containers, separate database, separate filestore. The full-isolation architecture described above, managed for you.
  • Multi-cloud deployment — place tenants on AWS, Hetzner, DigitalOcean, or any supported provider. Per-tenant region selection for data residency requirements.
  • Built-in resource limits — per-project CPU and memory allocation, no resource contention between projects.
  • Automatic SSL — wildcard or per-tenant certificates, provisioned automatically.
  • No Docker or Nginx configuration — deploy in minutes, not hours.

If you are hosting Odoo for multiple clients and want the isolation of the full-container approach without managing the container orchestration, explore the multi-tenant solution or check pricing.

Skip the infrastructure work

OEC.sh gives you fully isolated Odoo environments per client — separate containers, databases, and resource limits — on any cloud provider. No Docker or Nginx configuration required.

  • Free tier available
  • No credit card required
  • Full tenant isolation
Try OEC.sh Free

Frequently Asked Questions

How many tenants can I run per server?

Depends on tenant size. A 4-core, 16 GB server handles roughly 8–12 small tenants (under 10 users each) with full isolation. Medium tenants (10–50 users) need about 3 GB RAM each, so a 32 GB server fits 8–10. Use the Server Calculator for specific estimates.

Can tenants run different Odoo versions?

Only with full isolation (separate containers). Multi-company and multi-database both share a single Odoo binary, so all tenants are locked to the same version. With per-tenant containers, one client can run Odoo 17 while another runs Odoo 18.

How do I backup individual tenants?

With multi-database or full isolation, each tenant has its own PostgreSQL database. Run pg_dump per database. With full isolation, you can also snapshot the entire Docker volume. For multi-company (single database), you cannot backup one company without the others — it is one database.

Is multi-company or multi-database better?

Multi-company is simpler and cheaper but has no data isolation. Multi-database gives you real isolation with moderate complexity. If your tenants are external paying clients, multi-database is the minimum. If they are internal departments in the same organization, multi-company is usually sufficient.

How do I handle per-tenant custom modules?

With full isolation, each tenant has their own addons directory — install whatever you want. With multi-database, the module files are shared (same Odoo binary) but you can install different modules per database. With multi-company, all companies see all installed modules.

What is the minimum server for 10 tenants?

For 10 small tenants (under 10 users each) with full isolation: 8 CPU cores, 32 GB RAM, 200 GB SSD. For multi-database (shared Odoo binary): 4 cores, 16 GB RAM, 100 GB SSD. Multi-company needs the least: 2 cores, 8 GB RAM. These are starting points — monitor actual usage and scale accordingly.

Deploy Multi-Tenant Odoo in Minutes

Whether you are hosting 5 clients or 50, OEC.sh gives you fully isolated Odoo environments with automatic SSL, resource limits, and multi-cloud deployment — without managing Docker, Nginx, or PostgreSQL configuration.