diff --git a/gui/duniter-desktop b/gui/duniter-desktop
new file mode 100755
index 0000000000000000000000000000000000000000..6a0112e25bcaaf35967e8463dabd938fbf79f081
--- /dev/null
+++ b/gui/duniter-desktop
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# Get the directory name of this script, following symlinks
+pushd . >/dev/null
+DUNITER_DIRECTORY="${BASH_SOURCE[0]}";
+while [[ -h "${DUNITER_DIRECTORY}" ]]; do
+    cd "$(dirname "${DUNITER_DIRECTORY}")" >/dev/null
+    DUNITER_DIRECTORY="$(readlink "$(basename "${DUNITER_DIRECTORY}")")"
+done
+cd "$(dirname "${DUNITER_DIRECTORY}")" >/dev/null
+DUNITER_DIRECTORY="${PWD}";
+popd >/dev/null
+
+# Execute NW
+"${DUNITER_DIRECTORY}"/node_modules/.bin/nw "${DUNITER_DIRECTORY}"
diff --git a/release/Makefile b/release/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..8e2b531c8b393b50f9365100e3c2a1fea5004ea2
--- /dev/null
+++ b/release/Makefile
@@ -0,0 +1,166 @@
+# If “make” is called from a directory controlled by git (the main directory), it will create a directory
+# named “work” with a sub-directory taken from the “DEST” property, if provided. It will then clone itself
+# there, and the command will be executed from the clone.
+# If “make” is not called from a directory controlled by git, it will be executed in place.
+#
+# Requires the following dependencies:
+#  * Python 2.7 (build only)
+#  * NodeJS >=10.x (build and runtime)
+#  * npm (build only)
+#  * Rust (build only)
+#
+# Following parameters may be provided:
+#  * ARCH: The target architecture (default: autodetect).
+#  * PLATFORM: The target platform (default: autodetect).
+#  * ADD_DEBUG: Set to Y to add debug information in result (default: Y).
+#  * DEST: The destination sub-folder (default: empty).
+#
+# Main targets are:
+#  * desktop (default): Build a full desktop application.
+#  * server-gui: Build a server with GUI.
+#  * server: Build a server without GUI.
+#  * base-gui: Prepare any application requiring GUI.
+#  * base: Prepare any application.
+#  * wipe: Remove files which should not be delivered.
+#  * clean (only in main directory): remove the Makefile from target directory.
+#  * mrproper (only in main directory): remove the target directory.
+
+# This will depend on the current version
+NW_VERSION = 0.33.1
+ADDON_VERSION = 64
+DUNITER_UI_VERSION = 1.7.x
+DEV_FILES = $(wildcard .eslint* .git* .npm* .prettierignore Cargo.* deny.toml gui license-header.txt npm* \
+	tsconfig* tslint* yarn*)
+
+# This may be overriden by caller
+ARCH = $(shell uname -m | sed -e "s/86_//")
+PLATFORM = $(shell uname -s | tr A-Z a-z)
+ADD_DEBUG = Y
+DEST =
+
+# Keep all intermediate targets
+.SECONDARY:
+
+######
+# We are on project copy
+######
+ifneq ($(wildcard package.json),)
+unexport MAKEFLAGS
+unexport ARCH
+unexport PLATFORM
+unexport ADD_DEBUG
+unexport DEST
+
+export npm_config_unsafe_perm = true
+
+simplename = $(patsubst node_%,%,$(basename $(notdir $(1))))
+nodedep = node_modules/$(1)/package.json
+leveldown.COMPILED = leveldown/lib/binding/Release/node-v$(ADDON_VERSION)-$(PLATFORM)-$(ARCH)/leveldown.node
+leveldown.PREPARE = cp release/resources/leveldown-fix.json node_modules/leveldown/package.json
+leveldown.INTERMEDIATE = build/Release
+sqlite3.COMPILED = sqlite3/lib/binding/node-v$(ADDON_VERSION)-$(PLATFORM)-$(ARCH)/node_sqlite3.node
+sqlite3.INTERMEDIATE = lib/binding/node-webkit-v${NW_VERSION}-$(PLATFORM)-$(ARCH)
+nw.VERSION = @$(NW_VERSION)
+duniter-ui.VERSION = @$(DUNITER_UI_VERSION)
+
+# Build the desktop
+.PHONY: desktop
+desktop: index.html
+	$(MAKE) ADD_DEBUG=$(ADD_DEBUG) wipe
+
+# Build the server with GUI
+.PHONY: server-gui
+server-gui: base-gui
+	$(MAKE) ADD_DEBUG=$(ADD_DEBUG) wipe
+
+# Build the server
+.PHONY: server
+server: base
+	$(MAKE) ADD_DEBUG=$(ADD_DEBUG) wipe
+
+# Build the base with GUI
+.PHONY: base-gui
+base-gui: $(call nodedep,duniter-ui) base
+
+# Build the base
+.PHONY: base
+base: | node_modules
+
+index.html: $(call nodedep,duniter-ui) $(addprefix node_modules/,$(leveldown.COMPILED) $(sqlite3.COMPILED))
+	@sed -i "s/\"main\": \"index.js\",/\"main\": \"index.html\",/" package.json
+	@mv gui/* .
+
+node_modules/%.node: $(call nodedep,nw-gyp) $(call nodedep,nw)
+	@$(if $($(call simplename,$@).PREPARE),$($(call simplename,$@).PREPARE))
+	@PATH=$(shell npm bin):${PATH} node-pre-gyp \
+		-C node_modules/$(call simplename,$@) \
+		--runtime=node-webkit \
+		--target=$(NW_VERSION) \
+		--target_arch=$(ARCH) \
+		--target_platform=$(PLATFORM) \
+		configure build
+	@mkdir -p "$(dir $@)"
+	@cp "node_modules/$(call simplename,$@)/$($(call simplename,$@).INTERMEDIATE)/$(notdir $@)" "$@"
+
+node_modules/%/package.json: | node_modules
+	@NEON_BUILD_RELEASE="true" npm add $*$($*.VERSION)
+
+node_modules:
+	@npm uninstall husky
+	@NEON_BUILD_RELEASE="true" npm install
+
+# Wipe the delivery result
+.PHONY: wipe
+wipe:
+	@npm uninstall nw-gyp
+	@PATH=$(shell npm bin):${PATH} npm prune --production
+	@find -name "*.ts" ! -name "*.d.ts" -delete
+ifneq ($(ADD_DEBUG),Y)
+	@find -name "*.d.ts" -delete -o -name "*.js.map" -delete
+endif
+	@rm -rf $(DEV_FILES)
+	@rm -rf coverage coverage.* release test
+	@rm -rf target
+	@rm -rf neon/native/target
+	@rm -rf node_modules/sqlite3/build
+	@rm -rf node_modules/duniter-ui/node_modules
+
+######
+# We are on main project
+######
+else ifneq ($(wildcard ../package.json),)
+WORK = ../work
+TARGET_DIR = $(abspath $(WORK)/$(DEST)/)
+MAIN_ACTIONS = desktop server-gui server base-gui base wipe
+
+.PHONY: $(MAIN_ACTIONS)
+$(MAIN_ACTIONS): $(TARGET_DIR)/Makefile
+	@$(MAKE) -C $(TARGET_DIR) $@
+
+$(TARGET_DIR)/Makefile: | $(TARGET_DIR)
+	@find .. -mindepth 1 -maxdepth 1 ! -name work ! -name .git -print0 |\
+		xargs -0 -I{} cp -r {} "$(TARGET_DIR)" || exit 1
+	@cp Makefile "$(TARGET_DIR)"
+
+$(TARGET_DIR):
+	@mkdir -p "$(TARGET_DIR)"
+
+.PHONY: clean
+clean:
+	@rm "$(TARGET_DIR)/Makefile"
+
+.PHONY: mrproper
+mrproper:
+	@rm -rf "$(TARGET_DIR)"
+
+######
+# We are… lost?
+######
+else
+%:
+	$(error Cannot figure where I am)
+
+######
+# Main project or copy
+######
+endif
diff --git a/release/arch/arm/build-arm.sh b/release/arch/arm/build-arm.sh
index 3b58e5aceb914266aa95b61896fd457280fdbce3..deeea0b9257c222222623f19fa4c0e25a3d0a0c8 100755
--- a/release/arch/arm/build-arm.sh
+++ b/release/arch/arm/build-arm.sh
@@ -10,7 +10,6 @@ NODE_VERSION=10.20.1
 ARCH="`uname -m | sed -e \"s/86_//\"`"
 NVER="v$NODE_VERSION"
 DUNITER_TAG=$1
-DUNITER_UI_VER="1.7.x"
 
 # Folders
 INITIAL_DIRECTORY=`pwd`
@@ -63,54 +62,28 @@ fi
 rm -rf "$RELEASES"
 mkdir -p "$RELEASES"
 
-cp -r "$DOWNLOADS/duniter" "$RELEASES/duniter"
+cd "$DOWNLOADS/duniter"
+echo "Build Duniter server with GUI..."
+make -C release server-gui clean
+mv "$DOWNLOADS/duniter/work" "$RELEASES/duniter"
 cd ${RELEASES}/duniter
-
 echo "Copying Nodejs"
 cp -R "$DOWNLOADS/node-${NVER}-linux-${ARCH}" node
-
-# Build Duniter with GUI
-echo "Build Duniter with GUI..."
-export NEON_BUILD_RELEASE="true"
-npm add "duniter-ui@${DUNITER_UI_VER}" || exit 1
-npm i || exit 1
-npm prune --production || exit 1
-
-SRC=`pwd`
-echo $SRC
-
-# Clean unused UI modules
-rm -Rf node_modules/duniter-ui/node_modules
-
-# Remove non production folders
-rm -rf coverage test
-
-# Remove unused rust intermediate binaries
-rm -rf target
-rm -rf neon/native/target
-
-# Remove typescript files
-find ./ \( -name "*.js.map" -o -name "*.d.ts" -o -name "*.ts" \) -delete
-
 cd ..
-mkdir -p duniter_release
-cp -R ${SRC}/* duniter_release/
 
 # Creating DEB packaging
-mv duniter_release/release/extra/debian/package duniter-${ARCH}
+cp -r "$DOWNLOADS"/duniter/release/extra/debian/package duniter-${ARCH}
 mkdir -p duniter-${ARCH}/opt/duniter/
 mkdir -p duniter-${ARCH}/etc/bash_completion.d/
 chmod 755 duniter-${ARCH}/DEBIAN/post*
 chmod 755 duniter-${ARCH}/DEBIAN/pre*
 sed -i "s/Version:.*/Version:$DUNITER_DEB_VER/g" duniter-${ARCH}/DEBIAN/control
 echo "Extra..."
-mv duniter_release/release/extra/completion/duniter_completion.bash duniter-${ARCH}/etc/bash_completion.d/duniter_completion.bash
+cp "$DOWNLOADS"/duniter/release/extra/completion/duniter_completion.bash duniter-${ARCH}/etc/bash_completion.d/duniter_completion.bash
 echo "Zipping..."
-cd duniter_release
-pwd
-rm -Rf .git
+cd duniter
 zip -qr ../duniter.zip *
-cd ../
+cd ..
 mv duniter.zip duniter-${ARCH}/opt/duniter/
 echo "Making package package"
 fakeroot dpkg-deb --build duniter-${ARCH}
diff --git a/release/arch/linux/build-lin.sh b/release/arch/linux/build-lin.sh
old mode 100644
new mode 100755
index 90fd6759f8593d83f2da5670de87503d045b450a..10e886975c9295493bcf2b9d40c5d78cf9530357
--- a/release/arch/linux/build-lin.sh
+++ b/release/arch/linux/build-lin.sh
@@ -11,52 +11,6 @@ else
 	exit 1
 fi
 
-# ---------
-# Functions
-# ---------
-
-# Copy nw.js compiled module released library to node libraries.
-# -
-# Parameters:
-# 1. Module name.
-nw_copy() {
-	[[ -z ${1} ]] && exit 1
-	from_folder=lib/binding/Release/node-webkit-v${NW_VERSION}-linux-x64
-	if [[ ! -z ${2} ]]; then
-	  from_folder=${2}
-	fi
-	local dest=lib/binding/Release/node-v${ADDON_VERSION}-linux-x64
-	mkdir -p ${dest}
-	cp ${from_folder}/${1}.node \
-		${dest}/${1}.node || exit 1
-}
-
-# Copy nw.js compiled module library to node libraries, prefixing with node_.
-# -
-# Parameters:
-# 1. Module name.
-nw_copy_node() {
-	[[ -z ${1} ]] && exit 1
-	local dest=lib/binding/node-v${ADDON_VERSION}-linux-x64/
-	mkdir -p ${dest}
-	cp lib/binding/node-webkit-v${NW_VERSION}-linux-x64/node_${1}.node \
-		${dest}/node_${1}.node || exit 1
-}
-
-# Compile the module with nw.js.
-# -
-# Parameters:
-# 1. Module name.
-# 2. Action to be done to module after compilation, if needed.
-nw_compile() {
-	[[ -z ${1} ]] && exit 1
-	cd ${1} || exit 1
-	node-pre-gyp --runtime=node-webkit --target=${NW_VERSION} configure || exit 1
-	node-pre-gyp --runtime=node-webkit --target=${NW_VERSION} build || exit 1
-	[[ -z ${2} ]] || ${2} ${1} ${3}
-	cd ..
-}
-
 # Create description.
 # -
 # Parameters:
@@ -110,7 +64,7 @@ build_deb_pack() {
 	sed -i "s/Version:.*/Version:${DUNITER_DEB_VER}/g" "${RELEASES}/duniter-x64/DEBIAN/control" || exit 1
 
 	cd "${RELEASES}/${1}_/"
-	zip -qr "${RELEASES}/duniter-x64/opt/duniter/duniter.zip" * || exit 1
+	zip -yqr "${RELEASES}/duniter-x64/opt/duniter/duniter.zip" * || exit 1
 
 	sed -i "s/Package: .*/Package: ${2}/g" "${RELEASES}/duniter-x64/DEBIAN/control" || exit 1
 
@@ -128,17 +82,9 @@ NODE_VERSION=10.20.1
 NVER="v${NODE_VERSION}"
 DUNITER_TAG="v${1}"
 DUNITER_DEB_VER=" ${1}"
-ADDON_VERSION=64
-NW_VERSION=0.33.1
-NW_RELEASE="v${NW_VERSION}"
-NW="nwjs-${NW_RELEASE}-linux-x64"
-NW_GZ="${NW}.tar.gz"
-DUNITER_UI_VER="1.7.x"
 
 nvm install ${NVER} || exit 1
 nvm use ${NVER} || exit 1
-npm install -g node-pre-gyp || exit 1
-npm install -g nw-gyp || exit 1
 curl https://sh.rustup.rs -sSf | sh -s -- -y
 export PATH="$HOME/.cargo/bin:$PATH"
 
@@ -150,7 +96,8 @@ ROOT="${PWD}"
 WORK_NAME=work
 WORK="${ROOT}/${WORK_NAME}"
 DOWNLOADS="${WORK}/downloads"
-RELEASES="${WORK}/releases"
+RELEASES_SUBDIR="releases"
+RELEASES="${WORK}/${RELEASES_SUBDIR}"
 BIN="${WORK}/bin"
 
 mkdir -p "${DOWNLOADS}" "${RELEASES}" "${BIN}" || exit 1
@@ -161,9 +108,6 @@ rm -rf "${BIN}/"*.{deb,tar.gz}{,.desc} # Clean up
 # -----------
 
 cd "${DOWNLOADS}"
-curl -O https://dl.nwjs.io/${NW_RELEASE}/${NW_GZ} || exit 1
-tar xzf ${NW_GZ} || exit 1
-rm ${NW_GZ}
 curl -O http://nodejs.org/dist/${NVER}/node-${NVER}-linux-x64.tar.gz || exit 1
 tar xzf node-${NVER}-linux-x64.tar.gz || exit 1
 rm node-${NVER}-linux-x64.tar.gz
@@ -172,73 +116,31 @@ rm node-${NVER}-linux-x64.tar.gz
 # Releases
 # -----------
 
-# Prepare sources
-mkdir -p "${RELEASES}/duniter" || exit 1
-cp -r $(find "${ROOT}" -mindepth 1 -maxdepth 1 ! -name "${WORK_NAME}") "${RELEASES}/duniter" || exit 1
-cd "${RELEASES}/duniter"
-rm -Rf .gitignore .git || exit 1 # Remove git files
-
-# Build Duniter with GUI
-echo ">> VM: building modules..."
-export NEON_BUILD_RELEASE="true"
-npm add "duniter-ui@${DUNITER_UI_VER}" || exit 1
-npm i || exit 1
-npm prune --production || exit 1
-
-# Patch leveldown
-cp "${ROOT}/release/resources/leveldown-fix.json" "${RELEASES}/duniter/node_modules/leveldown/package.json" || exit 1
-
-# Remove non production folders
-rm -rf coverage release test
-
-# Remove unused rust intermediate binaries
-rm -rf target
-rm -rf neon/native/target
-
-# Remove typescript files
-find ./ \( -name "*.js.map" -o -name "*.d.ts" -o -name "*.ts" \) -delete
-
-cp -r "${RELEASES}/duniter" "${RELEASES}/desktop_" || exit 1
-cp -r "${RELEASES}/duniter" "${RELEASES}/server_" || exit 1
-
-# -------------------------------------
-# Build Desktop version against nw.js
-# -------------------------------------
-
-echo "${NW_RELEASE}"
-
-cd "${RELEASES}/desktop_/node_modules/"
-nw_compile leveldown nw_copy "build/Release/"
-nw_compile sqlite3 nw_copy_node
-
-# Unused binaries
-cd "${RELEASES}/desktop_/"
-rm -rf node_modules/sqlite3/build
+pushd "${ROOT}"
+make -C release DEST="${RELEASES_SUBDIR}/duniter" base-gui || exit 1
+cp -pr "${RELEASES}/duniter" "${RELEASES}/desktop_" || exit 1
+make -C release DEST="${RELEASES_SUBDIR}/desktop_" desktop clean || exit 1
+cp -pr "${RELEASES}/duniter" "${RELEASES}/server_" || exit 1
+make -C release DEST="${RELEASES_SUBDIR}/server_" server-gui clean || exit 1
+popd
 
 # --------------------------------
 # Embed nw.js in desktop version
 # --------------------------------
 
-# Install Nw.js
+# Embed Node.js to make Duniter modules installable
 mkdir -p "${RELEASES}/desktop_release" || exit 1
-cp -r "${DOWNLOADS}/${NW}/"* "${RELEASES}/desktop_release/" || exit 1
-# Embed Node.js with Nw.js to make Duniter modules installable
 cp -r "${DOWNLOADS}/node-${NVER}-linux-x64/lib" "${RELEASES}/desktop_release/" || exit 1
 cp -r "${DOWNLOADS}/node-${NVER}-linux-x64/include" "${RELEASES}/desktop_release/" || exit 1
 cp -r "${DOWNLOADS}/node-${NVER}-linux-x64/bin" "${RELEASES}/desktop_release/" || exit 1
-# Add some specific files for GUI
-cp "${RELEASES}/desktop_/gui/"* "${RELEASES}/desktop_release/" || exit 1
 # Add Duniter sources
 cp -R "${RELEASES}/desktop_/"* "${RELEASES}/desktop_release/" || exit 1
-# Insert Nw specific fields while they do not exist (1.3.3)
-sed -i "s/\"main\": \"index.js\",/\"main\": \"index.html\",/" "${RELEASES}/desktop_release/package.json" || exit 1
 # Add links for Node.js + NPM
 cd "${RELEASES}/desktop_release/bin"
 ln -s "../lib/node_modules/npm/bin/npm-cli.js" "./npm" -f || exit 1
 cd ..
 ln -s "./bin/node" "node" -f || exit 1
 ln -s "./bin/npm" "npm" -f || exit 1
-#sed -i "s/\"node-main\": \"\.\.\/sources\/bin\/duniter\",/\"node-main\": \".\/bin\/duniter\",/" "$RELEASES/desktop_release/package.json"
 rm -rf "${RELEASES}/desktop_"
 mv "${RELEASES}/desktop_release" "${RELEASES}/desktop_"
 
diff --git a/release/docker/Dockerfile b/release/docker/Dockerfile
index 08b7a9f4a516e3ac0ce10314b6f8fd7aba7a3877..7c1bb2bfe318b5c55857f42db42c453c4e16efd5 100644
--- a/release/docker/Dockerfile
+++ b/release/docker/Dockerfile
@@ -13,7 +13,9 @@ ARG DUNITER_UI_VER="1.7.x"
 RUN apk update && \
 	apk add ca-certificates curl && \
 	update-ca-certificates && \
-   apk add --update python make g++
+	apk add --update python make g++
+
+WORKDIR /duniter
 
 # copy source tree
 COPY ./ ./
@@ -22,15 +24,10 @@ COPY ./ ./
 RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
 
 # build duniter
-RUN export PATH="$HOME/.cargo/bin:$PATH" && \
-   export RUSTFLAGS="-C target-feature=-crt-static" && \
-   export NEON_BUILD_RELEASE="true" && \
-   npm add duniter-ui@${DUNITER_UI_VER} && \
-   npm i && npm prune --production && \
-   mv release/docker/duniter.sh docker.sh && \
-   rm -rf coverage release test && \
-   rm -rf target && \
-   rm -rf neon/native/target
+RUN PATH=${HOME}/.cargo/bin:${PATH} \
+	RUSTFLAGS="-C target-feature=-crt-static" \
+	make -C release ADD_DEBUG=N server-gui clean \
+	&& mv release/docker/duniter.sh work/docker.sh
 
 # ------------------------------------------------------------------------------
 # Final Stage
@@ -44,7 +41,7 @@ RUN addgroup -S -g 1111 duniter && \
 RUN mkdir -p /var/lib/duniter /etc/duniter && chown duniter:duniter /var/lib/duniter /etc/duniter
 
 # copy the build artifact from the build stage
-COPY --from=build --chown=duniter:duniter . /duniter/duniter
+COPY --from=build --chown=duniter:duniter /duniter/work /duniter/duniter
 
 # copy bootstrap script
 RUN cp /duniter/duniter/docker.sh /usr/bin/duniter && \
diff --git a/release/extra/debian/package/DEBIAN/postinst b/release/extra/debian/package/DEBIAN/postinst
index ae7ac47e952891759c83eb36105980acc5e22589..0880595b79963dc979553d50bd814080dcf49240 100755
--- a/release/extra/debian/package/DEBIAN/postinst
+++ b/release/extra/debian/package/DEBIAN/postinst
@@ -11,10 +11,8 @@ if [[ -f $DUN_ROOT/duniter.zip ]]; then
 fi
 
 # Duniter-Desktop
-if [[ -f $DUN_SOURCES/nw ]]; then
-  chmod +x $DUN_SOURCES/nw $DUN_SOURCES/lib $DUN_SOURCES/locales
-  chmod +rw $DUN_SOURCES/nw $DUN_SOURCES/lib $DUN_SOURCES/locales
-  ln -s $DUN_SOURCES/nw /usr/bin/duniter-desktop
+if [[ -f $DUN_SOURCES/duniter-desktop ]]; then
+  ln -s $DUN_SOURCES/duniter-desktop /usr/bin/duniter-desktop
   sed -i "s/DEB_PACKAGING=.*/DEB_PACKAGING=true/g" $DUN_SOURCES/duniter.sh
   # Links for Node + NPM
   cd $DUN_SOURCES