Odoo 18 on Docker
1. What? — Definition and context
Section titled “1. What? — Definition and context”Odoo is an open-source ERP (Enterprise Resource Planning) that centralises business data into a single database. This Odoo 18 Community instance runs on Docker, on its own dedicated PostgreSQL, with three custom modules that adapt the ERP to the technical workflow: GitHub sync, Claude Code telemetry, and web experiments for the website.
Modules in use
Section titled “Modules in use”Native modules enabled:
| Module | Usage |
|---|---|
| Project | Projects and tasks |
| Timesheets | account.analytic.line — fed by TimeTrackr |
| Website | Landing page and hub |
| CRM | Leads and conversion (to be extended) |
Custom modules maintained in odoo-stack/addons/:
| Module | Version | Usage |
|---|---|---|
project_github_sync | v18.0.5.6.0 | GitHub ↔ tasks sync + Claude Code telemetry + AI double-triage + estimation |
website_experiments | v18.0.1.1.0 | Interactive snippets (Flip Display, Glass Splat) for the Website Builder |
powered_by_odoo_remove | third-party | Removes the “Powered by Odoo” branding on public pages |
Business vision
Section titled “Business vision”To understand why Odoo was chosen over the alternatives, see Why Odoo.
2. Why? — Stakes and motivations
Section titled “2. Why? — Stakes and motivations”Problems solved
Section titled “Problems solved”| Problem | Odoo solution |
|---|---|
| Scattered data | Unified base: projects, time, contacts |
| No GitHub ↔ task traceability | Two-way sync via project_github_sync |
| Quote estimation by guesswork | x_estimated_hours / x_complexity fields + real actual_hours from timesheets |
| Manual issue triage | AI double-triage (x_ai_project_triaged_at + x_ai_personal_triaged_at) |
| Invisible Claude Code effort | Per-session telemetry (project.task.claude.session) with cost, tokens, LOC, TLDR, category |
| Fuzzy marketing attribution | Source tracking via Website (CV, business card, LinkedIn) |
Why Odoo 18 Community (vs Enterprise)?
Section titled “Why Odoo 18 Community (vs Enterprise)?”- Cost — Enterprise is billed per user/month. Community is free and self-hosted.
- Custom modules unrestricted — No restriction on installing
project_github_syncorwebsite_experimentson Community. - Functional sufficiency — The native Project, Timesheets, Website and CRM modules cover current needs.
- Migration possible — To Enterprise later if advanced modules (eCommerce, Manufacturing) are needed.
3. How? — Technical implementation
Section titled “3. How? — Technical implementation”Network architecture
Section titled “Network architecture”Docker Compose configuration (excerpt)
Section titled “Docker Compose configuration (excerpt)”services: odoo: image: odoo:18 volumes: - odoo-data:/var/lib/odoo - odoo-config:/etc/odoo - ./addons:/mnt/extra-addons environment: - HOST=odoo-postgres - USER=odoo - PASSWORD=${ODOO_DB_PASSWORD} healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8069/web/health"] interval: 30s timeout: 10s retries: 3 networks: [odoo-internal, webproxy]
odoo-postgres: image: postgres:15 volumes: [odoo-db:/var/lib/postgresql/data] environment: - POSTGRES_USER=odoo - POSTGRES_PASSWORD=${ODOO_DB_PASSWORD} - POSTGRES_DB=guig_db networks: [odoo-internal]project_github_sync module (v18.0.5.6.0)
Section titled “project_github_sync module (v18.0.5.6.0)”The module adds many fields to project.task (plus a dedicated model for Claude Code session history). The fields fall into four families.
GitHub fields (8)
Section titled “GitHub fields (8)”| Field | Type | Usage |
|---|---|---|
x_github_sync_enabled | Boolean (on project.project) | Enables sync on the project |
x_github_repo | Char | owner/repo source |
x_github_issue_id | Integer | Issue / PR number |
x_github_url | Char | Canonical GitHub URL |
x_github_commit_ids | One2many → project.task.commit | Linked commits (sha, message, author, url) |
x_github_commit_count | Integer (compute) | Number of linked commits |
x_github_milestone_id | Integer | GitHub milestone number |
x_github_parent_issue_id | Integer | Parent issue (sub-issues) |
Claude Code telemetry fields (13)
Section titled “Claude Code telemetry fields (13)”Fed by the SessionEnd hooks (see Claude Code Telemetry).
| Field | Type | Usage |
|---|---|---|
x_claude_time_total | Float (h) | Aggregated Claude Code time |
x_claude_cost_total | Float ($) | Aggregated cost |
x_claude_token_total | Integer | Total tokens (in + out + cache) |
x_claude_tokens_input / _output / _cache | Integer × 3 | Per-category detail |
x_claude_sessions | Integer | Number of sessions |
x_claude_lines_added / _removed | Integer × 2 | Aggregated LOC |
x_claude_last_session | Datetime | Last recorded session |
x_claude_cost_by_model | Text (JSON) | Breakdown per model (Sonnet/Opus/Haiku) |
x_claude_session_ids | One2many → project.task.claude.session | Session-by-session history |
x_claude_category | Selection | Auto category for generic tasks (no issue) |
Each project.task.claude.session row captures the detail of a session: ID, timestamps, tokens, cost, LOC, TLDR (short summary generated by the hook) and category. This lets us drill from the aggregate down to the source session.
Effort estimation fields (#188)
Section titled “Effort estimation fields (#188)”| Field | Type | Usage |
|---|---|---|
x_estimated_hours | Float | A priori estimate |
x_complexity | Selection | Complexity (low / medium / high) |
The ground truth comes from timesheets: actual_hours = SUM(account.analytic.line.unit_amount) WHERE task_id = X. The x_claude_time_total attribute is not a substitute — it measures Claude machine time, not human effort. See the Effort Estimator V1 workflow for the cohort-based estimation logic.
-- Paired samples (estimate + actual) for re-calibrationSELECT t.id, t.name, t.x_estimated_hours, COALESCE(SUM(a.unit_amount), 0) AS actual_hours, t.x_claude_time_total, t.x_claude_sessionsFROM project_task tLEFT JOIN account_analytic_line a ON a.task_id = t.idWHERE t.x_estimated_hours IS NOT NULLGROUP BY t.id, t.name, t.x_estimated_hours, t.x_claude_time_total, t.x_claude_sessionsHAVING COALESCE(SUM(a.unit_amount), 0) > 0;AI double-triage fields (#296)
Section titled “AI double-triage fields (#296)”| Field | Type | Usage |
|---|---|---|
x_ai_project_triaged_at | Datetime | Cache of last project-side triage (stage) |
x_ai_personal_triaged_at | Datetime | Cache of last personal-horizon triage (Today / This week / …) |
The N8N workflow Daily GitHub Issue Triage proposes stage + horizon assignments via codex-yolo, then stores the timestamp to skip re-triaging for 7 days (configurable TTL). The user validates via Telegram (/triage), which POSTs to an internal API that updates Odoo.
website_experiments module
Section titled “website_experiments module”Adds three drag-and-drop snippets to the Website Builder:
- Flip Display: animated split-flap display (airport-style).
- Glass Splat Desktop: 3 side-by-side panels with a fragmented-glass 3D render.
- Glass Splat Mobile: equivalent mobile carousel.
Asset side: CSS + IIFE JavaScript loaded through web.assets_frontend.
Filestore and ir_attachment
Section titled “Filestore and ir_attachment”The filestore lives in the odoo-data volume, under /var/lib/odoo/filestore/odoo/ (sub-folder named odoo historically, even though the DB is now guig_db). It stores every binary referenced by ir_attachment.store_fname.
Backup
Section titled “Backup”Backups are handled by scripts/backup-databases.sh (daily cron at 03:00) which produces:
pg_dump -U odoo guig_db→ SQL archivetar czf - -C /var/lib/odoo filestore→ filestore archive- Local rotation (7 d) + GDrive push (30 d) via rclone.
# Manual backupdocker exec odoo-postgres pg_dump -U odoo guig_db > odoo_backup.sql
# Restoredocker exec -i odoo-postgres psql -U odoo guig_db < odoo_backup.sql
# Manual filestore backupdocker exec odoo tar czf - -C /var/lib/odoo filestore > odoo-filestore.tar.gzN8N integration via XML-RPC
Section titled “N8N integration via XML-RPC”// Task creation from github-project-sync{ "url": "http://odoo:8069/xmlrpc/2/object", "method": "POST", "body": { "service": "object", "method": "execute_kw", "args": [ "guig_db", // database (NOT "odoo") 2, // uid after authentication "{{ $env.ODOO_PASSWORD }}", "project.task", "create", [{ "name": "Issue #123 — Fix bug", "project_id": 1, "x_github_issue_id": 123, "x_github_url": "https://github.com/owner/repo/issues/123" }] ] }}Module management
Section titled “Module management”# Install a new modulecp -r my_module odoo-stack/addons/docker restart odoo# UI: Apps → Update Apps List → Install
# Update an existing module in placedocker exec odoo odoo -u project_github_sync -d guig_db --stop-after-initTroubleshooting commands
Section titled “Troubleshooting commands”# Health checkcurl http://localhost:8069/web/health
# Odoo logsdocker logs odoo --tail 100
# PostgreSQL connectiondocker exec odoo-postgres pg_isready -U odoodocker exec odoo-postgres psql -U odoo -d guig_db -c "SELECT count(*) FROM project_task;"
# Odoo shell for debugdocker exec -it odoo odoo shell -d guig_db
# Look at recent Claude sessionsdocker exec odoo-postgres psql -U odoo -d guig_db \ -c "SELECT id, name, tldr, category FROM project_task_claude_session ORDER BY id DESC LIMIT 10;"4. What if? — Perspectives and limits
Section titled “4. What if? — Perspectives and limits”Current limits
Section titled “Current limits”| Limit | Impact | Mitigation |
|---|---|---|
| Community without vendor support | No Odoo hotline | Documentation + community |
| Single PostgreSQL instance | No native HA | Daily backups + monitoring |
| Custom modules to maintain | Future Odoo 19+ compatibility | Versioned migrations in migrations/ |
| Effective single user | All telemetry is attributed to a single employee | Multi-user mapping planned via timetrackr_user_mapping if needed |
Evolution scenarios
Section titled “Evolution scenarios”If the estimation pipeline becomes critical:
- Calibrate the V1 → V2 model on paired samples (
x_estimated_hoursvsactual_hours). - Extend telemetry to issue-less tasks (already addressed via
x_claude_category).
If AI triage gets noisy:
- Lower the cache TTL (currently 7 d) to re-triage more often.
- Filter at the workflow level by project or label.
If invoicing is needed:
- Activate the Invoicing module.
- Connect
account.analytic.lineto invoices via tasks/projects.
If migrating to Enterprise:
- Audit custom fields and existing migrations.
- Verify
project_github_synccompatibility (LGPL-3, should be fine). - Budget: per-user/month pricing.
Related pages
Section titled “Related pages”Infrastructure
Section titled “Infrastructure”- VPS Architecture — Overview
- Why Odoo — ERP comparison and choice
- TimeTrackr Stack — Source of
account.analytic.line
Workflows
Section titled “Workflows”- GitHub-Odoo Sync — Issues / PRs / commits sync
- Claude Code Telemetry — SessionStart / SessionEnd hooks
- Cal.com → Odoo bookings — Calendar integration
Reference
Section titled “Reference”- Glossary — ERP, XML-RPC, ir_attachment