Improving oci_management: system dependencies, no-surprise updates, and staging

Three new capabilities of our deployment orchestrator, and why we needed them

oci_management: stacked containers and an upgrade arrow

Deploying a fresh Odoo for a client should be boring: one button, and in a few minutes there is an instance up, with its database, its proxy and its SSL. That button is oci_management, our in-house module. These past weeks we added three things it was missing.

oci_management is an Odoo module that orchestrates deployments of Odoo instances on Docker (directly or inside a Proxmox LXC container). It registers the project and its servers, generates the docker-compose.yml, the git-aggregator repos.yml and the nginx configuration from templates, and runs the deployment over SSH. What follows is today under test in our pre-development environment (the task718-dev branch), not in production.

1. System dependencies driven by the manifest

Many modules need system software, not just Python libraries. A real example: our legal module needs LibreOffice (to generate documents with report_py3o), plus ocrmypdf and tesseract for OCR. Until now, getting all that into the image meant editing the Dockerfile by hand for each client. Fragile, and different in every deployment.

Now the manifest drives it. A scanner walks the external_dependencies of each installed module: the bin key (the executables the module expects on the PATH) is translated to apt packages with a known map (soffice to LibreOffice, tesseract to tesseract-ocr plus the Spanish language, ocrmypdf to ocrmypdf plus ghostscript...), and the deb key, if the module ships one, is taken verbatim. Out of that comes a system_requirements.txt that the image installs at build time. The Dockerfile is no longer touched; the manifest decides.

This mechanism, the manifest's external_dependencies, is exactly the one we put to the test by asking nine AI models in another post on this blog. Heads up: not all of them are clear on it.

2. Updating the code without surprises

The "Update Code" feature pulls the latest changes from the client's repository and updates the affected modules. The problem was in "affected": if it could not detect well what had changed, it ended up running a -u on the whole repository. With a custom mounted as a monorepo, that means trying to update dozens of modules at once, and crashing.

We tightened it in two places. If the commit before and after are the same, there is nothing to update and it says so clearly (before, it fell back to an "update everything just in case"). And when there are changes, only the modules that are actually installed in that database get updated. An update with no changes is now a clean no-op, not a game of Russian roulette.

3. Staging environments, and promotion via merge request

Before touching production it pays to test in an isolated clone. "Create Staging" brings up that clone with its own database and its own ports. It had a subtle very-Odoo bug: One2many fields are not copied by default, so the staging was born without repositories and there was no way for it to deploy.

Now, when creating the staging, we copy the repositories explicitly and point the custom to a staging-N branch. From there the flow is the natural one: "Clone from Production" brings the real data (neutralized: no outbound mail, no crons), you test whatever you need, and "Promote" opens a merge request from staging-N to the production branch. Nothing reaches production without going through that review.

What is still missing

Testing for real (we tell it in the next post) we also found what is not there: the module still cannot fully tear down a staging (containers, volumes, branch, database, vhost and certificate), and deploying a staging demands public DNS and SSL to start when it should not. They are noted as debt, with names attached. We prefer an honest list of pending items to a "it works already" that does not survive first real use.

-- Fer & Claude, LTC Labs

Preparing the next Linux: NixOS, SPICE, and the art of virtual display
A deep dive into QEMU guest agents, virtio channels, and why NixOS changes the game