1. Introducción
Odoo es una suite de gestión empresarial de código abierto: CRM, facturación, inventario, ventas, proyectos y decenas de módulos más, todos integrados sobre la misma base de datos. Esta guía lo levanta con Docker Compose, con PostgreSQL como base de datos y un proxy inverso con HTTPS propio delante, siguiendo el mismo patrón ya probado en otras prácticas de este canal.
localhost). Por eso esta guía monta HTTPS desde el principio, con un proxy inverso y un certificado autofirmado, en vez de dejarlo para más adelante.2. Variables de esta guía
Todos los bloques de configuración usan los siguientes marcadores en MAYÚSCULAS. Sustitúyelos por los valores reales del entorno donde se aplique esta guía.
| Marcador | Qué es | Dónde se obtiene |
|---|---|---|
IP_HOST_DOCKER | IP del host donde corre el stack con Docker | La interfaz de red de ese host |
POSTGRES_PASSWORD | Contraseña del usuario odoo en la base de datos | Se define en /docker/odoo/.env |
MASTER_PASSWORD_ODOO | Contraseña maestra de Odoo: protege la creación y el borrado de bases de datos completas | La eliges tú; se escribe en odoo.conf (sección 6) |
8443 u 8444), comprueba que el puerto 8445 que usa esta guía está libre, o cámbialo por otro en las secciones 7, 8 y 11.3. Arquitectura y flujo
Tres piezas: la base de datos, el propio Odoo, y un proxy inverso que sirve HTTPS delante de los dos.
[Navegador, en cualquier equipo de la red]
|
| https://IP_HOST_DOCKER:8445 (certificado autofirmado)
v
[proxy] :8445 (Nginx, proxy inverso HTTPS)
|
v
[odoo] :8069 (ERP, sin exponer directamente al host)
|
v
[db] (PostgreSQL)
4. Requisitos
- Un host Linux con Docker Engine y el plugin Docker Compose (
docker compose, sin guion) ya instalados. - No hace falta salida a Internet desde ese host para el funcionamiento básico: todo el flujo vive dentro de la red local.
- Un navegador cualquiera en algún equipo de la misma red.
- Al menos 2 GB de RAM libres para el contenedor de Odoo; con menos, el arranque puede ser muy lento o fallar bajo carga.
5. Levantar PostgreSQL y Odoo con Docker
Misma organización de siempre: cada stack en su carpeta dentro de /docker, credenciales en un .env aparte.
sudo mkdir -p /docker/odoo && cd /docker/odoo sudo mkdir -p addons
sudo nano .env
POSTGRES_PASSWORD=odoo_pwd
Y ahora el docker-compose.yml:
sudo nano docker-compose.yml
services:
db:
image: postgres:16-alpine
container_name: odoo-db
environment:
POSTGRES_USER: odoo
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: postgres
volumes:
- odoo-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U odoo -d postgres"]
interval: 5s
timeout: 5s
retries: 20
start_period: 20s
restart: unless-stopped
odoo:
image: odoo:19.0
container_name: odoo
environment:
HOST: db
USER: odoo
PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- odoo-web-data:/var/lib/odoo
- ./addons:/mnt/extra-addons
- ./odoo.conf:/etc/odoo/odoo.conf:ro
depends_on:
db:
condition: service_healthy
restart: unless-stopped
proxy:
image: nginx:alpine
container_name: proxy
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./proxy-selfsigned.crt:/etc/nginx/certs/proxy-selfsigned.crt:ro
- ./proxy-selfsigned.key:/etc/nginx/certs/proxy-selfsigned.key:ro
ports:
- "8445:8445"
depends_on:
- odoo
restart: unless-stopped
volumes:
odoo-db-data:
odoo-web-data:
odoo-db-data) y el de Odoo (odoo-web-data) van en volúmenes con nombre, no en carpetas del host: el proceso de Odoo dentro del contenedor corre con un usuario concreto (UID 101), y una carpeta del host montada directamente tendría el propietario equivocado, provocando errores de permisos al escribir datos. Con volúmenes con nombre, Docker gestiona esto sin que haya que tocar nada../addons sí es una carpeta normal del host: ahí es donde se colocarían módulos personalizados más adelante. Al ser de solo lectura para Odoo, no tiene el mismo problema de permisos.6. Configurar Odoo (odoo.conf)
sudo nano odoo.conf
[options] addons_path = /mnt/extra-addons admin_passwd = MASTER_PASSWORD_ODOO db_host = db db_port = 5432 db_user = odoo db_password = odoo_pwd proxy_mode = True
db_password aquí debe ser literal, igual que POSTGRES_PASSWORD del .env del paso 5: este archivo lo lee Odoo directamente dentro del contenedor, no pasa por la sustitución de variables de Docker Compose, así que no vale poner ${POSTGRES_PASSWORD} aquí.admin_passwd a un valor propio, la instalación queda con la contraseña maestra por defecto, ampliamente conocida. Cualquiera que la sepa y llegue al gestor de bases de datos puede crear o borrar bases de datos completas. Pon aquí una contraseña larga y única, distinta de la de la base de datos. proxy_mode = True tampoco es opcional: le dice a Odoo que confíe en las cabeceras que le pasa el proxy inverso (IP real del visitante, esquema HTTPS), en vez de las del propio proxy.7. Configurar el proxy inverso con HTTPS propio
Genera un certificado autofirmado con clave EC, válido para la IP del host:
cd /docker/odoo sudo openssl ecparam -name prime256v1 -genkey -noout -out proxy-selfsigned.key sudo openssl req -new -x509 -key proxy-selfsigned.key -out proxy-selfsigned.crt -days 825 \ -subj "/CN=IP_HOST_DOCKER" \ -addext "subjectAltName=IP:IP_HOST_DOCKER"
Ahora la configuración de Nginx:
sudo nano nginx.conf
resolver 127.0.0.11 valid=10s;
server {
listen 8445 ssl;
server_name IP_HOST_DOCKER;
ssl_certificate /etc/nginx/certs/proxy-selfsigned.crt;
ssl_certificate_key /etc/nginx/certs/proxy-selfsigned.key;
location / {
set $upstream_odoo http://odoo:8069;
proxy_pass $upstream_odoo;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
resolver 127.0.0.11 (el DNS interno de Docker) junto con set $upstream_odoo; proxy_pass $upstream_odoo; es imprescindible: sin la variable, Nginx intenta resolver odoo una sola vez al arrancar, y si en ese instante el contenedor de Odoo (que tarda más en inicializar que Nginx) todavía no está listo, Nginx se niega a arrancar con un error de host not found in upstream.8. Arrancar el stack y verificar
cd /docker/odoo docker compose up -d docker compose ps
Sigue el log de Odoo, que tarda algo más que otros servicios en estar listo la primera vez (crea el esquema base de datos vacío):
docker logs -f odoo
Busca una línea similar a HTTP service (werkzeug) running, sin errores de conexión a la base de datos por el camino. Ctrl+C para salir del log.
9. Crear la primera base de datos
A diferencia de otras prácticas de este canal, Odoo no se configura por línea de comandos: la primera base de datos se crea desde un asistente web. Entra en:
https://IP_HOST_DOCKER:8445
Rellena el formulario:
- Master Password: el mismo valor que pusiste en
admin_passwd(paso 6) - Nombre de la base de datos: el que quieras, por ejemplo
clockwork - Email y contraseña: serán las credenciales del usuario administrador de esa empresa dentro de Odoo
- Cargar datos de demostración: actívalo si es solo para probar y grabar; desactívalo para una instalación real de cliente
10. Cerrar el gestor de bases de datos
Con la base de datos ya creada, cierra el acceso público al gestor de bases de datos (la pantalla que permite crear, duplicar o borrar bases de datos enteras), para que solo sea accesible desde la propia configuración del servidor:
sudo nano /docker/odoo/odoo.conf # Añade esta línea al final: # list_db = False
cd /docker/odoo docker compose up -d --force-recreate odoo
list_db = False, la ruta del gestor de bases de datos deja de estar accesible desde fuera, aunque alguien conozca o adivine la contraseña maestra. Es el mismo tipo de cierre que ya hicisteis con el registro público de Vaultwarden: crear el recurso primero, cerrar el acceso público justo después.12. Tabla de puertos y componentes
| Puerto | Componente | Sentido de la conexión | Para qué sirve |
|---|---|---|---|
| 8445/tcp | proxy (Docker, Nginx) | Navegador → contenedor | Proxy inverso HTTPS (certificado autofirmado) |
| — | odoo (Docker, interno) | proxy → odoo | Servidor de aplicación de Odoo (Werkzeug); no publica ningún puerto al host |
| — | db (Docker, interno) | odoo → db | PostgreSQL; no necesita exponerse al host |
13. Errores comunes
| Problema | Causa probable | Solución |
|---|---|---|
El contenedor odoo no arranca, error de conexión a la base de datos |
db_password en odoo.conf no coincide con POSTGRES_PASSWORD del .env |
Revisar que ambos valores son literalmente iguales (sección 6) |
El contenedor proxy se reinicia en bucle con host not found in upstream "odoo" |
Nginx intentó resolver el nombre del contenedor una sola vez al arrancar y Odoo todavía no estaba listo | Confirmar que nginx.conf usa resolver 127.0.0.11 junto con la variable $upstream_odoo (sección 7), no un proxy_pass directo |
El navegador o curl -k dan un error de protocolo TLS raro (no el aviso normal de certificado no confiable) |
Puede ser un problema del propio sistema, no del certificado | Aislarlo con openssl s_server -key proxy-selfsigned.key -cert proxy-selfsigned.crt -accept 9999 -www fuera de Docker; si eso también falla, el problema es del sistema |
| El asistente de creación de base de datos no aparece, sale un error o una pantalla en blanco | Odoo aún no ha terminado de arrancar del todo | Esperar y revisar docker logs odoo hasta ver HTTP service (werkzeug) running |
Tras poner list_db = False, ya no se puede crear una segunda base de datos |
Comportamiento esperado: esa es justo la protección que añade | Para crear otra base de datos, poner temporalmente list_db = True, recrear el contenedor, crear la base de datos, y volver a cerrarlo |
14. Notas de ampliación
Este despliegue cubre una instalación funcional de un solo trabajador (proceso), suficiente para pruebas y demos. Para un uso en producción con varios usuarios simultáneos, conviene ampliar con:
- Múltiples workers (
workers = Nenodoo.conf), para que Odoo atienda varias peticiones a la vez en vez de una sola; requiere también configurar un puerto adicional de longpolling para el chat y las notificaciones en tiempo real. - Dominio propio + Let's Encrypt en vez de certificado autofirmado, si el servicio va a ser accesible desde fuera de la red local.
- Importar el certificado en el almacén de confianza de cada equipo cliente, para que el aviso de "certificado no confiable" desaparezca de forma permanente.
- Copias de seguridad del volumen de PostgreSQL y del volumen
odoo-web-data(contiene los archivos adjuntos y documentos subidos, no solo la base de datos). - Módulos personalizados colocados en
./addons, para extender Odoo con funcionalidad propia del cliente.