Deployment Models
This page explains the two structural concepts that shape how customer instances are organized: Subscriptions and Stages. It also documents the technical details of how each deployment strategy — Docker and DBFilter — provisions and routes traffic to customer instances.
Subscriptions
A subscription is the top-level record for a customer. It holds:
- The customer's identity and billing information
- The deployment strategy (Docker or DBFilter)
- The package (resource limits)
- The primary domain
- The infrastructure configuration (Nginx, Cloudflare, Portainer environment, etc.)
A subscription has a lifecycle of its own:
Draft → Confirmed → Building → Running → Stopped → Terminated
When a subscription is stopped, all its stages are stopped with it. When it is terminated, all stages are removed.
Stages
A stage is an individual deployed Odoo instance that belongs to a subscription. Every running subscription has at least one stage — the primary instance.
You can create additional stages on the same subscription, each with its own:
- Domain (e.g.
customer1-staging.sadeem.cloud) - Docker stack or database (independent of other stages)
- Git branch (deploy different code to each stage)
- Nginx proxy host
All stages on a subscription share the same Cloudflare connector and NPM server that are configured on the subscription. Each stage gets its own DNS record and proxy host within those shared services.
Example use cases:
| Stage | Domain | Purpose |
|---|---|---|
| Production | customer1.sadeem.cloud |
Live customer instance |
| Staging | customer1-staging.sadeem.cloud |
Testing before updates |
| Training | customer1-train.sadeem.cloud |
Separate environment for user training |
Stage Limit
The number of stages a subscription can have is controlled by the Stage Limit field on the subscription. This is set manually by the operator. When the count reaches the limit, the Create Stage button is disabled.
Stage States
Stages have their own lifecycle, independent of the subscription:
| State | Meaning |
|---|---|
| Building | Being provisioned |
| Running | Live and serving traffic |
| Stopped | Paused — proxy redirects visitors, data preserved |
| Terminating | Being removed |
| Canceled | Removed |
Docker Deployment Model
What Gets Created
When a Docker stage is built, Sadeem provisions the following resources:
- A Portainer stack under the configured Portainer environment
- An Odoo container running the selected Docker image
- A named Docker volume for the filestore (
/var/lib/odoo) — persists across container restarts - A PostgreSQL database — either a bundled PostgreSQL container inside the same stack, or a connection to a separately managed external PostgreSQL server
- A proxy host in Nginx Proxy Manager routing the stage's domain to the allocated port
Port Allocation
HTTP and WebSocket ports are auto-assigned at confirm time from portainer_environment.next_port. The environment tracks the next available port and increments it with each new stage.
http_port— always assigned; used for all Odoo HTTP trafficwebsocket_port— only assigned whendocker_worker_count > 1; used for Odoo's long-polling workers
Note
If you need to reserve or exclude specific port ranges on your host, configure that in the Portainer environment before confirming subscriptions.
How NPM Routes Traffic
Nginx Proxy Manager holds one proxy host per stage. The proxy host maps the stage's domain to server_ip:http_port on the Docker host. SSL termination happens at NPM — the Odoo container itself serves plain HTTP.
Stack Lifecycle
Confirm → ports assigned; stack definition prepared
Build → stack deployed in Portainer; container starts
Stop → stack paused in Portainer; container stops; data preserved in volume
Running → stack restarted in Portainer; container comes back up
Canceled → stack removed from Portainer; volume deleted
Traffic Flow
Internet → NPM (customer.domain.com) → host:http_port → Docker container (Odoo)
↓
PostgreSQL (bundled or external)
↓
/var/lib/odoo volume (filestore)
Per-Strategy Stage Example
Subscription: customer1
├── Stage: Production → stack on env-prod → port 8100 → customer1.sadeem.cloud
└── Stage: Staging → stack on env-dev → port 8103 → customer1-staging.sadeem.cloud
Stages on the same subscription can run on different Portainer environments — for example, production on a high-memory server and staging on a lighter development environment.
DBFilter Deployment Model
What --db-filter Does
--db-filter is an Odoo startup flag that restricts which databases are accessible on the running instance. It accepts a regex pattern and matches it against the HTTP_HOST header of each incoming request. Only databases whose name matches the domain in the request header are accessible.
This means each customer domain maps to exactly one database — Odoo itself acts as the router. No application-level routing code is required.
How NPM Routes Traffic
Unlike Docker, all customer domains point to the same Odoo port on the shared server (typically 8069). NPM creates one proxy host per stage domain, but all proxy hosts resolve to the same backend.
Internet → NPM (customer1.domain.com) ─┐
Internet → NPM (customer2.domain.com) ─┼→ shared-odoo:8069 (db_filter routes by domain)
Internet → NPM (customer3.domain.com) ─┘ ↓
PostgreSQL server
(databases: customer1, customer2, customer3)
Server Groups and Capacity Balancing
DBFilter servers are organized into server groups. A group can contain multiple shared Odoo servers running the same version and addon set. When a subscription is confirmed, Sadeem calls get_best_server() to select the server in the group with the most remaining capacity and assigns the subscription to it.
This lets you scale horizontally — add a new server to the group when existing servers approach their limit, and new subscriptions will automatically land on the less loaded server.
Tip
Set the Max Subscriptions limit on each DBFilter server to cap how many subscriptions it accepts before get_best_server() routes new ones elsewhere.
Database Creation
When a DBFilter stage is built, Sadeem calls the /web/database/create endpoint on the target Odoo server. The generated domain name is used as the database name. Odoo creates the database and runs the base module installation.
Template Restoration
If a Database Template is configured on the subscription or server group, Sadeem copies or restores the template database instead of creating a blank one. This is useful for pre-installing a standard set of modules or demo data for all new customers.
Note
The template database must already exist on the shared Odoo server and be accessible. It is copied at build time — changes to the template after the subscription is built do not propagate to existing stages.
Per-Strategy Stage Example
Subscription: customer1
├── Stage: Production → db: customer1_prod → customer1.sadeem.cloud
└── Stage: Staging → db: customer1_stg → customer1-staging.sadeem.cloud
Both stages live on the same shared Odoo instance. The --db-filter on that instance resolves each domain to its corresponding database.
SaaS Slave (Odoo-in-Odoo)
When SAAS Slave is enabled on a subscription, Sadeem installs the sadeem_saas_slave module inside the customer's Odoo instance after it finishes building. This gives the master Odoo additional control over the customer instance — such as managing installed modules remotely.
This is an optional feature and is disabled by default. See the SaaS Slave page for full documentation.
Choosing Your Model
| Scenario | Recommendation |
|---|---|
| One production instance per customer | One subscription, one stage (default) |
| Production + staging per customer | One subscription, two stages |
| Multiple customers, shared infrastructure | One subscription per customer, DBFilter strategy |
| Complete isolation per customer | One subscription per customer, Docker strategy |
| Mixed tiers (isolated premium, shared standard) | Docker for premium, DBFilter for standard — both on the same master |