Automation Rules
Automation rules let you define when and what oec.sh should do automatically — deploy staging when code is pushed to main, refresh UAT from production on demand, promote a tested build to production after human approval. Each rule pairs a trigger with an action, plus optional conditions to keep things running safely.
How It Works
A rule watches for a git event or waits to be called manually. When the trigger fires, the rule evaluates its conditions (branch patterns, cooldowns, file filters). If conditions pass — and an approval gate is not blocking — the action executes in the background and the result is logged.
Git push → trigger matches → conditions evaluated → [approval?] → action runs → run loggedYou can see all rules on the Automation tab inside a project. Each rule shows its trigger type, last run status, and the notification channels subscribed to its events.
Triggers
A trigger defines what event starts the rule.
Push (push)
Fires when code is pushed to a branch. Use branch patterns to scope it.
"trigger_config": {
"branch_patterns": ["main", "staging", "release/*"]
}Glob patterns are supported: main matches exactly, feature/* matches any branch starting with feature/, * matches everything.
Pull Request Opened (pr_open)
Fires when a pull request is opened. Useful for spinning up a testing deploy when a developer raises a PR.
"trigger_config": {
"target_branch_patterns": ["main"],
"source_branch_patterns": ["feature/*", "fix/*"]
}Both target_branch_patterns (the branch the PR targets) and source_branch_patterns (the PR's own branch) are optional — omit either to match all.
Pull Request Merged (pr_merge)
Fires when a pull request is merged. Avoids mid-review deploys — code only ships after the PR is approved and merged.
"trigger_config": {
"target_branch_patterns": ["main"]
}Tag Push (tag_push)
Fires when a git tag is pushed. Commonly used for release pipelines.
"trigger_config": {
"tag_pattern": "v*"
}The tag_pattern is a glob: v* matches v1.0.0, v2.3.1, etc. Omit it to fire on any tag.
Manual (manual)
No git event is required. The rule runs when explicitly triggered from the Automation tab or via API. Use this for operations that require a deliberate human decision — staging refreshes, UAT resets, on-demand deploys.
Manual rules can still have an approval gate (see Approval Gates).
Actions
The action is what oec.sh does when a rule fires.
Deploy (deploy)
Performs a full deployment of the target environment from its configured git branch. Rebuilds containers, runs migrations, restarts Odoo.
"action_config": {
"action_type": "deploy",
"target_env_id": "ENV_UUID",
"force": false
}Set force: true to redeploy even if the environment is already on the latest commit.
Quick Update (quick_update)
Runs a lightweight git pull + optional module operation + container restart, without rebuilding Docker images. Much faster than a full deploy (~30 seconds vs minutes). Use the mode field to control what happens after the git pull.
"action_config": {
"action_type": "quick_update",
"target_env_id": "ENV_UUID",
"mode": "pull_restart"
}Modes
| Mode | What it does | When to use |
|---|---|---|
pull_restart | Git pull → restart container | Code-only changes (default) |
update_all | Git pull → odoo -u all --stop-after-init → restart | All modules need upgrading |
update_specific | Git pull → odoo -u <modules> --stop-after-init → restart | Upgrade specific modules (schema changes, data updates) |
reinitialize_specific | Git pull → odoo -i <modules> --stop-after-init → restart | Install or reinitialize specific modules |
restart_only | Restart container only (no git pull) | Quickest restart, no code change |
For update_specific and reinitialize_specific, provide the module names in modules:
"action_config": {
"action_type": "quick_update",
"target_env_id": "ENV_UUID",
"mode": "update_specific",
"modules": ["sale", "purchase", "account_move_line_tax_details"]
}Difference between -u and -i:
update_specific(-u) — Upgrade existing modules: re-runs Python init methods, XML data files, migration scripts. Use when a module has schema or data changes.reinitialize_specific(-i) — Install or reinitialize modules: installs modules that aren't yet installed, or forces a clean re-initialization. Use when adding a new module to an existing database.
Quick Update skips Docker image rebuilds and Python dependency installation. Use a full Deploy when you've added new Python packages or system-level migrations.
Promote (promote)
Deploys the exact git SHA and module state from a source environment to a target environment — no rebuild from scratch. The target ends up running precisely what QA tested on the source.
"action_config": {
"action_type": "promote",
"source_env_id": "STAGING_ENV_UUID",
"target_env_id": "PROD_ENV_UUID"
}This is the safest way to move a tested build to production — there are no "build surprises" since the exact same artifact is promoted.
Clone, Neutralize & Deploy (clone_neutralize_deploy)
Clones a source environment's database (and optionally filestore) into a target environment, runs Odoo's native neutralize command to make it safe for non-production use, then deploys. Perfect for keeping staging in sync with production data.
"action_config": {
"action_type": "clone_neutralize_deploy",
"source_env_id": "PROD_ENV_UUID",
"target_env_id": "STAGING_ENV_UUID",
"neutralize": true,
"include_filestore": false,
"git_branch_override": null
}Neutralize disables outgoing emails, payment providers, crons, OAuth, webhooks, and external integrations so the cloned environment cannot accidentally contact real systems.
Set include_filestore: false to skip copying uploaded files — this makes the refresh ~80% faster and is sufficient for most development and QA scenarios.
Use git_branch_override to deploy a different branch after the clone (e.g. deploy develop branch onto the freshly-cloned data).
Neutralize uses Odoo's built-in odoo-bin neutralize command. It handles disabling correctly for all versions (Odoo 16, 17, 18, 19).
Destroy Environment (destroy_env)
Permanently destroys an environment — stops containers, removes database, releases resources. Use with care.
"action_config": {
"action_type": "destroy_env",
"target_env_id": "ENV_UUID"
}This action is irreversible. Always pair it with an approval gate for anything other than ephemeral test environments.
Conditions
Conditions are checked after the trigger fires but before the action runs. If any condition fails, the run is recorded as skipped with a reason.
Branch Patterns
Set in trigger_config.branch_patterns — the rule only fires if the pushed branch matches one of the patterns.
Cooldown
Prevents the rule from firing more than once within a time window.
"conditions": {
"cooldown_minutes": 5
}If the rule last ran less than cooldown_minutes ago, the run is skipped. Set to 0 (default) to allow unlimited frequency.
File Path Filters
Only fire if the commit changed files matching certain patterns — or skip if only excluded paths changed.
"conditions": {
"include_paths": ["requirements.txt", "manifest.json", "*.py"],
"exclude_paths": ["docs/*", "README.md", "*.mdx"]
}include_paths: At least one changed file must match one of these patterns. If none match, the run is skipped.exclude_paths: If all changed files match these patterns, the run is skipped. Useful for ignoring documentation-only commits.
Environment Status Requirement
Only run the action if the target environment is in a particular state.
"conditions": {
"require_env_status": "running"
}Options: "running", "stopped", or null (any status — the default).
Approval Gates
Add a human sign-off step before the action executes. Useful for production deployments where an unreviewed deploy could cause downtime.
"require_approval": true,
"approval_expires_minutes": 60Approval workflow:
- Rule fires → run is created with status
pending_approval - A team member reviews and approves or rejects from the run detail view
- Approved → action executes; Rejected → run stops
- If no action is taken within
approval_expires_minutes, the run expires automatically
Approval windows range from 5 to 1440 minutes (24 hours).
Circuit Breaker
The circuit breaker automatically disables a rule after a run of consecutive failures, preventing a broken rule from hammering your infrastructure.
"circuit_breaker": {
"enabled": true,
"max_consecutive_failures": 3,
"window_minutes": 60
}When max_consecutive_failures is reached, the rule is paused (is_active set to false) and must be manually re-enabled from the Automation tab. Successful runs reset the failure counter.
Recommended settings:
- Development/staging rules:
max_consecutive_failures: 3— allows a few transient failures - Production rules:
max_consecutive_failures: 1— fail fast, get a human involved immediately
Retry Policy
Control how many times individual deployment steps are retried before a run is marked as failed.
"retry_policy": {
"build_max_attempts": 2,
"container_start_max_attempts": 2,
"health_check_max_attempts": 5
}| Setting | Default | Range | What it retries |
|---|---|---|---|
build_max_attempts | 2 | 0–5 | Docker image build failures |
container_start_max_attempts | 2 | 0–5 | Container start failures |
health_check_max_attempts | 5 | 1–20 | Odoo health check failures |
Database migrations are never retried automatically (migration_retry is always false). This prevents double-migration data corruption.
Rule Priority
When multiple rules can fire on the same event, they execute in priority order. Lower numbers run first.
"priority": 10Valid range: 1–9999 (default: 100). Reorder rules from the Automation tab by dragging, or bulk-update priorities via the API.
Templates
The Automation tab includes a template gallery with 10 pre-built rule configurations for common workflows. Templates auto-resolve environment slots by matching environment names — select a template, confirm the environment mappings, and the rule is ready.
| Template | Trigger | Action | Best for |
|---|---|---|---|
| Deploy staging on push to main | Push → main | Deploy staging | Continuous integration |
| Quick-update dev on feature push | Push → feature/* | Quick Update dev | Fast inner loop |
| Deploy staging on PR merge | PR merge → main | Deploy staging | Review-gated deploys |
| Production release on version tag | Tag push v* | Deploy prod | Versioned releases |
| Promote staging to production | Tag push release/* | Promote | Tested build promotion |
| Refresh staging from production | Manual | Clone + neutralize | Fresh data for QA |
| Refresh UAT (no filestore) | Manual | Clone (fast) | ~80% faster refresh |
| Deploy PR to staging for review | PR open | Deploy staging | PR preview deploys |
| Quick-update staging on PR re-push | Push → PR branch | Quick Update | Fast re-test after fixes |
| Manual production deploy (approval) | Manual | Deploy prod | Safety-gated prod deploys |
Use Cases
Real-world rule configurations for the most common Odoo agency workflows. Each example shows the full rule JSON you'd send via API — or the equivalent settings in the UI rule editor.
1. Auto-deploy staging when code lands on main
The bread-and-butter CI rule. Every merge to main triggers a full staging deploy. A 5-minute cooldown prevents rapid-fire merges from queuing up multiple deploys.
{
"name": "Deploy staging on push to main",
"trigger_type": "push",
"trigger_config": { "branch_patterns": ["main"] },
"action_type": "deploy",
"action_config": {
"action_type": "deploy",
"target_env_id": "STAGING_ENV_UUID"
},
"conditions": { "cooldown_minutes": 5 },
"circuit_breaker": { "enabled": true, "max_consecutive_failures": 3 }
}2. Quick-update dev on every feature push (~30 seconds)
Fast inner-loop for developers. Any push to a feature/* branch triggers a git pull + restart on the dev environment — no Docker rebuild, no downtime, ready in ~30 seconds.
{
"name": "Quick update dev on feature push",
"trigger_type": "push",
"trigger_config": { "branch_patterns": ["feature/*", "fix/*", "chore/*"] },
"action_type": "quick_update",
"action_config": {
"action_type": "quick_update",
"target_env_id": "DEV_ENV_UUID",
"mode": "pull_restart"
},
"conditions": { "cooldown_minutes": 1 }
}3. Upgrade specific modules after a push
When you push module changes that include schema updates (new fields, model changes), automatically run odoo -u <modules> on the target environment. Faster than a full deploy for targeted module work.
{
"name": "Upgrade sales modules on push to module-dev",
"trigger_type": "push",
"trigger_config": { "branch_patterns": ["module-dev", "feature/sales-*"] },
"action_type": "quick_update",
"action_config": {
"action_type": "quick_update",
"target_env_id": "DEV_ENV_UUID",
"mode": "update_specific",
"modules": ["sale", "sale_management", "crm"]
},
"conditions": { "cooldown_minutes": 2 }
}Use update_specific (-u) for modules that already exist in the database and have code/schema changes. It re-runs init methods, loads updated XML data files, and runs migration scripts.
4. Install a new module after a push
When you add a new custom module to your addons path and push, automatically install it on the dev environment with odoo -i <module>. Use this instead of -u for modules not yet present in the database.
{
"name": "Install new module on push",
"trigger_type": "push",
"trigger_config": { "branch_patterns": ["feature/new-module-*"] },
"action_type": "quick_update",
"action_config": {
"action_type": "quick_update",
"target_env_id": "DEV_ENV_UUID",
"mode": "reinitialize_specific",
"modules": ["my_new_custom_module"]
},
"conditions": { "cooldown_minutes": 2 }
}Use reinitialize_specific (-i) when the module does not yet exist in the database. For modules that are installed but need updating, use update_specific (-u) instead.
5. Upgrade all modules after a release branch push
For release branches where you want a complete module refresh, run odoo -u all on staging after each push. Slower than specific mode but guarantees all modules are consistent.
{
"name": "Full module update on release push",
"trigger_type": "push",
"trigger_config": { "branch_patterns": ["release/*"] },
"action_type": "quick_update",
"action_config": {
"action_type": "quick_update",
"target_env_id": "STAGING_ENV_UUID",
"mode": "update_all"
},
"conditions": { "cooldown_minutes": 10 }
}6. Deploy staging only when Python or manifest files change
Skip deploys for documentation or config-only commits. The include_paths filter ensures a full deploy only runs when code that actually affects the running application changes.
{
"name": "Deploy staging on meaningful changes",
"trigger_type": "push",
"trigger_config": { "branch_patterns": ["main"] },
"action_type": "deploy",
"action_config": {
"action_type": "deploy",
"target_env_id": "STAGING_ENV_UUID"
},
"conditions": {
"cooldown_minutes": 5,
"include_paths": ["*.py", "__manifest__.py", "requirements.txt", "*.xml", "*.csv"],
"exclude_paths": ["docs/*", "*.md", "*.mdx", ".gitignore"]
}
}7. Refresh staging from production (manual, on demand)
Keep staging in sync with real customer data. Triggered manually — a developer clicks "Run" in the Automation tab when they need fresh data for a QA cycle. Neutralize ensures no emails or payments fire from the cloned data.
{
"name": "Refresh staging from production",
"trigger_type": "manual",
"trigger_config": {},
"action_type": "clone_neutralize_deploy",
"action_config": {
"action_type": "clone_neutralize_deploy",
"source_env_id": "PRODUCTION_ENV_UUID",
"target_env_id": "STAGING_ENV_UUID",
"neutralize": true,
"include_filestore": false,
"git_branch_override": null
},
"conditions": { "cooldown_minutes": 60 }
}Set include_filestore: false to skip uploaded attachments — the refresh is ~80% faster and sufficient for most QA scenarios. Set it to true only if you need to test file-related features.
8. Refresh UAT from production including filestore
When QA needs an exact copy of production including uploaded files (invoices, attachments), set include_filestore: true. Requires more time and disk space.
{
"name": "Full UAT refresh (with filestore)",
"trigger_type": "manual",
"trigger_config": {},
"action_type": "clone_neutralize_deploy",
"action_config": {
"action_type": "clone_neutralize_deploy",
"source_env_id": "PRODUCTION_ENV_UUID",
"target_env_id": "UAT_ENV_UUID",
"neutralize": true,
"include_filestore": true
},
"conditions": { "cooldown_minutes": 120 }
}9. Promote staging to production after a release tag
Push a v* tag on main → staging gets validated → production is promoted with the exact same build staging was tested on. No rebuild, no surprises.
{
"name": "Promote to production on release tag",
"trigger_type": "tag_push",
"trigger_config": { "tag_pattern": "v*" },
"action_type": "promote",
"action_config": {
"action_type": "promote",
"source_env_id": "STAGING_ENV_UUID",
"target_env_id": "PRODUCTION_ENV_UUID"
},
"require_approval": true,
"approval_expires_minutes": 120,
"circuit_breaker": { "enabled": true, "max_consecutive_failures": 1 }
}The max_consecutive_failures: 1 circuit breaker on the production rule ensures one failure immediately pauses the rule and alerts the team — fail fast rather than retrying.
10. Deploy to production with approval gate
Full production deploy triggered manually. Requires a team member to review and approve before the deploy executes. The 2-hour approval window gives enough time for async review.
{
"name": "Production deploy (requires approval)",
"trigger_type": "manual",
"trigger_config": {},
"action_type": "deploy",
"action_config": {
"action_type": "deploy",
"target_env_id": "PRODUCTION_ENV_UUID"
},
"require_approval": true,
"approval_expires_minutes": 120,
"circuit_breaker": { "enabled": true, "max_consecutive_failures": 1 }
}11. Deploy PR branch to staging when PR is opened
Automatically deploy the PR's branch to a staging environment when any PR targeting main is opened. QA can review the feature immediately without waiting for manual deploys.
{
"name": "Deploy PR to staging on PR open",
"trigger_type": "pr_open",
"trigger_config": {
"target_branch_patterns": ["main"],
"source_branch_patterns": ["feature/*", "fix/*"]
},
"action_type": "deploy",
"action_config": {
"action_type": "deploy",
"target_env_id": "STAGING_ENV_UUID"
},
"conditions": {
"cooldown_minutes": 5,
"require_env_status": "running"
}
}12. Quick-update staging every time a PR is re-pushed
After QA comments on a PR and the developer pushes a fix, staging should reflect the latest commit immediately. This rule targets the PR's source branch — any push refreshes staging without a full rebuild.
{
"name": "Quick update staging on PR re-push",
"trigger_type": "push",
"trigger_config": { "branch_patterns": ["feature/*", "fix/*"] },
"action_type": "quick_update",
"action_config": {
"action_type": "quick_update",
"target_env_id": "STAGING_ENV_UUID",
"mode": "pull_restart"
},
"conditions": {
"cooldown_minutes": 2,
"require_env_status": "running"
}
}13. Full deploy on PR merge to main
After a PR is merged (not just pushed), perform a full deploy — this is safer than pushing because you know the code has been code-reviewed and the branch is clean.
{
"name": "Full deploy staging on PR merge",
"trigger_type": "pr_merge",
"trigger_config": { "target_branch_patterns": ["main"] },
"action_type": "deploy",
"action_config": {
"action_type": "deploy",
"target_env_id": "STAGING_ENV_UUID"
},
"conditions": { "cooldown_minutes": 5 },
"circuit_breaker": { "enabled": true, "max_consecutive_failures": 3 }
}Choosing the Right Quick Update Mode
| Scenario | Recommended mode |
|---|---|
| Pushed a Python logic fix, no schema change | pull_restart |
| Added a new field to an existing model | update_specific with that module |
| Changed XML views or data files | update_specific with that module |
| Added a brand-new module to addons | reinitialize_specific with the new module |
| Multiple modules changed, unsure which | update_all |
| Changed only static assets or JS | pull_restart |
| Need containers to pick up env var changes | restart_only |
Added a Python package (requirements.txt) | Use Deploy (full rebuild needed) |
Run History
Every time a rule fires, a run is created — whether it executed, was skipped, or is waiting for approval.
Go to Automation tab → select a rule → Run History to see per-rule runs, or view all project runs across all rules from the project-level automation log.
Run Statuses
| Status | Meaning |
|---|---|
pending | Queued, waiting to be processed |
running | Action is executing |
completed | Action finished successfully |
failed | Action encountered an unrecoverable error |
skipped | Conditions were not met (shows skip reason) |
pending_approval | Waiting for a team member to approve |
approved | Approved, action is executing |
rejected | A team member rejected the run |
approval_expired | Approval window closed with no response |
What's in a Run
Each run record includes:
- The trigger that fired (event type, branch, commit SHA, actor)
- Start and end time
- Per-step timing breakdown
- Error message for failed runs
- Link to the deployment created by the run (for deploy/promote/quick-update actions)
Notifications
When a rule completes or fails, oec.sh fires automation_rule.completed or automation_rule.failed events. Subscribe to these with an outgoing webhook or email notification channel to alert your team.
The Automation tab shows small indicator pills on each rule row — green for webhooks, blue for email channels — so you can see at a glance which rules have notifications configured.
To set up notifications: go to Settings → Webhooks or Settings → Email Alerts and subscribe to automation_rule.completed and/or automation_rule.failed. You can scope a channel to the specific project if you only want events from one project.
See Notifications for setup instructions.
Permissions
Creating, editing, and deleting automation rules requires the Developer role or higher on the project.
Approving or rejecting pending runs requires the Admin role or higher.
Viewing rules and run history is available to all project members.
API
All automation rule operations are available via the REST API for CI/CD integration and programmatic management.
curl "https://api.oec.sh/api/v1/projects/PROJECT_ID/automation-rules" \
-H "Authorization: Bearer YOUR_TOKEN"