Automating Deployment Odoo: A Guide to Seamless CI/CD with Self-Hosted Runners
- John Julius Danker Khoo
- 1 day ago
- 4 min read
Managing Odoo deployments manually—transferring files, manually installing modules, and restarting services—can be a recipe for downtime and human error. In this guide, we’ll walk through how to set up a Reproducible Runner Automatic Deployment system that handles the heavy lifting for you.
Why Automate Your Odoo Deployment?
Before we dive into the "how," let’s look at the "why." A proper CI/CD pipeline for Odoo offers:
Reduced Downtime: Automated restarts and container management.
Smart Module Updates: Automatically detects changes in your custom/addons folder and updates modules individually.
Safety First: Automatic database backups with timestamps are created before every deployment for easy rollbacks.
Efficiency: No more manual file transfers; pushing code to GitHub triggers the entire process.
Step 1: Prepare the Server and GitHub Runner
To start, we need to set up the environment on your server (we'll refer to our project path as /odoo_automation_dev) to communicate with GitHub.
1.1 Download and Extract the Runner
Create a dedicated directory for the GitHub Actions runner:
mkdir -p /opt/actions-runner && cd /opt/actions-runner
# Download the latest runner package
curl -o actions-runner-linux-x64.tar.gz -L [https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz](https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz)
# Extract the installer
tar xzf ./actions-runner-linux-x64.tar.gz
1.2 Configure a Dedicated User
For security, never run your automation as root. Create a dedicated runner user:
sudo useradd -m runner
sudo chsh -s /bin/bash runner
sudo chown -R runner:runner /opt/actions-runner
1.3 Register the Runner
Navigate to your GitHub Repository Settings > Actions > Runners to get your token. Then, switch to the runner user and register the server:


sudo su runner
./config.sh --url [https://github.com/YourOrg/YourRepo](https://github.com/YourOrg/YourRepo) --token [YOUR_SECRET_TOKEN]


1.4 Install as a Service
To ensure the runner starts automatically after a server reboot, install it as a system service:
sudo ./svc.sh install
sudo ./svc.sh start
Step 2: Establish Secure Access (Deploy Keys)
Your runner needs permission to pull code from your private repository without a password. We use SSH Deploy Keys for this.
Generate the Key: ssh-keygen -t ed25519 -C "runner@odoo-server" -f ~/.ssh/id_ed25519 -N ""
Add to GitHub: Copy the content of ~/.ssh/id_ed25519.pub. In your GitHub repo, go to Settings > Deploy Keys, click Add deploy key, paste the key, and check "Allow write access".
Update Remote: Ensure your local repo uses the SSH URL: git remote set-url origin git@github.com:YourOrg/YourRepo.git

Step 3: The Automation Workflow
The "brain" of this setup is a .yml workflow file. Here is what happens under the hood when you push code:
3.1 Environment Setup
The runner navigates to your deployment folder and pulls the latest changes. It ensures the directory structure—such as your custom_addons—is perfectly aligned with your Docker volumes.
3.2 Database Safety Net
Before any code is changed, the system runs a backup:
# Example backup logic inside the workflow
docker compose exec -T db pg_dump -U odoo odoo_db > ./backups/odoo_backup_$(date +%Y%m%d_%H%M).dump
3.3 Smart Module Installation & Update
The script performs a "Smart Update" using click-odoo-contrib. Instead of updating the entire database (which is slow), it:
Scans for new modules: If a folder with a __manifest__.py is found but not installed, it runs --init.
Surgical Updates: It triggers updates only for modules where code changes were detected, skipping the Odoo core modules for a massive speed boost.
Step 4: Verification
Once the workflow completes, you can verify the status directly in GitHub Actions or on your server:
docker compose ps
Your Odoo instance is now running the latest code, with all modules updated and a fresh backup stored safely.
By following this guide, you've moved from a manual, risky deployment process to a professional CI/CD pipeline. Your developers can now focus on writing code, knowing that the deployment, installation, and backup processes are handled automatically.
Ensure you have click-odoo-contrib listed in your requirements.txt to enable the smart update features.



Smart Module Installation
The most powerful part of this workflow is how it handles Odoo modules. Instead of a blanket --update all command, which can be slow and risky, this script performs a surgical update.
1. Automatic Module Discovery
The workflow automatically scans your custom directories (like ./custom_addons/my_modules) for any folder containing a manifest.py or openerp.py file . It ignores hidden files and system directories to ensure only valid Odoo modules are processed .
2. "Install if New" Logic
For every module found, the script queries your PostgreSQL database directly to check if the module is already installed :
If missing: It triggers an --init command to install the module from scratch .
If present: It skips the initialization to save time.
3. The Performance Boost
Finally, the script runs click-odoo-update with the --ignore-core-addons flag . This tells Odoo to only look for changes in your custom code, skipping the thousands of standard Odoo core files, which results in a massive speed boost.
Step 3: Safety First: The Automated Rollback Strategy
We’ve all had that moment of panic when a deployment fails. This setup mitigates that risk by creating a "safety net" before a single line of code is changed.
Pre-Deployment Backup: Every time the workflow runs, it creates a database dump with a unique timestamp (e.g., odoo_backup_20260311_1659.dump) .
Containerized Backups: The backup is executed inside the database container using pg_dump, ensuring environment consistency.
Failure Handling: If a module fails to install, the script is designed to log the error and proceed with the next module rather than crashing the entire deployment.
Directory Structure & Configuration
To make this work, your repository should follow a predictable structure. This allows the runner to bind-mount your local folders into the Docker containers correctly.
Component | Host Path (Runner) | Container Path (Odoo) |
Project Root | /odoo_docker_ci_cd | / |
Addons Folder 1 | ./custom_addons/addons | /mnt/extra-addons |
Addons Folder 2 | ./custom_addons/excelroot | /mnt/extra-addons2 |
Final Checklist for Success
To ensure your environment is ready for this level of automation, double-check these requirements:
Python Dependencies: Ensure click-odoo-contrib is added to your requirements.txt so it is available for the smart update commands.
Permissions: The runner user must have ownership of the /opt/actions-runner and /odoo_docker_ci_cd directories.
Docker Compose: Your docker-compose.yml must define service names (like web and db) that match the ones used in your autodeploy.yml script.
By moving to this reproducible deployment model, you reduce manual file transfers, eliminate the need for manual module clicks, and significantly lower the risk of human error during production updates.



Comments