diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..3d47f986c41db29ec6dc0d5036bf760b3a1cf366
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+.idea/
+target/
+.settings/
+*.iml
+.project
+.classpath
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ff8d4b93545876bb7066a20816f0060378ddd836
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <!-- Inherited Icy Parent POM -->
+    <parent>
+        <groupId>org.bioimageanalysis.icy</groupId>
+        <artifactId>parent-pom-plugin</artifactId>
+        <version>1.0.3</version>
+    </parent>
+
+    <!-- Project Information -->
+    <artifactId>docker4icy</artifactId>
+    <version>3.2.7</version>
+
+    <packaging>jar</packaging>
+
+    <name>Docker for Icy</name>
+    <description>Yes, it is finally here. A user-friendly interface to Docker, letting you run containers within your plug-ins in no time.</description>
+    <url>http://icy.bioimageanalysis.org/plugin/docker-for-icy/</url>
+    <inceptionYear>2020</inceptionYear>
+
+    <organization>
+        <name>Institut Pasteur</name>
+        <url>https://pasteur.fr</url>
+    </organization>
+
+    <licenses>
+        <license>
+            <name>GNU GPLv3</name>
+            <url>https://www.gnu.org/licenses/gpl-3.0.en.html</url>
+            <distribution>repo</distribution>
+        </license>
+    </licenses>
+
+    <developers>
+        <developer>
+            <id>sdallongeville</id>
+            <name>Stéphane Dallongeville</name>
+            <url>https://research.pasteur.fr/fr/member/stephane-dallongeville/</url>
+            <roles>
+                <role>founder</role>
+                <role>lead</role>
+                <role>architect</role>
+                <role>developer</role>
+                <role>debugger</role>
+                <role>tester</role>
+                <role>maintainer</role>
+                <role>support</role>
+            </roles>
+        </developer>
+    </developers>
+
+    <!-- Project properties -->
+    <properties>
+
+    </properties>
+
+    <!-- Project build configuration -->
+    <build>
+
+    </build>
+
+    <!-- List of project's dependencies -->
+    <dependencies>
+        <!-- The core of Icy -->
+        <dependency>
+            <groupId>org.bioimageanalysis.icy</groupId>
+            <artifactId>icy-kernel</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.docker-java</groupId>
+            <artifactId>docker-java</artifactId>
+            <version>3.2.7</version>
+        </dependency>
+    </dependencies>
+
+    <!-- Icy Maven repository (to find parent POM) -->
+    <repositories>
+        <repository>
+            <id>icy</id>
+            <name>Icy's Nexus</name>
+            <url>https://icy-nexus.pasteur.fr/repository/Icy/</url>
+        </repository>
+    </repositories>
+</project>
diff --git a/src/main/java/plugins/adufour/docker/Docker4Icy.java b/src/main/java/plugins/adufour/docker/Docker4Icy.java
new file mode 100644
index 0000000000000000000000000000000000000000..35839ed783d07ebbd0f78587e478bde0afb037e2
--- /dev/null
+++ b/src/main/java/plugins/adufour/docker/Docker4Icy.java
@@ -0,0 +1,76 @@
+package plugins.adufour.docker;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+import icy.file.FileUtil;
+import icy.plugin.PluginLoader;
+import icy.plugin.abstract_.Plugin;
+import icy.plugin.classloader.JarClassLoader;
+import icy.plugin.interface_.PluginDaemon;
+import icy.util.JarUtil;
+
+/**
+ * Icy interface to <a href="https://www.docker.com/what-docker">Docker</a>. This daemon loads up
+ * necessary 3rd-party libraries into the class path at startup (and every time the plug-in list is
+ * reloaded). To interact with Docker, see the {@link DockerUtil} class.
+ * 
+ * @author Alexandre Dufour
+ */
+public class Docker4Icy extends Plugin implements PluginDaemon
+{
+    /** The temporary folder where the 3rd party libraries will be extracted */
+    private static final String libFolder = FileUtil.getTempDirectory() + "/Docker4Icy/";
+    
+    @Override
+    public void init()
+    {
+        try
+        {
+            // 1) Unpack and load 3rd-party JAR libraries via a temporary folder
+            URL url = DockerUtil.class.getResource("lib");
+            if (url != null)
+            {
+                String[] jarFiles = new File(url.getFile()).list();
+                for (String jarFile : jarFiles)
+                {
+                    String fileName = FileUtil.getFileName(jarFile);
+                    extractResource(libFolder + fileName, DockerUtil.class.getResource("lib" + File.separator + fileName));
+                    ((JarClassLoader) PluginLoader.getLoader()).add(new URL("file://" + libFolder + fileName));
+                }
+            }
+            else
+            {
+                String jarPath = FileUtil.getApplicationDirectory() + "/" + getDescriptor().getJarFilename();
+                for (String jarFile : JarUtil.getAllFiles(jarPath, false, false))
+                    if (jarFile.endsWith(".jar"))
+                    {
+                        String fileName = FileUtil.getFileName(jarFile);
+                        extractResource(libFolder + fileName, DockerUtil.class.getResource("lib/" + fileName));
+                        ((JarClassLoader) PluginLoader.getLoader()).add(libFolder + fileName);
+                    }
+            }
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+    }
+    
+    @Override
+    public void run()
+    {
+        // Don't run. I'm too fast for you anyway.
+    }
+    
+    @Override
+    public void stop()
+    {
+        // Delete the temporary folder:
+        // 1) for the beauty of saving space until the next run...
+        // 2) To prevent potential conflicts after upgrades
+        FileUtil.delete(new File(libFolder), true);
+    }
+    
+}
diff --git a/src/main/java/plugins/adufour/docker/DockerUtil.java b/src/main/java/plugins/adufour/docker/DockerUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..db7dd252e70b381d13283faefd79e3ac3599e526
--- /dev/null
+++ b/src/main/java/plugins/adufour/docker/DockerUtil.java
@@ -0,0 +1,168 @@
+package plugins.adufour.docker;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.command.CreateContainerCmd;
+import com.github.dockerjava.api.command.CreateContainerResponse;
+import com.github.dockerjava.api.model.AccessMode;
+import com.github.dockerjava.api.model.Bind;
+import com.github.dockerjava.api.model.Volume;
+import com.github.dockerjava.core.DefaultDockerClientConfig;
+import com.github.dockerjava.core.DockerClientBuilder;
+import com.github.dockerjava.core.command.ExecStartResultCallback;
+import com.github.dockerjava.core.command.PullImageResultCallback;
+
+/**
+ * Utility class to easily interface with (and run)
+ * <a href="https://www.docker.com/what-docker">Docker</a> within Icy, using the official
+ * <a href="https://github.com/docker-java/docker-java">docker-java API</a>.
+ * 
+ * @author Alexandre Dufour
+ */
+public class DockerUtil
+{
+    private static DockerClient docker = DockerClientBuilder.getInstance(DefaultDockerClientConfig.createDefaultConfigBuilder().build()).build();
+    
+    /**
+     * Starts a container with the specified image
+     * 
+     * @param image
+     *            the image to run (and download if necessary)
+     * @return the container ID
+     * @throws InterruptedException
+     */
+    public static String startContainer(String image) throws InterruptedException
+    {
+        return startContainer(image, (List<Bind>) null, null);
+    }
+    
+    /**
+     * Starts a container with the specified image and virtual folder binding(s)
+     * 
+     * @param image
+     *            the image to run (and download if necessary)
+     * @param bindings
+     *            Virtual bindings between host and container folders, e.g.
+     *            <code>{"/host/a", "/docker/b"}</code>. NB: by default, these mappings are set to
+     *            "read-write" mode. For further control, use
+     *            {@link #startContainer(String, List, String)}
+     * @return the container ID
+     * @throws InterruptedException
+     */
+    public static String startContainer(String image, Map<String, String> bindings) throws InterruptedException
+    {
+        return startContainer(image, bindings, null);
+    }
+    
+    /**
+     * Starts a container with the specified image and virtual folder binding(s)
+     * 
+     * @param image
+     *            the image to run (and download if necessary)
+     * @param bindings
+     *            Virtual bindings between host and container folders, e.g.
+     *            <code>new Bind("/host/a", "/docker/b", AccessMode.rw)</code>
+     * @return the container ID
+     * @throws InterruptedException
+     */
+    public static String startContainer(String image, List<Bind> bindings) throws InterruptedException
+    {
+        return startContainer(image, bindings, null);
+    }
+    
+    /**
+     * Starts a container with the specified image and virtual folder binding(s)
+     * 
+     * @param image
+     *            the image to run (and download if necessary)
+     * @param bindings
+     *            Virtual bindings between host and container folders, e.g.
+     *            <code>{"/host/a", "/docker/b"}</code>. NB: by default, these mappings are set to
+     *            "read-write" mode. For further control, use
+     *            {@link #startContainer(String, List, String)}
+     * @param workingDirectory
+     *            the directory where the container should start from (e.g.
+     *            <code>"/docker/b/subdir"</code>). This is equivalent to (but faster than) starting
+     *            the container and then calling <code>"cd /docker/b/subdir"</code>
+     * @return the container ID
+     * @throws InterruptedException
+     */
+    public static String startContainer(String image, Map<String, String> bindings, String workingDirectory) throws InterruptedException
+    {
+        List<Bind> bindingList = null;
+        
+        if (bindings != null)
+        {
+            // Convert user mappings to Docker bindings
+            bindingList = new ArrayList<Bind>(bindings.size());
+            for (Entry<String, String> binding : bindings.entrySet())
+                bindingList.add(new Bind(binding.getKey(), new Volume(binding.getValue()), AccessMode.rw));
+        }
+        
+        return startContainer(image, bindingList, workingDirectory);
+    }
+    
+    /**
+     * Starts a container with the specified image and virtual folder binding(s)
+     * 
+     * @param image
+     *            the image to run (and download if necessary)
+     * @param bindings
+     *            Virtual bindings between host and container folders, e.g.
+     *            <code>new Bind("/host/a", "/docker/b", AccessMode.rw)</code>
+     * @param workingDirectory
+     *            the directory where the container should start from (e.g.
+     *            <code>"/docker/b/subdir"</code>). This is equivalent to (but faster than) starting
+     *            the container and then calling <code>"cd /docker/b/subdir"</code>
+     * @return the container ID
+     * @throws InterruptedException
+     */
+    public static String startContainer(String image, List<Bind> bindings, String workingDirectory) throws InterruptedException
+    {
+        System.out.println("Fetching image " + image);
+        docker.pullImageCmd(image).exec(new PullImageResultCallback()).awaitCompletion();
+        
+        System.out.println("Starting container using " + image);
+        
+        CreateContainerCmd createCommand = docker.createContainerCmd(image);
+        
+        if (bindings != null) createCommand = createCommand.withBinds(bindings);
+        
+        if (workingDirectory != null) createCommand = createCommand.withWorkingDir(workingDirectory);
+        
+        // Emulate a TTY and attach output and error streams
+        createCommand = createCommand.withTty(true).withAttachStdout(true).withAttachStderr(true);
+        
+        // Finally, start the container
+        CreateContainerResponse container = createCommand.exec();
+        String containerID = container.getId();
+        docker.startContainerCmd(containerID).exec();
+        
+        return containerID;
+    }
+    
+    public static void stopContainer(String containerID)
+    {
+        docker.stopContainerCmd(containerID).exec();
+    }
+    
+    /**
+     * Runs the specified command in the given container and awaits completion
+     * 
+     * @param containerID
+     *            the ID of the container where the command should run
+     * @param command
+     *            the command to run (e.g. <code>"echo hello Icy world!"</code>
+     * @throws InterruptedException
+     *             if the calling thread was interrupted before completion of the command
+     */
+    public static void runCommand(String containerID, String command) throws InterruptedException
+    {
+        String cmd = docker.execCreateCmd(containerID).withAttachStdout(true).withAttachStderr(true).withTty(true).withCmd(command.split(" ")).exec().getId();
+        docker.execStartCmd(cmd).exec(new ExecStartResultCallback(System.out, System.err)).awaitCompletion();
+    }
+}