# stateless-container-for-onebspace-development Stateless hot-reload **Odoo 19** development workspace for the `1b-agent/os-dev` repo, deployed on the **Easypanel** project `onebspace`. This repo is the **blueprint** — the live state is on the [Easypanel onebspace project](http://45.137.70.123:3000) (`odoo-dev` + `odoo-dev-db` services) and the bind-mounted workspace at `/root/Documents/trae_projects/one_b_os/os-dev/`. --- ## 1. What this gives you - **No image rebuilds for code edits.** Edit any `.py` in `os-dev/odoo/addons/...` and `--dev=reload` (watchdog) restarts Odoo in milliseconds. - **Full hot-reload flags** baked into `odoo.conf`: ```ini dev_mode = reload,qweb,assets,xml,werkzeug ``` - `reload` → restart on `.py` changes - `qweb` → recompile QWeb per request - `assets` → unbundle JS/CSS - `xml` → re-read XML views from disk - `werkzeug` → in-browser tracebacks - **Image built by Easypanel from an inline Dockerfile** on every deploy (`source.type=dockerfile`), no Gitea Container Registry needed. - **Bind-mount** of the entire `1b-agent/os-dev` workspace at `/mnt/os-dev` so the upstream addons, your custom addons, and the core Odoo code are all editable in place. --- ## 2. Components | File | Purpose | |------|---------| | `Dockerfile` | Verbatim copy of [`github.com/odoo/docker/19.0/Dockerfile`](https://github.com/odoo/docker/blob/master/19.0/Dockerfile) + `pip install -r requirements.txt --break-system-packages`. SHA-pinned release: `ODOO_RELEASE=20260817`, `ODOO_SHA=7e6c102efc7e2ad56801432fef7e2e0d2f703e5a` | | `entrypoint.sh` | Postgres env-var → `--db_host/--db_user/...` propagation (copied from upstream) | | `wait-for-psql.py` | Waits for Postgres before starting Odoo (copied from upstream) | | `odoo.conf` | Bake-time Odoo config; full hot-reload; `addons_path` includes our `/mnt/os-dev/{addons,odoo/addons}` | | `requirements.txt` | Python deps from the upstream `odoo/19.0` requirements.txt | | `easypanel.schema.json` | Easypanel service payload using **`source.type=dockerfile`** + inline Dockerfile | | `deploy.py` | Calls Easypanel\'s native tRPC API directly (bypasses the MCP wrapper that only supports `source.type=image`) to push the inline Dockerfile, set env, and deploy | | `.gitea/workflows/build.yml` | Optional: auto-build & push `os-dev:19.0` to a Gitea Container Registry on every push to `1b-agent/os-dev`'s `19.0` branch | --- ## 3. How it was built (research summary) ### 3.1 The official `odoo/docker` image - Base: `ubuntu:noble` (not Debian) - Pre-installs `python3-watchdog` (what makes `--dev=reload` work) - Entrypoint waits for Postgres (`wait-for-psql.py --timeout=30`) - Default `odoo.conf` has `addons_path = /mnt/extra-addons`, `data_dir = /var/lib/odoo` — both declared as `VOLUME` ### 3.2 dev_mode flags (Odoo 19 CLI reference) | Flag | Effect | |------|--------| | `reload` | restart `odoo-bin` on `.py` change (needs `python3-watchdog`) | | `qweb` | recompile QWeb templates per request | | `assets` | serve JS/CSS unbundled → edit-and-refresh | | `xml` | re-read XML view files on update | | `werkzeug` | show full Python tracebacks in browser | ### 3.3 Why `source.type=dockerfile` (not image pull) Easypanel\'s `services.app.createFromSchema` (the MCP wrapper) only supports `source.type=image`, and Easypanel couldn\'t pull from the Gitea Container Registry due to TLS cert mismatch + missing package pre-creation. Easypanel itself supports `source.type=dockerfile` (inline Dockerfile build) — but the MCP wrapper doesn\'t surface it. We **bypassed the MCP** and called the native tRPC API directly: ``` POST /api/trpc/services.app.createService POST /api/trpc/services.app.updateSourceDockerfile POST /api/trpc/services.app.updateEnv POST /api/trpc/services.app.deployService ``` The Dockerfile is stored on Easypanel\'s filesystem at `/etc/easypanel/projects///code/Dockerfile` and rebuilt on every deploy. ### 3.4 Self-contained inline Dockerfile Easypanel\'s `source.type=dockerfile` has **no `COPY` access** (per docs: *"Files from a repository or upload are not automatically available to this source type"*), so all `COPY`-ed files are inlined as heredocs (`RUN cat > /file <<\'MARKER\'`). This produces a single ~17 KB Dockerfile with zero `COPY` instructions. --- ## 4. Reproducing this setup on a fresh Easypanel ### 4.1 Prereqs - An Easypanel instance with a project (we used `onebspace`) - A clone of `1b-agent/os-dev` at `/root/Documents/trae_projects/one_b_os/os-dev/` - An Easypanel bearer token (Settings → API → Generate Token, or grab the MCP token from `/root/.trae-server/data/Machine/mcp.json`) ### 4.2 Run the deploy script ```bash cd stateless-container-for-onebspace-development python3 deploy.py deploy ``` This will: 1. Generate the inline Dockerfile from `../os-dev/*` files 2. Generate a fresh DB password and save to `.db_password` 3. Patch `CHANGE_ME_DB_PASSWORD` in the env block 4. Call the four Easypanel tRPC procedures in order 5. Build the image in-place and start the container Verify: ```bash curl -sS -o /dev/null -w "HTTP %{http_code} bytes=%{size_download}\\n" \ http://127.0.0.1:18069/web/database/manager # expected: HTTP 200 bytes=70194 ``` --- ## 5. Daily workflow ```bash # edit code in place — auto-reload picks it up vim /root/Documents/trae_projects/one_b_os/os-dev/odoo/addons/web/controllers/main.py # restart Odoo manually if needed docker restart $(docker ps --filter "name=onebspace_odoo-dev" --format "{{.ID}}" | head -1) # rebuild the image (only when requirements.txt or Dockerfile changes) python3 deploy.py redeploy ``` --- ## 6. Verified working state - `Odoo version 19.0-20260817` (matches `ODOO_SHA=7e6c102e…`) - HTTP 200 (70,194 bytes) on `/web/database/manager` - Addons loaded from `/mnt/os-dev/addons`, `/mnt/os-dev/odoo/addons`, plus upstream `/usr/lib/python3/dist-packages/odoo/addons` - `AutoReload watcher running with watchdog` + `HTTP service (werkzeug) running` - Bind-mount `/root/Documents/trae_projects/one_b_os/os-dev/` → `/mnt/os-dev/` verified (24 addons in both views)