Skip to main content
Technical Guide

Odoo Docker Deployment: Production Guide

Deploy Odoo with Docker Compose for production environments. Complete setup with PostgreSQL, Nginx reverse proxy, SSL certificates, automated backups, and security best practices.

25-30 min read
Updated February 2026
Odoo 14-19 Compatible

Quick Start: Odoo Docker in 5 Minutes

The fastest way to get Odoo running locally. This uses the official Odoo Docker image from Docker Hub, maintained by Odoo S.A.

One-Command Start

Run Odoo 19 with PostgreSQL using a single docker compose command. Create this file and run it:

docker-compose.yml (minimal)yaml
services:
  odoo:
    image: odoo:19.0
    depends_on: [db]
    ports:
      - "8069:8069"
    environment:
      - HOST=db
      - USER=odoo
      - PASSWORD=odoo
  db:
    image: postgres:16
    environment:
      - POSTGRES_USER=odoo
      - POSTGRES_PASSWORD=odoo
      - POSTGRES_DB=postgres
Terminalbash
docker compose up -d
# Open http://localhost:8069 in your browser

That is it. Odoo is running at localhost:8069. Create your first database through the web interface.

Docker Hub Image Tags

Official Odoo images are tagged by version. Pick the version you need:

Image TagOdoo VersionStatus
odoo:19.0Odoo 19Latest stable
odoo:18.0Odoo 18 LTSLong-term support
odoo:17.0Odoo 17Supported
odoo:16.0Odoo 16Maintenance only

This minimal setup is for testing only. For production, you need persistent volumes, Nginx, SSL, backups, and security hardening. All of this is covered in the sections below.

Why Docker for Odoo?

Docker has become the standard way to deploy Odoo in production. The official Odoo Docker images are well-maintained and used by organizations of all sizes. If you are deploying Odoo from scratch or containerizing an existing installation, here is why Docker works well for Odoo:

Isolation and Consistency

Docker containers run your Odoo installation identically across development, staging, and production. No more 'works on my machine' issues.

Easy Version Management

Switch between Odoo versions by changing a single line in your docker-compose file. Run multiple versions side-by-side for testing upgrades.

Simplified Deployment

Deploy your entire Odoo stack with a single command. Docker Compose orchestrates Odoo, PostgreSQL, and Nginx together.

Horizontal Scaling

When demand increases, Docker lets you scale Odoo workers across multiple containers or servers using orchestration tools like Swarm or Kubernetes.

Prerequisites

Before starting your Odoo Docker deployment, make sure you have the following in place:

Docker Engine

Docker 20.10+ installed and running

Installation guide

Docker Compose

Docker Compose v2.0+ (included with Docker Desktop)

Installation guide

Domain Name

A registered domain pointing to your server IP

VPS or Server

Minimum 4GB RAM, 2 vCPUs, 40GB storage

Basic Linux Knowledge

Familiarity with command line and SSH

Project Structure

Create a well-organized directory structure for your Odoo Docker deployment. This structure separates configuration, custom modules, and data for maintainability:

Directory Structuretext
odoo-docker/
├── docker-compose.yml          # Main compose file
├── docker-compose.override.yml # Development overrides
├── .env                        # Environment variables
├── nginx/
│   └── odoo.conf              # Nginx configuration
├── addons/
│   └── custom_modules/        # Your custom Odoo modules
├── config/
│   └── odoo.conf              # Odoo configuration file
└── backups/
    └── .gitkeep               # Backup storage directory

Create this structure with the following commands:

mkdir -p odoo-docker/{nginx,addons/custom_modules,config,backups}
cd odoo-docker
touch docker-compose.yml .env
touch nginx/odoo.conf config/odoo.conf

Docker Compose Configuration

The docker-compose.yml file defines your entire Odoo stack. This production-ready configuration includes Odoo, PostgreSQL, and proper networking:

docker-compose.ymlyaml
version: '3.8'

services:
  odoo:
    image: odoo:19.0
    container_name: odoo
    depends_on:
      - db
    ports:
      - "8069:8069"
      - "8072:8072"  # Longpolling port
    volumes:
      - odoo-data:/var/lib/odoo
      - ./addons:/mnt/extra-addons
      - ./config:/etc/odoo
    environment:
      - HOST=db
      - USER=odoo
      - PASSWORD=${POSTGRES_PASSWORD}
    command: ["--proxy-mode"]
    restart: unless-stopped
    networks:
      - odoo-network

  db:
    image: postgres:15
    container_name: odoo-db
    environment:
      - POSTGRES_USER=odoo
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=postgres
      - PGDATA=/var/lib/postgresql/data/pgdata
    volumes:
      - postgres-data:/var/lib/postgresql/data/pgdata
    restart: unless-stopped
    networks:
      - odoo-network
    # Uncomment for external access (not recommended for production)
    # ports:
    #   - "5432:5432"

  nginx:
    image: nginx:alpine
    container_name: odoo-nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/odoo.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certbot/conf:/etc/letsencrypt:ro
      - ./certbot/www:/var/www/certbot:ro
    depends_on:
      - odoo
    restart: unless-stopped
    networks:
      - odoo-network

volumes:
  odoo-data:
  postgres-data:

networks:
  odoo-network:
    driver: bridge

Key Configuration Notes

  • - The --proxy-mode flag enables Odoo to work correctly behind a reverse proxy
  • - Port 8072 is used for longpolling (real-time features like chat)
  • - Named volumes ensure data persists across container restarts

Odoo Docker: Community vs Enterprise

The official Docker images on Docker Hub are for Odoo Community Edition only. Enterprise requires a different approach.

Community Edition (Free)

  • - Pull directly from Docker Hub: docker pull odoo:19.0
  • - Full source available, LGPL-3.0 license
  • - Supports 1000+ OCA modules for added functionality
  • - No Odoo subscription required

Enterprise Edition (Requires License)

  • - Not available on public Docker Hub
  • - Requires active Odoo Enterprise subscription
  • - Build a custom image by adding Enterprise addons to the Community base image
  • - Enterprise source available from Odoo's private GitHub repositories
Dockerfile.enterprisedockerfile
FROM odoo:19.0

# Copy Enterprise addons (requires Odoo subscription)
COPY ./enterprise /mnt/enterprise-addons

# Add Enterprise addons to the addons path
ENV ODOO_EXTRA_ADDONS=/mnt/enterprise-addons

OEC.sh supports both editions. Deploy Community for free or bring your Enterprise license. OCA modules are supported out of the box. See our OCA modules guide.

Environment Variables

Store sensitive configuration in a .env file. Never commit this file to version control.

.envbash
# Database Configuration
POSTGRES_PASSWORD=your_secure_password_here

# Odoo Configuration (optional overrides)
ODOO_DB_HOST=db
ODOO_DB_PORT=5432
ODOO_DB_USER=odoo

# Domain Configuration
DOMAIN=erp.yourcompany.com

# Email Configuration (optional)
SMTP_HOST=smtp.yourprovider.com
SMTP_PORT=587
SMTP_USER=your_email@yourcompany.com
SMTP_PASSWORD=your_smtp_password

Security Warning

Use a strong, unique password for POSTGRES_PASSWORD. Add .env to your .gitignore file. In production, consider using Docker secrets or a secrets manager.

You can also create an Odoo configuration file for additional settings:

config/odoo.confini
[options]
addons_path = /mnt/extra-addons
data_dir = /var/lib/odoo

# Database settings
db_host = db
db_port = 5432
db_user = odoo
db_password = False  # Set via environment variable

# Performance tuning
workers = 4
max_cron_threads = 2
limit_memory_hard = 2684354560
limit_memory_soft = 2147483648
limit_time_cpu = 600
limit_time_real = 1200

# Proxy settings
proxy_mode = True

# Logging
logfile = /var/log/odoo/odoo.log
log_level = info

Nginx Reverse Proxy

Nginx acts as a reverse proxy handling SSL termination, static file caching, and WebSocket connections for Odoo real-time features:

nginx/odoo.confnginx
upstream odoo {
    server odoo:8069;
}

upstream odoo-longpolling {
    server odoo:8072;
}

# HTTP - redirect all requests to HTTPS
server {
    listen 80;
    server_name erp.yourcompany.com;

    # Let's Encrypt challenge
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS
server {
    listen 443 ssl http2;
    server_name erp.yourcompany.com;

    # SSL certificates
    ssl_certificate /etc/letsencrypt/live/erp.yourcompany.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/erp.yourcompany.com/privkey.pem;

    # SSL settings
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # Proxy settings
    proxy_read_timeout 720s;
    proxy_connect_timeout 720s;
    proxy_send_timeout 720s;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;

    # Gzip compression
    gzip on;
    gzip_types text/css text/plain text/xml application/xml application/javascript application/json;
    gzip_min_length 1000;

    # Longpolling (real-time features)
    location /longpolling {
        proxy_pass http://odoo-longpolling;
    }

    # WebSocket support
    location /websocket {
        proxy_pass http://odoo-longpolling;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    # Main Odoo application
    location / {
        proxy_pass http://odoo;
        proxy_redirect off;
    }

    # Static files caching
    location ~* /web/static/ {
        proxy_pass http://odoo;
        proxy_cache_valid 200 60m;
        expires 24h;
        add_header Cache-Control "public, immutable";
    }

    # File upload size limit
    client_max_body_size 100M;
}

SSL with Lets Encrypt

Set up free SSL certificates using Certbot. First, create a temporary Nginx config for the initial certificate request, then switch to the full configuration:

Step 1: Initial Setup

# Create directories for Certbot
mkdir -p certbot/conf certbot/www

# Start Nginx with a basic config first (HTTP only)
# Create a temporary nginx config that serves HTTP
cat > nginx/odoo.conf << 'EOF'
server {
    listen 80;
    server_name erp.yourcompany.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 200 'OK';
        add_header Content-Type text/plain;
    }
}
EOF

# Start Nginx
docker compose up -d nginx

Step 2: Obtain Certificate

# Request SSL certificate
docker run -it --rm \
  -v ./certbot/conf:/etc/letsencrypt \
  -v ./certbot/www:/var/www/certbot \
  certbot/certbot certonly \
  --webroot \
  --webroot-path=/var/www/certbot \
  --email your@email.com \
  --agree-tos \
  --no-eff-email \
  -d erp.yourcompany.com

Step 3: Enable Full Configuration

# Replace nginx config with the full SSL version (shown above)
# Then restart Nginx
docker compose restart nginx

# Set up auto-renewal (add to crontab)
0 0 1 * * docker run --rm -v ./certbot/conf:/etc/letsencrypt -v ./certbot/www:/var/www/certbot certbot/certbot renew --quiet && docker compose restart nginx

Odoo Docker for Development

Use Docker Compose overrides to create a development-friendly setup with hot-reloading and debugging tools, without touching your production configuration.

docker-compose.override.ymlyaml
services:
  odoo:
    # Mount source code for live editing
    volumes:
      - ./custom-addons:/mnt/extra-addons
      - ./odoo.conf:/etc/odoo/odoo.conf
    # Enable developer mode with auto-reload
    command: ["--dev=reload,qweb,xml", "--proxy-mode"]
    # Expose debug port
    ports:
      - "8069:8069"
      - "8072:8072"

  # pgAdmin for database inspection
  pgadmin:
    image: dpage/pgadmin4
    ports:
      - "5050:80"
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@example.com
      PGADMIN_DEFAULT_PASSWORD: admin
    depends_on: [db]

Hot Reload

The --dev=reload flag watches Python files for changes and automatically restarts workers. XML and QWeb template changes apply without restart using --dev=qweb,xml.

Database Management

Access pgAdmin at localhost:5050 to inspect tables, run queries, and monitor database performance. Connect using the PostgreSQL container hostname (db) and the credentials from your .env file.

Docker Compose automatically merges docker-compose.override.yml with your main file. Run docker compose up and both files apply. Use docker compose -f docker-compose.yml up to skip the override for production-like testing.

Data Persistence

Docker volumes keep your data intact across container restarts and updates. Understanding volume management matters for any production deployment:

odoo-data Volume

Contains the Odoo filestore including attachments, session files, and uploaded documents.

/var/lib/odoo

postgres-data Volume

Contains the PostgreSQL database files with all your business data.

/var/lib/postgresql/data
# List Docker volumes
docker volume ls

# Inspect a volume
docker volume inspect odoo-docker_odoo-data

# Find volume location on host
docker volume inspect odoo-docker_postgres-data --format '{{ .Mountpoint }}'

Backup Strategy

You need a reliable backup strategy for any production Odoo deployment. Back up both the database and filestore regularly. For more backup strategies including automated off-site storage, see our Odoo backup and recovery guide:

Database Backup Script

backup.shbash
#!/bin/bash

# Configuration
BACKUP_DIR="./backups"
DATE=$(date +%Y%m%d_%H%M%S)
DB_CONTAINER="odoo-db"
DB_USER="odoo"
RETENTION_DAYS=30

# Create backup directory
mkdir -p $BACKUP_DIR

# Backup database
echo "Backing up database..."
docker exec $DB_CONTAINER pg_dumpall -U $DB_USER | gzip > "$BACKUP_DIR/db_backup_$DATE.sql.gz"

# Backup Odoo filestore
echo "Backing up filestore..."
docker run --rm \
  -v odoo-docker_odoo-data:/data:ro \
  -v $(pwd)/$BACKUP_DIR:/backup \
  alpine tar czf /backup/filestore_backup_$DATE.tar.gz -C /data .

# Remove old backups
echo "Removing backups older than $RETENTION_DAYS days..."
find $BACKUP_DIR -name "*.gz" -mtime +$RETENTION_DAYS -delete

echo "Backup completed: $DATE"
echo "Files:"
ls -lh $BACKUP_DIR/*_$DATE.*

Restore from Backup

# Restore database
gunzip -c backups/db_backup_20241220_030000.sql.gz | docker exec -i odoo-db psql -U odoo

# Restore filestore
docker run --rm \
  -v odoo-docker_odoo-data:/data \
  -v $(pwd)/backups:/backup:ro \
  alpine tar xzf /backup/filestore_backup_20241220_030000.tar.gz -C /data

Off-site Backup with S3

# Install AWS CLI and configure credentials
# Then add to your backup script:

# Upload to S3
aws s3 cp $BACKUP_DIR/db_backup_$DATE.sql.gz s3://your-bucket/odoo-backups/
aws s3 cp $BACKUP_DIR/filestore_backup_$DATE.tar.gz s3://your-bucket/odoo-backups/

# Or use rclone for other providers (Backblaze B2, Cloudflare R2, etc.)
rclone copy $BACKUP_DIR remote:odoo-backups/

Backup Best Practices

  • - Automate backups using cron (daily minimum)
  • - Store backups off-site (different location/provider)
  • - Test restore procedures regularly
  • - Encrypt sensitive backups before upload

Production Hardening

A basic Docker setup works for testing. Production deployments need security hardening, resource controls, and monitoring.

Run as Non-Root User

The official Odoo image already runs as the odoo user (UID 101). Verify this is maintained in your compose file and never override with user: root.

Resource Limits

Prevent runaway containers from consuming all server resources:

docker-compose.yml (production additions)yaml
services:
  odoo:
    deploy:
      resources:
        limits:
          cpus: '4.0'
          memory: 4G
        reservations:
          cpus: '1.0'
          memory: 1G
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8069/web/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "5"

  db:
    deploy:
      resources:
        limits:
          memory: 2G

Network Isolation

Only Nginx should be exposed to the internet. Odoo and PostgreSQL should communicate on an internal Docker network:

  • - Never expose port 5432 (PostgreSQL) to the host
  • - Never expose port 8069 directly in production; route through Nginx
  • - Use Docker network policies to restrict container-to-container traffic

Restart Policies and Health Checks

Use restart: unless-stopped for all production services. The health check above lets Docker detect unresponsive Odoo instances and automatically restart them. Set start_period high enough for Odoo to initialize (60s minimum, longer for large databases).

Scaling Considerations

As your Odoo deployment grows, you will need to scale resources. For detailed performance optimization techniques, here are considerations for production scaling:

Horizontal Scaling with Workers

Odoo supports multiple worker processes to handle concurrent requests. Configure workers based on CPU cores:

# In odoo.conf - Rule of thumb: (CPU cores * 2) + 1
workers = 4
max_cron_threads = 2
limit_memory_hard = 2684354560
limit_memory_soft = 2147483648

Load Balancing

For high-availability deployments, use multiple Odoo containers behind a load balancer. Enable sticky sessions for session consistency:

upstream odoo_cluster {
    ip_hash;  # Sticky sessions
    server odoo1:8069;
    server odoo2:8069;
    server odoo3:8069;
}

Redis for Session Storage

When scaling horizontally, use Redis for shared session storage across Odoo instances:

services:
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    volumes:
      - redis-data:/data
    networks:
      - odoo-network

Troubleshooting Odoo Docker

Common issues and how to fix them.

Container won't start: "database connection refused"

The Odoo container starts before PostgreSQL is ready. Add a depends_on with a health check condition:

services:
  db:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U odoo"]
      interval: 5s
      timeout: 5s
      retries: 5
  odoo:
    depends_on:
      db:
        condition: service_healthy

"Permission denied" on volume mounts

The Odoo container runs as UID 101 (odoo user). Your host-mounted directories must be owned by this UID. Fix with: sudo chown -R 101:101 ./addons ./config. Named volumes (like odoo-data:) are managed by Docker and don't have this issue.

Worker timeout: "Worker (PID) exceeded max time"

This happens when a request exceeds the configured timeout. Common causes: large report generation, heavy data imports, or not enough RAM. Increase limit_time_real in odoo.conf (default 120s) and check that your container has enough memory allocated.

Slow performance with high database query times

Default PostgreSQL settings are conservative. Tune shared_buffers (25% of RAM), work_mem (64MB), and effective_cache_size (75% of RAM). Mount a custom postgresql.conf into the container. See our PostgreSQL tuning for Odoo guide.

Custom addons not loading

Check that your addons are in the correct path and that addons_path in odoo.conf includes your mount point. After adding new modules, restart Odoo and update the apps list from Settings > Apps > Update Apps List. Check container logs with docker compose logs odoo for path errors.

Odoo Docker on Cloud Providers

This guide works on any Linux server with Docker installed. Here are provider-specific guides for deploying your Odoo Docker stack:

When to Use Managed Hosting

Self-managing Docker gives you full control but adds real operational overhead. Consider managed hosting when:

Self-Managed Docker

  • Full control over infrastructure
  • Potentially lower hosting costs
  • Requires DevOps expertise
  • Manual backup management
  • Security updates on you

Managed Hosting (OEC.sh)

  • Automated backups and SSL
  • Choose from 8+ cloud providers
  • Git integration for deployments
  • Monitoring and alerting built-in
  • Focus on your business, not servers

Managing Docker infrastructure is not your core business?

OEC.sh handles all the complexity of Odoo Docker deployments while giving you full control. Deploy to your preferred cloud provider in minutes, not hours.

  • Free tier available
  • No credit card required
  • 5-minute setup
Try OEC.sh Free

Frequently Asked Questions

Is Docker good for Odoo production?

Yes, Docker works well for production Odoo when configured properly. The official Odoo Docker images are maintained by Odoo S.A. and used by thousands of organizations. The key is proper configuration of persistent volumes, backups, security settings, and resource limits. For production deployments where you don't want to manage the Docker stack yourself, OEC.sh handles the operational side while you keep full control of your infrastructure.

What's the best Docker image for Odoo?

The official Odoo Docker images from Docker Hub (odoo:17.0, odoo:16.0, etc.) are the best choice for most deployments. These images are maintained by Odoo S.A., regularly updated, and optimized for production use. They include all necessary dependencies and follow Docker best practices. For Enterprise, you'll need to build a custom image extending the official Community image. Avoid unofficial third-party images as they may have security vulnerabilities or lack proper maintenance.

How do I backup Odoo in Docker?

Backup both the PostgreSQL database and Odoo filestore. For the database, use 'docker exec db pg_dump -U odoo postgres > backup.sql'. For filestore, backup the mounted odoo-data volume using 'docker run --rm -v odoo-data:/data -v ./backups:/backup alpine tar czf /backup/filestore.tar.gz /data'. Automate with cron and store backups off-site using S3, Backblaze B2, or Cloudflare R2. OEC.sh provides automated daily backups to 7+ storage providers with one-click restore. For a complete guide, see our Odoo backup and recovery documentation.

Should I use Docker Compose for Odoo?

Yes, Docker Compose is the standard way to run Odoo in Docker. It handles orchestration of multiple containers (Odoo, PostgreSQL, Nginx) and manages networking, volumes, and environment variables in a single file. You can version control your entire infrastructure config this way. For large-scale production, consider Kubernetes or managed platforms like OEC.sh, but Docker Compose works great for small to medium deployments and development environments.

How many workers should I configure in Docker?

For production Odoo in Docker, use the formula: workers = (CPU cores * 2) + 1. For example, 4 CPU cores = 9 workers. Each worker consumes 150-300MB RAM, so set your container memory limits accordingly. Configure this in your odoo.conf file mounted into the container. Also set max_cron_threads (typically 2), limit_memory_hard (2.5GB per worker), and limit_memory_soft (2GB per worker). Monitor resource usage and adjust based on actual workload. For detailed performance tuning, see our Odoo performance optimization guide.

How to scale Odoo with Docker?

Scale Odoo horizontally by running multiple Odoo containers behind a load balancer (Nginx or HAProxy) with sticky sessions enabled. Share the PostgreSQL database and filestore volume across instances, or use Redis for session storage. Configure your docker-compose.yml to run multiple replicas: 'docker compose up --scale odoo=3'. For high availability, use Docker Swarm or Kubernetes. Scaling Docker manually does require ops expertise. OEC.sh handles horizontal scaling automatically if you'd rather not manage it yourself.

How do I update Odoo in Docker?

To update Odoo in Docker: (1) Backup your database and filestore first, (2) Pull the new image with 'docker pull odoo:17.0', (3) Stop the current container with 'docker compose down', (4) Update the image tag in docker-compose.yml, (5) Start with 'docker compose up -d'. For major version upgrades (e.g., 16.0 to 17.0), use Odoo's database migration tools or OpenUpgrade first. Always test upgrades in a staging environment before applying to production.

Can I run multiple Odoo instances in Docker?

Yes, run multiple Odoo instances by creating separate docker-compose files in different directories, or use different service names and ports. Each instance needs its own database (can share the PostgreSQL server). Use Nginx virtual hosts to route traffic based on domain names. This is useful for running multiple companies, staging environments, or different Odoo versions side-by-side. For complex multi-tenant setups, consider OEC.sh which manages multiple instances with isolated environments.

Need Help with Your Odoo Docker Deployment?

Running Docker yourself or using a managed platform? Either way, we can help you get Odoo running in production.