[Blog](https://deploywithox.com/blog) [Guides](https://deploywithox.com/blog#c-guides) # How to self-host Odoo 19 on your own VPS, no Docker Run Odoo 19.0 on your own Ubuntu server with ox: Python 3.12, PostgreSQL and one threaded server process, from the config that ran on a new server. Saurav Sharma Oct 17, 2026 10 min read On this page - [What you need](https://deploywithox.com/blog/self-host-odoo#what-you-need) - [The steps](https://deploywithox.com/blog/self-host-odoo#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-odoo#config) - [Postgres and the data folder](https://deploywithox.com/blog/self-host-odoo#services) - [Updates and backups](https://deploywithox.com/blog/self-host-odoo#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-odoo#faq) To self-host Odoo you need an Ubuntu server, a fork of the Odoo repo with one `ox.toml` file added, and a nine-line variables file. In our run the first deploy took about 3 minutes, and ox set up Postgres and installed Odoo's base module into it. **How we tested** On Oct 17, 2026, ox deployed Odoo 19.0 from commit [`e1ed4fe0`](https://github.com/odoo/odoo/commit/e1ed4fe077ba98ce7ac6802a43bb68242081dee9) on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory. The run then redeployed it, backed it up and rebooted the server. Then a separate test agent, one that did not write the config, signed in, turned on the Contacts app, added a company, and saw it listed after a reload, and checked the app's services and its backup. [Odoo](https://github.com/odoo/odoo) is a suite of web business apps: CRM, a website builder, eCommerce, warehouse, projects, accounting, point of sale, HR, marketing, manufacturing and more. You switch on the ones you need from the Apps screen, and they work alone or together as an ERP. The server is a Python program, `odoo-bin`, that keeps its records in PostgreSQL and its attachments in a data folder. The code is under the LGPL version 3. ox deployed Odoo 19.0 (commit e1ed4fe0) on Oct 17, 2026 on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory; the first deploy took 3 minutes and a redeploy took 71 seconds. - **First deploy:** 3 minutes, from adding the project until the app passed the run's first checks. - **Redeploy of the same commit:** 71 seconds, until the app passed the checks again. - **Server:** DigitalOcean, 8 vCPUs and 16 GB of memory (`s-8vcpu-16gb`), with this app alone on it. Every one of the 85 apps we deployed ran on this one size, so it was not picked for this app. - **Memory in use after the reboot:** 746 MB of 15986 MB, for the whole server: Ubuntu, the app, its services, Caddy and the ox agent. On a server with no apps, ox's tests on real servers hold the agent to 40 MB or less and Caddy to 50 MB or less. - **Services ox runs for it:** `postgres`. - **Source:** [github.com/odoo/odoo](https://github.com/odoo/odoo) at 19.0, commit [`e1ed4fe0`](https://github.com/odoo/odoo/commit/e1ed4fe077ba98ce7ac6802a43bb68242081dee9). The Odoo log-in page The Odoo Apps page after signing in The Odoo Contacts list with the new contact Acme Corp Verify ## What you need - An Ubuntu LTS server with root access. The facts above name the size the run used and the memory in use after its reboot. - A GitHub account to fork `odoo/odoo`, and an ox account, which you open by signing in with GitHub. - An email address and a password for the first admin. The run used an `example.com` address. - A mail server, if you want Odoo to send invitations and password resets. The run used a test mail catcher, so no real mail left the server, and email was not tested. ## The steps Open Add a server in the ox dashboard. It shows one command with a token that works once. Run it as root on your server. It installs the ox agent and Caddy only. Fork `odoo/odoo` on GitHub and check out commit `e1ed4fe0`, the Odoo 19.0 code the run deployed. Put the `ox.toml` shown below at the root of the repo, then commit and push. No other file was added. Next write `odoo.env` on your laptop with the nine keys from the variables section. You choose the admin email and password, the five database keys are references ox fills in, and the two mail keys name the mail server Odoo sends through. With the [ox CLI](https://deploywithox.com/docs/cli) signed in, run: Shell ```bash ox new /odoo ox review odoo --from-file odoo.env --wait ``` The project is named `odoo`, after the repo. The review saves your values and starts the first deploy, and `--wait` prints the log until it ends. The [quickstart](https://deploywithox.com/docs/quickstart) shows the same two steps in the browser. When the run is green, open the project's address and sign in with the admin email and password. The first page after a deploy builds Odoo's web assets, so it loads slower than later ones. A new Odoo shows only the Apps and Settings menus. Open the Apps screen, find Contacts and press Activate. Odoo installs it, which takes a few seconds to about a minute behind a loading overlay, then reloads. Open Contacts, press New, type a name and an email, and save. The test agent added a company this way, reloaded, and found it in the list. The run served Odoo on its preview address only. A domain of your own was not part of it, and the [domains page](https://deploywithox.com/docs/operate/domains) shows how ox adds one. Users beyond the admin were not tested either. ## The ox.toml, line by line TOML ```toml # Odoo 19 from source, as Odoo's own docker image runs it: one threaded # server (workers = 0) that serves the web client, /websocket and the cron # threads on one port. Odoo reads its database from PGHOST, PGPORT, PGUSER, # PGPASSWORD and PGDATABASE (set in ox.vars from the postgres service), and # keeps its filestore (attachments, asset bundles) and sessions in the kept # `data` folder. The database manager is off (--no-database-list), so no # visitor can create, copy or drop a database. required_variables = ["ODOO_ADMIN_EMAIL", "ODOO_ADMIN_PASSWORD"] packages = [ "libmagic1t64", # python-magic # wkhtmltox's .deb dependencies (PDF reports), unpacked by the build "ca-certificates", "fontconfig", "fonts-dejavu-core", "libfreetype6", "libjpeg-turbo8", "libpng16-16t64", "libssl3t64", "libstdc++6", "libx11-6", "libxcb1", "libxext6", "libxrender1", "xfonts-75dpi", "xfonts-base", "zlib1g", ] [tools] python = "3.12" # requirements.txt pins Ubuntu 24.04's versions for 3.12 [app] start = 'export PATH="$PWD/wkhtmltox/usr/local/bin:$PATH"; exec python odoo-bin --data-dir data --http-interface 127.0.0.1 --http-port "$PORT" --proxy-mode --workers 0 --no-database-list' health = "/web/health" [build] # wkhtmltopdf is not packaged for Ubuntu 26.04: unpack the patched-Qt build # Odoo's docker image installs (wkhtmltox 0.12.6.1-3) into the release. commands = [ 'arch=$(dpkg --print-architecture) && mkdir -p wkhtmltox && curl -fsSL -o wkhtmltox.deb "https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.jammy_${arch}.deb" && dpkg-deb -x wkhtmltox.deb wkhtmltox && rm wkhtmltox.deb && wkhtmltox/usr/local/bin/wkhtmltopdf --version', ] # First deploy: install `base` into the empty database. Later deploys update # the modules whose version on disk is newer. Then the default admin/admin # account becomes the operator's login, once. Each guard prints its answer, # and a connection error stops the deploy instead of re-initialising. migrate = ''' state=$(python - <<'PY' import psycopg2 cur = psycopg2.connect('').cursor() cur.execute("SELECT to_regclass('public.ir_module_module') IS NOT NULL") if cur.fetchone()[0]: cur.execute("SELECT state FROM ir_module_module WHERE name = 'base'") row = cur.fetchone() print('yes' if row and row[0] == 'installed' else 'no') else: print('no') PY ) echo "odoo database initialised: $state" if [ "$state" = no ]; then python odoo-bin --data-dir data --no-http --stop-after-init -i base else python odoo-bin module upgrade all --outdated -D data fi python odoo-bin shell --data-dir data --no-http <<'PY' import os admin = env.ref('base.user_admin') if admin.login == 'admin': admin.write({'login': os.environ['ODOO_ADMIN_EMAIL'], 'email': os.environ['ODOO_ADMIN_EMAIL'], 'password': os.environ['ODOO_ADMIN_PASSWORD']}) env.cr.commit() print('odoo admin login set to', admin.login) else: print('odoo admin already set up as', admin.login) PY ''' [services] postgres = { extensions = ["pg_trgm"] } # Odoo's trigram indexes and search [storage] keep = ["data"] # filestore (attachments, asset bundles) and sessions ``` - `required_variables` names the admin email and password. ox asks for them before the first deploy starts, because the last migrate step needs both. - `packages` has `libmagic1t64`, the library behind `python-magic`, and the fonts, SSL, X11 and image libraries the unpacked `wkhtmltopdf` links against. - There is no `install` line. ox sees `requirements.txt` and installs it with uv into a virtual environment, adding the C compilers and the LDAP and PostgreSQL headers its packages build with. `ox check` lists them. - `[tools]` pins Python 3.12. Odoo 19 accepts 3.10 to 3.14, and `requirements.txt` marks its pins for 3.12 as the versions Ubuntu 24.04 ships. - `[app] start` puts the unpacked `wkhtmltopdf` on `PATH` and runs `odoo-bin` with `exec`. `--data-dir data` keeps attachments and sessions in the kept folder. The interface and port flags listen on loopback and the port ox gives it, with Caddy in front. `--proxy-mode` makes Odoo trust forwarded headers, which its help allows only behind a trusted proxy. `--workers 0` is threaded mode: one process serves the web client and runs the scheduled jobs. `--no-database-list` turns off the database manager and picker. - `health` is `/web/health`, Odoo's own check, which answers `{"status": "pass"}`. ox switches traffic to a new release only once it answers. - The build `commands` set up PDF reports. Odoo calls `wkhtmltopdf` to print them, and the config's comment says Ubuntu 26.04 does not package it. The command downloads the 0.12.6.1-3 build for the server's CPU type, unpacks it into the release with `dpkg-deb` and prints its version, so a binary that cannot run stops the build. Printing a PDF was not tested. - `migrate` starts with a guard: a short Python script asks the database whether Odoo's `base` module is installed. On an empty database, `odoo-bin -i base` installs it. On later deploys, `odoo-bin module upgrade all --outdated` upgrades the modules whose version on disk is newer. A connection error stops the deploy, so an unreachable database never looks empty. - The rest of `migrate` turns Odoo's stock admin into yours. A fresh install has the login `admin` with the password `admin`. An `odoo-bin shell` script sets that account's login, email and password from the two variables, once, and only while the login is still `admin`. - `[services]` asks for Postgres with the `pg_trgm` extension. Odoo's database tools create it in every database they make, and its trigram search indexes use it. - `[storage] keep` links `data` to the project's data folder, so the filestore and sessions survive each deploy. There are no workers or cron jobs. Odoo runs its scheduled jobs as threads in the web process, and the test agent saw one, "Mail: Email Queue Manager", finish in that process's log. Shell ```bash ODOO_ADMIN_EMAIL ODOO_ADMIN_PASSWORD PGDATABASE PGHOST PGPORT PGUSER PGPASSWORD ODOO_SMTP_SERVER ODOO_SMTP_PORT ``` `ODOO_ADMIN_EMAIL` and `ODOO_ADMIN_PASSWORD` are the first admin's login, the two values you choose. Odoo reads its database from libpq's usual names. The run set `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD` and `PGDATABASE` to `${postgres.host}`, `${postgres.port}`, `${postgres.user}`, `${postgres.password}` and `${postgres.database}`, references to the Postgres service. ox fills such names in by itself only when a repo's example file lists one, and Odoo's repo has none. `PGDATABASE` names the one database Odoo uses, which its help says to set when the database list is off. See [variables](https://deploywithox.com/docs/operate/variables) for how references work. `ODOO_SMTP_SERVER` and `ODOO_SMTP_PORT` set Odoo's outgoing mail server, like its `--smtp` and `--smtp-port` options. The run pointed both at a test mail catcher. ## Postgres and the data folder Odoo gets one database and one login role on the server's shared Postgres. With the database list off, `/web/database/manager` says it is disabled and `/web/database/list` answers Access Denied, so no visitor can create or drop a database. ## Updates and backups To update, push a newer commit to your fork or run `ox deploy odoo --wait`. ox builds the new release beside the running one and runs `migrate`, which upgrades the modules with newer code. Traffic moves over when `/web/health` answers. Moving to a new Odoo major version was not tested. Backups need no setup. The run's backups held two Postgres dumps, one from Back up now and one ox took just before the redeploy's migrate, and an archive of the `data` folder. Run `ox backups odoo` to list them. A restore was not tested. The [backups page](https://deploywithox.com/docs/operate/backups) covers keeping copies in an S3 bucket. ## Frequently asked questions ### Do I need Docker to self-host Odoo? No. Odoo is a Python program that runs from its source tree. ox installed Python 3.12 and the packages in `requirements.txt` on the server and ran `odoo-bin` as a system service beside Postgres and Caddy. There is no image, and an update is a push to your fork. ### How do I sign in to a new Odoo? Set ODOO_ADMIN_EMAIL and ODOO_ADMIN_PASSWORD before the first deploy. The migrate step swaps them in for the stock admin login and password, once, and you sign in at the project's address with that email. The log-in page has no sign-up link. Adding other users was not tested. ### Which database does Odoo need? PostgreSQL. Odoo reads the connection from PGHOST, PGPORT, PGUSER, PGPASSWORD and PGDATABASE. In this run they pointed at the Postgres ox set up for the project, with the `pg_trgm` extension on. Odoo installed its base module into that empty database on the first deploy. ### How much memory does Odoo use? The facts above give what the whole server used after the run's reboot, with Odoo's base module installed, Postgres, Caddy and the ox agent running. Apps you turn on later, and real data, were not measured. ox works on any server from 1 GB up and tells you when memory is why Odoo stopped. New to running apps on your own server? [How ox self-hosts your apps](https://deploywithox.com/self-hosting) shows what ox sets up on your server. On this page - [What you need](https://deploywithox.com/blog/self-host-odoo#what-you-need) - [The steps](https://deploywithox.com/blog/self-host-odoo#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-odoo#config) - [Postgres and the data folder](https://deploywithox.com/blog/self-host-odoo#services) - [Updates and backups](https://deploywithox.com/blog/self-host-odoo#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-odoo#faq) - [Do I need Docker to self-host Odoo?](https://deploywithox.com/blog/self-host-odoo#faq-docker) - [How do I sign in to a new Odoo?](https://deploywithox.com/blog/self-host-odoo#faq-admin) - [Which database does Odoo need?](https://deploywithox.com/blog/self-host-odoo#faq-database) - [How much memory does Odoo use?](https://deploywithox.com/blog/self-host-odoo#faq-memory) Written by Saurav Sharma Builds ox. Writes about running apps on servers you own, without Docker and without a platform bill. [Older post How to self-host Open WebUI on your own VPS, no Docker](https://deploywithox.com/blog/self-host-open-webui) [Newer post How to self-host nopCommerce online store on your own VPS](https://deploywithox.com/blog/self-host-nopcommerce) ## Keep reading - Guides [Self-Hosted Open-Source Apps Deployed with ox: All 85](https://deploywithox.com/blog/self-hosted-open-source-apps-deployed-with-ox) ox deployed 85 open-source apps from pinned commits on fresh servers, with no Docker. The stacks, services, deploy times and a table of every app. Saurav Sharma Oct 17, 2026 16 min read - Guides [How to self-host AFFiNE on your own VPS without Docker](https://deploywithox.com/blog/self-host-affine) Run AFFiNE v0.27.4 on your own Ubuntu server with ox: Postgres with pgvector, Redis, a Rust and Node build, and an admin made from two variables. Saurav Sharma Oct 17, 2026 12 min read - Guides [How to self-host Akaunting on your own VPS without Docker](https://deploywithox.com/blog/self-host-akaunting) Run Akaunting 3.2.4 on your own Ubuntu server with ox: PHP, MariaDB, a scheduler and the first company and admin made on deploy, from the config that ran. Saurav Sharma Oct 17, 2026 10 min read ## Deploy to a server you own ox sets up systemd, Caddy and PostgreSQL on your Ubuntu server and deploys on every push. Free for your first project. [Sign up with GitHub](https://deploywithox.com/login/github) [Read the docs](https://deploywithox.com/docs)