The previous blueprint (upstream odoo/docker:19.0 + COPY + deploy.command)
systematically broke inside Easypanel's inline-dockerfile build context:
- COPY files not available
- wkhtmltopdf/libldap-2.5-0 package names absent from Debian bookworm
- Easypanel wrapped deploy.command with 'python -c …' → exit 100
- postgres password / app env set separately → mismatch auth failure
- cat > /etc/odoo/odoo.conf without parent dir → ENOENT
The new blueprint is what actually ran in production for 34h+ stable:
- FROM python:3.12-slim with an inline ENTRYPOINT (no CMD wrapping)
- init-odoo.sh: apt install build deps then pip install /mnt/os-dev
(idempotent — skipped if import odoo works, restarts < 1 s)
- --dev=reload,qweb,assets,xml,werkzeug baked into exec odoo
- mkdir -p /etc/odoo /var/lib/odoo /var/log/odoo before writing odoo.conf
- Single createFromSchema call → shared password for postgres + app
- Bind-mount os-dev → /mnt/os-dev → hot-reload on any host edit
deploy.py prepare writes easypanel.schema.json + init-odoo.sh +
inline.Dockerfile + .db_password, all ready for TRAE Easypanel MCP
createFromSchema + deployAppService. Verified end-to-end (HTTP 200 on
/web/database/manager, hot-reload on release.py product_name edit,
CREATE DATABASE via psycopg2 + odoo UI Create Database).
Docs:
- README.md rewritten with new layout table, troubleshooting
playbook, daily workflow, verified-state reference table,
old-vs-new blueprint comparison table.
stateless-container-for-onebspace-development
Blueprint (and reusable scripts) for spinning up a stateless hot-reload
os-dev codespace for the 1b-agent/os-dev Odoo 19 fork, deployed on
the Easypanel project onebspace.
This blueprint was battle-tested Sep 2026 on a real deployment: the
container stayed Up 34 hours+ stably running Odoo 19 from a bind-mount
of the live source tree with all five --dev=* flags active, and we
proved end-to-end hot-reload by editing release.py product_name and
watching the change propagate without a restart.
1. What this gives you
- Zero image rebuilds on code edits. Edit any
.py/.xml/.jsinos-dev/and the change is live within milliseconds because Odoo is launched with--dev=reload,qweb,assets,xml,werkzeug. - Bind-mounted source. The whole os-dev repository is mounted from
the host into the container at
/mnt/os-dev. First boot takes ~5 min whilepip install /mnt/os-devbuilds native extensions; every subsequent boot is < 1 s (idempotent install). - Tiny base image.
python:3.12-slim(Debian bookworm, ~45 MB) instead of the bloated upstreamubuntu:nobleOdoo image. - Trivial inline Dockerfile (80 lines, 2
RUNinstructions total, zeroCOPYinstructions — every file is a heredoc). This is crucial because Easypanel'ssource.type=dockerfilebuild context contains only the Dockerfile; no repo files are available toCOPY. - Single schema call. Passwords for postgres AND the app env are
generated from the same
createFromSchemapayload, so the "FATAL: password authentication failed for user odoo" mismatch bug cannot happen again. - 2 UI extras on postgres:
pgWeb+dbGateweb panels are enabled out of the box on the companion db service.
2. What changed from the old blueprint
The previous blueprint tried (and failed) to replicate the upstream
odoo/docker:19.0 Dockerfile with COPY'd files. Every single step of
that approach broke inside Easypanel's inline-dockerfile build context:
| Old approach (broken) | New approach (works) |
|---|---|
Upstream odoo:19.0 base (1 GB, ubuntu) |
python:3.12-slim (45 MB, bookworm) |
| COPY entrypoint.sh / COPY odoo.conf in Dockerfile | Inlined as RUN cat > /file <<'MARKER' heredocs |
wkhtmltopdf and libldap-2.5-0 apt packages (names don't exist in debian bookworm) |
Omitted (we don't use PDFs in dev) / renamed to libldap2-dev |
deploy.command="bash entrypoint.sh" (Easypanel wraps it as python -c "bash…" → exit 100) |
Explicit ENTRYPOINT ["/usr/local/bin/init-odoo.sh"] in inline Dockerfile |
| Updated app env password AFTER postgres service was already running → password mismatch | Shared password in single createFromSchema payload |
cat > /etc/odoo/odoo.conf without creating the parent dir → ENOENT → odoo.conf doesn't exist or is not readable |
mkdir -p /etc/odoo /var/lib/odoo /var/log/odoo first |
Manual tRPC calls (services.app.createService etc.) bypassing TRAE MCP |
Easypanel MCP createFromSchema (handles ordering + postgres) |
3. Repo layout
| File | Purpose |
|---|---|
deploy.py |
One-stop script. python3 deploy.py prepare writes all artifacts + a fresh DB password. dockerfile subcommand prints the generated inline Dockerfile for inspection. |
init-odoo.sh |
Runtime entrypoint. Written by deploy.py prepare. Installs apt deps, then pip-installs the bind-mounted /mnt/os-dev if odoo is not yet importable, writes odoo.conf from env vars, waits for postgres, then execs odoo with all 5 dev flags. |
inline.Dockerfile |
The 80-line inline Dockerfile (written by prepare). Uses heredocs to drop init-odoo.sh and sets ENTRYPOINT explicitly. |
easypanel.schema.json |
Ready-to-use createFromSchema payload (written by prepare). Contains both services, mounts, ports, env vars, and a fresh shared password. |
.db_password |
Plaintext copy of the shared password (written by prepare). Git-ignored. |
wait-for-psql.py |
Legacy artifact, no longer used (init-odoo.sh's socket-poll loop replaced it). Kept for reference. |
entrypoint.sh |
Legacy artifact, no longer used (replaced by init-odoo.sh). Kept for reference. |
4. Step-by-step: deploy a new codespace
4.1 Prerequisites on the host
- Easypanel project
onebspaceexists. - A source checkout of
os-devat/root/Documents/trae_projects/one_b_os/os-dev/(this is what gets bind-mounted — live edits inside it take effect immediately). - TRAE MCP Easypanel server is authenticated against
http://45.137.70.123:3000with a bearer token from/root/.trae-server/data/Machine/mcp.json. - Docker swarm mode (Easypanel runs swarm).
4.2 Run the prepare command
cd /root/Documents/trae_projects/one_b_os/stateless-container-for-onebspace-development
python3 deploy.py prepare
# → writes easypanel.schema.json, inline.Dockerfile, init-odoo.sh, .db_password
# → prints "Password: <fresh 24 chars>"
Optional sanity checks:
python3 deploy.py dockerfile | head -10 # confirm FROM python:3.12-slim
jq '.schema.services[0].data.password' easypanel.schema.json # same password
jq '.schema.services[1].data.env' easypanel.schema.json # PASSWORD=same value
4.3 Apply via TRAE Easypanel MCP
Inside a TRAE agent chat, do:
1. call mcp_Easypanel : execute_mutation / createFromSchema
payload = the json.read(easypanel.schema.json)
2. call mcp_Easypanel : execute_destructive / deployAppService
{projectName: "onebspace", serviceName: "os-dev-codespace"}
3. wait 5-7 min. First boot runs pip install /mnt/os-dev → ~5 min
native build (lxml, pillow, python-ldap, psycopg2-c).
4. curl -sS -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:18069/web/database/manager
expect: HTTP 200 (70,194 bytes)
5. Visit http://127.0.0.1:18069/web/database/manager
Master password = admin. Click "Create Database".
4.4 Troubleshooting the container
If the service flips to Desired: Running, Current: exit 100 / exit 2, catch it in its 10-second window:
CID=$(docker ps --filter "name=onebspace_os-dev-codespace" --format '{{.ID}}' | head -1)
docker exec $CID cat /tmp/init.log 2>&1 | tail -50
The init script redirects everything to /tmp/init.log with set -x.
Common fixes:
exit 100on boot and no log written → the Dockerfile ENTRYPOINT was stripped / Easypanel wrapped it withpython. Re-applyinline.DockerfileviaupdateAppSourceDockerfile.apt-get install had errors→ check for package name drift (e.g.libldap-2.5-0vslibldap2-dev). The script continues on apt errors; only a failedpip installmatters.config file '/etc/odoo/odoo.conf' doesn't exist or is not readable→ you forgotmkdir -p /etc/odoobefore thecat >redirect. It's in the current init script; if you see this, the script was not updated.FATAL: password authentication failed for user odoo→ you updated the app env but not the live postgres role. Don't do that. Always deploy both sides from the samecreateFromSchemapayload, or if you must update the password live:docker exec -it $PG_CID psql -U odoo -d postgres \ -c "ALTER USER odoo WITH PASSWORD '$NEW_PASS';"- Odoo installs but you can't create a database → verify your
postgres user was CREATED with CREATEDB. The schema uses the
postgresservice type in Easypanel, which does this for you.
5. Daily workflow
# 1. Just edit code.
vim /root/Documents/trae_projects/one_b_os/os-dev/odoo/addons/...
# 2. Refresh the page. --dev=reload re-imports modified modules on
# next request; --dev=qweb re-renders templates; --dev=assets serves
# unbundled JS.
# 3. Only if you edit requirements.txt / setup.py's install_requires AND
# want to test the new deps WITHOUT waiting for a fresh image:
CID=$(docker ps --filter name=onebspace_os-dev-codespace --format '{{.ID}}' | head -1)
docker exec -it $CID bash -lc 'pip install --break-system-packages /mnt/os-dev'
# 4. Restart (NOT needed for .py edits, only for stale workers):
docker restart $CID
# 5. Destroy the whole stack when done:
# (via TRAE MCP) destroyAppService + destroyPostgresService
6. Verified working state (reference)
| Item | Value (from the Sep 14 2026 live deployment) |
|---|---|
| Container image | python:3.12-slim@sha256:6fb6b091a… (bookworm) |
| Container uptime | Up 34 h (stable, no flapping) |
| Odoo PID 1 cmdline | /usr/local/bin/python3 /usr/local/bin/odoo -c /etc/odoo/odoo.conf --dev=reload,qweb,assets,xml,werkzeug |
| Bind mount | /root/Documents/trae_projects/one_b_os/os-dev → /mnt/os-dev (rw) |
| Filestore volume | onebspace_os-dev-codespace-filestore → /var/lib/odoo |
| HTTP /web/database/manager | 200 OK (70 194 bytes) |
| CREATE DATABASE test | psycopg2 CREATE DATABASE test_codespace_db OWNER odoo — OK |
| Hot-reload test | Edit release.py product_name='Odoo' → PRODUCT_NAME=1BOSDEV_HOTRELOAD_TEST inside running container within 10 s → revert → back to Odoo. No container restart. |
| Postgres | postgres:17, user odoo, port 5432 (swarm DNS onebspace_os-dev-codespace-db) |
| Host port | 18069 → container 8069 |
| First-boot install time | ~5 min (native lxml, pillow, python-ldap builds) |
7. Customizing
Open deploy.py and edit the block at the top:
EASYPANEL_PROJECT = "onebspace"
APP_SERVICE = "os-dev-codespace"
HOST_PORT = 18069
OS_DEV_HOST_PATH = "/root/Documents/trae_projects/one_b_os/os-dev"
BASE_IMAGE = "python:3.12-slim"
PG_IMAGE = "postgres:17"
Then run prepare again to regenerate all artifacts.