Base Odoo runtime image.
This repository owns the base runtime build for Odoo 19. It compiles a
deterministic runtime from the upstream Odoo source, then layers in uv,
PostgreSQL 17 client tools, and compatibility paths used by downstream
deployment tooling. The runtime also includes the Launchplane substrate needed
by Launchplane-managed Odoo lanes.
This repository provides a stable base runtime for downstream project images.
runtime: base Odoo runtime + PostgreSQL client + uv toolingruntime-devtools:runtimeplus Playwright-managed Chromium test tooling
Both images default to the ubuntu user for compatibility with existing
restore and SSH mount workflows.
runtimestays runtime-first and does not write IDE-oriented Python path entries.runtime-devtoolswrites a minimalodoo_paths.pthfor generic non-core source and addon roots used in local development tooling:/odoo/opt/project/addons/opt/extra_addons
runtime-devtoolsexposes Playwright-managed Chromium throughCHROME_BINat/usr/local/bin/chromium-playwright.
The image reserves /opt/launchplane/addons for image-owned Odoo addons that
support Launchplane-managed runtime behavior. These addons are separate from
upstream Odoo source, downstream project addons in /opt/project/addons, and
shared business addons in /opt/extra_addons.
launchplane_runtime_health is loaded as a server-wide module by default. It
exposes GET /launchplane/health with auth="none" and save_session=False
so Launchplane can verify the exact runtime serving a public lane without
depending on tenant database or Website state. The endpoint returns JSON shaped
like:
{"runtime_identity":null,"status":"pass"}When LAUNCHPLANE_RUNTIME_IDENTITY_JSON is present and valid, the parsed object
is returned as runtime_identity. If the variable is present but malformed, the
endpoint returns a failing response instead of echoing raw environment text.
Downstream runtime overrides must preserve /opt/launchplane/addons in the
effective addons path and base,web,launchplane_runtime_health in the effective
server-wide modules. /odoo/odoo-bin normalizes server-mode invocations to keep
those image-owned defaults present.
/odoo/odoo-binis a compatibility wrapper over upstream/usr/local/bin/odoo-source-bin.- The wrapper must preserve Odoo subcommands (
server,shell,db, etc.). - Runtime defaults (
--db_host,--addons-path, etc.) are injected only for server-style invocations so non-server commands keep upstream argument parsing semantics. - Server-style invocations always include
/opt/launchplane/addonsin the effective addons path andlaunchplane_runtime_healthin the server-wide module list while preservingbase,web.
- Downstream images inherit a single shared Python environment at
/venv. /venvis not configurable; downstream images must extend it additively and must not recreate it.- The image reserves these downstream layout paths:
/opt/runtime/opt/project/opt/project/addons/opt/extra_addons
- The image reserves
/opt/launchplane/addonsfor image-owned Launchplane runtime addons. Downstream images must not replace this directory. - Layout 2 is selected by
/opt/runtime/.odoo-python-sync-layoutcontaining2. Each populated dependency root must contain bothpyproject.tomlanduv.lock, plus.odoo-python-source.jsonwith anowner/repositoryidentity, exact lowercase 40-character source commit, and optional repository-relativelock_path. Legacy two-field markers default the lock path touv.lock. /opt/runtimeowns the support/runtime lock./opt/projectowns the tenant uv workspace lock. Both root pyprojects are static dependency catalogs withtool.uv.package = falseand no build system. The helper checks and exports the support lock first and the tenant lock second with frozen semantics, then installs both exports in one dependency-free operation that cannot replace packages inherited from the base image.- Layout 2 tenant workspace members must exactly match owned addon projects
under
/opt/project/addons. Owned projects use static package metadata,tool.uv.package = false, exact build-tool versions supplied by a lock, and code and package-metadata installation with--no-depsand no build isolation. A nested.odoo-python-source.jsonapplies to projects beneath that directory, so staged shared-addon projects retain their own repository and commit attribution instead of inheriting the tenant root marker. Ownedrequirements*.txt, mutable VCS references, and local or archive dependency paths fail closed. - Support-only and tenant-only layout 2 roots are accepted for staging and tests but emit non-publishable evidence. Support-only layouts may still carry ordinary addon code when it has no Python dependency metadata. Publishable artifacts require both lock scopes.
ODOO_PYTHON_SYNC_SKIP_ADDONSremains a legacy-layout compatibility option. Layout 2 rejects it because skipping installation while exporting the full workspace lock would produce misleading dependency evidence.- Without the layout marker,
odoo-python-sync.sh <prod|dev>preserves the bounded legacy single-root behavior and writes non-publishable evidence. The legacy path remains available only while downstream producers migrate. odoo-fetch-addons.shfetches external addon repositories declared inODOO_ADDON_REPOSITORIESinto/opt/extra_addons. Every entry must useowner/repository@<exact-commit>; branch and tag refs fail closed. The helper verifies the fetched Git commit before removing repository metadata and stores authoritative source sidecars beside checkouts rather than trusting marker files committed inside external repositories.- Layout 2 treats external dependency files as an explicit compatibility lane. Their repository commit, repo-relative path, format, and SHA-256 are recorded; they may add compatible packages but cannot replace base- or lock-owned package identities.
- Every layout 2 sync finishes with
uv pip checkand writes deterministic platform-specific package evidence to/opt/launchplane/evidence/dependency-provenance.json. The producer combines one sidecar per target platform before publishing artifact provenance. - VCS package evidence uses
owner/repositoryidentities for GitHub sources and sanitized HTTPS or SSH URLs for other hosts, with any terminal.gitsuffix removed.packages_sha256hashes the exact canonical package array embedded in the evidence. - Neither helper bakes in project-specific policy; downstream images choose
which external repositories to fetch and whether to sync
prodordevdependencies.
- Every run builds test images first and executes smoke checks.
- Publish only happens after smoke checks pass.
schedule(weekly) publishesnightly-*tags and immutablesha-*tags.pushtomainpublishes stable19.0-*tags and immutablesha-*tags.pull_requestruns verify-only (no image publishing) and reports one stableimage-verificationmerge gate after lint, source resolution, override validation, both image builds, smoke tests, and vulnerability scans pass.- Fork pull requests fail closed before using trusted self-hosted runners. A maintainer must recreate an accepted fork change on a branch in this repository before it can pass the image-verification merge gate.
This lets us keep a weekly canary stream while protecting stable tags behind the same verification gate.
- Verify jobs run on the
chris-testing-buildself-hosted lane and reuse a persistent per-runner Buildx builder for single-platform smoke images. Pull request verification uses a per-run apt refresh epoch so Ubuntu package layers are rehydrated for each verification run. - Publish jobs run on the
chris-testing-publish-cacheself-hosted lane and reuse a persistent per-runner Buildx builder, with GHCR registry cache as the portable fallback. - The publish workflow prunes cache entries older than 14 days after each run so local BuildKit state stays warm without growing forever.
This keeps the expensive multi-arch publish path warm on the self-hosted runner while still giving us a recoverable remote cache when a builder is recreated.
The GHCR retention workflow keeps stable and nightly tags, preserves the newest
10 immutable sha-* tags per image suffix, and prunes untagged versions older
than 7 days.
GHCR retention is repo-local package hygiene. This repo owns the package tags it publishes, so the retention workflow may delete only this repo's old package versions; it does not own Launchplane runtime state, tenant deployments, provider targets, product profiles, managed secrets, or artifact evidence.
- A scheduled
Runner Healthworkflow tracks root filesystem and Docker root usage onchris-testingdaily. - The health workflow runs on
chris-testing-publish-cacheso it does not consume thechris-testing-buildverification lane. - The check fails when usage crosses the configured thresholds so operators get a visible GitHub Actions alert before the runner reaches saturation.
Runner health is repo-local image-build lane telemetry. It tracks the self-hosted cache lane used by this image build workflow and does not replace Launchplane-owned runner-host hygiene, runtime inventory, or provider health records.
The workflow resolves the current odoo/odoo 19.0 commit and pins that exact
revision into the build. This gives repeatable artifacts per run and makes
scheduled updates explicit.
uv is copied from Astral's official container image and pinned by tag+digest
in the Dockerfile. A GitHub-native Dependabot config watches that image
reference and opens update PRs whenever a new uv release is available.
docker build \
-t ghcr.io/cbusillo/odoo-docker:19.0-runtime \
--target runtime \
.
docker build \
-t ghcr.io/cbusillo/odoo-docker:19.0-devtools \
--target runtime-devtools \
.bash scripts/test-check-requirements-overrides.shchecks exact pins, explicitly allowed unpinned requirements, non-exact constraints, and removed requirements for the override freshness gate.scripts/test-odoo-bin-wrapper.shchecks wrapper argument handling without a container build.scripts/smoke-runtime.sh <image-reference>checks the runtime image helper contract and Odoo CLI availability.scripts/smoke-devtools.sh <image-reference>checks the devtools image browser tooling and addon path setup.scripts/smoke-db-init.sh <image-reference>checks database-backed Odoo initialization.scripts/test-downstream-helpers.sh <image-reference>checks downstream Python sync and external addon fetch behavior, including legacy compatibility, strict partial and layered locks, stale and incomplete lock failures, owned workspace enforcement, exact external source handling, conflict isolation, final environment checks, and deterministic evidence.
- Do not add credentials or access tokens in this repo.
- Proprietary addons should be fetched by downstream builds using BuildKit
secrets via
odoo-fetch-addons.sh. requirements-overrides.txtcarries reviewed secure or shared downstream compatibility pins when upstream Odoo requirements cannot yet resolve the required release. Keep those pins narrow and letscripts/check-requirements-overrides.pyreject removed upstream requirements, unexpected unpinned or ranged requirements, and upstream exact pins that make an override removable.