Odoo is an open-source business suite: CRM, sales, invoicing and accounting, inventory, e-commerce, website builder, projects, HR and dozens more apps, all sharing one database. The Community Edition is free and open source. The Enterprise Edition adds more apps and support. Self-hosting Odoo on a VPS gives you full control over data and customisation.
Sizing
| Users / load | VPS |
|---|---|
| A few users, light use | 2 vCPU, 4 GB RAM |
| 10–50 users | 4 vCPU, 8 GB RAM |
| Larger, or with a website shop | 8+ vCPU, 16 GB+, tuned PostgreSQL, maybe a separate DB server |
Odoo is Python with PostgreSQL. Disk needs depend on attachments (the filestore).
Docker Compose
# /opt/odoo/compose.yaml
services:
odoo:
image: odoo:18
restart: unless-stopped
depends_on: [db]
ports:
- "127.0.0.1:8069:8069"
- "127.0.0.1:8072:8072" # websocket / longpolling (when workers > 0)
volumes:
- odoo-data:/var/lib/odoo
- ./config:/etc/odoo
- ./addons:/mnt/extra-addons
environment:
HOST: db
USER: odoo
PASSWORD: change-me-db
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_DB: postgres
POSTGRES_USER: odoo
POSTGRES_PASSWORD: change-me-db
volumes:
- pg-data:/var/lib/postgresql/data
volumes:
odoo-data:
pg-data:
Use the Odoo version you need (17, 18…). Upgrades between major versions require data migration.
odoo.conf essentials
./config/odoo.conf:
[options]
admin_passwd = <a long random master password>
db_host = db
db_user = odoo
db_password = change-me-db
addons_path = /mnt/extra-addons
proxy_mode = True
list_db = False
workers = 4
max_cron_threads = 1
limit_memory_soft = 2147483648
limit_memory_hard = 2684354560
limit_time_cpu = 600
limit_time_real = 1200
admin_passwd(the master password) protects the database manager, where databases are created, duplicated, dropped and backed up. Make it strong.list_db = Falsehides the database selector and manager from the public once you’ve created your database.workers: with multiple workers, Odoo uses separate processes, which is needed for production performance. Rule of thumb: (CPU cores × 2) + 1, limited by RAM.proxy_mode = Trueis required behind Nginx, so Odoo trusts the forwarded headers.
cd /opt/odoo && docker compose up -d
Nginx with HTTPS
upstream odoo { server 127.0.0.1:8069; }
upstream odoochat { server 127.0.0.1:8072; }
server {
server_name erp.example.com;
client_max_body_size 100M;
proxy_read_timeout 720s;
location /websocket {
proxy_pass http://odoochat;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://odoo;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
}
gzip on;
gzip_types text/css application/javascript application/json;
# listen 443 ssl; certbot --nginx -d erp.example.com
}
(Older Odoo versions use /longpolling instead of /websocket.) See Nginx WebSocket proxy and Let’s Encrypt.
First setup
Open https://erp.example.com, create the database (company name, language, admin email and password), then install the apps you need: CRM, Sales, Invoicing, Inventory, Website… Configure outgoing and incoming mail servers (Settings → Technical) for notifications and email integration. See transactional email services.
Custom and community addons
Put modules (e.g. from the Odoo Community Association, OCA) into ./addons, restart, update the apps list, and install. Pin module versions to your Odoo version. Test on a staging copy before production. Duplicate the database from the database manager.
Backups
You need both the PostgreSQL database and the filestore (attachments in /var/lib/odoo/filestore/DBNAME):
- the database manager’s backup (zip including the filestore), or
pg_dumpplus a tar of the filestore, automated:
docker compose exec -T db pg_dump -U odoo -Fc mycompany > /backups/odoo-$(date +%F).dump
docker run --rm -v odoo_odoo-data:/data -v /backups:/b alpine tar czf /b/odoo-filestore-$(date +%F).tgz -C /data filestore
Copy them off-server, and test restores. See pg_dump and backup strategy.
Security
- Strong master password,
list_db = False, and HTTPS only. - Keep PostgreSQL internal. Enable 2FA for users (built into Odoo).
- Apply Odoo security updates (pull the image with the same major tag), after a backup. See updating self-hosted apps.
Quick reference
| Item | Value |
|---|---|
| HTTP port | 8069 |
| Websocket port | 8072 (/websocket) |
| Config | odoo.conf (workers, proxy_mode, admin_passwd) |
| Addons | /mnt/extra-addons |
| Filestore | /var/lib/odoo/filestore |
Related: ERPNext and PostgreSQL tuning.