Skip to main content
GitLab CI/CD

Odoo GitLab CI/CD Guide: Auto-Deploy Pipeline Setup

Set up complete GitLab CI/CD pipelines for your Odoo deployment. Auto-deploy on push, run automated tests, and manage staging environments with production-ready .gitlab-ci.yml configurations.

OEC.sh Supports GitLab Natively

Unlike Odoo.sh which only supports GitHub, OEC.sh provides native GitLab integration with direct webhooks - no submodule workarounds needed.

20-25 min read
Updated January 2026
Odoo 16.0 - 18.0

1Why GitLab for Odoo

While Odoo.sh only supports GitHub, many enterprises and agencies prefer GitLab for its self-hosting capabilities and integrated DevOps features. GitLab provides a complete DevOps platform in 2026 that works exceptionally well for Odoo deployments:

Self-Hosted Option

Run GitLab on your own infrastructure for complete data sovereignty and compliance

Built-in CI/CD

Native pipeline integration - no third-party services needed for automation

Advanced Issue Tracking

Integrated boards, epics, and milestones for project management

Free Private Repos

Unlimited private repositories with no user limits on the free tier

Container Registry

Built-in Docker registry for storing and managing your Odoo images

Environment Management

Dynamic environments for staging, review apps, and production

Odoo.sh GitLab Limitation

According to Odoo.sh FAQ: GitLab requires a "submodule workaround" that is "less comfortable" than native GitHub support. OEC.sh eliminates this limitation with native GitLab webhooks.

2Pipeline Architecture

A well-structured GitLab CI/CD pipeline for Odoo typically includes five stages. This architecture assumes you have already deployed Odoo on a server. Here is the recommended pipeline architecture:

lintCode quality checks with pylint-odoo, flake8
testRun Odoo unit tests and integration tests
buildBuild Docker images, prepare assets
deploy-stagingDeploy to staging environment
deploy-productionDeploy to production (manual trigger)

Jobs Overview

JobStageTrigger
pylint-odoolintAll branches
flake8lintAll branches
odoo-teststestMerge requests + main
build-imagebuildmain + staging
deploy-stagingdeploy-stagingstaging branch
deploy-productiondeploy-productionmain branch (manual)

3Complete .gitlab-ci.yml Examples

Here are production-ready .gitlab-ci.yml configurations for different Odoo deployment scenarios.

Basic Pipeline

A minimal pipeline with linting, testing, and deployment:

.gitlab-ci.yml - Basic Pipelineyaml
1# GitLab CI/CD Pipeline for Odoo
2# Tested with Odoo 17.0 and 18.0
3
4stages:
5 - lint
6 - test
7 - deploy
8
9variables:
10 ODOO_VERSION: "17.0"
11 POSTGRES_DB: odoo_test
12 POSTGRES_USER: odoo
13 POSTGRES_PASSWORD: odoo
14 POSTGRES_HOST_AUTH_METHOD: trust
15
16# Cache pip dependencies
17cache:
18 key: "${CI_COMMIT_REF_SLUG}"
19 paths:
20 - .cache/pip
21
22# Linting with pylint-odoo
23pylint:
24 stage: lint
25 image: python:3.10
26 before_script:
27 - pip install pylint-odoo
28 script:
29 - pylint --load-plugins=pylint_odoo -d all -e odoolint addons/
30 allow_failure: true
31
32# Run Odoo tests
33test:
34 stage: test
35 image: odoo:17.0
36 services:
37 - postgres:15
38 variables:
39 HOST: postgres
40 script:
41 - odoo --db_host=postgres --db_user=odoo --db_password=odoo
42 -d test_db -i base --test-enable --stop-after-init
43 --log-level=test
44 only:
45 - merge_requests
46 - main
47
48# Deploy to staging
49deploy-staging:
50 stage: deploy
51 image: alpine:latest
52 before_script:
53 - apk add --no-cache openssh-client rsync
54 - eval $(ssh-agent -s)
55 - echo "$SSH_PRIVATE_KEY" | ssh-add -
56 - mkdir -p ~/.ssh && chmod 700 ~/.ssh
57 - ssh-keyscan -H $STAGING_SERVER >> ~/.ssh/known_hosts
58 script:
59 - rsync -avz --delete addons/ $SSH_USER@$STAGING_SERVER:/opt/odoo/custom-addons/
60 - ssh $SSH_USER@$STAGING_SERVER "sudo systemctl restart odoo"
61 environment:
62 name: staging
63 url: https://staging.example.com
64 only:
65 - staging

Advanced Pipeline with Odoo Tests

A comprehensive pipeline with module-specific tests and Docker builds. For more on Docker-based deployments, see our Odoo Docker guide:

.gitlab-ci.yml - Advanced Pipelineyaml
1# Advanced GitLab CI/CD for Odoo with Docker
2# Supports multi-branch deployments and review apps
3
4stages:
5 - lint
6 - test
7 - build
8 - deploy-staging
9 - deploy-production
10
11variables:
12 DOCKER_DRIVER: overlay2
13 DOCKER_TLS_CERTDIR: "/certs"
14 REGISTRY: registry.gitlab.com
15 IMAGE_NAME: $CI_REGISTRY_IMAGE/odoo
16 ODOO_VERSION: "17.0"
17
18.docker_login: &docker_login
19 - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
20
21# ============ LINT STAGE ============
22pylint-odoo:
23 stage: lint
24 image: python:3.10-slim
25 cache:
26 key: pip-cache
27 paths:
28 - .cache/pip
29 variables:
30 PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
31 before_script:
32 - pip install pylint-odoo
33 script:
34 - find addons -name '*.py' -not -path '*/migrations/*' | head -100 |
35 xargs pylint --load-plugins=pylint_odoo -d all -e odoolint --exit-zero
36 rules:
37 - if: $CI_PIPELINE_SOURCE == "merge_request_event"
38 - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
39
40flake8:
41 stage: lint
42 image: python:3.10-slim
43 before_script:
44 - pip install flake8
45 script:
46 - flake8 addons --max-line-length=120 --ignore=E501,W503,W504
47 allow_failure: true
48 rules:
49 - if: $CI_PIPELINE_SOURCE == "merge_request_event"
50
51# ============ TEST STAGE ============
52.test_template: &test_template
53 stage: test
54 image: odoo:$ODOO_VERSION
55 services:
56 - name: postgres:15
57 alias: db
58 variables:
59 POSTGRES_DB: postgres
60 POSTGRES_USER: odoo
61 POSTGRES_PASSWORD: odoo
62
63odoo-unit-tests:
64 <<: *test_template
65 script:
66 - |
67 odoo --db_host=db --db_user=odoo --db_password=odoo \
68 -d test_db \
69 --addons-path=/usr/lib/python3/dist-packages/odoo/addons,/mnt/extra-addons,addons \
70 -i $(ls addons | tr '\n' ',' | sed 's/,$//') \
71 --test-enable \
72 --stop-after-init \
73 --log-level=test
74 rules:
75 - if: $CI_PIPELINE_SOURCE == "merge_request_event"
76 - if: $CI_COMMIT_BRANCH == "main"
77 - if: $CI_COMMIT_BRANCH == "staging"
78 artifacts:
79 when: always
80 paths:
81 - odoo.log
82 expire_in: 1 week
83
84# ============ BUILD STAGE ============
85build-docker:
86 stage: build
87 image: docker:24-dind
88 services:
89 - docker:24-dind
90 before_script:
91 - *docker_login
92 script:
93 - docker build -t $IMAGE_NAME:$CI_COMMIT_SHA -t $IMAGE_NAME:latest .
94 - docker push $IMAGE_NAME:$CI_COMMIT_SHA
95 - docker push $IMAGE_NAME:latest
96 rules:
97 - if: $CI_COMMIT_BRANCH == "main"
98 - if: $CI_COMMIT_BRANCH == "staging"
99
100# ============ DEPLOY STAGING ============
101deploy-staging:
102 stage: deploy-staging
103 image: alpine:latest
104 before_script:
105 - apk add --no-cache openssh-client
106 - eval $(ssh-agent -s)
107 - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
108 - mkdir -p ~/.ssh && chmod 700 ~/.ssh
109 - ssh-keyscan -H $STAGING_HOST >> ~/.ssh/known_hosts
110 script:
111 - |
112 ssh $SSH_USER@$STAGING_HOST << 'EOF'
113 cd /opt/odoo
114 docker pull $IMAGE_NAME:$CI_COMMIT_SHA
115 docker-compose down
116 docker-compose up -d
117 # Wait for Odoo to start
118 sleep 30
119 # Run migrations
120 docker-compose exec -T odoo odoo -u all -d $ODOO_DATABASE --stop-after-init
121 EOF
122 environment:
123 name: staging
124 url: https://staging.example.com
125 rules:
126 - if: $CI_COMMIT_BRANCH == "staging"
127
128# ============ DEPLOY PRODUCTION ============
129deploy-production:
130 stage: deploy-production
131 image: alpine:latest
132 before_script:
133 - apk add --no-cache openssh-client
134 - eval $(ssh-agent -s)
135 - echo "$SSH_PRIVATE_KEY_PROD" | tr -d '\r' | ssh-add -
136 - mkdir -p ~/.ssh && chmod 700 ~/.ssh
137 - ssh-keyscan -H $PRODUCTION_HOST >> ~/.ssh/known_hosts
138 script:
139 - |
140 ssh $SSH_USER@$PRODUCTION_HOST << 'EOF'
141 cd /opt/odoo
142 # Backup database before deployment
143 docker-compose exec -T db pg_dump -U odoo $ODOO_DATABASE > backup_$(date +%Y%m%d_%H%M%S).sql
144 docker pull $IMAGE_NAME:$CI_COMMIT_SHA
145 docker-compose down
146 docker-compose up -d
147 sleep 30
148 docker-compose exec -T odoo odoo -u all -d $ODOO_DATABASE --stop-after-init
149 EOF
150 environment:
151 name: production
152 url: https://erp.example.com
153 rules:
154 - if: $CI_COMMIT_BRANCH == "main"
155 when: manual

Multi-Branch Deployment

Deploy different branches to different environments automatically:

.gitlab-ci.yml - Multi-Branchyaml
1# Multi-branch deployment configuration
2# feature/* -> review apps
3# staging -> staging server
4# main -> production
5
6stages:
7 - test
8 - deploy
9
10# Review apps for feature branches
11deploy-review:
12 stage: deploy
13 script:
14 - echo "Deploying to review environment..."
15 - ./scripts/deploy-review.sh $CI_COMMIT_REF_SLUG
16 environment:
17 name: review/$CI_COMMIT_REF_SLUG
18 url: https://$CI_COMMIT_REF_SLUG.review.example.com
19 on_stop: stop-review
20 rules:
21 - if: $CI_MERGE_REQUEST_ID
22
23stop-review:
24 stage: deploy
25 script:
26 - ./scripts/stop-review.sh $CI_COMMIT_REF_SLUG
27 environment:
28 name: review/$CI_COMMIT_REF_SLUG
29 action: stop
30 rules:
31 - if: $CI_MERGE_REQUEST_ID
32 when: manual
33
34# Staging deployment
35deploy-staging:
36 stage: deploy
37 script:
38 - ./scripts/deploy.sh staging
39 environment:
40 name: staging
41 url: https://staging.example.com
42 rules:
43 - if: $CI_COMMIT_BRANCH == "staging"
44
45# Production deployment
46deploy-production:
47 stage: deploy
48 script:
49 - ./scripts/deploy.sh production
50 environment:
51 name: production
52 url: https://erp.example.com
53 rules:
54 - if: $CI_COMMIT_BRANCH == "main"
55 when: manual

4Setting Up Environment Variables

Configure these CI/CD variables in GitLab under Settings > CI/CD > Variables. Mark sensitive values as "Masked" and "Protected".

Required Variables

VariableDescriptionProtected
SSH_PRIVATE_KEYSSH key for staging server accessYes
SSH_PRIVATE_KEY_PRODSSH key for production serverYes
SSH_USERSSH username for deploymentNo
STAGING_HOSTStaging server hostname/IPNo
PRODUCTION_HOSTProduction server hostname/IPYes
ODOO_DATABASEDatabase name on serversNo
ODOO_ADMIN_PASSWORDOdoo master passwordYes

Setting Up SSH Keys

Generate a dedicated SSH key pair for CI/CD deployments:

Generate Deploy Keysbash
# Generate SSH key pair (on your local machine)
ssh-keygen -t ed25519 -C "gitlab-ci-deploy" -f gitlab-deploy-key
# Copy public key to your servers
ssh-copy-id -i gitlab-deploy-key.pub user@staging-server
ssh-copy-id -i gitlab-deploy-key.pub user@production-server
# Add private key to GitLab CI/CD variables
cat gitlab-deploy-key
# Copy the output and add as SSH_PRIVATE_KEY variable in GitLab

Secrets Management Best Practices

Use 'Masked' for all sensitive values to prevent log exposure
Use 'Protected' for production-only variables
Rotate SSH keys regularly (at least annually)
Use environment-specific variable scopes
Never commit secrets to repository - use .gitignore

5Running Odoo Tests in CI

Automated testing is crucial for reliable deployments. Here is how to set up different types of tests in your pipeline.

Unit Tests

Unit test job configurationyaml
odoo-unit-tests:
stage: test
image: odoo:17.0
services:
- name: postgres:15
alias: db
variables:
POSTGRES_USER: odoo
POSTGRES_PASSWORD: odoo
POSTGRES_DB: postgres
script:
# Install custom module dependencies
- pip install -r requirements.txt || true
# Run tests for all custom modules
- |
MODULES=$(ls addons | tr '\n' ',' | sed 's/,$//')
odoo --db_host=db --db_user=odoo --db_password=odoo \
--addons-path=/usr/lib/python3/dist-packages/odoo/addons,addons \
-d test_db \
-i $MODULES \
--test-enable \
--stop-after-init \
--log-level=test \
2>&1 | tee odoo-test.log
# Check for test failures
- "! grep -q 'FAIL:' odoo-test.log"
artifacts:
when: always
paths:
- odoo-test.log
expire_in: 1 week

Linting with pylint-odoo

Pylint configurationyaml
pylint-odoo:
stage: lint
image: python:3.10-slim
cache:
key: pip-lint
paths:
- .cache/pip
before_script:
- pip install pylint-odoo
script:
# Check for Odoo-specific issues
- |
pylint --load-plugins=pylint_odoo \
--rcfile=.pylintrc \
--output-format=colorized \
addons/
allow_failure: true
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"

Sample .pylintrc for Odoo

.pylintrcini
[MASTER]
load-plugins=pylint_odoo
[ODOOLINT]
readme_template_url = https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst
manifest_required_authors = Your Company Name
manifest_required_keys = license
manifest_deprecated_keys = description,active
[MESSAGES CONTROL]
disable=all
enable=odoolint
[REPORTS]
output-format=colorized

Code Quality with Flake8

Flake8 configurationyaml
flake8:
stage: lint
image: python:3.10-slim
before_script:
- pip install flake8 flake8-docstrings
script:
- |
flake8 addons \
--max-line-length=120 \
--ignore=E501,W503,W504,D100,D104 \
--exclude=migrations,__pycache__ \
--count \
--statistics
allow_failure: true

6Staging Environments

Staging environments let you test changes before production. GitLab offers powerful environment management features. For more comprehensive staging strategies, see our CI/CD and Staging guide.

Branch-Based Staging

Deploy specific branches to dedicated staging environments:

Branch-based staging deploymentyaml
deploy-staging:
stage: deploy
script:
- ./scripts/deploy.sh staging
environment:
name: staging
url: https://staging.example.com
rules:
- if: $CI_COMMIT_BRANCH == "staging"
deploy-qa:
stage: deploy
script:
- ./scripts/deploy.sh qa
environment:
name: qa
url: https://qa.example.com
rules:
- if: $CI_COMMIT_BRANCH == "qa"

Review Apps for Merge Requests

Create temporary environments for each merge request to review changes:

Review app configurationyaml
# Deploy review app for each MR
deploy-review:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client docker-cli
script:
- |
# Create unique database for this review
REVIEW_DB="review_${CI_MERGE_REQUEST_IID}"
# Deploy to review server
ssh $SSH_USER@$REVIEW_SERVER << EOF
docker run -d --name odoo-review-$CI_MERGE_REQUEST_IID \
-p $((8069 + CI_MERGE_REQUEST_IID)):8069 \
-e DB_NAME=$REVIEW_DB \
$IMAGE_NAME:$CI_COMMIT_SHA
EOF
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://review-$CI_MERGE_REQUEST_IID.example.com
on_stop: stop-review
auto_stop_in: 1 week
rules:
- if: $CI_MERGE_REQUEST_ID
# Cleanup review app
stop-review:
stage: deploy
image: alpine:latest
script:
- |
ssh $SSH_USER@$REVIEW_SERVER << EOF
docker stop odoo-review-$CI_MERGE_REQUEST_IID || true
docker rm odoo-review-$CI_MERGE_REQUEST_IID || true
EOF
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
rules:
- if: $CI_MERGE_REQUEST_ID
when: manual

Database Cloning for Staging

Clone production data to staging for realistic testing:

scripts/clone-db-to-staging.shbash
#!/bin/bash
# Clone production database to staging
# Run this as a scheduled pipeline job
set -e
PROD_HOST="production.example.com"
STAGING_HOST="staging.example.com"
DB_NAME="odoo_production"
STAGING_DB="odoo_staging"
echo "Creating backup from production..."
ssh $SSH_USER@$PROD_HOST "pg_dump -U odoo $DB_NAME | gzip" > /tmp/prod_backup.sql.gz
echo "Restoring to staging..."
gunzip -c /tmp/prod_backup.sql.gz | ssh $SSH_USER@$STAGING_HOST "psql -U odoo $STAGING_DB"
echo "Anonymizing staging data..."
ssh $SSH_USER@$STAGING_HOST << 'EOF'
psql -U odoo odoo_staging << SQL
-- Anonymize emails
UPDATE res_partner SET email = 'test+' || id || '@example.com' WHERE email IS NOT NULL;
-- Clear sensitive data
UPDATE res_users SET password = 'demo' WHERE login != 'admin';
-- Disable outgoing mail
UPDATE ir_mail_server SET active = false;
SQL
EOF
echo "Database cloned and anonymized successfully!"

OEC.sh GitLab Integration

While you can build your own CI/CD pipeline, OEC.sh provides native GitLab integration that handles everything automatically. Explore all platform features or check out developer-focused capabilities:

  • Native GitLab webhook support - no submodule workarounds
  • Auto-deploy on push to any branch
  • Branch-based environments (staging, production, feature branches)
  • Automatic Odoo tests before deployment
  • One-click rollback to any previous deployment
  • Database backups before every deployment
  • Zero-downtime deployments
  • Support for gitlab.com and self-hosted GitLab

Connecting Your GitLab Repository

1

Connect GitLab in OEC.sh

Link your GitLab account (gitlab.com or self-hosted) in project settings

2

Select Repository and Branch

Choose your Odoo project repository and main branch

3

Push and Deploy

Webhooks are auto-configured - push to deploy instantly

8Best Practices

Follow these best practices for reliable and secure GitLab CI/CD pipelines for Odoo in 2026:

Use Merge Request Pipelines

Run tests on merge requests before merging to catch issues early:

# Run full tests only on MRs and protected branches
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Protect Production Branch

In GitLab Settings > Repository > Protected Branches, protect your main/production branch:

  • Require merge request approvals
  • Prevent direct pushes
  • Require passing pipelines before merge
  • Use manual deployment to production

Use GitLab Environments

Track deployments with GitLab environments for visibility and rollback:

environment:
name: production
url: https://erp.example.com
deployment_tier: production

Cache Dependencies

Speed up pipelines by caching pip packages and node modules:

cache:
key:
files:
- requirements.txt
paths:
- .cache/pip
- venv/
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

Backup Before Deploy

Always create a database backup before deploying to production:

# Add to deployment script
pg_dump -U odoo $ODOO_DATABASE | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz

9Frequently Asked Questions

Related Guides

Skip the Pipeline Setup

OEC.sh provides native GitLab integration out of the box. Connect your repository and deploy instantly - no CI/CD configuration required.

GitLab
Native Support
0
Config Needed
1-Click
Rollback