Skip to content
Guide • • 23 min read

Self-Hosting Odoo 19 on Cloudron

Self-Hosting Odoo 19 on Cloudron
Photo by Mario Gogh / Unsplash

Odoo is an open-source suite of business apps: CRM, sales, invoicing, inventory, purchasing, projects, website and more. It's one of the strongest options for a small business that wants an all-in-one system without per-seat SaaS fees. Self-hosting Odoo has always had a cost, though. You have to run PostgreSQL, a Python environment, a PDF renderer and a reverse proxy, and then back all of it up and keep it patched.

Cloudron takes much of that work off your hands. It runs each app in its own locked-down container and handles DNS, Let's Encrypt certificates, single sign-on, scheduled backups and one-click updates.

This guide explains how to approach self-hosting Odoo 19 on Cloudron, how the community-built package works, how to install it, and how to run it safely. It's written for two readers:

Status at time of writing (October 2026)Odoo 19.0 was released in September 2025. Odoo's published support table lists standard support until September 2028 (planned). Odoo 20.0 was released in September 2026, so 19.0 is no longer the newest major version, but it is fully supported.The package is a Cloudron community app. It is published independently, and Cloudron does not review it. Odoo is not in Cloudron's official App Store; it has been a long-standing wishlist request on the Cloudron forum.The package's GitHub repository has moved from CombinedTurtle/Odoo-Cloudron to SchneiderWasTaken/Odoo-Cloudron. Old links redirect. This guide describes the repository as of commit 9695cc1 (11 August 2026, package version 1.0.38).Check before installing: the newest entry in the package's CloudronVersions.json (1.0.38) points to the Docker image sentientlemon/odoocommunity:1.0.38. When this guide was written, that tag could not be found on Docker Hub, while 1.0.37, 1.0.32 and two undocumented 2.0.x tags could. The 2.0.x image metadata shows it was built from Odoo 20.0 source. See Installing and Updates for what this means for you.

Contents

  1. What you get with Odoo 19 Community
  2. Cloudron's container rules
  3. How the package works
  4. Before you install: checklist
  5. Installing the package
  6. First login and hardening
  7. Email and LDAP integration
  8. Custom and community modules
  9. Backups done properly
  10. Migrating an existing Odoo 19 database
  11. Sizing and performance
  12. Updates and lifecycle
  13. Troubleshooting
  14. Known limitations and a packager to-do list
  15. Conclusion
  16. References

1. What you get with Odoo 19 Community

Odoo Community is the free, LGPLv3-licensed edition of Odoo. It covers the core of most small businesses: CRM, Sales, Invoicing, Purchase, Inventory, Manufacturing, Project, Website, eCommerce, Point of Sale, Discuss, Employees and many other apps. Odoo Enterprise adds proprietary apps and features on top. Studio, full Accounting, Documents, Sign, Helpdesk and the newer AI features are examples. Check Odoo's edition comparison before you commit if you depend on a specific feature.

Things to know about Odoo 19 specifically:


2. Cloudron's container rules

To understand why the package is built the way it is, you need to know the rules Cloudron enforces on every app. All of these come from Cloudron's packaging documentation.

Rule What it means in practice
Read-only filesystem At runtime an app can write only to /app/data (persistent and backed up, provided by the localstorage addon), /run (cleared on restart) and /tmp (cleaned periodically). Everything else, including the app code in /app/code, is read-only.
Ownership is not preserved Cloudron states that file ownership and permissions under /app/data are not preserved across restarts, backups and restores. The start script must fix ownership on every boot.
Start as root, run as a normal user The start script runs as root so it can chown and do setup. Cloudron recommends using gosu to drop privileges before launching the app.
Addon credentials can change Addons such as LDAP and sendmail provide connection details through environment variables. Cloudron warns these "are subject to change every time the app restarts". Apps should read them at runtime and never store them permanently.
x86-64 only Cloudron does not support ARM, so images must be linux/amd64.
Community apps are unreviewed Cloudron 9.1+ can install third-party packages from a CloudronVersions.json URL ("Add custom app"). Cloudron says plainly that these "are not reviewed" and that you should install only from developers you trust.

Odoo doesn't fit these rules out of the box. It expects a writable home directory for attachments and sessions, a PostgreSQL server it can manage, and a reverse proxy that sends WebSocket traffic to a separate port. The package's job is to bridge that gap.


3. How the package works

3.1 Processes inside the container

The container's entrypoint is supervisord, which runs as root and manages three programs:

Program Runs as Purpose
start-postgres.sh → postgres root, then postgres via gosu Internal PostgreSQL 17, listening on localhost:5432 only.
start-odoo.sh → odoo root for setup, then cloudron via su Prepares directories and config, handles first-run init and restores, then starts Odoo.
nginx root Internal reverse proxy on port 8069, the port Cloudron's proxy talks to. Normal traffic goes to Odoo's HTTP workers on 8070. /websocket and /longpolling go to Odoo's gevent worker on 8072.

The internal nginx was added in package 1.0.38. Odoo has used WebSockets for real-time features (Discuss, live chat, notifications) since version 16. In multi-worker mode those connections must be routed to the dedicated gevent port. Odoo's deployment docs say the same, and without that routing users see "real-time connection lost". Earlier package versions lacked this.

3.2 Where your data lives

Everything persistent is under /app/data, which Cloudron includes in every app backup:

/app/data/
├── postgresql/            # PostgreSQL data directory (owned by postgres, mode 0700)
├── postgresql_16_backup/  # only present after an automatic 16 → 17 upgrade (see §12)
├── filestore/odoo_prod/   # Odoo attachments: uploaded files, images, generated PDFs
├── sessions/              # Odoo web sessions
├── addons/                # your custom / third-party modules
├── restore/               # drop-zone for the database restore workflow (§10)
├── odoo.conf              # GENERATED on every boot from a template in the image
├── .db_pass               # random password for the internal 'odoo' DB role
├── .odoo_initialized      # marker: first-run initialisation has completed
└── *.log                  # supervisord, PostgreSQL and nginx logs

Odoo itself is configured with data_dir = /app/data, a single database called odoo_prod, dbfilter = ^odoo_prod$, list_db = False and proxy_mode = True.

3.3 Why PostgreSQL runs inside the container

The original article said Cloudron's managed PostgreSQL addon was too restrictive because "Odoo expects to create its own users and databases on the fly." That's only partly true. Odoo's database manager does create databases. But Odoo runs perfectly well against a single pre-created database, and this package configures Odoo exactly that way anyway (db_name, dbfilter, list_db = False).

Running PostgreSQL inside the container is a design choice, and it has trade-offs:

Benefit Cost
Full superuser control: pg_restore, any extension, any role layout Cloudron's addon services normally handle database backups for you. Here the database is just files in /app/data, so backup consistency is your problem (see §9).
The package chooses the PostgreSQL version (17 here, while Cloudron's addon defaults to 14.9) Major PostgreSQL upgrades become the package's job. This one does an automatic pg_upgrade (see §12).
Self-contained: one container, one backup PostgreSQL shares the app's memory limit with Odoo.

Inside the container, the cluster is set up with trust authentication on 127.0.0.1. Any process in the container can therefore connect as the postgres superuser without a password. Only code running inside the app container can reach it, but a malicious or buggy custom module could too. Keep that in mind when you vet third-party modules.

3.4 Privilege dropping

The original article said /app/data "is owned by the cloudron user" and that PostgreSQL ran "as the root postgres user". Neither statement is accurate. Here is what actually happens:

3.5 The configuration is regenerated on every boot

On every start, /app/data/odoo.conf is rebuilt from /app/code/odoo.conf.template inside the image. The only value filled in at runtime is the internal database password. The original article said the template adapts to "dynamically assigned resources", but it doesn't. Worker count and memory limits are fixed in the template.

Two practical consequences follow:

3.6 First boot

On first start (when .odoo_initialized doesn't exist yet), the script:

  1. Creates the odoo PostgreSQL role, using the random password from .db_pass, and the odoo_prod database owned by that role.
  2. Runs odoo -i base --stop-after-init --without-demo=all to initialise an empty database.
  3. Sets the admin user's password to changeme, writing it in plain text with SQL. Odoo hashes plain-text passwords stored this way automatically.
  4. Creates the .odoo_initialized marker.

Initialisation takes a minute or two, so the first page load may be slow.

3.7 Domains

On each boot, the script sets Odoo's web.base.url to https://$CLOUDRON_APP_DOMAIN. The manifest enables multiDomain, so you can add domain aliases in Cloudron. If the Website module is installed, you can map each alias to a separate Odoo website under Settings → Websites.


4. Before you install: checklist


5. Installing the package

Option A: Install as a community app (no command line)

  1. In the Cloudron dashboard, open App Store.
  2. Click the Add custom app menu (top right) and choose the community-app option.
  3. Choose a subdomain (for example erp) and install.

Paste the versions URL:

https://raw.githubusercontent.com/SchneiderWasTaken/Odoo-Cloudron/main/CloudronVersions.json

The old CombinedTurtle URL redirects to the same file.

Important: see the status note at the top of this guide. If the install fails because the image can't be pulled, the newest listed version (1.0.38) may still be unpublished on Docker Hub. Cloudron lets you pin a version by adding @<version> to the URL:

https://raw.githubusercontent.com/SchneiderWasTaken/Odoo-Cloudron/main/CloudronVersions.json@1.0.32

Version 1.0.32 predates the WebSocket fix (§3.1), so real-time chat notifications may show "connection lost" until you update.

You can check whether a tag exists before installing:

docker manifest inspect sentientlemon/odoocommunity:1.0.38 >/dev/null && echo "tag exists"

Building from source means you know exactly what's in the image, you choose the Odoo build, and you can fix the template settings discussed in §3.5 and §14.

1. Get the package and the Odoo 19 source.

git clone https://github.com/SchneiderWasTaken/Odoo-Cloudron.git
cd Odoo-Cloudron

# Latest Odoo 19.0 nightly source build (or download from https://www.odoo.com/page/download)
curl -LO https://nightly.odoo.com/19.0/nightly/tgz/odoo_19.0.latest.tar.gz
tar xzf odoo_19.0.latest.tar.gz        # extracts to a folder like odoo-19.0.post2026MMDD

2. Point the Dockerfile at your source folder. Change this line to match the extracted folder name:

COPY odoo-19.0.post20260307 /app/code/odoo

3. Fix the port setting before building. At the commit reviewed here, odoo.conf.template sets http_port = 8069. But the internal nginx also listens on 8069 and expects Odoo's HTTP workers on 8070. Two processes can't bind the same port, so set the following in the template:

http_port = 8070
gevent_port = 8072

While you're editing it, replace admin_passwd = changeme with a long random value, and review workers and the memory limits (§11).

4. Build and install. You can do this either of two ways.

Build on the Cloudron server. This is the CLI's default. From the package directory:

npm install -g cloudron
cloudron login my.example.com
cloudron install --location erp.example.com

The CLI uploads the directory and the server builds the image. Use a .dockerignore file to keep the tarball and other large files out of the upload.

Build locally and push to a registry.

DOCKER_DEFAULT_PLATFORM=linux/amd64 docker build -t registry.example.com/you/odoo19-cloudron:1.0.0 .
docker push registry.example.com/you/odoo19-cloudron:1.0.0
cloudron install --image registry.example.com/you/odoo19-cloudron:1.0.0 --location erp.example.com

Either way, Cloudron registers DNS, gets a Let's Encrypt certificate, puts its own reverse proxy in front of the app, and waits for the health check (/web/health) to pass.


6. First login and hardening

Open https://erp.example.com and sign in:

Then, right away:

  1. Change the admin password under Settings → Users & Companies → Users.
  2. Change the admin login from admin to something unique, and use a separate everyday account for normal work. Odoo's deployment guide advises against using admin as a login.
  3. Enable two-factor authentication (TOTP, included in Community) for every administrator.
  4. Set your company details, currency and localisation before anyone enters transactions.
  5. Remember the master password. It's set in the image template (§3.5). It isn't reachable while list_db = False, but if you built the image yourself, make sure it isn't changeme.
  6. Review Cloudron access control. Because the package uses Cloudron's LDAP addon, the app's access setting controls which Cloudron users can authenticate over LDAP once you enable it (§7).

7. Email and LDAP integration

The manifest requests Cloudron's sendmail (outgoing mail) and LDAP addons. The package doesn't configure Odoo to use them automatically, so you set them up by hand. To see the values, open the app's Web Terminal in Cloudron and run:

printenv | grep -E 'CLOUDRON_(MAIL|LDAP)_'
Caveat: Cloudron documents that addon environment variables can change whenever the app restarts. Values you type into Odoo's settings are stored in the database and won't follow those changes. If mail or LDAP login stops working after a server reboot or a restore, check the current values first.

Outgoing email

  1. Enable Developer Mode (Settings → Activate the developer mode).
  2. Go to Settings → Technical → Email → Outgoing Mail Servers and create a server:
    • SMTP Server: CLOUDRON_MAIL_SMTP_SERVER
    • Port: CLOUDRON_MAIL_SMTP_PORT
    • Connection encryption: None. This is an internal relay port with TLS disabled. Use CLOUDRON_MAIL_SMTPS_PORT with SSL/TLS if you prefer, but note the server's certificate is self-signed.
    • Username / Password: CLOUDRON_MAIL_SMTP_USERNAME / CLOUDRON_MAIL_SMTP_PASSWORD
  3. Use Test Connection, then send a test email.
  4. Set your default "From" address to match CLOUDRON_MAIL_FROM or your domain, so messages aren't rejected or flagged as spoofed. If your server's IP has poor deliverability, configure a smart host in Cloudron's email settings. All apps use it automatically.

The package doesn't request Cloudron's recvmail addon. To have Odoo receive mail (leads from email, replies to chatter), set up an incoming IMAP server in Odoo by hand against a real mailbox.

LDAP single sign-on with Cloudron users

  1. Settings → General Settings → Integrations → LDAP Authentication: tick it and save. This installs the auth_ldap module, which is part of Community.
  2. Add an LDAP server:
    • Server address / port: CLOUDRON_LDAP_SERVER / CLOUDRON_LDAP_PORT
    • Login DN / password: CLOUDRON_LDAP_BIND_DN / CLOUDRON_LDAP_BIND_PASSWORD
    • LDAP base: CLOUDRON_LDAP_USERS_BASE_DN
    • LDAP filter: (&(objectclass=user)(|(username=%s)(mail=%s))). This is Cloudron's suggested filter, rewritten with Odoo's %s placeholder. Odoo substitutes the typed login into each %s, so users can sign in with a username or an email address.
    • Create user: enabled, with a suitable template user that sets the default access rights for new LDAP users.
  3. Sign in with a Cloudron account to test it. Odoo creates the user from the template on first login. You still assign roles inside Odoo.

8. Custom and community modules

Odoo's ecosystem of third-party modules (OCA and others) is one of its biggest strengths. This package supports them through /app/data/addons, so they are included in backups and survive restarts and updates.

Installing a module

  1. Make sure the module is built for Odoo 19.0. Modules from other major versions won't load.
  2. Upload the module folder, the one containing __manifest__.py, into /app/data/addons/. Ways to do this:
    • Cloudron File Manager for the app. Its root is /app/data, so open addons/.
    • The original article and the README mention SFTP on port 222. The manifest doesn't enable the localstorage FTP option, which Cloudron uses to offer SFTP for an app. Expect to use the File Manager or CLI instead.
  3. Restart the app from the Cloudron dashboard.
  4. In Odoo, enable Developer Mode, open Apps → Update Apps List, then search for and install the module.

The Cloudron CLI from your computer:

cloudron sync push --app erp.example.com ./my_module /app/data/addons

What happens at boot

Every start, the script copies each module in /app/data/addons that has a __manifest__.py into /run/odoo-overrides/odoo/addons/. It then adds that location to both PYTHONPATH and Odoo's addons_path. Because of this:

You can override built-in modules. The README presents this as similar to Odoo.sh. If a folder has the same name as a core module (for example sale or mail), your copy wins, and its display name gets a "(User)" suffix so you can tell. To start from the stock version:

cloudron exec --app erp.example.com -- cp -r \
  /app/code/venv/lib/python3.12/site-packages/odoo/addons/mail /app/data/addons/

Overriding core modules is powerful and risky. Every upstream fix to that module is hidden behind your copy until you merge it by hand.

Limitations


9. Backups done properly

This is the most important section for anyone running a business on this setup.

What Cloudron backs up

Cloudron backs up each app's /app/data on a schedule you set, before every app update, and whenever you click Create Backup. For this package, that covers your attachments, sessions, modules, logs and the raw PostgreSQL data files.

The problem: live database files

Cloudron backs up /app/data at the file level. Unless the package provides a hook, nothing makes a database-aware snapshot. Cloudron's own docs show it knows this is a problem for databases:

This package doesn't use either mechanism. A file-level copy of a running PostgreSQL cluster isn't guaranteed to be consistent. A restore might start cleanly after PostgreSQL's crash recovery, or it might not, and you won't know until you need it.

The fix: add a nightly logical dump

Have PostgreSQL write a consistent dump into /app/data shortly before Cloudron's scheduled backup runs. Cloudron then backs up a reliable copy alongside the raw files.

  1. Run bash /app/data/dump-db.sh once by hand. Confirm it succeeds and that the dump file appears in the File Manager.

In the app's Cron section in Cloudron, add a job about 30 minutes before your backup window. Cron times are in UTC. For example:

30 1 * * * bash /app/data/dump-db.sh

In the app's Web Terminal, create /app/data/dump-db.sh:

cat > /app/data/dump-db.sh <<'EOF'
#!/bin/bash
set -euo pipefail
PGVER=$(cat /app/data/postgresql/PG_VERSION)
mkdir -p /app/data/dumps
/usr/lib/postgresql/${PGVER}/bin/pg_dump -h 127.0.0.1 -U postgres -Fc \
    -f /app/data/dumps/odoo_prod.dump.tmp odoo_prod
mv /app/data/dumps/odoo_prod.dump.tmp /app/data/dumps/odoo_prod.dump
echo "Dump OK: $(date -u) $(du -h /app/data/dumps/odoo_prod.dump | cut -f1)"
EOF

The dump goes into Cloudron's off-site backups automatically. The attachments in filestore/ are ordinary files and back up correctly as they are.

Test your restore

A backup you've never restored is a hope, not a plan. At least once, and again after major changes:

  1. Use Cloudron's Clone action to restore the latest backup to a temporary subdomain.
  2. Log in and check recent records and attachments.
  3. If the clone's database is damaged, restore from the dump using the procedure in §10: copy dumps/odoo_prod.dump to restore/database.dump and restart.

Neutralise the clone straight away, so it doesn't send emails or run scheduled actions against real customers:

cloudron exec --app erp-test.example.com -- su - cloudron -s /bin/bash -c \
  "/app/code/venv/bin/odoo neutralize -c /app/data/odoo.conf -d odoo_prod"

Then restart the clone. The clone starts with production settings, so do this as soon as it's up, or delete the clone when you've finished checking.


10. Migrating an existing Odoo 19 database

The package includes a restore workflow triggered by a sentinel file. You put a dump in a known location and restart. On boot, the start script finds the dump, replaces the database, and cleans up after itself.

Requirements

Step by step

  1. Take a Cloudron backup of the app first. The restore drops the current database before it checks the dump.
  2. Collect the filestore from the source. It's usually ~/.local/share/Odoo/filestore/<source_db_name>/, or <data_dir>/filestore/<source_db_name>/.
    • /app/data/restore/database.dump
    • the contents of the source filestore folder to /app/data/restore/filestore/
  3. Restart the app, then watch Logs for === DATABASE RESTORE DETECTED === and then === RESTORE COMPLETE ===.
  4. Log in with the source database's credentials. The admin / changeme defaults no longer apply.

Upload to the app:For example:

cloudron push --app erp.example.com database.dump /app/data/restore/database.dump
cloudron sync push --app erp.example.com ./filestore /app/data/restore

Create a custom-format dump on the source server:

pg_dump -Fc --no-owner -d <source_db_name> -f database.dump

The pg_dump you use must not be newer than the target's PostgreSQL (17). pg_restore can read dumps from older versions but not newer ones.

What the script does

  1. Disconnects existing sessions, drops odoo_prod and creates it again, owned by the odoo role.
  2. Runs pg_restore --no-owner --role=odoo, so the restored tables belong to the role Odoo connects as.
  3. Treats the restore as successful if pg_restore exits cleanly, or if it exits with errors but more than 10 tables exist. pg_restore often reports harmless warnings, but this check could also accept a partial restore. Read the log.
  4. On success it:
    • replaces filestore/odoo_prod with your uploaded filestore;
    • sets web.base.url to the Cloudron domain;
    • clears every website's domain field to prevent redirect loops (set them again under Settings → Websites);
    • deletes the restore files.
  5. On failure it keeps the restore files and leaves an empty, uninitialised odoo_prod. Odoo won't work until you fix the problem. Because the files are kept, every later restart tries the restore again (and drops the database again) until the restore succeeds or you remove the files. To go back to your previous data, use Cloudron's Restore from the backup you took in step 1.

Using an Odoo database-manager .zip backup

The script also accepts a plain SQL file at /app/data/restore/dump.sql, and it cleans up manifest.json and *.zip files. That suggests you can unzip an Odoo backup straight into restore/. Be careful. Odoo's zip backups are created with pg_dump --no-owner, and the script loads plain SQL as the postgres superuser. The restored tables are then likely to be owned by postgres, not odoo, and Odoo will fail with permission denied errors. The safer route is to restore the zip on any machine with PostgreSQL, produce a -Fc dump as described above, and use the database.dump path.


11. Sizing and performance

The template's defaults are workers = 2 and max_cron_threads = 1, plus the gevent worker that multi-worker mode always starts. All of these share the app's 2 GiB Cloudron memory limit with PostgreSQL and nginx.

From Odoo's deployment guide:

One setting deserves attention. Odoo 19's default per-worker memory limits are 2 GiB soft and 2.5 GiB hard. That's bigger than the whole container. A runaway report or import can therefore push the container into Cloudron's out-of-memory killer before Odoo's own worker recycling kicks in, and that restarts everything, PostgreSQL included. For a 2–4 GiB container, set limits along these lines in the template:

workers = 2
max_cron_threads = 1
limit_memory_soft = 671088640    ; 640 MiB
limit_memory_hard = 1073741824   ; 1 GiB
limit_time_cpu = 600
limit_time_real = 1200

Then raise the app's memory in Cloudron → App → Resources as you add users. Remember that this package regenerates odoo.conf from the template on every boot, so these changes need a rebuilt image (§3.5).


12. Updates and lifecycle

Package updates

Community apps update automatically when the packager publishes a new version. Cloudron takes a backup before each update and keeps those pre-update backups for three weeks. Three things to watch:

  1. Major Odoo versions. Docker Hub already has undocumented 2.0.x images built from Odoo 20.0 source. If a future release moves this app from Odoo 19 to 20 under the same app ID, an automatic update would start Odoo 20 code against your Odoo 19 database. That isn't a supported upgrade path. For production, consider turning off automatic updates for this app (Cloudron → App → Updates), read each changelog, and test updates on a clone first.
  2. The PostgreSQL 16 → 17 upgrade. Newer images include both versions. If they find a version 16 data directory, they run pg_upgrade --link automatically and keep the old directory as /app/data/postgresql_16_backup. That directory is not a usable backup. PostgreSQL's documentation says that in link mode "you will not be able to access your old cluster once you start the new cluster." It also adds to the size of your backups. Once you've confirmed the upgraded app works, delete it. Also note that if pg_upgrade fails, the script's fallback tries to start PostgreSQL 17 on the version 16 data, which won't work. In that case, restore the pre-update backup from Cloudron.

Module data updates. A new image with a newer 19.0 build applies Python fixes immediately. Fixes in module data (views, security rules, data files) only apply when the module is upgraded, and the start script never runs -u. After a package update, during a quiet period and with a fresh backup:

cloudron exec --app erp.example.com -- su - cloudron -s /bin/bash -c \
  "/app/code/venv/bin/odoo -c /app/data/odoo.conf -d odoo_prod -u all --stop-after-init --no-http"

Then restart the app.

Planning for Odoo 20

Odoo 19 is supported until about September 2028, so there's no rush. When you do move, treat it as a project: migrate a copy of the database (OpenUpgrade for Community), port your custom modules to 20.0, test, then switch over.


13. Troubleshooting

Start with Cloudron → App → Logs. Odoo and nginx write to the app log. PostgreSQL writes to /app/data/postgresql*.log, and supervisord to /app/data/supervisord.log.

Symptom Likely cause / fix
502 or the app never becomes healthy after a self-build Odoo and nginx both trying to use port 8069. Set http_port = 8070 (§5, step 3).
"Real-time connection lost" in Discuss or live chat Package older than 1.0.38 (no WebSocket routing), or a custom build without the internal nginx.
Health check passes but Odoo shows errors /web/health doesn't check the database by default. Look in the Odoo and PostgreSQL logs.
PostgreSQL won't start after a restore Ownership or a stale PID. The script fixes both on boot, so check postgresql_error.log. After a 16 → 17 upgrade failure, restore the pre-update backup.
App restarts under load; Cloudron sends out-of-memory alerts Container memory limit hit. Lower Odoo's limit_memory_*, reduce workers, or raise the memory limit (§11).
A new module doesn't appear Missing __manifest__.py at the top of the folder, no restart, Developer Mode off, or the module is for another Odoo version.
Module fails to install with ImportError Missing Python dependency. Add it to the Dockerfile and rebuild (§8).
Restore keeps happening on every restart The previous restore failed and left its files behind. Read the log, fix the dump, or delete /app/data/restore/*.
Permission denied for table … after a restore You used the dump.sql path. Restore again from a -Fc database.dump (§10).
Emails stop sending after a reboot Cloudron addon credentials changed. Compare printenv with Odoo's outgoing mail server settings (§7).
PDFs missing headers or footers The image uses Ubuntu's wkhtmltopdf. Odoo requires the patched-Qt 0.12.6 build for headers and footers (§14).
Disk and backup size keep growing Logs in /app/data/*.log are never rotated, plus postgresql_16_backup/. Clear old logs and remove the old cluster after upgrading.
App crashes on boot and you can't get a shell Use Cloudron Recovery mode (or cloudron debug). The filesystem becomes writable and the app doesn't start on its own.

14. Known limitations and a packager to-do list

This package solves the hard parts of running Odoo on Cloudron. Here are the gaps worth knowing about, for operators and for anyone thinking of contributing:

  1. Image availability. The versions file lists a 1.0.38 image that wasn't on Docker Hub when this was written. Publish it, or roll the file back.
  2. Separate Odoo 19 from Odoo 20. Odoo 20 should be a separate app ID or come with an explicit migration step, so automatic updates can't cross a major version.
  3. Port mismatch in the repository's odoo.conf.template (http_port should be 8070).
  4. Consistent database backups. Use Cloudron's backupCommand/restoreCommand (with the database in persistentDirs), or at least ship the dump job from §9. The alternative is the managed PostgreSQL addon (§3.3).
  5. Configuration overrides. Let operators set workers, memory limits and SMTP without rebuilding. For example, merge a /app/data/odoo.local.conf, or size workers from the cgroup memory limit as Cloudron's cheat sheet shows.
  6. Wire up the addons automatically. Write SMTP settings (smtp_server, smtp_port, smtp_user, smtp_password, email_from) and LDAP settings from the CLOUDRON_* variables on every boot, so credential changes are picked up.
  7. Secrets. Generate a random admin_passwd and initial admin password on first boot and show them in the post-install message or a protected file. Use a non-admin login.
  8. PDF quality. Install the patched wkhtmltopdf 0.12.6.1 build that Odoo recommends instead of Ubuntu's package.
  9. Restore safety. Restore into a temporary database and only swap it in after checks pass. Load plain SQL as the odoo role. Don't retry endlessly.
  10. Hardening. Run nginx workers as a non-root user, use gosu for Odoo as well as PostgreSQL, and send logs to stdout as Cloudron recommends.
  11. Upgrade fallback. Fix the pg_upgrade failure path so it starts PostgreSQL 16 binaries against the version 16 data. Remove the link-mode "backup" directory after a successful upgrade.

15. Conclusion

Odoo 19 Community on Cloudron is a practical setup for a small business that wants a capable ERP on its own server. Cloudron handles certificates, DNS, off-site backups, single sign-on and upgrades. The package deals with the awkward parts of Odoo: the database, the read-only filesystem, WebSocket routing, custom modules and imports.

It's still a community package, though, and it needs care. To run it safely:

Do those things and you get what the original article promised: a self-contained business system that you control, without having to become a full-time DevOps engineer.


References

Odoo

Cloudron

Other