diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5442c68bb8cf59a17e67570d02a7b468fa229385..ee99df6d9c7d8cdf9165c85b8723010cab4672bf 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,7 +1,8 @@
 stages:
   - quality
+  - build
   - tests
-  #- build
+  - deploy
 
 workflow:
   rules:
@@ -21,7 +22,7 @@ workflow:
 fmt_and_clippy:
   extends: .env
   rules:
-    - if: $CI_COMMIT_TAG
+    - if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH == "master"'
       when: never
     - if: $CI_PIPELINE_SOURCE == "merge_request_event"
     - when: manual
@@ -32,18 +33,91 @@ fmt_and_clippy:
     - cargo clippy -- -V
     - cargo clippy --all --tests -- -D warnings
 
-tests:
+build_debug:
+  extends: .env
+  rules:
+    - if: $CI_COMMIT_TAG
+      when: never
+    - if: '$CI_PIPELINE_SOURCE == "merge_request_event" || $CI_COMMIT_BRANCH == "master"'
+    - when: manual
+  stage: build
+  script:
+    - cargo build
+    - mkdir build
+    - mv target/debug/duniter build/duniter
+  artifacts:
+    paths:
+      - build/
+
+build_release:
+  extends: .env
+  rules:
+    - if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH =~ /^release/'
+    - when: never
+  stage: build
+  script:
+    - cargo build --release
+    - mkdir build
+    - mv target/release/duniter build/duniter
+  artifacts:
+    paths:
+      - build/
+    expire_in: 1 day
+
+tests_debug:
   extends: .env
   rules:
     - if: $CI_COMMIT_REF_NAME =~ /^wip*$/
       when: manual
     - if: $CI_COMMIT_TAG
       when: never
-    - if: '$CI_MERGE_REQUEST_ID ||$CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH =~ /^release/'
+    - if: '$CI_MERGE_REQUEST_ID || $CI_COMMIT_BRANCH == "master"'
     - when: manual
   stage: tests
   variables:
+    DUNITER_BINARY_PATH: "../build/duniter"
+    DUNITER_INTEGRATION_TESTS_SPAWN_NODE_DURATION: "20"
+  script:
+    - cargo test
+  dependencies:
+    - build_debug
+
+tests_release:
+  extends: .env
+  rules:
+    - if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH =~ /^release/'
+    - when: never
+  stage: tests
+  variables:
+    DUNITER_BINARY_PATH: "../build/duniter"
     DUNITER_INTEGRATION_TESTS_SPAWN_NODE_DURATION: "10"
   script:
-    - cargo build
     - cargo test
+  dependencies:
+    - build_release
+
+.docker-build-app-image:
+  stage: deploy
+  image: docker:18.06
+  tags:
+    - redshift
+  services:
+    - docker:18.06-dind
+  before_script:
+    - docker info
+  script:
+    - docker pull $CI_REGISTRY_IMAGE:$IMAGE_TAG || true
+    - docker build --cache-from $CI_REGISTRY_IMAGE:$IMAGE_TAG --pull -t "$CI_REGISTRY_IMAGE:$IMAGE_TAG" -f $DOCKERFILE_PATH .
+    - docker login -u "duniterteam" -p "$DUNITERTEAM_PASSWD"
+    - docker tag "$CI_REGISTRY_IMAGE:$IMAGE_TAG" "duniter/duniter-v2s:$IMAGE_TAG"
+    - docker push "duniter/duniter-v2s:$IMAGE_TAG"
+
+deploy_docker_debug:
+  extends: .docker-build-app-image
+  rules:
+    - if: $CI_COMMIT_TAG
+      when: never
+    - if: $CI_COMMIT_BRANCH == "master"
+  variables:
+    DOCKERFILE_PATH: "docker/duniter-debug.Dockerfile"
+    IMAGE_TAG: "debug-sha-$CI_COMMIT_SHORT_SHA"
diff --git a/docker/duniter-debug.Dockerfile b/docker/duniter-debug.Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..fa2784770f8765c891be7581e47e1975880a6d43
--- /dev/null
+++ b/docker/duniter-debug.Dockerfile
@@ -0,0 +1,36 @@
+# Duniter debug node
+#
+# Requires to run from repository root and to copy the binary in the build folder
+# (part of the CI workflow)
+
+FROM docker.io/library/ubuntu:20.04 AS builder
+
+RUN apt-get update && apt-get install -y ca-certificates && update-ca-certificates
+
+FROM debian:buster-slim
+LABEL maintainer "elois@duniter.org"
+LABEL description="Binary for duniter debug node"
+
+RUN useradd -m -u 1000 -U -s /bin/sh -d /duniter duniter && \
+	mkdir -p /duniter/.local/share && \
+	mkdir /data && \
+	chown -R duniter:duniter /data && \
+	ln -s /data /duniter/.local/share/duniter && \
+	rm -rf /usr/bin /usr/sbin
+
+COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
+
+USER duniter
+
+COPY --chown=duniter build/duniter /duniter/duniter
+RUN chmod uog+x /duniter/duniter
+
+# 30333 for p2p
+# 9933 for RPC call
+# 9944 for Websocket
+# 9615 for Prometheus (metrics)
+EXPOSE 30333 9933 9944 9615
+
+VOLUME ["/data"]
+
+ENTRYPOINT ["/duniter/duniter"]
diff --git a/integration-tests/tests/common/mod.rs b/integration-tests/tests/common/mod.rs
index ebb179f90e498e438094706fda732627a4bf7611..90770ed3e9378b218785dda1832f3678143f88fd 100644
--- a/integration-tests/tests/common/mod.rs
+++ b/integration-tests/tests/common/mod.rs
@@ -41,11 +41,14 @@ impl Drop for Process {
 }
 
 pub async fn spawn_node() -> (Api, Client, Process) {
+    let duniter_binary_path = std::env::var("DUNITER_BINARY_PATH")
+        .unwrap_or_else(|_| "../target/debug/duniter".to_owned());
+
     let p2p_port = portpicker::pick_unused_port().expect("No ports free");
     let rpc_port = portpicker::pick_unused_port().expect("No ports free");
     let ws_port = portpicker::pick_unused_port().expect("No ports free");
     let process = Process(
-        Command::new("../target/debug/duniter")
+        Command::new(duniter_binary_path)
             .args([
                 "--execution=Native",
                 "--no-telemetry",