Blog
Odoo Docker : du Compose au test de restauration
Par L’équipe Ofleet9 min de lecture
Contenus
Pour lancer Odoo Docker, il faut une image Odoo, PostgreSQL et deux volumes persistants. Pour lui confier votre ERP, nous vous conseillons un test de plus : restaurer une pièce jointe après avoir recréé l'application. C'est le fil de ce tutoriel. Nous partons d'une base Community 20 neuve, sur un hôte Linux avec Docker Engine et Compose v2 ou ultérieur. Pas d'une migration de production déguisée en changement de tag.
Le périmètre est volontairement petit : une base erp, un serveur, Nginx installé sur l'hôte. Les fichiers sont vérifiés le 3 novembre 2026. Enterprise demande en plus ses modules et un abonnement valide ; ils ne sont pas livrés dans l'image Community.
Pour ce tutoriel, nous avons lancé cette pile, créé une pièce jointe, puis restauré la base et le filestore dans des volumes neufs. Le fichier est revenu intact. Nous avons aussi vérifié le websocket derrière Nginx avec un certificat de test. Cela ne simule ni votre charge ni vos modules.
1. Fixer la version d'Odoo Docker avant de lancer#
Utilisez un tag daté, puis conservez le digest réellement téléchargé. Le registre des images officielles (nouvel onglet) liste 20.0-20261102 à cette date. 20.0 et latest bougent : ils ne permettent pas, seuls, de retrouver exactement votre déploiement.
Dans un nouveau dossier de travail, préparez les secrets locaux :
umask 077
mkdir -p config addons backups
chmod 755 addons
printf 'PG_ADMIN_PASSWORD=%s\nDB_PASSWORD=%s\n' \
"$(openssl rand -hex 32)" "$(openssl rand -hex 32)" > .env
printf '.env\nconfig/odoo.conf\nbackups/\n' >> .gitignoreGardez .env hors de Git et limitez l'accès au dossier. Ces variables ne sont pas un coffre-fort : un administrateur Docker peut les lire. Ne réutilisez pas le mot de passe PostgreSQL pour le compte administrateur d'Odoo.
2. Séparer les conteneurs des données#
Compose peut recréer les conteneurs sans supprimer les volumes nommés. C'est ce que nous voulons. Enregistrez ce compose.yaml pour Odoo 20 et PostgreSQL 16 :
services:
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${PG_ADMIN_PASSWORD:?required}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
interval: 5s
timeout: 5s
retries: 12
web:
image: odoo:20.0-20261102
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
HOST: db
PORT: "5432"
USER: odoo
PASSWORD: ${DB_PASSWORD:?required}
ports:
- "127.0.0.1:8069:8069"
- "127.0.0.1:8072:8072"
volumes:
- web-data:/var/lib/odoo
- ./config/odoo.conf:/etc/odoo/odoo.conf:ro
- ./addons:/mnt/extra-addons:ro
volumes:
db-data:
web-data:PostgreSQL n'a aucun port publié. Les ports Odoo ne sont accessibles que sur la boucle locale ; vérifiez-le aussi depuis une autre machine. Le réseau Compose relie les services, mais ne bloque pas leurs connexions sortantes.
PostgreSQL en conteneur peut convenir en production si vous maîtrisez son stockage et sa restauration. Une base externe se justifie aussi pour séparer sa maintenance. Dans les deux cas, le conteneur ne remplace pas la sauvegarde.
Démarrez seulement PostgreSQL, puis ouvrez son terminal :
docker compose up -d --wait db
docker compose exec db psql -U postgres -d postgresExécutez ce SQL et ces commandes psql. À l'invite de \password, saisissez deux fois la valeur DB_PASSWORD de .env :
CREATE ROLE odoo LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE;
\password odoo
CREATE DATABASE erp OWNER odoo;
\qLe rôle Odoo n'est donc pas le superutilisateur créé par l'image PostgreSQL. Il possède ici sa base : ce choix simple ne remplace pas un cloisonnement plus strict des droits de propriétaire.
3. Initialiser la base sans l'ouvrir au public#
La configuration choisit une seule base et désactive sa liste. Créez config/odoo.conf, remplacez le mot de passe maître par une valeur aléatoire distincte et rendez le fichier lisible par l'utilisateur odoo du conteneur, sans ouvrir le dossier aux autres utilisateurs de l'hôte :
[options]
admin_passwd = REMPLACER_PAR_UN_SECRET_ALEATOIRE
db_name = erp
dbfilter = ^erp$
list_db = False
data_dir = /var/lib/odoo
addons_path = /mnt/extra-addons
http_interface = 0.0.0.0
workers = 2
max_cron_threads = 1
proxy_mode = True
gevent_port = 8072Avec un dossier config privé (chmod 700 config), chmod 644 config/odoo.conf rend le fichier monté lisible dans le conteneur sans l'ouvrir aux autres utilisateurs de l'hôte.
Deux workers servent d'exemple, pas de promesse de capacité. Pour la RAM, les crons et les pointes, reprenez le calcul des ressources d'un serveur Odoo. proxy_mode suppose que l'accès normal passe exclusivement par votre proxy.
L'image officielle et son entrypoint (nouvel onglet) lisent HOST, PORT, USER et PASSWORD. N'ajoutez pas un db_password différent dans le fichier : cette valeur prendrait le pas sur la variable attendue. Le data_dir place le filestore de cette base dans /var/lib/odoo/filestore/erp.
docker compose pull
docker image inspect --format '{{index .RepoDigests 0}}' odoo:20.0-20261102 postgres:16
docker compose config --quiet
docker compose run --rm web odoo -d erp -i base \
--without-demo=all --stop-after-init --workers=0 --max-cron-threads=0
docker compose up -d web
docker compose logs --tail=100 webAvant toute exposition publique, passez par un tunnel SSH vers 127.0.0.1:8069. Connectez-vous au compte initial admin, mot de passe admin, puis changez immédiatement les identifiants. Ajoutez une pièce jointe de test identifiable. Elle servira de témoin à la restauration.
4. Brancher le proxy et vérifier le websocket#
Avec des workers, /websocket doit atteindre le port événementiel 8072. Le reste va sur 8069. La documentation de déploiement Odoo 20 (nouvel onglet) décrit ce routage et la terminaison HTTPS.
Pour Nginx sur l'hôte, placez ce fragment dans le contexte http, généralement via sites-enabled. Remplacez le domaine et les chemins par votre certificat déjà installé, puis testez avec nginx -t avant de recharger :
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name erp.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name erp.example.com;
ssl_certificate /etc/letsencrypt/live/erp.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/erp.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 50m;
proxy_read_timeout 720s;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
location ^~ /web/database {
return 403;
}
location /websocket {
proxy_pass http://127.0.0.1:8072;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
location / {
proxy_pass http://127.0.0.1:8069;
}
}Les en-têtes sont répétés dans le bloc websocket parce que Nginx ne reprend pas ceux du parent lorsqu'un bloc redéfinit ses propres proxy_set_header. Les limites de taille et de délai sont des exemples à adapter.
Dans le navigateur, ouvrez l'onglet Réseau et vérifiez une connexion websocket en statut 101. Vérifiez aussi que /web/database/manager renvoie 403. Une page de connexion qui s'affiche ne valide pas ces deux points. Prévoyez le renouvellement du certificat et une alerte d'échec.
5. Ajouter vos modules sans modifier le conteneur à la main#
Montez les modules compatibles Odoo 20 dans addons, chacun avec son __manifest__.py. Un dépôt OCA peut contenir plusieurs modules : ce sont leurs dossiers qui doivent être trouvés dans addons_path. Redémarrez Odoo après le déploiement et installez le module voulu sur une copie de test.
Pour des dépendances Python ou Linux supplémentaires, construisez une image dérivée versionnée. Faire un pip install dans le conteneur en cours perd la modification à sa prochaine recréation. Conservez le commit de chaque dépôt de modules avec le digest de l'image : revenir à l'image précédente sans retrouver le code correspondant ne reconstitue pas votre application.
6. Sauvegarder ensemble la base et le filestore#
Arrêtez tous les processus qui écrivent dans cette base avant les deux copies. Ici, l'unique service web contient aussi les crons. Exécutez ce bloc dans un shell Bash dédié, depuis le dossier Compose :
set -eu
umask 077
backup="backups/$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$backup"
docker compose stop -t 120 web
trap 'docker compose start web' EXIT
docker compose exec -T db pg_dump -U odoo -Fc erp > "$backup/db.dump.part"
docker compose run --rm --no-deps --entrypoint tar web \
-C /var/lib/odoo -czf - filestore/erp > "$backup/filestore.tar.gz.part"
mv "$backup/db.dump.part" "$backup/db.dump"
mv "$backup/filestore.tar.gz.part" "$backup/filestore.tar.gz"
sha256sum "$backup/db.dump" "$backup/filestore.tar.gz" > "$backup/SHA256SUMS"Le shell relance web à sa sortie, même si une copie échoue. Un dossier avec des fichiers .part reste incomplet. Conservez aussi la configuration, les références d'images et le code des modules. Chiffrez la copie hors serveur ; un volume Docker sur le même disque ne couvre pas la perte de l'hôte.

Pour restaurer, utilisez un autre hôte isolé, sans accès aux services externes :
- Reprenez les mêmes fichiers et images. Refaites l'étape 2 pour créer un rôle
odooet une baseerpvides. - Copiez le jeu de sauvegarde et vérifiez ses sommes de contrôle. Ne lancez pas encore
web. - Importez les deux parties avec les commandes ci-dessous ; elles visent uniquement cette nouvelle base vide.
- Avant de démarrer, neutralisez les intégrations sortantes de vos modules. Gardez le réseau de test isolé et les crons désactivés tant que ce contrôle n'est pas terminé.
docker compose exec -T db pg_restore -U odoo -d erp \
--no-owner --exit-on-error < backups/JEU/db.dump
docker compose run --rm --no-deps --entrypoint tar web \
-C /var/lib/odoo -xzf - < backups/JEU/filestore.tar.gzSur la copie, mettez workers = 0 et max_cron_threads = 0, puis démarrez derrière votre accès de test. Retrouvez la pièce jointe témoin et ouvrez-la. Un import SQL réussi avec un document introuvable reste une restauration ratée.
7. Mettre à jour sans confondre correctif et migration#
Un nouveau build de la même version majeure et une montée vers Odoo 20 demandent des opérations différentes.
| Changement | Avant de toucher à la production |
|---|---|
| Nouveau build Odoo 20 | Restaurer une copie, tester modules et rapports, sauvegarder, changer le tag puis recréer web |
| Odoo 19 vers 20 | Migrer les données et porter les modules ; changer l'image ne fait ni l'un ni l'autre |
| PostgreSQL vers une autre version majeure | Préparer une migration PostgreSQL ; ne pas réutiliser aveuglément l'ancien volume |
Pour un correctif validé, docker compose pull web puis docker compose up -d --no-deps web conservent les volumes. N'utilisez pas docker compose down -v sur les données à garder. Si une mise à jour a changé le schéma, revenir au tag précédent ne suffit pas : restaurez le jeu cohérent prévu pour le retour arrière.
Au quotidien, surveillez le disque, les erreurs, les sauvegardes et leurs restaurations. Si vous préférez consacrer ce temps à vos modules, comparez les modes d'hébergement Odoo. Vous pouvez aussi essayer Ofleet pour tester votre code sur une instance séparée, sans monter cette pile vous-même.
Essayez Ofleet gratuitement !
150 € de crédit, sans engagement.
Démarrer mon essaiSans engagement
À lire aussi
odoohébergementOdoo AWS : déployer sur EC2 et préparer la reprise
Odoo AWS sur EC2 : choisissez PostgreSQL local ou RDS, protégez les ports, sauvegardez le filestore avec la base et calculez un budget mensuel daté.5 novembre 20268 min de lecturePar L’équipe OfleetLire l’article
odoohébergementinfogéranceInfogérance Odoo : ce que vous pouvez gérer vous-même
Infogérance Odoo ou exploitation en autonomie : attribuez les tâches, vérifiez vos sauvegardes et préparez une restauration isolée avec des preuves.5 novembre 202610 min de lecturePar L’équipe OfleetLire l’article
odoohébergementSécurité Odoo : vérifier son instance auto-hébergée
Sécurité Odoo : contrôlez le gestionnaire de bases, le reverse proxy, la double authentification et vos sauvegardes avec des vérifications concrètes.2 novembre 20267 min de lecturePar L’équipe OfleetLire l’article