diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ca2bdf39afe6617cc923ba91db699a6b74086005..6fba59c3cb117ff04f0a71aca447ab0256f2b4c0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,16 +1,15 @@
+
 workflow:
   rules:
-    - if: $CI_MERGE_REQUEST_ID               # Execute jobs in merge request context
-    - if: $CI_COMMIT_BRANCH == 'master'      # Execute jobs when a new commit is pushed to master branch
+    - if: $CI_MERGE_REQUEST_ID || $CI_COMMIT_REF_NAME =~ /master/ # Execute jobs in merge request context, or commit in master branch
 
 stages:
   - Docker build
   - Static Analysis
   - Tests
+  - Ship
 
-Build the docker image:
-  stage: Docker build
-  allow_failure: false
+.dind_base:
   tags: [dind]
   image: docker/compose:1.29.2
   variables:
@@ -24,6 +23,13 @@ Build the docker image:
     # we use $CI_REGISTRY_PASSWORD here which is a special variable provided by GitLab
     # https://docs.gitlab.com/ce/ci/variables/predefined_variables.html
     - echo -n $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
+
+Build the docker image:
+  stage: Docker build
+  allow_failure: false
+  extends: .dind_base
+  except:
+    - master
   script:
     - docker info
     - >
@@ -36,11 +42,9 @@ Build the docker image:
       --label "org.opencontainers.image.revision=$CI_COMMIT_SHA"
       --label "org.opencontainers.image.version=$CI_COMMIT_REF_NAME"
       --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
-      --build-arg "BASE_IMAGE=gitlab-registry.irstea.fr/remi.cresson/otbtf/otbtf3.0:cpu-basic-dev"
+      --build-arg "BASE_IMAGE=gitlab-registry.irstea.fr/remi.cresson/otbtf/3.2.1:cpu-basic-dev"
       .
     - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
-    - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME $CI_REGISTRY_IMAGE:latest
-    - docker push $CI_REGISTRY_IMAGE:latest
 
 .static_analysis_base:
   image: $CI_REGISTRY_IMAGE:latest
@@ -50,7 +54,7 @@ Build the docker image:
 flake8:
   extends: .static_analysis_base
   script:
-   - sudo apt update && sudo apt install -y flake8 && python -m flake8 --max-line-length=120 $PWD/decloud
+   - sudo apt update && sudo apt install -y flake8 && python -m flake8 -ignore=E402 --max-line-length=120 $PWD/decloud
 
 pylint:
   extends: .static_analysis_base
@@ -125,3 +129,13 @@ train_from_tfrecords:
   script:
     - pytest -o log_cli=true --log-cli-level=INFO --junitxml=report_train_from_tfrecords.xml tests/train_from_tfrecords_unittest.py
 
+deploy:
+  stage: Ship
+  only:
+    - master
+  extends: .dind_base
+  script:
+    - echo "Shipping!"
+    - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
+    - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME $CI_REGISTRY_IMAGE:latest
+    - docker push $CI_REGISTRY_IMAGE:latest
diff --git a/decloud/core/system.py b/decloud/core/system.py
index b4810135a43bc0e960fb46704991e8a61fa77046..8114052489da3a761ade056f9003ab5716e5c3ba 100644
--- a/decloud/core/system.py
+++ b/decloud/core/system.py
@@ -41,10 +41,16 @@ def get_commit_hash():
     """ Return the git hash of the repository """
     repo = git.Repo(os.path.dirname(os.path.realpath(__file__)), search_parent_directories=True)
 
+    commit_hash = "nohash"
     try:
-        commit_hash = repo.active_branch.name + "_" + repo.head.object.hexsha[0:5]
-    except TypeError:
-        commit_hash = 'DETACHED_' + repo.head.object.hexsha[0:5]
+        commit_hash = repo.head.object.hexsha[0:5]
+    except (ValueError, TypeError) as e:
+        print(f"Unable to get commit hash! {e}")
+
+    try:
+        commit_hash = repo.active_branch.name + "_" + commit_hash
+    except (ValueError, TypeError) as e:
+        print(f"Unable to get branch name! {e}")
 
     return commit_hash
 
@@ -55,7 +61,7 @@ def get_directories(root):
     :param root: root directory
     :return: list of directories
     """
-    return [pathify(root) + item for item in os.listdir(root)]
+    return [os.path.join(root, item) for item in os.listdir(root)]
 
 
 def get_files(directory, ext=None):