diff --git a/.gitignore b/.gitignore
index 3d47f986c41db29ec6dc0d5036bf760b3a1cf366..8d47cace3a5bd898da9fb12bed716d60838191a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@ target/
 .settings/
 *.iml
 .project
-.classpath
\ No newline at end of file
+.classpath
+**/.DS_Store
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index ba54d56baa570ae6cd51be5be46ed0b0016eea31..35b09fe05ce5040d4f8622428b6c27adb769530d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,20 +6,20 @@
 
     <!-- Inherited Icy Parent POM -->
     <parent>
-		<artifactId>pom-icy</artifactId>
         <groupId>org.bioimageanalysis.icy</groupId>
-        <version>2.0.0</version>
-	</parent>
+        <artifactId>pom-icy</artifactId>
+        <version>2.2.0</version>
+    </parent>
 
     <!-- Project Information -->
     <artifactId>docker4icy</artifactId>
-    <version>3.2.7</version>
+    <version>4.0.0</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>
+    <url>https://icy.bioimageanalysis.org/plugin/docker-for-icy/</url>
     <inceptionYear>2020</inceptionYear>
 
     <organization>
@@ -55,31 +55,15 @@
 
     <!-- Project properties -->
     <properties>
-		<artifact-to-include>docker-java</artifact-to-include>
+        <artifact-to-extract>docker-java</artifact-to-extract>
     </properties>
 
-    <profiles>
-        <profile>
-            <id>icy-plugin-extract-library</id>
-            <activation>
-                <activeByDefault>true</activeByDefault>
-            </activation>
-        </profile>
-    </profiles>
-
     <!-- List of project's dependencies -->
     <dependencies>
-        <!-- The core of Icy -->
-        <dependency>
-            <groupId>org.bioimageanalysis.icy</groupId>
-            <artifactId>icy-kernel</artifactId>
-			<version>${icy-kernel.version}</version>
-        </dependency>
-
         <dependency>
             <groupId>com.github.docker-java</groupId>
             <artifactId>docker-java</artifactId>
-            <version>3.2.7</version>
+            <version>3.3.0</version>
         </dependency>
     </dependencies>
 
diff --git a/src/main/java/plugins/adufour/docker/Docker4Icy.java b/src/main/java/plugins/adufour/docker/Docker4Icy.java
index 35839ed783d07ebbd0f78587e478bde0afb037e2..82ca5f947b10e0d2598bb7aba51fd46585300bda 100644
--- a/src/main/java/plugins/adufour/docker/Docker4Icy.java
+++ b/src/main/java/plugins/adufour/docker/Docker4Icy.java
@@ -15,62 +15,62 @@ 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 */
+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
-        {
+    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)
-            {
+            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));
+                if (jarFiles != null) {
+                    for (String jarFile : jarFiles) {
+                        String fileName = FileUtil.getFileName(jarFile);
+                        URL urlLib = DockerUtil.class.getResource("lib" + File.separator + fileName);
+                        if (urlLib != null) {
+                            extractResourceTo(libFolder + fileName, urlLib);
+                            ((JarClassLoader) PluginLoader.getLoader()).add(new URL("file://" + libFolder + fileName));
+                        }
+                    }
                 }
             }
-            else
-            {
+            else {
                 String jarPath = FileUtil.getApplicationDirectory() + "/" + getDescriptor().getJarFilename();
                 for (String jarFile : JarUtil.getAllFiles(jarPath, false, false))
-                    if (jarFile.endsWith(".jar"))
-                    {
+                    if (jarFile.endsWith(".jar")) {
                         String fileName = FileUtil.getFileName(jarFile);
-                        extractResource(libFolder + fileName, DockerUtil.class.getResource("lib/" + fileName));
-                        ((JarClassLoader) PluginLoader.getLoader()).add(libFolder + fileName);
+                        URL urlLib = DockerUtil.class.getResource("lib/" + fileName);
+                        if (urlLib != null) {
+                            extractResourceTo(libFolder + fileName, urlLib);
+                            ((JarClassLoader) PluginLoader.getLoader()).add(libFolder + fileName);
+                        }
                     }
             }
         }
-        catch (IOException e)
-        {
+        catch (IOException e) {
             e.printStackTrace();
         }
     }
-    
+
     @Override
-    public void run()
-    {
+    public void run() {
         // Don't run. I'm too fast for you anyway.
     }
-    
+
     @Override
-    public void stop()
-    {
+    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
index db7dd252e70b381d13283faefd79e3ac3599e526..8db1b2082fd26cfe4b852423f077a43766b288e1 100644
--- a/src/main/java/plugins/adufour/docker/DockerUtil.java
+++ b/src/main/java/plugins/adufour/docker/DockerUtil.java
@@ -6,163 +6,142 @@ import java.util.Map;
 import java.util.Map.Entry;
 
 import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.async.ResultCallback;
 import com.github.dockerjava.api.command.CreateContainerCmd;
 import com.github.dockerjava.api.command.CreateContainerResponse;
+import com.github.dockerjava.api.command.PullImageResultCallback;
 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();
-    
+public class DockerUtil {
+    private static final 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)
+     *
+     * @param image the image to run (and download if necessary)
      * @return the container ID
      * @throws InterruptedException
      */
-    public static String startContainer(String image) 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)}
+     *
+     * @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
-    {
+    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>
+     *
+     * @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
-    {
+    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>
+     *
+     * @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
-    {
+    public static String startContainer(String image, Map<String, String> bindings, String workingDirectory) throws InterruptedException {
         List<Bind> bindingList = null;
-        
-        if (bindings != null)
-        {
+
+        if (bindings != null) {
             // Convert user mappings to Docker bindings
-            bindingList = new ArrayList<Bind>(bindings.size());
+            bindingList = new ArrayList<>(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>
+     *
+     * @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
-    {
+    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);
-        
+
+        final CreateContainerCmd createCommand = docker.createContainerCmd(image);
+
+        if (bindings != null && createCommand.getHostConfig() != null)
+            createCommand.getHostConfig().withBinds(bindings);
+
+        if (workingDirectory != null)
+            createCommand.withWorkingDir(workingDirectory);
+
         // Emulate a TTY and attach output and error streams
-        createCommand = createCommand.withTty(true).withAttachStdout(true).withAttachStderr(true);
-        
+        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)
-    {
+
+    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
+     *
+     * @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
-    {
+    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();
+        docker.execStartCmd(cmd).exec(new ResultCallback.Adapter<>()).awaitCompletion();
     }
 }