diff --git a/Cargo.lock b/Cargo.lock
index ef1ee7c1be3b8786b2492ab4f06f0fd8c327b614..a0c66aad6718bf32de1241b6014caf9e3916c4ad 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5202,6 +5202,16 @@ version = "0.3.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
 
+[[package]]
+name = "placeholder"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a488674d1157858898c95280e599d63584bb2daf3a11031ce5e76887d666c16"
+dependencies = [
+ "lazy_static",
+ "regex",
+]
+
 [[package]]
 name = "platforms"
 version = "2.0.0"
@@ -9000,9 +9010,14 @@ dependencies = [
  "anyhow",
  "clap",
  "frame-metadata",
+ "hex",
+ "memmap2 0.5.0",
  "parity-scale-codec",
+ "placeholder",
  "run_script",
  "scale-info",
+ "serde",
+ "serde_json",
  "version-compare",
  "version_check",
 ]
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
index 43fefae3b990d0bb6f201a4c725ff0a2da4d34bd..86498acee01d49d40da1c933aeb44610599ba939 100644
--- a/xtask/Cargo.toml
+++ b/xtask/Cargo.toml
@@ -17,7 +17,12 @@ anyhow = "1.0.32"
 clap = { version = "3.0", features = ["derive"] }
 codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full", "bit-vec"] }
 frame-metadata = "14.0.0"
+hex = "0.4"
+memmap2 = "0.5.0"
+placeholder = "1.1.3"
 run_script = "0.6.3"
 scale-info = { version = "1.0.0", features = ["bit-vec"] }
+serde = "1"
+serde_json = "1.0"
 version_check = "0.9.2"
 version-compare = "0.0.11"
diff --git a/xtask/res/runtime_release_notes.template b/xtask/res/runtime_release_notes.template
new file mode 100644
index 0000000000000000000000000000000000000000..c33733edee15ce0f85ebb5001b2761cb5ca39e03
--- /dev/null
+++ b/xtask/res/runtime_release_notes.template
@@ -0,0 +1,18 @@
+# Runtimes
+
+The runtimes have been built using [{srtool_version}](https://github.com/paritytech/srtool) and `{rustc_version}`.
+
+## ÄžDev
+
+```
+🏋️ Runtime Size:		{runtime_human_size} ({runtime_size} bytes)
+🔥 Core Version:		{core_version}
+🗜 Compressed:		Yes: {compression_percent} %
+🎁 Metadata version:		{metadata_version}
+🗳️ system.setCode hash:   {proposal_hash}
+#️⃣ Blake2-256 hash:    {blake2_256}
+```
+
+# Changes
+
+{changes}
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 0873750f8aec03fe40bfa1af0eeb88ce920f7968..85cf639b47f3c20579771a6f77de90cde11a0928 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -15,6 +15,7 @@
 // along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
 
 mod gen_calls_doc;
+mod release_runtime;
 
 use anyhow::Result;
 use clap::Parser;
@@ -37,6 +38,8 @@ enum DuniterXTaskCommand {
     },
     /// Generate calls documentation
     GenCallsDoc,
+    /// Release a new runtime
+    ReleaseRuntime { spec_version: u32 },
     /// Execute unit tests and integration tests
     /// End2tests are skipped
     Test,
@@ -60,6 +63,9 @@ fn main() -> Result<()> {
     match args.command {
         DuniterXTaskCommand::Build { production } => build(production),
         DuniterXTaskCommand::GenCallsDoc => gen_calls_doc::gen_calls_doc(),
+        DuniterXTaskCommand::ReleaseRuntime { spec_version } => {
+            release_runtime::release_runtime(spec_version)
+        }
         DuniterXTaskCommand::Test => test(),
     }
 }
diff --git a/xtask/src/release_runtime.rs b/xtask/src/release_runtime.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2cc175c5cae3047eab26fe0ee6f03523f30d8df6
--- /dev/null
+++ b/xtask/src/release_runtime.rs
@@ -0,0 +1,139 @@
+// Copyright 2021 Axiom-Team
+//
+// This file is part of Substrate-Libre-Currency.
+//
+// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Substrate-Libre-Currency is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
+
+use anyhow::{anyhow, Context, Result};
+use serde::Deserialize;
+use std::io::Read;
+use std::process::Command;
+
+#[derive(Deserialize)]
+struct Srtool {
+    gen: String,
+    rustc: String,
+    runtimes: SrtoolRuntimes,
+}
+
+#[derive(Deserialize)]
+struct SrtoolRuntimes {
+    compact: SrtoolRuntime,
+    compressed: SrtoolRuntime,
+}
+
+#[derive(Deserialize)]
+struct SrtoolRuntime {
+    subwasm: SrtoolRuntimeSubWasm,
+}
+
+#[derive(Deserialize)]
+struct SrtoolRuntimeSubWasm {
+    core_version: String,
+    metadata_version: u32,
+    size: u32,
+    blake2_256: String,
+    proposal_hash: String,
+}
+
+pub(super) fn release_runtime(_spec_version: u32) -> Result<()> {
+    // Get current dir
+    let pwd = std::env::current_dir()?
+        .into_os_string()
+        .into_string()
+        .map_err(|_| anyhow!("Fail to read current dir path: invalid utf8 string!"))?;
+
+    // TODO: check spec_version in the code and bump if necessary (with a commit)
+
+    // TODO: create and push a git tag runtime-{spec_version}
+
+    // Build the new runtime
+    println!("Build gdev-runtime… (take a while)");
+    let output = Command::new("docker")
+        .args([
+            "run",
+            "-i",
+            "--rm",
+            "-e",
+            "PACKAGE=gdev-runtime",
+            "-e",
+            "RUNTIME_DIR=runtime/gdev",
+            "-v",
+            &format!("{}:/build", pwd),
+            "paritytech/srtool:1.60.0",
+            "build",
+            "--app",
+            "--json",
+            "-cM",
+        ])
+        .output()?;
+
+    // Read the srtool json output
+    let srtool: Srtool = serde_json::from_str(
+        std::str::from_utf8(&output.stdout)?
+            .lines()
+            .last()
+            .ok_or(anyhow!("empty srtool output"))?,
+    )
+    .with_context(|| "Fail to parse srtool json output")?;
+
+    // Generate release notes
+    let release_notes =
+        gen_release_notes(srtool).with_context(|| "Fail to generate release notes")?;
+
+    // TODO: Call gitlab API to publish the release notes (and upload the wasm)
+    println!("{}", release_notes);
+
+    Ok(())
+}
+
+fn gen_release_notes(srtool: Srtool) -> Result<String> {
+    // Read template file
+    const RELEASE_NOTES_TEMPLATE_FILEPATH: &str = "xtask/res/runtime_release_notes.template";
+    let mut file = std::fs::File::open(RELEASE_NOTES_TEMPLATE_FILEPATH)?;
+    let mut template = String::new();
+    file.read_to_string(&mut template)?;
+
+    // Prepare srtool values
+    let uncompressed_size = srtool.runtimes.compact.subwasm.size;
+    let wasm = srtool.runtimes.compressed.subwasm;
+    let compression_percent = (1.0 - (wasm.size as f64 / uncompressed_size as f64)) * 100.0;
+
+    // TODO: get changes (list of MRs) from gitlab API
+    let changes = String::new();
+
+    // Fill template values
+    let mut values = std::collections::HashMap::new();
+    values.insert("srtool_version".to_owned(), srtool.gen);
+    values.insert("rustc_version".to_owned(), srtool.rustc);
+    values.insert(
+        "runtime_human_size".to_owned(),
+        format!("{} KB", wasm.size / 1_024),
+    );
+    values.insert("runtime_size".to_owned(), wasm.size.to_string());
+    values.insert("core_version".to_owned(), wasm.core_version);
+    values.insert(
+        "compression_percent".to_owned(),
+        format!("{:.2}", compression_percent),
+    );
+    values.insert(
+        "metadata_version".to_owned(),
+        wasm.metadata_version.to_string(),
+    );
+    values.insert("proposal_hash".to_owned(), wasm.proposal_hash);
+    values.insert("blake2_256".to_owned(), wasm.blake2_256);
+    values.insert("changes".to_owned(), changes);
+
+    // Render template
+    placeholder::render(&template, &values).map_err(|e| anyhow!("Fail to render template: {}", e))
+}