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, flake8testRun Odoo unit tests and integration testsbuildBuild Docker images, prepare assetsdeploy-stagingDeploy to staging environmentdeploy-productionDeploy to production (manual trigger)Jobs Overview
| Job | Stage | Trigger |
|---|---|---|
| pylint-odoo | lint | All branches |
| flake8 | lint | All branches |
| odoo-tests | test | Merge requests + main |
| build-image | build | main + staging |
| deploy-staging | deploy-staging | staging branch |
| deploy-production | deploy-production | main 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:
1# GitLab CI/CD Pipeline for Odoo2# Tested with Odoo 17.0 and 18.034stages:5 - lint6 - test7 - deploy89variables:10 ODOO_VERSION: "17.0"11 POSTGRES_DB: odoo_test12 POSTGRES_USER: odoo13 POSTGRES_PASSWORD: odoo14 POSTGRES_HOST_AUTH_METHOD: trust1516# Cache pip dependencies17cache:18 key: "${CI_COMMIT_REF_SLUG}"19 paths:20 - .cache/pip2122# Linting with pylint-odoo23pylint:24 stage: lint25 image: python:3.1026 before_script:27 - pip install pylint-odoo28 script:29 - pylint --load-plugins=pylint_odoo -d all -e odoolint addons/30 allow_failure: true3132# Run Odoo tests33test:34 stage: test35 image: odoo:17.036 services:37 - postgres:1538 variables:39 HOST: postgres40 script:41 - odoo --db_host=postgres --db_user=odoo --db_password=odoo42 -d test_db -i base --test-enable --stop-after-init43 --log-level=test44 only:45 - merge_requests46 - main4748# Deploy to staging49deploy-staging:50 stage: deploy51 image: alpine:latest52 before_script:53 - apk add --no-cache openssh-client rsync54 - eval $(ssh-agent -s)55 - echo "$SSH_PRIVATE_KEY" | ssh-add -56 - mkdir -p ~/.ssh && chmod 700 ~/.ssh57 - ssh-keyscan -H $STAGING_SERVER >> ~/.ssh/known_hosts58 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: staging63 url: https://staging.example.com64 only:65 - stagingAdvanced 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:
1# Advanced GitLab CI/CD for Odoo with Docker2# Supports multi-branch deployments and review apps34stages:5 - lint6 - test7 - build8 - deploy-staging9 - deploy-production1011variables:12 DOCKER_DRIVER: overlay213 DOCKER_TLS_CERTDIR: "/certs"14 REGISTRY: registry.gitlab.com15 IMAGE_NAME: $CI_REGISTRY_IMAGE/odoo16 ODOO_VERSION: "17.0"1718.docker_login: &docker_login19 - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY2021# ============ LINT STAGE ============22pylint-odoo:23 stage: lint24 image: python:3.10-slim25 cache:26 key: pip-cache27 paths:28 - .cache/pip29 variables:30 PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"31 before_script:32 - pip install pylint-odoo33 script:34 - find addons -name '*.py' -not -path '*/migrations/*' | head -100 |35 xargs pylint --load-plugins=pylint_odoo -d all -e odoolint --exit-zero36 rules:37 - if: $CI_PIPELINE_SOURCE == "merge_request_event"38 - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH3940flake8:41 stage: lint42 image: python:3.10-slim43 before_script:44 - pip install flake845 script:46 - flake8 addons --max-line-length=120 --ignore=E501,W503,W50447 allow_failure: true48 rules:49 - if: $CI_PIPELINE_SOURCE == "merge_request_event"5051# ============ TEST STAGE ============52.test_template: &test_template53 stage: test54 image: odoo:$ODOO_VERSION55 services:56 - name: postgres:1557 alias: db58 variables:59 POSTGRES_DB: postgres60 POSTGRES_USER: odoo61 POSTGRES_PASSWORD: odoo6263odoo-unit-tests:64 <<: *test_template65 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=test74 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: always80 paths:81 - odoo.log82 expire_in: 1 week8384# ============ BUILD STAGE ============85build-docker:86 stage: build87 image: docker:24-dind88 services:89 - docker:24-dind90 before_script:91 - *docker_login92 script:93 - docker build -t $IMAGE_NAME:$CI_COMMIT_SHA -t $IMAGE_NAME:latest .94 - docker push $IMAGE_NAME:$CI_COMMIT_SHA95 - docker push $IMAGE_NAME:latest96 rules:97 - if: $CI_COMMIT_BRANCH == "main"98 - if: $CI_COMMIT_BRANCH == "staging"99100# ============ DEPLOY STAGING ============101deploy-staging:102 stage: deploy-staging103 image: alpine:latest104 before_script:105 - apk add --no-cache openssh-client106 - eval $(ssh-agent -s)107 - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -108 - mkdir -p ~/.ssh && chmod 700 ~/.ssh109 - ssh-keyscan -H $STAGING_HOST >> ~/.ssh/known_hosts110 script:111 - |112 ssh $SSH_USER@$STAGING_HOST << 'EOF'113 cd /opt/odoo114 docker pull $IMAGE_NAME:$CI_COMMIT_SHA115 docker-compose down116 docker-compose up -d117 # Wait for Odoo to start118 sleep 30119 # Run migrations120 docker-compose exec -T odoo odoo -u all -d $ODOO_DATABASE --stop-after-init121 EOF122 environment:123 name: staging124 url: https://staging.example.com125 rules:126 - if: $CI_COMMIT_BRANCH == "staging"127128# ============ DEPLOY PRODUCTION ============129deploy-production:130 stage: deploy-production131 image: alpine:latest132 before_script:133 - apk add --no-cache openssh-client134 - eval $(ssh-agent -s)135 - echo "$SSH_PRIVATE_KEY_PROD" | tr -d '\r' | ssh-add -136 - mkdir -p ~/.ssh && chmod 700 ~/.ssh137 - ssh-keyscan -H $PRODUCTION_HOST >> ~/.ssh/known_hosts138 script:139 - |140 ssh $SSH_USER@$PRODUCTION_HOST << 'EOF'141 cd /opt/odoo142 # Backup database before deployment143 docker-compose exec -T db pg_dump -U odoo $ODOO_DATABASE > backup_$(date +%Y%m%d_%H%M%S).sql144 docker pull $IMAGE_NAME:$CI_COMMIT_SHA145 docker-compose down146 docker-compose up -d147 sleep 30148 docker-compose exec -T odoo odoo -u all -d $ODOO_DATABASE --stop-after-init149 EOF150 environment:151 name: production152 url: https://erp.example.com153 rules:154 - if: $CI_COMMIT_BRANCH == "main"155 when: manualMulti-Branch Deployment
Deploy different branches to different environments automatically:
1# Multi-branch deployment configuration2# feature/* -> review apps3# staging -> staging server4# main -> production56stages:7 - test8 - deploy910# Review apps for feature branches11deploy-review:12 stage: deploy13 script:14 - echo "Deploying to review environment..."15 - ./scripts/deploy-review.sh $CI_COMMIT_REF_SLUG16 environment:17 name: review/$CI_COMMIT_REF_SLUG18 url: https://$CI_COMMIT_REF_SLUG.review.example.com19 on_stop: stop-review20 rules:21 - if: $CI_MERGE_REQUEST_ID2223stop-review:24 stage: deploy25 script:26 - ./scripts/stop-review.sh $CI_COMMIT_REF_SLUG27 environment:28 name: review/$CI_COMMIT_REF_SLUG29 action: stop30 rules:31 - if: $CI_MERGE_REQUEST_ID32 when: manual3334# Staging deployment35deploy-staging:36 stage: deploy37 script:38 - ./scripts/deploy.sh staging39 environment:40 name: staging41 url: https://staging.example.com42 rules:43 - if: $CI_COMMIT_BRANCH == "staging"4445# Production deployment46deploy-production:47 stage: deploy48 script:49 - ./scripts/deploy.sh production50 environment:51 name: production52 url: https://erp.example.com53 rules:54 - if: $CI_COMMIT_BRANCH == "main"55 when: manual4Setting Up Environment Variables
Configure these CI/CD variables in GitLab under Settings > CI/CD > Variables. Mark sensitive values as "Masked" and "Protected".
Required Variables
| Variable | Description | Protected |
|---|---|---|
| SSH_PRIVATE_KEY | SSH key for staging server access | Yes |
| SSH_PRIVATE_KEY_PROD | SSH key for production server | Yes |
| SSH_USER | SSH username for deployment | No |
| STAGING_HOST | Staging server hostname/IP | No |
| PRODUCTION_HOST | Production server hostname/IP | Yes |
| ODOO_DATABASE | Database name on servers | No |
| ODOO_ADMIN_PASSWORD | Odoo master password | Yes |
Setting Up SSH Keys
Generate a dedicated SSH key pair for CI/CD deployments:
# 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 serversssh-copy-id -i gitlab-deploy-key.pub user@staging-serverssh-copy-id -i gitlab-deploy-key.pub user@production-server# Add private key to GitLab CI/CD variablescat gitlab-deploy-key# Copy the output and add as SSH_PRIVATE_KEY variable in GitLabSecrets Management Best Practices
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
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 weekLinting with pylint-odoo
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
[MASTER]load-plugins=pylint_odoo[ODOOLINT]readme_template_url = https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rstmanifest_required_authors = Your Company Namemanifest_required_keys = licensemanifest_deprecated_keys = description,active[MESSAGES CONTROL]disable=allenable=odoolint[REPORTS]output-format=colorizedCode Quality with Flake8
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: true6Staging 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:
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:
# Deploy review app for each MRdeploy-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 appstop-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: manualDatabase Cloning for Staging
Clone production data to staging for realistic testing:
#!/bin/bash# Clone production database to staging# Run this as a scheduled pipeline jobset -ePROD_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.gzecho "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;SQLEOFecho "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
Connect GitLab in OEC.sh
Link your GitLab account (gitlab.com or self-hosted) in project settings
Select Repository and Branch
Choose your Odoo project repository and main branch
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 branchesrules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHProtect 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: productionCache 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 scriptpg_dump -U odoo $ODOO_DATABASE | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz9Frequently Asked Questions
Related Guides
CI/CD and Staging Environments
Complete guide to staging workflows, testing strategies, and CI/CD best practices.
Odoo Docker Deployment
Production-ready Docker Compose setup with container best practices.
Deploy Odoo on Cloud Server
Manual deployment guide covering Ubuntu, PostgreSQL, Nginx, and SSL setup.
Backup and Recovery Guide
Automated backup strategies and disaster recovery for production Odoo.
OEC.sh for Developers
Learn how OEC.sh streamlines development workflows with Git integration.
OEC.sh Pricing
Compare plans and find the right tier for your GitLab-integrated Odoo deployment.