Skip to content

nuobit/docker-odoo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

60 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Odoo 10.0 Docker Image

This repository contains a custom Odoo 10.0 Docker image with a robust bootstrap/entrypoint system designed for long-running environments and legacy Odoo installations.

The image is intended for users who need:

  • Odoo 10 (Python 2.7, Node 6)
  • Git-aggregated source code
  • Repeatable container bootstrapping
  • Explicit control over upgrades and initialization

Image

ghcr.io/<org>/odoo-10.0

Note: Replace <org> with your organization name (e.g., nuobit, mycompany, etc.)

Available tags

  • 1.0.0, 1.0.1, etc. — immutable release versions
  • edge — latest build from main branch (moving tag, may break)

Recommended: always pin to a specific numbered tag in production (e.g., 1.0.0).

Tag strategy:

  • Numbered versions (1.0.0): Stable, tested releases that never change
  • edge: Bleeding edge, rebuilt on every commit - use only for testing

Base characteristics

  • Base OS: Debian Stretch (EOL, using archive.debian.org)
  • Python: 2.7
  • Node.js: 6.x
  • wkhtmltopdf: 0.12.1.4 (static Debian package)
  • User: non-root (odoo, uid 99910)
  • Healthcheck: HTTP check on /web/database/selector every 120s

This image is legacy by design and intended for environments that must keep Odoo 10 running.


User ID management

The container uses a fixed UID 99910 for the odoo user, deliberately hardcoded in both the Dockerfile and docker-compose configuration.

Why we force a specific UID

Instead of letting Docker auto-assign UIDs, we explicitly control the user ID to ensure:

  • Predictable ownership: Files created by the container always have the same UID across all environments
  • Dockerfile-compose consistency: The UID in the image matches the UID specified in user: directive
  • Manageable permissions: Administrators know exactly which UID to chown on host directories

Why 99910?

  • Arbitrary but intentional: We chose a high, arbitrary UID outside typical ranges
  • High UID range (90000+): Avoids collision with:
    • System users (typically < 1000)
    • Regular host users (typically 1000-60000)
    • Service users (typically 60000-90000)
  • Version indicator: The 10 suffix represents Odoo version 10, making it identifiable
  • Consistent across deployments: Same UID in development, staging, and production

The problem we solve

Without a fixed UID, Docker might assign UID 1000 to the container user. If your host user also has UID 1000:

  • Files created by the container appear to belong to your host user
  • Your host user can accidentally modify container-owned files
  • Permissions become confusing and unpredictable
  • Different environments might get different UIDs, breaking reproducibility

With a fixed high UID (99910), there's no collision, and ownership is always clear.

How to use

You do NOT need to create this user on the host. Linux uses numeric UIDs, not usernames. The container's odoo user (UID 99910) can access files owned by UID 99910 on the host, regardless of username.

Set ownership on host bind-mounted directories:

# Example for a container (could be odoo10-1, odoo10-customer1, etc.)
sudo chown -R 99910:99910 /srv/docker/data/odoo10-1

Restrict permissions for security:

sudo chmod -R o-rwx /srv/docker/data/odoo10-1

Note: Both the container name (odoo10-1) and host path (/srv/docker/data/) are arbitrary examples. You can use any naming convention and path structure that suits your environment:

  • Container: odoo10-customer1, odoo10-prod, etc.
  • Host path: /srv/docker/data/, /opt/containers/, /home/user/docker/, etc.

Running multiple containers

You can run multiple Odoo 10 containers on the same host (e.g., odoo10-1, odoo10-2, odoo10-customer1), all using the same UID 99910.

This is NOT a problem as long as:

  • ✅ Each container has its own separate bind-mounted directories
  • ✅ Directories are named clearly to match the container (e.g., /srv/docker/data/odoo10-1, /srv/docker/data/odoo10-2)

What to avoid:

  • Never share the same data directory between multiple containers
  • ❌ Don't bind /srv/docker/data/odoo-shared to both odoo10-1 and odoo10-2

Why this works:

  • Linux permissions are per-file, not per-user
  • Multiple containers with UID 99910 can coexist peacefully
  • Each container only accesses its own mounted directories
  • File conflicts are impossible when directories are separate

Example multi-container setup:

# Container 1
sudo chown -R 99910:99910 /srv/docker/data/odoo10-1
sudo chmod -R o-rwx /srv/docker/data/odoo10-1

# Container 2 (different directory, same UID - no problem!)
sudo chown -R 99910:99910 /srv/docker/data/odoo10-2
sudo chmod -R o-rwx /srv/docker/data/odoo10-2

This ensures:

  • Container can read/write bind-mounted volumes
  • Files have consistent ownership across environments
  • No confusion with existing host users
  • Other users cannot access sensitive data
  • Multiple containers coexist without permission conflicts

Runtime layout

Container internal paths:

Container Path Repository File Purpose
/opt/odoo/scripts/entrypoint.sh scripts/entrypoint.sh Container entrypoint
/opt/odoo/scripts/lib/common.sh scripts/lib/common.sh Shared functions and variables
/opt/odoo/scripts/bin/updatemodules scripts/bin/updatemodules.sh Update modules script
/opt/odoo/scripts/bin/shell scripts/bin/shell.sh Interactive shell script
/opt/odoo/scripts/bin/fetchbasereqs scripts/bin/fetchbasereqs.sh Fetch base requirements script
/opt/odoo/scripts/bin/fetchreqs scripts/bin/fetchreqs.sh Fetch repo requirements script
/opt/odoo/scripts/bin/fetchcode scripts/bin/fetchcode.sh Fetch git repositories script
/opt/odoo/scripts/bin/genaddonspath scripts/bin/genaddonspath.py Generate addons_path from repos.yaml
/opt/odoo/scripts/bin/db scripts/bin/db.sh Database management script
/opt/odoo/scripts/bin/snapshot scripts/bin/snapshot.sh Snapshot create/restore script
/opt/odoo/dist/defaults.env config/defaults.env Image default configuration
/opt/odoo/dist/constraints.txt config/constraints.txt Python package version constraints
/opt/odoo/scripts/assets/pfbfer.zip scripts/assets/pfbfer.zip ReportLab Type1 fonts archive

Runtime directories (created at runtime or via volume mounts):

Path Purpose
/opt/odoo/snapshots/ Snapshot storage directory (bind-mounted)
/opt/odoo/config/ Instance configuration directory (bind-mounted)
/opt/odoo/config/odoo.conf Odoo configuration file — at minimum set db_host, db_user, db_password and admin_passwd
/opt/odoo/config/repos.yaml Git-aggregator config — contains all OCA repos with a 10.0 branch and actual Odoo modules (see Localization repos)
/opt/odoo/config/settings.env Instance-level overrides (optional)
/opt/odoo/src Odoo source code (git-aggregated, typically volume-mounted)
/var/lib/odoo Odoo data directory (filestore, sessions, typically volume-mounted)

Note: Paths shown are inside the container. Use volume mounts in docker-compose to map host directories to container paths.


EntryPoint behavior

The container uses a stateful bootstrap mechanism:

  • On first container start:
    • fetches base Python requirements
    • fetches git repositories using git-aggregator
    • generates addons_path from repos.yaml and updates odoo.conf
    • fetches Odoo Python requirements
    • installs ReportLab Type1 fonts
  • On subsequent restarts:
    • skips bootstrap
    • runs Odoo directly

Bootstrap state is stored in:

/var/lib/odoo/.bootstrap/

Available scripts

These scripts can be executed inside the running container:

Script Description Usage
db Database management docker compose exec <container> db <command> [args...]
snapshot Snapshot create/restore docker compose exec <container> snapshot <command> [args...]
updatemodules Update modules docker compose exec <container> updatemodules <database> <all|module_list|changed>
shell Interactive Odoo shell docker compose exec <container> shell <database>
fetchbasereqs Fetch base requirements docker compose exec <container> fetchbasereqs
fetchreqs Fetch repo requirements docker compose exec <container> fetchreqs <repo_path>
fetchcode Fetch git repositories docker compose exec <container> fetchcode [addon_path] [jobs]

Database management with db

The db script provides database management commands:

# Create a database (with unaccent extension):
docker compose exec <container> db create <database>

# Initialize Odoo base module in database:
docker compose exec <container> db init <database>
docker compose exec <container> db init <database> demo   # with demo data

# Import a SQL dump (reads from stdin, use -T flag):
bzcat dump.sql.bz2    | docker compose exec -T <container> db import <database>
zcat dump.sql.gz      | docker compose exec -T <container> db import <database>
unzip -p dump.sql.zip | docker compose exec -T <container> db import <database>
7z x -so dump.sql.7z  | docker compose exec -T <container> db import <database>
cat dump.sql          | docker compose exec -T <container> db import <database>

# Drop a database:
docker compose exec <container> db drop <database>

# Reset (drop and recreate) a database:
docker compose exec <container> db reset <database>

# Block/unblock connections (block also terminates existing connections):
docker compose exec <container> db block all <database>      # block everyone (including superusers)
docker compose exec <container> db block users <database>    # block regular users only
docker compose exec <container> db unblock all <database>    # unblock everyone
docker compose exec <container> db unblock users <database>  # unblock regular users

# Terminate all active connections:
docker compose exec <container> db terminate <database>

# List databases owned by DB_OWNER:
docker compose exec <container> db list

# List PostgreSQL users:
docker compose exec <container> db users

# Create/drop the DB owner user:
docker compose exec <container> db createuser
docker compose exec <container> db dropuser

Automatic connection termination on drop/reset

The drop and reset commands automatically handle active connections before dropping a database:

  1. Block all new connections — sets datallowconn = false on the database, preventing anyone (including superusers) from opening new connections
  2. Terminate existing connections — kills all active backends connected to the database
  3. Drop the database

This eliminates the race condition where new connections could sneak in between termination and the actual drop. You do not need to manually stop Odoo or disconnect clients — the script handles it for you.

Importing a SQL dump

The import command reads a SQL dump from stdin and pipes it to psql. Decompress on the host side and pipe through docker compose exec -T:

# bzip2
bzcat dump.sql.bz2 | docker compose exec -T <container> db import <database>

# gzip
zcat dump.sql.gz | docker compose exec -T <container> db import <database>

# zip
unzip -p dump.sql.zip | docker compose exec -T <container> db import <database>

# 7z
7z x -so dump.sql.7z | docker compose exec -T <container> db import <database>

# plain SQL
cat dump.sql | docker compose exec -T <container> db import <database>

Important: Always use the -T flag with docker compose exec when piping stdin — without it, Docker allocates a TTY which corrupts the data stream.

The database must already exist (use db create first). During import, user connections are blocked and existing connections are terminated to prevent interference.

Connection control

The block and unblock subcommands control who can connect to a database:

Command SQL effect Who's blocked
db block all <dbname> datallowconn = false + terminate Everyone (including superusers)
db block users <dbname> CONNECTION LIMIT 0 + terminate Regular users only (superusers can still connect)
db unblock all <dbname> datallowconn = true Nobody
db unblock users <dbname> CONNECTION LIMIT -1 Nobody (unlimited)
db terminate <dbname> Terminates all active backends N/A (kills existing connections only)

Both block commands also terminate all existing connections after setting the limit, ensuring no stale sessions remain.

Use block users / unblock users when you need to prevent regular users from connecting while keeping superuser access for maintenance. Use all for a complete lockout (e.g., before dropping a database). Use terminate when you just need to kill active connections without changing connection limits.

Internally, these dispatch to two low-level helpers that can also be called directly in scripts:

Helper Purpose
do_set_datallowconn <dbname> <true|false> Controls whether any connections are allowed
do_set_user_connection_limit <dbname> <limit> Sets non-superuser connection limit (-1 = unlimited, 0 = blocked, N = max N)
do_terminate_all_connections <dbname> Kills all active backends on the database

do_terminate_all_connections excludes its own psql session (pg_backend_pid()) to avoid self-termination — this matters when the target database name matches DB_PGDB (e.g., both are postgres).

Configuration

Connection settings are read from odoo.conf (db_host, db_user, db_password). Admin credentials come from defaults.env (or overridden in settings.env):

Variable Source Description
DB_HOST db_host in odoo.conf PostgreSQL host
DB_PGUSER defaults.env PostgreSQL admin user
DB_PGDB defaults.env PostgreSQL maintenance database
DB_PGUSER_PASSWORD settings.env PostgreSQL admin password (prompted if unset)
CONFIRM_LEVEL defaults.env Confirmation level: all, deletions, none

Options

Options go after the subcommand: db drop -f mydb.

Short Long Description Applies to
-f --force Skip confirmation prompts (sets CONFIRM_LEVEL=none) drop, reset

Interactive shell access:

docker compose exec -it <container> bash

Running Python scripts via stdin: When piping a script to shell, use the -T flag:

docker compose exec -T <container> shell <database> < myscript.py

Snapshot management with snapshot

The snapshot script provides named create and restore of a complete Odoo environment state: database (schema + data) and filestore (attachments, images, reports). It is designed for development workflows like "save state before testing something destructive" and for creating portable copies of an environment.

Note: The existing db import command (SQL-only, stdin-based) remains unchanged and serves a different purpose — importing external SQL dumps. snapshot is for local named create/restore workflows with full environment state (DB + filestore).

# Save current state before a risky operation
docker compose exec <container> snapshot create mydb before-migration

# Save with a note
docker compose exec <container> snapshot create -n "before upgrading account module" mydb before-migration

# Save database only (skip filestore)
docker compose exec <container> snapshot create --no-filestore mydb quick-save

# List available snapshots
docker compose exec <container> snapshot list

# Restore to the original database (dbname from metadata)
docker compose exec <container> snapshot restore before-migration

# Restore to a different database name
docker compose exec <container> snapshot restore before-migration mydb-test

# Delete a snapshot
docker compose exec <container> snapshot remove before-migration

# Skip confirmation prompts
docker compose exec <container> snapshot restore -f before-migration mydb
docker compose exec <container> snapshot remove -f old-snapshot

# Verbose output (show pg_dump/pg_restore/rsync progress)
docker compose exec <container> snapshot create -v mydb before-migration
docker compose exec <container> snapshot restore -v before-migration

Commands

Command Description
create [-v|--verbose] [-j|--jobs N] [-n|--note "text"] [--no-filestore] <dbname> <snapshot-name> Create a snapshot of DB + filestore
restore [-f|--force] [-v|--verbose] [-j|--jobs N] <snapshot-name> [dbname] Restore a named snapshot into a database
list List available snapshots with metadata
remove [-f|--force] <snapshot-name> Delete a snapshot

Argument order follows the Unix cp/rsync convention: source first, destination second. On restore, dbname is optional — if omitted, it defaults to the database name recorded at backup time.

Options

Options go after the subcommand: snapshot create -v mydb snap-name.

Short Long Description Applies to
-f --force Skip confirmation prompts (sets CONFIRM_LEVEL=none) restore, remove
-v --verbose Show detailed output from pg_dump, pg_restore, and rsync (default: errors only) create, restore
-j --jobs Number of parallel workers for pg_dump/pg_restore (overrides SNAPSHOT_JOBS) create, restore
-n --note Optional description stored in metadata create
--no-filestore Skip filestore (database only) create

Storage

Snapshots are stored in a dedicated bind-mounted directory (default: /opt/odoo/snapshots). Each snapshot is a directory containing a metadata.json, a db/ subdirectory (PostgreSQL directory-format dump), and optionally a filestore/ subdirectory.

The snapshot directory is a sibling of data/ on the host, allowing each to live on a different volume, disk, or partition — e.g., data/ on a fast SSD, snapshots/ on a larger HDD.

Configuration

Variable Default Description
SNAPSHOT_DIR /opt/odoo/snapshots Snapshot storage directory (bind-mounted)
SNAPSHOT_JOBS 2 Parallel workers for pg_dump/pg_restore

Both can be overridden in settings.env.

For design rationale and directory format details, see docs/snapshot.md.


Deploying an instance

This repository is for building the Docker image, not for running containers directly. To deploy an instance, copy the template files from deploy/ to your instance directory and customize them.

1. Copy template files

# Create the instance directory and copy all deploy files
cp -r deploy/* /srv/docker/stack/odoo10-1/

# Create data directories
mkdir -p /srv/docker/data/odoo10-1/{src,data,snapshots}

# Set ownership
sudo chown -R 99910:99910 /srv/docker/stack/odoo10-1/config
sudo chown -R 99910:99910 /srv/docker/data/odoo10-1

Important — config directory ownership: The config/ directory (and its contents) must be owned by UID 99910 on the host. During bootstrap, the genaddonspath script writes the generated addons_path directly into odoo.conf. If the container cannot write to this file, bootstrap will fail and Odoo will start without the correct addons path.

# Required: ensure the container can write to config files
sudo chown -R 99910:99910 /srv/docker/stack/odoo10-1/config

2. Customize configuration

Edit the copied files for your instance:

  • config/odoo.conf — at minimum set db_host, db_user, db_password and admin_passwd
  • config/repos.yaml — enable/disable OCA repos as needed
  • config/settings.env — set DB_PGUSER_PASSWORD and any overrides
  • docker-compose.yml — adjust image tag, ports, network name

3. Start the container

cd /srv/docker/stack/odoo10-1
docker compose up -d

Directory structure convention

The deploy/ folder mirrors the target instance layout:

deploy/                              /srv/docker/stack/odoo10-1/
├── docker-compose.yml         →     ├── docker-compose.yml
└── config/                    →     └── config/  (bind-mounted to /opt/odoo/config/)
    ├── odoo.conf                        ├── odoo.conf
    ├── repos.yaml                       ├── repos.yaml
    └── settings.env                     └── settings.env

Runtime data is stored separately:

/srv/docker/data/odoo10-1/
├── src/         # Source code (git-aggregated)
├── data/        # Odoo filestore, sessions, etc.
└── snapshots/   # Named snapshots (database + filestore)
  • /srv/docker/stack/<container>/Configuration (version-controlled, backed up separately)
  • /srv/docker/data/<container>/Runtime data (large, requires regular backups)

Note: The /srv/docker/ base path and the folder names are arbitrary conventions—use any directory structure that fits your organization's standards.

Example docker-compose configuration

services:
  odoo10-1:
    container_name: odoo10-1
    image: ghcr.io/<org>/odoo-10.0:1.0.0
    user: "99910:99910"
    ports:
      - "127.0.0.1:8010:8069"
    volumes:
      - /srv/docker/stack/odoo10-1/config:/opt/odoo/config
      - /srv/docker/data/odoo10-1/src:/opt/odoo/src
      - /srv/docker/data/odoo10-1/data:/var/lib/odoo
      - /srv/docker/data/odoo10-1/snapshots:/opt/odoo/snapshots
    restart: unless-stopped
    mem_limit: 4g
    networks: [pg96-1-net]

networks:
  pg96-1-net:
    external: true

Environment variables

Image defaults (config/defaults.env)

Baked into the image at /opt/odoo/dist/defaults.env (from config/defaults.env in the repo). Can be overridden by instance settings.env.

Variable Default Description
PYTHON_BIN /usr/bin/python Python interpreter
DIST_DIR (auto) Image dist directory (/opt/odoo/dist/)
DIST_CONSTRAINTS ${DIST_DIR}/constraints.txt pip constraints file
INSTANCE_DIR ${HOME}/config Instance config directory (bind-mounted)
INSTANCE_SETTINGS ${INSTANCE_DIR}/settings.env Instance overrides file
INSTANCE_REPOS ${INSTANCE_DIR}/repos.yaml Git-aggregator config
SRC_DIR ${HOME}/src Source code directory
SRC_ODOO_REPO_DIR odoo Odoo repo directory name (excluded from generated addons_path)
ODOO_DIR ${SRC_DIR}/odoo Odoo source directory
ODOO_BIN ${ODOO_DIR}/odoo-bin Odoo executable
ODOO_CONF ${INSTANCE_DIR}/odoo.conf Odoo config file
ODOO_DATA_DIR /var/lib/odoo Odoo data directory
DB_PGUSER postgres PostgreSQL admin user
DB_PGDB postgres PostgreSQL maintenance database
CONFIRM_LEVEL all Confirmation level: all, deletions, none
SNAPSHOT_DIR /opt/odoo/snapshots Snapshot storage directory (bind-mounted)
SNAPSHOT_JOBS 2 Parallel workers for pg_dump/pg_restore

Instance overrides (settings.env)

Bind-mounted at /opt/odoo/config/settings.env. Sourced after defaults.env, overrides any value. Changes take effect on next script execution or container restart — no image rebuild or container recreation needed.

A reference template is available in the deploy/ directory of the project repository.

Variable Description
DB_PGUSER_PASSWORD PostgreSQL admin password (prompted if unset)
CONFIRM_LEVEL Confirmation level: all, deletions, none

Building locally

docker build -t odoo-10.0:local .

Building and publishing to registry

Note: Replace <org> with your organization name in all commands below.

Build and push edge version

# Build with edge tag
docker build -t ghcr.io/<org>/odoo-10.0:edge .

# Push to registry
docker push ghcr.io/<org>/odoo-10.0:edge

Build and tag a release version

# Build with version tag (always 3 numbers: MAJOR.MINOR.PATCH)
docker build -t ghcr.io/<org>/odoo-10.0:1.0.0 .

# Push the tag
docker push ghcr.io/<org>/odoo-10.0:1.0.0

Best practices for tagging

  1. Always use semantic versioning with 3 numbers: MAJOR.MINOR.PATCH

    • Example: 1.0.0, 1.2.5, 2.0.0
    • MAJOR (1st number): Breaking changes, incompatible updates
    • MINOR (2nd number): New features, backwards compatible
    • PATCH (3rd number): Bug fixes only, no new features
    • Never use fewer than 3 numbers - always use the format X.Y.Z
  2. Production recommendations:

    • ✅ Always pin to full 3-number version: ghcr.io/<org>/odoo-10.0:1.0.0
    • ❌ Never use edge in production
    • ❌ Never use short versions like 1.0 or 1
  3. Test before tagging:

    • Build and test locally first
    • Only push to registry after validation
    • Tag releases from tested commits only

Localization repos (l10n)

The repos.yaml file includes all OCA addon repositories that have a 10.0 branch with actual Odoo modules. Localization repos are commented out by default to avoid fetching unnecessary country-specific code.

To enable a localization, edit your instance repos.yaml and uncomment the relevant l10n-* block. For example, to enable Spanish localization:

./oca/l10n-spain:
    remotes:
        oca: https://github.com/OCA/l10n-spain.git
    target:
        oca 10.0
    merges:
        - oca 10.0

After uncommenting, re-run fetchcode to clone the newly enabled repos.


OpenUpgrade (database migration)

The repos.yaml file includes a commented-out entry for OCA/OpenUpgrade. OpenUpgrade is a patched fork of Odoo that adds migration scripts for upgrading a database from a previous Odoo version (e.g. 9.0 to 10.0).

When to enable it:

  • You are migrating an existing database from Odoo 9.0 (or earlier) to 10.0
  • You need the OpenUpgrade migration scripts to transform data and schema

How to use:

  1. Uncomment the ./openupgrade block in repos.yaml
  2. Run fetchcode to clone the OpenUpgrade repo
  3. Run the migration following the OpenUpgrade documentation
  4. After a successful migration, comment it back out and restart normally with the standard Odoo/OCB source

Do not leave OpenUpgrade enabled in normal operation — it is only needed during the migration process itself.


Notes & limitations

  • Debian Stretch and Python 2.7 are EOL
  • TLS / CA issues may occur in restricted networks
  • Internet access is required at first bootstrap (pip, fonts)
  • This image is not suitable for new Odoo deployments

License & disclaimer

This image is provided as-is for legacy compatibility. Odoo is a trademark of Odoo S.A.

Use at your own risk.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors