Skip to content

Production Deployment Runbook

Step-by-step guide for deploying to AWS Lightsail with real-world troubleshooting solutions.

Prerequisites

AWS Infrastructure

Service Purpose
Lightsail/EC2 Application server (Ubuntu 22.04+)
RDS PostgreSQL Database server
Route 53/DNS Domain name configuration
S3 (optional) Backups and file storage

Server Requirements

  • Ubuntu 22.04 or later
  • Docker and Docker Compose installed
  • Git configured with SSH key
  • Ports 80, 443 open in firewall/security group

DNS Configuration

Point these domains to your server IP:

Domain Purpose
yourdomain.com Landing page
erp.yourdomain.com Odoo ERP
service.yourdomain.com (or pwa.) Field Service PWA
docs.yourdomain.com Documentation

Phase 1: Server Setup

1.1 Install Docker

# Update system
sudo apt update && sudo apt upgrade -y

# Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER

# Log out and back in for group changes
exit
# SSH back in

# Verify Docker
docker --version
docker compose version

1.2 Setup GitHub SSH Key

# Generate SSH key
ssh-keygen -t ed25519 -C "your-email@example.com"

# Start SSH agent
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

# Copy public key
cat ~/.ssh/id_ed25519.pub
# Add to GitHub → Settings → SSH and GPG keys

# Test connection
ssh -T git@github.com

1.3 Clone Repository

cd ~
git clone git@github.com:yourorg/odoo15-jdx-production.git
cd odoo15-jdx-production

Phase 2: Environment Configuration

2.1 Create Production .env

cp .env.example .env
nano .env

2.2 Critical .env Settings

# Environment
ENVIRONMENT=production
GIT_BRANCH=main
COMPOSE_FILE=docker-compose.yml:docker-compose.prod.yml

# Database (AWS RDS)
DB_HOST=your-db.xxxxx.us-east-1.rds.amazonaws.com
DB_PORT=5432
DB_NAME=production
DB_USER=your_user
DB_PASSWORD=your_secure_password

# Domains
DOMAIN_LANDING=yourdomain.com
DOMAIN_ERP=erp.yourdomain.com
DOMAIN_PWA=service.yourdomain.com
DOMAIN_DOCS=docs.yourdomain.com

# Flask (MUST change for production)
FLASK_ENV=production
PWA_SECRET_KEY=<generate-with-python>
LANDING_SECRET_KEY=<generate-with-python>

# SSL
LETSENCRYPT_EMAIL=admin@yourdomain.com

2.3 Generate Secret Keys

# Generate secure random keys
python3 -c "import secrets; print(secrets.token_hex(32))"

2.4 IMPORTANT: Quote Special Characters

Values with special characters (&, (), :, ,, etc.) MUST be quoted:

# WRONG - will cause errors
COMPANY_NAME=JDX Blinds & Shutters
COMPANY_PHONE=(512) 910-4020

# CORRECT - quoted values
COMPANY_NAME="JDX Blinds & Shutters"
COMPANY_PHONE="(512) 910-4020"
COMPANY_ADDRESS="123 Main St, Suite 100, Austin, TX 78701"
COMPANY_HOURS="Mon-Fri: 9AM-6PM, Sat: 10AM-4PM"
META_TITLE="Company Name | Custom Window Treatments"
META_DESCRIPTION="Description with special chars & symbols."

2.5 Verify Configuration

./production_deploy.sh config

Phase 3: Database Connection

3.1 Test RDS Connection

# Test from server
psql -h your-db.xxxxx.rds.amazonaws.com -U your_user -d production -c "SELECT 1;"

3.2 Restore Production Database (if migrating)

# Upload backup to server
scp backup.sql.gz ubuntu@server-ip:~/

# Restore
gunzip -c backup.sql.gz | psql -h your-db.rds.amazonaws.com -U your_user -d production

Phase 4: Odoo Data Directory

4.1 Create Data Directory

mkdir -p ~/odoo15-jdx-production/odoo-data

4.2 Restore Filestore (if migrating)

# Extract filestore backup
tar -xzf filestore-backup.tar.gz -C ~/odoo15-jdx-production/odoo-data/

4.3 Fix Permissions

CRITICAL: Odoo runs as uid 101 inside the container.

sudo chown -R 101:101 ~/odoo15-jdx-production/odoo-data/

Phase 5: Odoo Configuration

5.1 Production odoo.conf Settings

The configs/odoo.conf file contains critical production settings:

[options]
addons_path = /mnt/extra-addons/helpdesk,/mnt/extra-addons/odoo
data_dir = /var/lib/odoo

; Database settings - CRITICAL for production
db_name = production
dbfilter =

; Performance settings
workers = 0
max_cron_threads = 1
limit_memory_hard = 2684354560
limit_memory_soft = 2147483648
limit_time_cpu = 600
limit_time_real = 1200

; Logging
log_level = info
logrotate = True

; Disable demo data
without_demo = all

; Security settings for production
list_db = True
proxy_mode = True
admin_passwd = your_secure_admin_password

5.2 Critical Settings Explained

Setting Value Purpose
db_name production Forces Odoo to use this database
dbfilter (empty) MUST be empty - setting a value causes redirect issues
list_db True Combined with db_name, auto-selects the database
proxy_mode True Required when behind nginx - trusts X-Forwarded headers

5.3 docker-compose.prod.yml Command Override

Production uses command-line overrides for SSL:

command: ["--", "--addons-path=/mnt/extra-addons/helpdesk,/mnt/extra-addons/odoo", "--db_sslmode=require"]

Important: Only --db_sslmode=require is set via command line. All other settings come from odoo.conf.

5.4 Common Mistakes

Mistake Symptom Fix
Setting dbfilter = ^production$ Redirects to database selector Set dbfilter = (empty)
Using --list_db on command line error: no such option Use list_db in config file only
Using --proxy_mode on command line error: no such option Use proxy_mode in config file only
Missing proxy_mode = True HTTPS/redirect issues behind nginx Add to odoo.conf

5.5 Verify Configuration

# Check config inside container
docker compose exec odoo cat /etc/odoo/odoo.conf | grep -E "db_name|dbfilter|list_db|proxy_mode"

# Expected output:
# db_name = production
# dbfilter =
# list_db = True
# proxy_mode = True

# Test login page works
curl -sI https://erp.yourdomain.com/web | grep -i location
# Should NOT show /web/database/selector

Phase 6: SSL Certificate Setup

6.1 Create Certificate Directory

mkdir -p nginx/ssl/live/yourdomain.com

6.2 Stop Nginx (if running)

docker compose stop nginx

6.3 Request Let's Encrypt Certificates

docker run --rm -p 80:80 \
  -v $(pwd)/nginx/ssl:/etc/letsencrypt \
  certbot/certbot certonly \
  --standalone \
  --non-interactive \
  --agree-tos \
  --email admin@yourdomain.com \
  -d yourdomain.com \
  -d www.yourdomain.com \
  -d erp.yourdomain.com \
  -d service.yourdomain.com \
  -d docs.yourdomain.com

6.4 Fix Certificate Permissions

Let's Encrypt creates restrictive permissions. Fix them:

# Fix archive directory permissions
sudo chmod 755 nginx/ssl/archive
sudo chmod 755 nginx/ssl/archive/yourdomain.com*
sudo chmod 644 nginx/ssl/archive/yourdomain.com*/*.pem

# Fix accounts directory
sudo chmod 755 nginx/ssl/accounts

Let's Encrypt uses symlinks from live/ to archive/. If nginx can't read them, copy actual files:

# Remove symlinks
sudo rm nginx/ssl/live/yourdomain.com/*.pem

# Copy actual certificate files
sudo cp nginx/ssl/archive/yourdomain.com*/fullchain1.pem nginx/ssl/live/yourdomain.com/fullchain.pem
sudo cp nginx/ssl/archive/yourdomain.com*/privkey1.pem nginx/ssl/live/yourdomain.com/privkey.pem

# Fix permissions
sudo chmod 644 nginx/ssl/live/yourdomain.com/*.pem

6.6 Configure Nginx

./production_deploy.sh configure-nginx

Phase 7: Deploy Application

7.1 Build and Start

# Full deployment
./production_deploy.sh deploy

# Or step by step:
docker compose build
docker compose up -d

7.2 If Nginx Fails to Start

# Check nginx logs
docker compose logs nginx --tail=30

# Common fixes:

# 1. Certificate not found - recreate container
docker compose up -d --force-recreate nginx

# 2. Permission denied - fix ssl permissions (see 5.4)

# 3. Volume mount issue - verify mount works
docker run --rm -v $(pwd)/nginx/ssl:/etc/letsencrypt:ro alpine ls -la /etc/letsencrypt/live/

7.3 Verify All Services

# Check container status
docker compose ps

# Test each service
curl -I https://yourdomain.com
curl -I https://erp.yourdomain.com
curl -I https://service.yourdomain.com
curl -I https://docs.yourdomain.com

# Check Odoo health from inside network
docker compose exec nginx curl -s http://odoo:8069/web/health

Phase 8: Post-Deployment

8.1 SSL Auto-Renewal Cron

sudo crontab -e

Add this line:

0 3 * * 0 cd /home/ubuntu/odoo15-jdx-production && docker run --rm -v $(pwd)/nginx/ssl:/etc/letsencrypt certbot/certbot renew --quiet

8.2 Verify Auto-Start on Reboot

All services have restart: always in docker-compose.prod.yml, so they auto-start after reboot.

# Test reboot
sudo reboot

# After reconnecting, verify
docker compose ps

Troubleshooting

Odoo Permission Denied

PermissionError: [Errno 13] Permission denied: '/var/lib/odoo/sessions'

Fix:

sudo chown -R 101:101 odoo-data/
docker compose restart odoo

SSL Certificate Not Found

cannot load certificate "/etc/letsencrypt/live/domain/fullchain.pem": No such file or directory

Fixes:

  1. Check mount works:

    docker run --rm -v $(pwd)/nginx/ssl:/etc/letsencrypt:ro alpine ls -la /etc/letsencrypt/live/yourdomain.com/
    

  2. Fix permissions:

    sudo chmod 755 nginx/ssl/archive
    sudo chmod 755 nginx/ssl/archive/yourdomain.com*
    

  3. Copy actual files instead of symlinks (see 5.5)

  4. Force recreate container:

    docker compose up -d --force-recreate nginx
    

Nginx Crash Loop

# Check logs
docker compose logs nginx --tail=30

# If SSL issue, temporarily use HTTP-only config
# Edit nginx/conf.d/default.conf to comment out SSL blocks

# Or recreate with new volumes
docker compose stop nginx
docker compose rm -f nginx
docker compose up -d nginx

.env Parse Errors

line 133: unexpected character "&" in variable name

Fix: Quote values with special characters:

# Wrong
COMPANY_NAME=JDX Blinds & Shutters

# Correct
COMPANY_NAME="JDX Blinds & Shutters"

Container Won't Pull Image

pull access denied for odoo15-jdx, repository does not exist

Fix: Build the image first:

docker compose build
docker compose up -d

Health Check Fails for Odoo

In production mode, Odoo port 8069 is not exposed. Test through nginx:

# From inside nginx container
docker compose exec nginx curl -s http://odoo:8069/web/health

Odoo Redirects to Database Selector

/web redirects to /web/database/selector

Cause: Incorrect dbfilter or list_db settings in odoo.conf.

Fix: Ensure these exact settings in configs/odoo.conf:

db_name = production
dbfilter =
list_db = True
proxy_mode = True

Key insight: dbfilter must be empty (not ^production$ or any regex). Setting any value causes Odoo to redirect to the database selector.

Verify:

# Check config
docker compose exec odoo cat /etc/odoo/odoo.conf | grep -E "db_name|dbfilter|list_db"

# Test redirect - should NOT go to /web/database/selector
curl -sI https://erp.yourdomain.com/web | grep -i location

Odoo Command Line Option Errors

odoo: error: no such option: --list_db
odoo: error: no such option: --proxy_mode

Cause: These options are config-file only, not command line.

Fix: Set in configs/odoo.conf, not in docker-compose command:

Option Where to Set
list_db Config file only
proxy_mode Config file only
db_sslmode Command line OK
database Command line OK
db-filter Command line OK

Quick Reference

Deployment Commands

./production_deploy.sh deploy          # Full deployment
./production_deploy.sh update          # Quick update
./production_deploy.sh start           # Start services
./production_deploy.sh stop            # Stop services
./production_deploy.sh restart         # Restart all
./production_deploy.sh status          # Check status
./production_deploy.sh logs odoo       # View logs
./production_deploy.sh health          # Health check
./production_deploy.sh config          # Show config

SSL Commands

./production_deploy.sh init-ssl        # Full SSL setup
./production_deploy.sh configure-nginx # Generate nginx config
./production_deploy.sh renew-ssl       # Renew certificates

Direct Docker Commands

docker compose ps                      # Container status
docker compose logs -f service         # Follow logs
docker compose restart service         # Restart one service
docker compose up -d --force-recreate service  # Recreate container
docker compose exec service command    # Run command in container

Production URLs

Service URL
Landing Page https://yourdomain.com
Odoo ERP https://erp.yourdomain.com
Field Service PWA https://service.yourdomain.com
Documentation https://docs.yourdomain.com

File Locations

Path Purpose
~/odoo15-jdx-production/ Application root
odoo-data/ Odoo filestore and sessions
nginx/ssl/ SSL certificates
nginx/conf.d/default.conf Active nginx config
.env Environment configuration
backups/ Local backup storage