diff --git a/src/librustdoc/externalfiles.rs b/src/librustdoc/externalfiles.rs
index 8b5a3a2ba6131..6ac04a1fcbb95 100644
--- a/src/librustdoc/externalfiles.rs
+++ b/src/librustdoc/externalfiles.rs
@@ -30,6 +30,7 @@ impl ExternalHtml {
         edition: Edition,
         playground: &Option<Playground>,
     ) -> Option<ExternalHtml> {
+        let mut images_to_copy = Vec::new();
         let codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
         let ih = load_external_files(in_header, diag)?;
         let bc = load_external_files(before_content, diag)?;
@@ -37,14 +38,16 @@ impl ExternalHtml {
         let bc = format!(
             "{}{}",
             bc,
-            Markdown(&m_bc, &[], id_map, codes, edition, playground).to_string()
+            Markdown(&m_bc, &[], id_map, codes, edition, playground, &mut images_to_copy, &None)
+                .to_string()
         );
         let ac = load_external_files(after_content, diag)?;
         let m_ac = load_external_files(md_after_content, diag)?;
         let ac = format!(
             "{}{}",
             ac,
-            Markdown(&m_ac, &[], id_map, codes, edition, playground).to_string()
+            Markdown(&m_ac, &[], id_map, codes, edition, playground, &mut images_to_copy, &None)
+                .to_string()
         );
         Some(ExternalHtml { in_header: ih, before_content: bc, after_content: ac })
     }
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index c87964af0200c..74295811e1dd9 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -23,10 +23,13 @@ use rustc_data_structures::fx::FxHashMap;
 use rustc_span::edition::Edition;
 use std::borrow::Cow;
 use std::cell::RefCell;
+use std::collections::hash_map::DefaultHasher;
 use std::collections::VecDeque;
 use std::default::Default;
 use std::fmt::Write;
+use std::hash::{Hash, Hasher};
 use std::ops::Range;
+use std::path::{Path, PathBuf};
 use std::str;
 
 use crate::html::highlight;
@@ -44,7 +47,7 @@ fn opts() -> Options {
 
 /// When `to_string` is called, this struct will emit the HTML corresponding to
 /// the rendered version of the contained markdown string.
-pub struct Markdown<'a>(
+pub struct Markdown<'a, 'b>(
     pub &'a str,
     /// A list of link replacements.
     pub &'a [(String, String)],
@@ -52,17 +55,25 @@ pub struct Markdown<'a>(
     pub &'a mut IdMap,
     /// Whether to allow the use of explicit error codes in doctest lang strings.
     pub ErrorCodes,
-    /// Default edition to use when parsing doctests (to add a `fn main`).
+    /// Default edition to use when parsing dcotests (to add a `fn main`).
     pub Edition,
     pub &'a Option<Playground>,
+    /// images_to_copy
+    pub &'b mut Vec<(String, PathBuf)>,
+    /// static_root_path
+    pub &'b Option<String>,
 );
 /// A tuple struct like `Markdown` that renders the markdown with a table of contents.
-pub struct MarkdownWithToc<'a>(
+pub struct MarkdownWithToc<'a, 'b>(
     pub &'a str,
     pub &'a mut IdMap,
     pub ErrorCodes,
     pub Edition,
     pub &'a Option<Playground>,
+    /// images_to_copy
+    pub &'b mut Vec<(String, PathBuf)>,
+    /// static_root_path
+    pub &'b Option<String>,
 );
 /// A tuple struct like `Markdown` that renders the markdown escaping HTML tags.
 pub struct MarkdownHtml<'a>(
@@ -550,6 +561,56 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for Footnotes<'a, I> {
     }
 }
 
+struct LocalImages<'a, 'b, I: Iterator<Item = Event<'a>>> {
+    inner: I,
+    images_to_copy: &'b mut Vec<(String, PathBuf)>,
+    static_root_path: &'b Option<String>,
+}
+
+impl<'a, 'b, I: Iterator<Item = Event<'a>>> LocalImages<'a, 'b, I> {
+    fn new(
+        iter: I,
+        images_to_copy: &'b mut Vec<(String, PathBuf)>,
+        static_root_path: &'b Option<String>,
+    ) -> Self {
+        LocalImages { inner: iter, images_to_copy, static_root_path }
+    }
+}
+
+impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for LocalImages<'a, 'b, I> {
+    type Item = Event<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let event = self.inner.next();
+        if let Some(Event::Start(Tag::Image(type_, ref url, ref title))) = event {
+            if url.starts_with("http://") || url.starts_with("https://") {
+                // Not a local image, move on!
+            }
+            if let Ok(url) = Path::new(&url.clone().into_string()).canonicalize() {
+                let mut hasher = DefaultHasher::new();
+                url.hash(&mut hasher);
+                let hash = format!("{:x}", hasher.finish());
+                let static_folder_path = format!("static/{}", hash);
+                if self.images_to_copy.iter().find(|(h, _)| *h == hash).is_none() {
+                    self.images_to_copy.push((hash, url));
+                }
+                return Some(match self.static_root_path {
+                    Some(p) => {
+                        let s = format!("../{}", Path::new(p).join(&static_folder_path).display());
+                        Event::Start(Tag::Image(type_, CowStr::Boxed(s.into()), title.clone()))
+                    }
+                    None => Event::Start(Tag::Image(
+                        type_,
+                        CowStr::Boxed(format!("../{}", static_folder_path).into()),
+                        title.clone(),
+                    )),
+                });
+            }
+        }
+        event
+    }
+}
+
 pub fn find_testable_code<T: test::Tester>(
     doc: &str,
     tests: &mut T,
@@ -720,9 +781,18 @@ impl LangString {
     }
 }
 
-impl Markdown<'_> {
+impl Markdown<'_, '_> {
     pub fn to_string(self) -> String {
-        let Markdown(md, links, mut ids, codes, edition, playground) = self;
+        let Markdown(
+            md,
+            links,
+            mut ids,
+            codes,
+            edition,
+            playground,
+            images_to_copy,
+            static_root_path,
+        ) = self;
 
         // This is actually common enough to special-case
         if md.is_empty() {
@@ -742,6 +812,7 @@ impl Markdown<'_> {
 
         let p = HeadingLinks::new(p, None, &mut ids);
         let p = LinkReplacer::new(p, links);
+        let p = LocalImages::new(p, images_to_copy, static_root_path);
         let p = CodeBlocks::new(p, codes, edition, playground);
         let p = Footnotes::new(p);
         html::push_html(&mut s, p);
@@ -750,9 +821,17 @@ impl Markdown<'_> {
     }
 }
 
-impl MarkdownWithToc<'_> {
+impl MarkdownWithToc<'_, '_> {
     pub fn to_string(self) -> String {
-        let MarkdownWithToc(md, mut ids, codes, edition, playground) = self;
+        let MarkdownWithToc(
+            md,
+            mut ids,
+            codes,
+            edition,
+            playground,
+            images_to_copy,
+            static_root_path,
+        ) = self;
 
         let p = Parser::new_ext(md, opts());
 
@@ -762,6 +841,7 @@ impl MarkdownWithToc<'_> {
 
         {
             let p = HeadingLinks::new(p, Some(&mut toc), &mut ids);
+            let p = LocalImages::new(p, images_to_copy, static_root_path);
             let p = CodeBlocks::new(p, codes, edition, playground);
             let p = Footnotes::new(p);
             html::push_html(&mut s, p);
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index c73960fe33b9c..f7994213fef72 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -72,6 +72,7 @@ use crate::html::item_type::ItemType;
 use crate::html::markdown::{self, ErrorCodes, IdMap, Markdown, MarkdownHtml, MarkdownSummaryLine};
 use crate::html::sources;
 use crate::html::{highlight, layout, static_files};
+use crate::markdown::generate_static_images;
 
 use minifier;
 
@@ -203,6 +204,10 @@ crate struct SharedContext {
     pub edition: Edition,
     pub codes: ErrorCodes,
     playground: Option<markdown::Playground>,
+    /// Local images to move into the static folder.
+    ///
+    /// The tuple contains the hash as first argument and the image original path.
+    pub images_to_copy: RefCell<Vec<(String, PathBuf)>>,
 }
 
 impl Context {
@@ -482,9 +487,10 @@ pub fn run(
         edition,
         codes: ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build()),
         playground,
+        images_to_copy: RefCell::new(Vec::new()),
     };
 
-    let dst = output;
+    let dst = output.clone();
     scx.ensure_dir(&dst)?;
     krate = sources::render(&dst, &mut scx, krate)?;
     let (new_crate, index, cache) =
@@ -1349,6 +1355,8 @@ impl Context {
         );
         self.shared.fs.write(&settings_file, v.as_bytes())?;
 
+        generate_static_images(&self.dst, &*self.shared.images_to_copy.borrow());
+
         Ok(())
     }
 
@@ -1801,6 +1809,7 @@ fn render_markdown(
     is_hidden: bool,
 ) {
     let mut ids = cx.id_map.borrow_mut();
+    let mut images_to_copy = cx.shared.images_to_copy.borrow_mut();
     write!(
         w,
         "<div class='docblock{}'>{}{}</div>",
@@ -1812,7 +1821,9 @@ fn render_markdown(
             &mut ids,
             cx.shared.codes,
             cx.shared.edition,
-            &cx.shared.playground
+            &cx.shared.playground,
+            &mut images_to_copy,
+            &cx.shared.static_root_path,
         )
         .to_string()
     )
@@ -3660,6 +3671,7 @@ fn render_impl(
         write!(w, "</h3>");
         if let Some(ref dox) = cx.shared.maybe_collapsed_doc_value(&i.impl_item) {
             let mut ids = cx.id_map.borrow_mut();
+            let mut images_to_copy = cx.shared.images_to_copy.borrow_mut();
             write!(
                 w,
                 "<div class='docblock'>{}</div>",
@@ -3669,7 +3681,9 @@ fn render_impl(
                     &mut ids,
                     cx.shared.codes,
                     cx.shared.edition,
-                    &cx.shared.playground
+                    &cx.shared.playground,
+                    &mut images_to_copy,
+                    &cx.shared.static_root_path,
                 )
                 .to_string()
             );
diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs
index 912a40722b8af..4f0fce3ba78c1 100644
--- a/src/librustdoc/markdown.rs
+++ b/src/librustdoc/markdown.rs
@@ -1,6 +1,6 @@
-use std::fs::File;
+use std::fs::{self, File};
 use std::io::prelude::*;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 
 use rustc_feature::UnstableFeatures;
 use rustc_span::edition::Edition;
@@ -76,10 +76,21 @@ pub fn render(
 
     let mut ids = IdMap::new();
     let error_codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
+    let mut images_to_copy = Vec::new();
     let text = if !options.markdown_no_toc {
-        MarkdownWithToc(text, &mut ids, error_codes, edition, &playground).to_string()
+        MarkdownWithToc(
+            text,
+            &mut ids,
+            error_codes,
+            edition,
+            &playground,
+            &mut images_to_copy,
+            &None,
+        )
+        .to_string()
     } else {
-        Markdown(text, &[], &mut ids, error_codes, edition, &playground).to_string()
+        Markdown(text, &[], &mut ids, error_codes, edition, &playground, &mut images_to_copy, &None)
+            .to_string()
     };
 
     let err = write!(
@@ -122,7 +133,27 @@ pub fn render(
             diag.struct_err(&format!("cannot write to `{}`: {}", output.display(), e)).emit();
             6
         }
-        Ok(_) => 0,
+        Ok(_) => {
+            generate_static_images(&output, &images_to_copy);
+            0
+        }
+    }
+}
+
+pub fn generate_static_images<P: AsRef<Path>>(target_dir: P, images_to_copy: &[(String, PathBuf)]) {
+    if images_to_copy.is_empty() {
+        return;
+    }
+    let target_dir = target_dir.as_ref().join("static");
+    let _ = fs::create_dir(&target_dir);
+    for (hash, image_to_copy) in images_to_copy {
+        if fs::copy(image_to_copy, target_dir.join(hash)).is_err() {
+            eprintln!(
+                "Couldn't copy `{}` into `{}`...",
+                image_to_copy.display(),
+                target_dir.display()
+            );
+        }
     }
 }
 
diff --git a/src/test/rustdoc/copy-local-img.rs b/src/test/rustdoc/copy-local-img.rs
new file mode 100644
index 0000000000000..1e0f49121e23b
--- /dev/null
+++ b/src/test/rustdoc/copy-local-img.rs
@@ -0,0 +1,10 @@
+#![crate_name = "foo"]
+
+// @has static/8a40d4987fbb905
+// @has foo/struct.Enum.html
+// @has - '//img[@src="../static/8a40d4987fbb905"]' ''
+
+/// Image test!
+///
+/// ![osef](src/test/rustdoc/copy-local-img.rs)
+pub struct Enum;