karaf-issues mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "ASF GitHub Bot (JIRA)" <j...@apache.org>
Subject [jira] [Commented] (KARAF-5644) Add docker feature
Date Sun, 05 Aug 2018 17:12:01 GMT

    [ https://issues.apache.org/jira/browse/KARAF-5644?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16569520#comment-16569520 ] 

ASF GitHub Bot commented on KARAF-5644:
---------------------------------------

jbonofre closed pull request #546: [KARAF-5644] Add Docker feature
URL: https://github.com/apache/karaf/pull/546
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/assemblies/docker/Dockerfile b/assemblies/docker/Dockerfile
new file mode 100644
index 0000000000..2f84e3cf75
--- /dev/null
+++ b/assemblies/docker/Dockerfile
@@ -0,0 +1,37 @@
+################################################################################
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
+FROM java:8-jre-alpine
+
+# Karaf environment variables
+ENV KARAF_INSTALL_PATH=/opt
+ENV KARAF_HOME $KARAF_INSTALL_PATH/apache-karaf
+ENV PATH $PATH:$KARAF_HOME/bin
+
+# karaf_dist can point to a directory or a tarball on the local system
+ARG karaf_dist=NOT_SET
+
+# Install build dependencies and karaf
+ADD $karaf_dist $KARAF_INSTALL_PATH
+RUN set -x && \
+  ln -s $KARAF_INSTALL_PATH/apache-karaf* $KARAF_HOME
+
+COPY docker-entrypoint.sh /
+
+EXPOSE 8101 1099 44444 8181
+ENTRYPOINT ["/docker-entrypoint.sh"]
diff --git a/assemblies/docker/README.md b/assemblies/docker/README.md
new file mode 100644
index 0000000000..666785c208
--- /dev/null
+++ b/assemblies/docker/README.md
@@ -0,0 +1,47 @@
+Apache Karaf docker
+
+# Installation
+
+Install the most recent stable version of docker
+https://docs.docker.com/installation/
+
+Install the most recent stable version of docker-compose
+https://docs.docker.com/compose/install/
+
+# Build
+
+Images are based on the official Java Alpine (OpenJDK 8) image. If you want to
+build the Karaf image run:
+
+    sh build.sh
+
+or
+
+    docker build -t karaf .
+
+If you want to build the container for a specific version of Karaf
+you can configure it with the KARAF_VERSION arg:
+
+    docker build --build-arg KARAF_VERSION=4.2.0 -t "karaf:4.2.0" karaf
+
+# Run
+
+- Run Karaf with interactive mode
+
+    docker-compose run karaf
+
+- Run Karaf as a daemon (without interaction)
+
+    docker-compose up
+
+- Kill Karaf
+
+    docker-compose kill
+
+### Ports
+
+- The Karaf SSH server is on 8101
+- The Karaf WebContainer is on 8888
+- The Karaf JMX MBean server is on 1099 (default, not exposed to host) and 44444 (default, not exposed to host)
+
+Edit the `docker-compose.yml` file to edit port settings.
\ No newline at end of file
diff --git a/assemblies/docker/build.sh b/assemblies/docker/build.sh
new file mode 100755
index 0000000000..7dfd156def
--- /dev/null
+++ b/assemblies/docker/build.sh
@@ -0,0 +1,100 @@
+#!/bin/sh
+
+################################################################################
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
+usage() {
+  cat <<HERE
+Usage:
+  build.sh --from-local-dist [--image-name <image>]
+  build.sh --from-release --karaf-version <x.x.x> [--image-name <image>]
+  build.sh --help
+
+  If the --image-name flag is not used the built image name will be 'karaf'.
+HERE
+  exit 1
+}
+
+while [ $# -ge 1 ]
+do
+key="$1"
+  case $key in
+    --from-local-dist)
+    FROM_LOCAL="true"
+    ;;
+    --from-release)
+    FROM_RELEASE="true"
+    ;;
+    --image-name)
+    IMAGE_NAME="$2"
+    shift
+    ;;
+    --karaf-version)
+    KARAF_VERSION="$2"
+    shift
+    ;;
+    --help)
+    usage
+    ;;
+    *)
+    # unknown option
+    ;;
+  esac
+  shift
+done
+
+IMAGE_NAME=${IMAGE_NAME:-karaf}
+
+# TMPDIR must be contained within the working directory so it is part of the
+# Docker context. (i.e. it can't be mktemp'd in /tmp)
+TMPDIR=_TMP_
+
+cleanup() {
+    rm -rf "${TMPDIR}"
+}
+trap cleanup EXIT
+
+mkdir -p "${TMPDIR}"
+
+if [ -n "${FROM_RELEASE}" ]; then
+
+  [ -n "${KARAF_VERSION}" ] || usage
+
+  KARAF_BASE_URL="$(curl -s https://www.apache.org/dyn/closer.cgi\?preferred\=true)karaf/${KARAF_VERSION}/"
+  KARAF_DIST_FILE_NAME="apache-karaf-${KARAF_VERSION}.tar.gz"
+  CURL_OUTPUT="${TMPDIR}/${KARAF_DIST_FILE_NAME}"
+
+  echo "Downloading ${KARAF_DIST_FILE_NAME} from ${KARAF_BASE_URL}"
+  curl -s ${KARAF_BASE_URL}${KARAF_DIST_FILE_NAME} --output ${CURL_OUTPUT}
+
+  KARAF_DIST="${CURL_OUTPUT}"
+
+elif [ -n "${FROM_LOCAL}" ]; then
+
+  DIST_DIR=../apache-karaf/target/apache-karaf-*.tar.gz
+  KARAF_DIST=${TMPDIR}/apache-karaf.tar.gz
+  echo "Using karaf dist: ${DIST_DIR}"
+  cp ${DIST_DIR} ${KARAF_DIST}
+
+else
+
+  usage
+
+fi
+
+docker build --build-arg karaf_dist="${KARAF_DIST}" -t "${IMAGE_NAME}" .
diff --git a/assemblies/docker/docker-compose.yml b/assemblies/docker/docker-compose.yml
new file mode 100644
index 0000000000..6a18327ca8
--- /dev/null
+++ b/assemblies/docker/docker-compose.yml
@@ -0,0 +1,35 @@
+################################################################################
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
+version: "2.1"
+services:
+  karaf:
+    image: ${KARAF_DOCKER_IMAGE_NAME:-karaf}
+    expose:
+      - "1099"
+      - "44444"
+      - "8101"
+      - "8181"
+    ports:
+      - "8101:8101"
+      - "1099:1099"
+      - "44444:44444"
+      - "8181:8181"
+    command: karaf
+    stdin_open: true
+    tty: true
diff --git a/assemblies/docker/docker-entrypoint.sh b/assemblies/docker/docker-entrypoint.sh
new file mode 100755
index 0000000000..4ccda8b7e6
--- /dev/null
+++ b/assemblies/docker/docker-entrypoint.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+################################################################################
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
+echo "Starting Apache Karaf"
+exec $KARAF_HOME/bin/karaf
+
+exec "$@"
\ No newline at end of file
diff --git a/assemblies/features/enterprise/pom.xml b/assemblies/features/enterprise/pom.xml
index ede00aa7ef..a31193c8c2 100644
--- a/assemblies/features/enterprise/pom.xml
+++ b/assemblies/features/enterprise/pom.xml
@@ -107,6 +107,7 @@
             <artifactId>org.apache.aries.jndi.legacy.support</artifactId>
             <scope>provided</scope>
         </dependency>
+
         <!-- application-without-isolation deps -->
         <dependency>
             <groupId>org.apache.karaf.features</groupId>
@@ -155,12 +156,14 @@
             <artifactId>org.apache.aries.application.deployment.management</artifactId>
             <scope>provided</scope>
         </dependency>
+
         <!-- without isolation -->
         <dependency>
             <groupId>org.apache.aries.application</groupId>
             <artifactId>org.apache.aries.application.runtime</artifactId>
             <scope>provided</scope>
         </dependency>
+
         <!-- subsystems -->
         <dependency>
             <groupId>org.apache.karaf.services</groupId>
@@ -181,6 +184,7 @@
             <scope>provided</scope>
         </dependency>
 
+        <!-- hibernate deps -->
         <dependency>
             <groupId>org.hibernate</groupId>
             <artifactId>hibernate-osgi</artifactId>
diff --git a/assemblies/features/enterprise/src/main/feature/feature.xml b/assemblies/features/enterprise/src/main/feature/feature.xml
index 48e6100d0e..048af06ae6 100644
--- a/assemblies/features/enterprise/src/main/feature/feature.xml
+++ b/assemblies/features/enterprise/src/main/feature/feature.xml
@@ -229,7 +229,7 @@ com.atomikos.icatch.log_base_dir=${karaf.data}/atomikos
     </feature>
 
     <feature name="jms" description="JMS service and commands" version="${project.version}">
-        <details>JMS support provinding service, commands, and MBean.</details>
+        <details>JMS support providing service, commands, and MBean.</details>
         <feature>pax-jms-pool</feature>
         <feature>pax-jms-config</feature>
         <feature>pax-jms-pool-transx</feature>
@@ -273,4 +273,12 @@ com.atomikos.icatch.log_base_dir=${karaf.data}/atomikos
         </conditional>
     </feature>
 
+    <feature name="docker" description="Docker service and commands" version="${project.version}">
+        <details>Docker support providing service, commands, and MBean.</details>
+        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-core/2.9.6</bundle>
+        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-annotations/2.9.6</bundle>
+        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-databind/2.9.6</bundle>
+        <bundle>mvn:org.apache.karaf.docker/org.apache.karaf.docker.core/${project.version}</bundle>
+    </feature>
+
 </features>
diff --git a/docker/pom.xml b/docker/pom.xml
new file mode 100644
index 0000000000..27bd0252ff
--- /dev/null
+++ b/docker/pom.xml
@@ -0,0 +1,118 @@
+<?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">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+            http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.karaf</groupId>
+        <artifactId>karaf</artifactId>
+        <version>4.2.1-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <groupId>org.apache.karaf.docker</groupId>
+    <artifactId>org.apache.karaf.docker.core</artifactId>
+    <packaging>bundle</packaging>
+    <name>Apache Karaf :: Docker :: Core</name>
+
+    <properties>
+        <appendedResourcesDirectory>${basedir}/../etc/appended-resources</appendedResourcesDirectory>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>[2,3)</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>${project.basedir}/src/main/resources</directory>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>${project.basedir}/src/main/resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>**/*.info</include>
+                </includes>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Export-Package>
+                            org.apache.karaf.docker;-noimport:=true
+                        </Export-Package>
+                        <Import-Package>
+                            com.fasterxml.jackson*;version="[2,3)",
+                            *
+                        </Import-Package>
+                        <Private-Package>
+                            org.apache.karaf.docker.command,
+                            org.apache.karaf.docker.command.completers,
+                            org.apache.karaf.docker.internal,
+                            org.apache.karaf.docker.internal.osgi,
+                            org.apache.karaf.util,
+                            org.apache.karaf.util.json
+                        </Private-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/docker/src/main/java/org/apache/karaf/docker/Container.java b/docker/src/main/java/org/apache/karaf/docker/Container.java
new file mode 100644
index 0000000000..24ea99c0f9
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/Container.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+/**
+ * Represent a Docker Container.
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Container {
+
+    @JsonProperty("Id")
+    private String id;
+
+    @JsonProperty("Image")
+    private String image;
+
+    @JsonProperty("ImageID")
+    private String imageId;
+
+    @JsonProperty("Command")
+    private String command;
+
+    @JsonProperty("Created")
+    private long created;
+
+    @JsonProperty("State")
+    private String state;
+
+    @JsonProperty("Status")
+    private String status;
+
+    @JsonProperty("Ports")
+    private List<Port> ports;
+
+    @JsonProperty("SizeRw")
+    private long sizeRw;
+
+    @JsonProperty("SizeRootFS")
+    private long sizeRootFs;
+
+    @JsonProperty("Names")
+    private List<String> names;
+
+    public String getId() {
+        return id;
+    }
+
+    public String getImage() {
+        return image;
+    }
+
+    public String getCommand() {
+        return command;
+    }
+
+    public long getCreated() {
+        return created;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public List<Port> getPorts() {
+        return ports;
+    }
+
+    public long getSizeRw() {
+        return sizeRw;
+    }
+
+    public long getSizeRootFs() {
+        return sizeRootFs;
+    }
+
+    public List<String> getNames() {
+        return names;
+    }
+
+    public void setNames(List<String> names) {
+        this.names = names;
+    }
+
+    public String getImageId() {
+        return imageId;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/ContainerConfig.java b/docker/src/main/java/org/apache/karaf/docker/ContainerConfig.java
new file mode 100644
index 0000000000..92397d4856
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/ContainerConfig.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Map;
+
+/**
+ * Represents configuration of a Docker Container.
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ContainerConfig {
+
+    @JsonProperty("Hostname")
+    private String hostname;
+
+    @JsonProperty("User")
+    private String user;
+
+    @JsonProperty("AttachStdin")
+    private boolean attachStdin;
+
+    @JsonProperty("AttachStdout")
+    private boolean attachStdout;
+
+    @JsonProperty("AttachStderr")
+    private boolean attachStderr;
+
+    @JsonProperty("PortSpecs")
+    private String portSpecs;
+
+    @JsonProperty("Tty")
+    private boolean tty;
+
+    @JsonProperty("OpenStdin")
+    private boolean openStdin;
+
+    @JsonProperty("StdinOnce")
+    private boolean stdinOnce;
+
+    @JsonProperty("Env")
+    private String env;
+
+    @JsonProperty("Cmd")
+    private String[] cmd;
+
+    @JsonProperty("Image")
+    private String image;
+
+    @JsonProperty("WorkingDir")
+    private String workingDir;
+
+    @JsonProperty("NetworkDisabled")
+    private boolean networkDisabled;
+
+    @JsonProperty("ExposedPorts")
+    private Map<String, Map<String, String>> exposedPorts;
+
+    @JsonProperty("HostConfig")
+    private HostConfig hostConfig;
+
+    public String getHostname() {
+        return hostname;
+    }
+
+    public void setHostname(String hostname) {
+        this.hostname = hostname;
+    }
+
+    public String getUser() {
+        return user;
+    }
+
+    public void setUser(String user) {
+        this.user = user;
+    }
+
+    public boolean isAttachStdin() {
+        return attachStdin;
+    }
+
+    public void setAttachStdin(boolean attachStdin) {
+        this.attachStdin = attachStdin;
+    }
+
+    public boolean isAttachStdout() {
+        return attachStdout;
+    }
+
+    public void setAttachStdout(boolean attachStdout) {
+        this.attachStdout = attachStdout;
+    }
+
+    public boolean isAttachStderr() {
+        return attachStderr;
+    }
+
+    public void setAttachStderr(boolean attachStderr) {
+        this.attachStderr = attachStderr;
+    }
+
+    public String getPortSpecs() {
+        return portSpecs;
+    }
+
+    public void setPortSpecs(String portSpecs) {
+        this.portSpecs = portSpecs;
+    }
+
+    public boolean isTty() {
+        return tty;
+    }
+
+    public void setTty(boolean tty) {
+        this.tty = tty;
+    }
+
+    public boolean isOpenStdin() {
+        return openStdin;
+    }
+
+    public void setOpenStdin(boolean openStdin) {
+        this.openStdin = openStdin;
+    }
+
+    public boolean isStdinOnce() {
+        return stdinOnce;
+    }
+
+    public void setStdinOnce(boolean stdinOnce) {
+        this.stdinOnce = stdinOnce;
+    }
+
+    public String getEnv() {
+        return env;
+    }
+
+    public void setEnv(String env) {
+        this.env = env;
+    }
+
+    public String[] getCmd() {
+        return cmd;
+    }
+
+    public void setCmd(String[] cmd) {
+        this.cmd = cmd;
+    }
+
+    public String getImage() {
+        return image;
+    }
+
+    public void setImage(String image) {
+        this.image = image;
+    }
+
+    public String getWorkingDir() {
+        return workingDir;
+    }
+
+    public void setWorkingDir(String workingDir) {
+        this.workingDir = workingDir;
+    }
+
+    public boolean isNetworkDisabled() {
+        return networkDisabled;
+    }
+
+    public void setNetworkDisabled(boolean networkDisabled) {
+        this.networkDisabled = networkDisabled;
+    }
+
+    public Map<String, Map<String, String>> getExposedPorts() {
+        return exposedPorts;
+    }
+
+    public void setExposedPorts(Map<String, Map<String, String>> exposedPorts) {
+        this.exposedPorts = exposedPorts;
+    }
+
+    public HostConfig getHostConfig() {
+        return hostConfig;
+    }
+
+    public void setHostConfig(HostConfig hostConfig) {
+        this.hostConfig = hostConfig;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/DockerClient.java b/docker/src/main/java/org/apache/karaf/docker/DockerClient.java
new file mode 100644
index 0000000000..95b486154e
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/DockerClient.java
@@ -0,0 +1,327 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Service to manipulate docker via REST API.
+ */
+public class DockerClient {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(DockerClient.class);
+
+    public static final String DEFAULT_URL = "http://localhost:2375";
+
+    private String url;
+    private ObjectMapper mapper;
+
+    public DockerClient(String url) {
+        if (url == null) {
+            this.url = DEFAULT_URL;
+        } else {
+            this.url = url;
+        }
+        mapper = new ObjectMapper();
+    }
+
+    public Info info() throws Exception {
+        URL dockerUrl = new URL(this.url + "/info");
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("GET");
+
+        Info info = mapper.readValue(connection.getInputStream(), Info.class);
+        return info;
+    }
+
+    public Version version() throws Exception {
+        URL dockerUrl = new URL(this.url + "/version");
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("GET");
+
+        Version version = mapper.readValue(connection.getInputStream(), Version.class);
+        return version;
+    }
+
+    public List<Container> ps(boolean showAll) throws Exception {
+        URL dockerUrl = new URL(this.url + "/containers/json?all=" + showAll);
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("GET");
+
+        List<Container> containers = mapper.readValue(connection.getInputStream(), new TypeReference<List<Container>>(){});
+        for (Container container : containers) {
+            cleanName(container);
+        }
+
+        return containers;
+    }
+
+    private void cleanName(Container container) throws Exception {
+        List<String> cleanNames = new ArrayList<>();
+        for (String name : container.getNames()) {
+            if (name.startsWith("/")) {
+                name = name.substring(1);
+            }
+            cleanNames.add(name);
+        }
+        container.setNames(cleanNames);
+    }
+
+    public Container inspect(String id) throws Exception {
+        URL dockerUrl = new URL(this.url + "/containers/" + id + "/json?size=1");
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("GET");
+
+        Container container = mapper.readValue(connection.getInputStream(), Container.class);
+        return container;
+    }
+
+    public Top top(String id) throws Exception {
+        URL dockerUrl = new URL(this.url + "/containers/" + id + "/top?ps_args=aux");
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("GET");
+
+        Top top = mapper.readValue(connection.getInputStream(), Top.class);
+        return top;
+    }
+
+    public void create(ContainerConfig config, String name) throws Exception {
+        URL dockerUrl = new URL(this.url + "/containers/create?name=" + name);
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("POST");
+        connection.setRequestProperty("Content-Type", "application/json");
+        connection.setDoOutput(true);
+
+        mapper.writeValue(connection.getOutputStream(), config);
+
+        if (connection.getResponseCode() != 201) {
+            throw new IllegalStateException("Can't create Docker container " + name + ": " + connection.getResponseMessage());
+        }
+    }
+
+    public void rm(String id, boolean removeVolumes, boolean force) throws Exception {
+        URL dockerUrl = new URL(this.url + "/containers/" + id + "?v=" + removeVolumes + "&force=" + force);
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("DELETE");
+
+        if (connection.getResponseCode() != 204) {
+            throw new IllegalStateException("Can't remove Docker container " + id + ": " + connection.getResponseMessage());
+        }
+    }
+
+    public void start(String id) throws Exception {
+        URL dockerUrl = new URL(this.url + "/containers/" + id + "/start");
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("POST");
+
+        if (connection.getResponseCode() != 204) {
+            throw new IllegalStateException("Can't start Docker container " + id + ": " + connection.getResponseMessage());
+        }
+    }
+
+    public void stop(String id, int timeToWait) throws Exception {
+        URL dockerUrl = new URL(this.url + "/containers/" + id + "/stop?t=" + timeToWait);
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("POST");
+
+        if (connection.getResponseCode() != 204) {
+            throw new IllegalStateException("Can't stop Docker container " + id + ": " + connection.getResponseMessage());
+        }
+    }
+
+    public void restart(String id, int timeToWait) throws Exception {
+        URL dockerUrl = new URL(this.url + "/containers/" + id + "/restart?t=" + timeToWait);
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("POST");
+
+        if (connection.getResponseCode() != 204) {
+            throw new IllegalStateException("Can't restart Docker container " + id + ": " + connection.getResponseMessage());
+        }
+    }
+
+    public void kill(String id, String signal) throws Exception {
+        URL dockerUrl = new URL(this.url + "/containers/" + id + "/kill?signal=" + signal);
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("POST");
+
+        if (connection.getResponseCode() != 204) {
+            throw new IllegalStateException("Can't kill Docker container " + id + ": " + connection.getResponseMessage());
+        }
+    }
+
+    public void rename(String id, String name) throws Exception {
+        URL dockerUrl = new URL(this.url + "/containers/" + id + "/rename?name=" + name);
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("POST");
+
+        if (connection.getResponseCode() != 204) {
+            throw new IllegalStateException("Can't rename Docker container " + id + ": " + connection.getResponseMessage());
+        }
+    }
+
+    public void pause(String id) throws Exception {
+        URL dockerUrl = new URL(this.url + "/containers/" + id + "/pause");
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("POST");
+
+        if (connection.getResponseCode() != 204) {
+            throw new IllegalStateException("Can't pause Docker container " + id + ": " + connection.getResponseMessage());
+        }
+    }
+
+    public void unpause(String id) throws Exception {
+        URL dockerUrl = new URL(this.url + "/containers/" + id + "/unpause");
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("POST");
+
+        if (connection.getResponseCode() != 204) {
+            throw new IllegalStateException("Can't unpause Docker container " + id + ": " + connection.getResponseMessage());
+        }
+    }
+
+    public String logs(String id, boolean stdout, boolean stderr, boolean timestamps, boolean details) throws Exception {
+        URL dockerUrl = new URL(this.url + "/containers/" + id + "/logs?stdout=" + stdout + "&stderr=" + stderr + "&timestamps=" + timestamps + "&details=" + details);
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("GET");
+
+        StringBuffer buffer = new StringBuffer();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+        String line;
+        while ((line = reader.readLine()) != null) {
+            buffer.append(line).append("\n");
+        }
+        return buffer.toString();
+    }
+
+    public void commit(String container, ContainerConfig config, String message, String repo, String tag) throws Exception {
+        URL dockerUrl = new URL(this.url + "/commit?container=" + container + "&comment=" + message + "&repo=" + repo + "&tag=" + tag);
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("POST");
+        connection.setDoOutput(true);
+
+        mapper.writeValue(connection.getOutputStream(), config);
+
+        if (connection.getResponseCode() != 201) {
+            throw new IllegalStateException("Can't commit Docker container " + container + ": " + connection.getResponseMessage());
+        }
+    }
+
+    public List<Image> images(boolean showAll) throws Exception {
+        URL dockerUrl = new URL(this.url + "/images/json?all=" + showAll);
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("GET");
+
+        List<Image> images = mapper.readValue(connection.getInputStream(), new TypeReference<List<Image>>(){});
+        return images;
+    }
+
+    public void pull(String name, String tag, boolean verbose) throws Exception {
+        URL dockerUrl = new URL(this.url + "/images/create?fromImage=" + name + "&tag=" + tag);
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("POST");
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+        String line;
+        while ((line = reader.readLine()) != null) {
+            LOGGER.debug(line);
+            if (verbose) {
+                System.out.println(line);
+            }
+        }
+
+        if (connection.getResponseCode() != 200) {
+            throw new IllegalStateException("Can't pull image " + name + ": " + connection.getResponseMessage());
+        }
+    }
+
+    public void push(String name, String tag, boolean verbose) throws Exception {
+        URL dockerUrl = new URL(this.url + "/images/" + name + "/push?tag=" + tag);
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("POST");
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+        String line;
+        while ((line = reader.readLine()) != null) {
+            LOGGER.debug(line);
+            if (verbose) {
+                System.out.println(line);
+            }
+        }
+
+        if (connection.getResponseCode() != 200) {
+            throw new IllegalStateException("Can't push image " +  name + ": " + connection.getResponseMessage());
+        }
+    }
+
+    public void tag(String name, String repo, String tag) throws Exception {
+        URL dockerUrl = new URL(this.url + "/images/" + name + "/tag?repo=" + repo + "&tag=" + tag);
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("POST");
+
+        if (connection.getResponseCode() != 201) {
+            throw new IllegalStateException("Can't tag image " + name + ": " + connection.getResponseMessage());
+        }
+    }
+
+    public void rmi(String name, boolean force, boolean noprune) throws Exception {
+        URL dockerUrl = new URL(this.url + "/images/" + name + "?force=" + force + "&noprune=" + noprune);
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("DELETE");
+
+        if (connection.getResponseCode() != 200) {
+            throw new IllegalStateException("Can't remove image " + name + ": " + connection.getResponseMessage());
+        }
+    }
+
+    public List<ImageSearch> search(String term) throws Exception {
+        URL dockerUrl = new URL(this.url + "/images/search?term=" + term);
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("GET");
+
+        List<ImageSearch> images = mapper.readValue(connection.getInputStream(), new TypeReference<List<ImageSearch>>(){});
+        return images;
+    }
+
+    public Image getImage(String name) throws Exception {
+        URL dockerUrl = new URL(this.url + "/images/" + name + "/json");
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("GET");
+
+        Image image = mapper.readValue(connection.getInputStream(), Image.class);
+        return image;
+    }
+
+    public List<ImageHistory> history(String name) throws Exception {
+        URL dockerUrl = new URL(this.url + "/images/" + name + "/history");
+        HttpURLConnection connection = (HttpURLConnection) dockerUrl.openConnection();
+        connection.setRequestMethod("GET");
+
+        List<ImageHistory> images = mapper.readValue(connection.getInputStream(), new TypeReference<List<ImageHistory>>(){});
+        return images;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/DockerMBean.java b/docker/src/main/java/org/apache/karaf/docker/DockerMBean.java
new file mode 100644
index 0000000000..fd498ebe11
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/DockerMBean.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker;
+
+import javax.management.MBeanException;
+import javax.management.openmbean.TabularData;
+import java.util.Map;
+
+public interface DockerMBean {
+
+    TabularData ps(boolean showAll, String url) throws MBeanException;
+
+    Map<String, String> info(String url) throws MBeanException;
+
+    Map<String, String> version(String url) throws MBeanException;
+
+    void provision(String name, String sshPort, String jmxRmiPort, String jmxRmiRegistryPort, String httpPort, boolean copy, String url) throws MBeanException;
+
+    void rm(String container, boolean removeVolumes, boolean force, String url) throws MBeanException;
+
+    void rename(String container, String newName, String url) throws MBeanException;
+
+    void start(String container, String url) throws MBeanException;
+
+    void stop(String container, int timeToWait, String url) throws MBeanException;
+
+    void restart(String container, int timeToWait, String url) throws MBeanException;
+
+    void kill(String container, String signal, String url) throws MBeanException;
+
+    void pause(String container, String url) throws MBeanException;
+
+    void unpause(String container, String url) throws MBeanException;
+
+    String logs(String container, boolean stdout, boolean stderr, boolean timestamps, boolean details, String url) throws MBeanException;
+
+    void commit(String container, String repo, String tag, String message, String url) throws MBeanException;
+
+    TabularData images(String url) throws MBeanException;
+
+    TabularData search(String term, String url) throws MBeanException;
+
+    void tag(String image, String tag, String repo, String url) throws MBeanException;
+
+    void rmi(String image, boolean force, boolean noprune, String url) throws MBeanException;
+
+    void pull(String image, String tag, String url) throws MBeanException;
+
+    void push(String image, String tag, String url) throws MBeanException;
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/DockerService.java b/docker/src/main/java/org/apache/karaf/docker/DockerService.java
new file mode 100644
index 0000000000..438a349a46
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/DockerService.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker;
+
+import java.util.List;
+
+public interface DockerService {
+
+    List<Container> ps(boolean showAll, String url) throws Exception;
+
+    Container inspect(String name, String url) throws Exception;
+
+    Info info(String url) throws Exception;
+
+    Version version(String url) throws Exception;
+
+    void create(String name, String url) throws Exception;
+
+    void create(String name, String url, ContainerConfig config) throws Exception;
+
+    void provision(String name, String sshPort, String jmxRmiPort, String jmxRmiRegistryPort, String httpPort, boolean copy, String url) throws Exception;
+
+    void rm(String name, boolean removeVolumes, boolean force, String url) throws Exception;
+
+    void start(String name, String url) throws Exception;
+
+    void stop(String name, int timeToWait, String url) throws Exception;
+
+    void restart(String name, int timeToWait, String url) throws Exception;
+
+    void pause(String name, String url) throws Exception;
+
+    void unpause(String name, String url) throws Exception;
+
+    void kill(String name, String signal, String url) throws Exception;
+
+    void rename(String name, String newName, String url) throws Exception;
+
+    String logs(String name, boolean stdout, boolean stderr, boolean timestamps, boolean details, String url) throws Exception;
+
+    Top top(String name, String url) throws Exception;
+
+    void commit(String name, String repo, String tag, String message, String url) throws Exception;
+
+    List<Image> images(String url) throws Exception;
+
+    void pull(String image, String tag, boolean verbose, String url) throws Exception;
+
+    void push(String image, String tag, boolean verbose, String url) throws Exception;
+
+    List<ImageHistory> history(String image, String url) throws Exception;
+
+    List<ImageSearch> search(String term, String url) throws Exception;
+
+    void tag(String image, String repo, String tag, String url) throws Exception;
+
+    void rmi(String image, boolean force, boolean noprune, String url) throws Exception;
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/HostConfig.java b/docker/src/main/java/org/apache/karaf/docker/HostConfig.java
new file mode 100644
index 0000000000..a9665ff790
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/HostConfig.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+import java.util.Map;
+
+@JsonIgnoreProperties(ignoreUnknown = false)
+public class HostConfig {
+
+    @JsonProperty("Binds")
+    private String[] binds;
+
+    @JsonProperty("Links")
+    private String[] links;
+
+    @JsonProperty("Memory")
+    private long memory;
+
+    @JsonProperty("MemorySwap")
+    private long memorySwap;
+
+    @JsonProperty("LxcConf")
+    private String[] lxcConf;
+
+    @JsonProperty("PortBindings")
+    private Map<String, List<HostPortBinding>> portBindings;
+
+    @JsonProperty("PublishAllPorts")
+    private boolean publishAllPorts;
+
+    @JsonProperty("Privileged")
+    private boolean privileged;
+
+    @JsonProperty("Dns")
+    private String[] dns;
+
+    @JsonProperty("VolumesFrom")
+    private String[] volumesFrom;
+
+    @JsonProperty("NetworkMode")
+    private String networkMode;
+
+    public String[] getBinds() {
+        return binds;
+    }
+
+    public void setBinds(String[] binds) {
+        this.binds = binds;
+    }
+
+    public String[] getLinks() {
+        return links;
+    }
+
+    public void setLinks(String[] links) {
+        this.links = links;
+    }
+
+    public long getMemory() {
+        return memory;
+    }
+
+    public void setMemory(long memory) {
+        this.memory = memory;
+    }
+
+    public long getMemorySwap() {
+        return memorySwap;
+    }
+
+    public void setMemorySwap(long memorySwap) {
+        this.memorySwap = memorySwap;
+    }
+
+    public String[] getLxcConf() {
+        return lxcConf;
+    }
+
+    public void setLxcConf(String[] lxcConf) {
+        this.lxcConf = lxcConf;
+    }
+
+    public Map<String, List<HostPortBinding>> getPortBindings() {
+        return portBindings;
+    }
+
+    public void setPortBindings(Map<String, List<HostPortBinding>> portBindings) {
+        this.portBindings = portBindings;
+    }
+
+    public boolean isPublishAllPorts() {
+        return publishAllPorts;
+    }
+
+    public void setPublishAllPorts(boolean publishAllPorts) {
+        this.publishAllPorts = publishAllPorts;
+    }
+
+    public boolean isPrivileged() {
+        return privileged;
+    }
+
+    public void setPrivileged(boolean privileged) {
+        this.privileged = privileged;
+    }
+
+    public String[] getDns() {
+        return dns;
+    }
+
+    public void setDns(String[] dns) {
+        this.dns = dns;
+    }
+
+    public String[] getVolumesFrom() {
+        return volumesFrom;
+    }
+
+    public void setVolumesFrom(String[] volumesFrom) {
+        this.volumesFrom = volumesFrom;
+    }
+
+    public String getNetworkMode() {
+        return networkMode;
+    }
+
+    public void setNetworkMode(String networkMode) {
+        this.networkMode = networkMode;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/HostPortBinding.java b/docker/src/main/java/org/apache/karaf/docker/HostPortBinding.java
new file mode 100644
index 0000000000..226ced2e1d
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/HostPortBinding.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class HostPortBinding {
+
+    @JsonProperty("HostIp")
+    private String hostIp;
+
+    @JsonProperty("HostPort")
+    private String hostPort;
+
+    public String getHostIp() {
+        return hostIp;
+    }
+
+    public void setHostIp(String hostIp) {
+        this.hostIp = hostIp;
+    }
+
+    public String getHostPort() {
+        return hostPort;
+    }
+
+    public void setHostPort(String hostPort) {
+        this.hostPort = hostPort;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/Image.java b/docker/src/main/java/org/apache/karaf/docker/Image.java
new file mode 100644
index 0000000000..03d20da18b
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/Image.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents a Docker image.
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Image {
+
+    @JsonProperty("Id")
+    private String id;
+
+    @JsonProperty("Containers")
+    private int containers;
+
+    @JsonProperty("RepoTags")
+    private List<String> repoTags;
+
+    @JsonProperty("Labels")
+    private Map<String, String> labels;
+
+    @JsonProperty("Created")
+    private long created;
+
+    @JsonProperty("Size")
+    private long size;
+
+    @JsonProperty("VirtualSize")
+    private long virtualSize;
+
+    public String getId() {
+        return id;
+    }
+
+    public List<String> getRepoTags() {
+        return repoTags;
+    }
+
+    public long getCreated() {
+        return created;
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public int getContainers() {
+        return containers;
+    }
+
+    public Map<String, String> getLabels() {
+        return labels;
+    }
+
+    public long getVirtualSize() {
+        return virtualSize;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/ImageHistory.java b/docker/src/main/java/org/apache/karaf/docker/ImageHistory.java
new file mode 100644
index 0000000000..5dae411a7d
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/ImageHistory.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ImageHistory {
+
+    @JsonProperty("Id")
+    private String id;
+
+    @JsonProperty("Created")
+    private long created;
+
+    @JsonProperty("CreatedBy")
+    private String createdBy;
+
+    @JsonProperty("Tags")
+    private List<String> tags;
+
+    @JsonProperty("Size")
+    private long size;
+
+    @JsonProperty("Comment")
+    private String comment;
+
+    public String getId() {
+        return id;
+    }
+
+    public long getCreated() {
+        return created;
+    }
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    public List<String> getTags() {
+        return tags;
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/ImageSearch.java b/docker/src/main/java/org/apache/karaf/docker/ImageSearch.java
new file mode 100644
index 0000000000..019e0d69de
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/ImageSearch.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Represents a Docker image search result.
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ImageSearch {
+
+    @JsonProperty("name")
+    private String name;
+
+    @JsonProperty("star_count")
+    private int starCount;
+
+    @JsonProperty("is_official")
+    private boolean official;
+
+    @JsonProperty("is_automated")
+    private boolean automated;
+
+    @JsonProperty("description")
+    private String description;
+
+    public String getName() {
+        return name;
+    }
+
+    public int getStarCount() {
+        return starCount;
+    }
+
+    public boolean isOfficial() {
+        return official;
+    }
+
+    public boolean isAutomated() {
+        return automated;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/Info.java b/docker/src/main/java/org/apache/karaf/docker/Info.java
new file mode 100644
index 0000000000..6b1af57f37
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/Info.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Info {
+
+    @JsonProperty("Containers")
+    private int containers;
+
+    @JsonProperty("Debug")
+    private boolean debug;
+
+    @JsonProperty("Driver")
+    private String driver;
+
+    @JsonProperty("DriverStatus")
+    private List<List<String>> driverStatus;
+
+    @JsonProperty("ExecutionDriver")
+    private String executionDriver;
+
+    @JsonProperty("IPv4Forwarding")
+    private boolean ipv4Forwarding;
+
+    @JsonProperty("Images")
+    private int images;
+
+    @JsonProperty("IndexServerAddress")
+    private String indexServerAddress;
+
+    @JsonProperty("InitPath")
+    private String initPath;
+
+    @JsonProperty("InitSha1")
+    private String initSha1;
+
+    @JsonProperty("KernelVersion")
+    private String kernelVersion;
+
+    @JsonProperty("MemoryLimit")
+    private boolean memoryLimit;
+
+    @JsonProperty("NEventsListener")
+    private boolean nEventsListener;
+
+    @JsonProperty("NFd")
+    private int nfd;
+
+    @JsonProperty("NGoroutines")
+    private int ngoroutines;
+
+    @JsonProperty("SwapLimit")
+    private boolean swapLimit;
+
+    public int getContainers() {
+        return containers;
+    }
+
+    public void setContainers(int containers) {
+        this.containers = containers;
+    }
+
+    public boolean isDebug() {
+        return debug;
+    }
+
+    public void setDebug(boolean debug) {
+        this.debug = debug;
+    }
+
+    public String getDriver() {
+        return driver;
+    }
+
+    public void setDriver(String driver) {
+        this.driver = driver;
+    }
+
+    public List<List<String>> getDriverStatus() {
+        return driverStatus;
+    }
+
+    public void setDriverStatus(List<List<String>> driverStatus) {
+        this.driverStatus = driverStatus;
+    }
+
+    public String getExecutionDriver() {
+        return executionDriver;
+    }
+
+    public void setExecutionDriver(String executionDriver) {
+        this.executionDriver = executionDriver;
+    }
+
+    public boolean isIpv4Forwarding() {
+        return ipv4Forwarding;
+    }
+
+    public void setIpv4Forwarding(boolean ipv4Forwarding) {
+        this.ipv4Forwarding = ipv4Forwarding;
+    }
+
+    public int getImages() {
+        return images;
+    }
+
+    public void setImages(int images) {
+        this.images = images;
+    }
+
+    public String getIndexServerAddress() {
+        return indexServerAddress;
+    }
+
+    public void setIndexServerAddress(String indexServerAddress) {
+        this.indexServerAddress = indexServerAddress;
+    }
+
+    public String getInitPath() {
+        return initPath;
+    }
+
+    public void setInitPath(String initPath) {
+        this.initPath = initPath;
+    }
+
+    public String getInitSha1() {
+        return initSha1;
+    }
+
+    public void setInitSha1(String initSha1) {
+        this.initSha1 = initSha1;
+    }
+
+    public String getKernelVersion() {
+        return kernelVersion;
+    }
+
+    public void setKernelVersion(String kernelVersion) {
+        this.kernelVersion = kernelVersion;
+    }
+
+    public boolean isnEventsListener() {
+        return nEventsListener;
+    }
+
+    public void setnEventsListener(boolean nEventsListener) {
+        this.nEventsListener = nEventsListener;
+    }
+
+    public int getNfd() {
+        return nfd;
+    }
+
+    public void setNfd(int nfd) {
+        this.nfd = nfd;
+    }
+
+    public int getNgoroutines() {
+        return ngoroutines;
+    }
+
+    public void setNgoroutines(int ngoroutines) {
+        this.ngoroutines = ngoroutines;
+    }
+
+    public boolean isMemoryLimit() {
+        return memoryLimit;
+    }
+
+    public void setMemoryLimit(boolean memoryLimit) {
+        this.memoryLimit = memoryLimit;
+    }
+
+    public boolean isSwapLimit() {
+        return swapLimit;
+    }
+
+    public void setSwapLimit(boolean swapLimit) {
+        this.swapLimit = swapLimit;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/Port.java b/docker/src/main/java/org/apache/karaf/docker/Port.java
new file mode 100644
index 0000000000..5fe371c485
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/Port.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Represents a port mapping in a docker container.
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Port {
+
+    @JsonProperty("PrivatePort")
+    private int privatePort;
+
+    @JsonProperty("PublicPort")
+    private int publicPort;
+
+    @JsonProperty("Type")
+    private String type;
+
+    public int getPrivatePort() {
+        return privatePort;
+    }
+
+    public int getPublicPort() {
+        return publicPort;
+    }
+
+    public String getType() {
+        return type;
+    }
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/Top.java b/docker/src/main/java/org/apache/karaf/docker/Top.java
new file mode 100644
index 0000000000..9a2a8a6ee9
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/Top.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Top {
+
+    @JsonProperty("Titles")
+    private List<String> titles;
+
+    @JsonProperty("Processes")
+    private List<List<String>> processes;
+
+    public List<String> getTitles() {
+        return titles;
+    }
+
+    public List<List<String>> getProcesses() {
+        return processes;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/Version.java b/docker/src/main/java/org/apache/karaf/docker/Version.java
new file mode 100644
index 0000000000..a6af45af96
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/Version.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Version {
+
+    @JsonProperty("Version")
+    private String version;
+
+    @JsonProperty("Os")
+    private String os;
+
+    @JsonProperty("KernelVersion")
+    private String kernelVersion;
+
+    @JsonProperty("GoVersion")
+    private String goVersion;
+
+    @JsonProperty("GitCommit")
+    private String gitCommit;
+
+    @JsonProperty("Arch")
+    private String arch;
+
+    @JsonProperty("ApiVersion")
+    private String apiVersion;
+
+    @JsonProperty("BuildTime")
+    private String buildTime;
+
+    @JsonProperty("Experimental")
+    private String experimental;
+
+    public String getVersion() {
+        return version;
+    }
+
+    public String getOs() {
+        return os;
+    }
+
+    public String getKernelVersion() {
+        return kernelVersion;
+    }
+
+    public String getGoVersion() {
+        return goVersion;
+    }
+
+    public String getGitCommit() {
+        return gitCommit;
+    }
+
+    public String getArch() {
+        return arch;
+    }
+
+    public String getApiVersion() {
+        return apiVersion;
+    }
+
+    public String getBuildTime() {
+        return buildTime;
+    }
+
+    public String getExperimental() {
+        return experimental;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/CommitCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/CommitCommand.java
new file mode 100644
index 0000000000..7a2406c9d2
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/CommitCommand.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.command.completers.ContainersNameCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "docker", name = "commit", description = "Create a new image from a container's changes")
+@Service
+public class CommitCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "container", description = "Name or ID of the container", required = true, multiValued = false)
+    @Completion(ContainersNameCompleter.class)
+    String container;
+
+    @Argument(index = 1, name = "repository", description = "Repository", required = true, multiValued = false)
+    String repo;
+
+    @Argument(index = 2, name = "tag", description = "Tag", required = true, multiValued = false)
+    String tag;
+
+    @Option(name = "--message", description = "Commit message",  required = false, multiValued = false)
+    String message = "";
+
+    @Override
+    public Object execute() throws Exception {
+        getDockerService().commit(container, repo, tag, message, url);
+        return null;
+    }
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/CreateCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/CreateCommand.java
new file mode 100644
index 0000000000..06786b12e6
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/CreateCommand.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.ContainerConfig;
+import org.apache.karaf.docker.HostConfig;
+import org.apache.karaf.docker.HostPortBinding;
+import org.apache.karaf.docker.command.completers.ImagesRepoTagsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Command(scope = "docker", name = "create", description = "Create a new container")
+@Service
+public class CreateCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "name", description = "The container of the Docker container", required = true, multiValued = false)
+    String name;
+
+    @Option(name = "--hostname", description = "Hostname of the Docker container", required = false, multiValued = false)
+    String hostname = "docker";
+
+    @Option(name = "--user", description = "User of the Docker container", required = false, multiValued = false)
+    String user = "";
+
+    @Option(name = "--tty", description = "Enable TTY for the Docker container", required = false, multiValued = false)
+    boolean tty = true;
+
+    @Option(name = "--attachStdout", description = "Attach stdout for the Docker container", required = false, multiValued = false)
+    boolean attachStdout = true;
+
+    @Option(name = "--attachStderr", description = "Attach stderr for the Docker container", required = false, multiValued = false)
+    boolean attachStderr = true;
+
+    @Option(name = "--attachStdin", description = "Attach stdin for the Docker container", required = false, multiValued = false)
+    boolean attachStdin = true;
+
+    @Option(name = "--image", description = "Image to use for the Docker container", required = false, multiValued = false)
+    @Completion(ImagesRepoTagsCompleter.class)
+    String image = "karaf:4.2.0";
+
+    @Option(name = "--cmd", description = "Command to execute when starting the Docker container", required = false, multiValued = false)
+    String cmd = "/bin/karaf";
+
+    @Option(name = "--workingDir", description = "Working directory of the Docker container", required = false, multiValued = false)
+    String workingDir = "";
+
+    @Option(name = "--openStdin", description = "Enable and open stdin for the Docker container", required = false, multiValued = false)
+    boolean openStdin = true;
+
+    @Option(name = "--stdinOnce", description = "Enable single use of std in the Docker container", required = false, multiValued = false)
+    boolean stdinOnce = true;
+
+    @Option(name = "--exposedPort", description = "Port to expose from the Docker container", required = false, multiValued = false)
+    String exposedPort = "8101/tcp";
+
+    @Option(name = "--hostPrivileged", description = "Set host config privileges for the Docker container", required = false, multiValued = false)
+    boolean hostPrivileged = false;
+
+    @Option(name = "--hostPublishAllPorts", description = "Expose all ports for the Docker container", required = false, multiValued = false)
+    boolean hostPublishAllPorts = false;
+
+    @Option(name = "--hostNetworkMode", description = "Define the network mode for the Docker container", required = false, multiValued = false)
+    String hostNetworkMode = "bridge";
+
+    @Option(name = "--hostPortBinding", description = "Define the port binding for the Docker container", required = false, multiValued = false)
+    String hostPortBinding = "8101";
+
+    @Override
+    public Object execute() throws Exception {
+        ContainerConfig containerConfig = new ContainerConfig();
+        containerConfig.setTty(tty);
+        containerConfig.setAttachStdin(attachStdin);
+        containerConfig.setAttachStderr(attachStderr);
+        containerConfig.setAttachStdout(attachStdout);
+        containerConfig.setImage(image);
+        containerConfig.setHostname(hostname);
+        containerConfig.setUser(user);
+        containerConfig.setCmd(new String[]{ cmd });
+        containerConfig.setWorkingDir(workingDir);
+        containerConfig.setOpenStdin(openStdin);
+        containerConfig.setStdinOnce(stdinOnce);
+        Map<String, Map<String, String>> exposedPorts = new HashMap<>();
+        exposedPorts.put(exposedPort, new HashMap<>());
+        containerConfig.setExposedPorts(exposedPorts);
+        HostConfig hostConfig = new HostConfig();
+        hostConfig.setPrivileged(hostPrivileged);
+        hostConfig.setPublishAllPorts(hostPublishAllPorts);
+        hostConfig.setNetworkMode(hostNetworkMode);
+        hostConfig.setLxcConf(new String[]{});
+
+        Map<String, List<HostPortBinding>> portBindings = new HashMap<>();
+        List<HostPortBinding> hostPortBindings = new ArrayList<>();
+        HostPortBinding portBinding = new HostPortBinding();
+        portBinding.setHostIp("");
+        portBinding.setHostPort(hostPortBinding);
+        hostPortBindings.add(portBinding);
+        portBindings.put(exposedPort, hostPortBindings);
+
+        getDockerService().create(name, url, containerConfig);
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/DockerCommandSupport.java b/docker/src/main/java/org/apache/karaf/docker/command/DockerCommandSupport.java
new file mode 100644
index 0000000000..6d63add7ef
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/DockerCommandSupport.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.DockerService;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+
+public abstract class DockerCommandSupport implements Action {
+
+    @Option(name = "-u", aliases = "--url", description = "The location of the Docker REST API", required = false, multiValued = false)
+    String url;
+
+    @Reference
+    private DockerService dockerService;
+
+    public DockerService getDockerService() {
+        return dockerService;
+    }
+
+    public void setDockerService(DockerService dockerService) {
+        this.dockerService = dockerService;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/HistoryCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/HistoryCommand.java
new file mode 100644
index 0000000000..43d244865d
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/HistoryCommand.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.ImageHistory;
+import org.apache.karaf.docker.command.completers.ImagesRepoTagsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+import java.util.List;
+
+@Command(scope = "docker", name = "history", description = "Show the history of an image")
+@Service
+public class HistoryCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "image", description = "Name or ID of the image", required = true, multiValued = false)
+    @Completion(ImagesRepoTagsCompleter.class)
+    String image;
+
+    @Override
+    public Object execute() throws Exception {
+        List<ImageHistory> histories = getDockerService().history(image, url);
+        ShellTable table = new ShellTable();
+        table.column("ID");
+        table.column("Created");
+        table.column("Created By");
+        table.column("Tags");
+        table.column("Size");
+        for (ImageHistory history : histories) {
+            table.addRow().addContent(history.getId(),
+                    history.getCreated(),
+                    history.getCreatedBy(),
+                    history.getComment(),
+                    history.getTags(),
+                    history.getSize());
+        }
+        table.print(System.out);
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/ImagesCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/ImagesCommand.java
new file mode 100644
index 0000000000..ff5fa9b420
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/ImagesCommand.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.Image;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+@Command(scope = "docker", name = "images", description = "List Docker images")
+@Service
+public class ImagesCommand extends DockerCommandSupport {
+
+    @Override
+    public Object execute() throws Exception {
+        ShellTable table = new ShellTable();
+        table.column("Id");
+        table.column("RepoTags");
+        table.column("Created");
+        table.column("Labels");
+        table.column("Size");
+        table.column("Virtual Size");
+        for (Image image : getDockerService().images(url)) {
+            table.addRow().addContent(image.getId(),
+                    image.getRepoTags(),
+                    image.getCreated(),
+                    image.getLabels(),
+                    image.getSize(),
+                    image.getVirtualSize());
+        }
+        table.print(System.out);
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/InfoCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/InfoCommand.java
new file mode 100644
index 0000000000..239922ba04
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/InfoCommand.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.Info;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "docker", name = "info", description = "Display system-wide information")
+@Service
+public class InfoCommand extends DockerCommandSupport {
+
+    @Override
+    public Object execute() throws Exception {
+        Info info = getDockerService().info(url);
+        System.out.println("Containers: " + info.getContainers());
+        System.out.println("Debug: " + info.isDebug());
+        System.out.println("Driver: " + info.getDriver());
+        System.out.println("ExecutionDriver: " + info.getExecutionDriver());
+        System.out.println("IPv4Forwarding: " + info.isIpv4Forwarding());
+        System.out.println("Images: " + info.getImages());
+        System.out.println("IndexServerAddress: " + info.getIndexServerAddress());
+        System.out.println("InitPath: " + info.getInitPath());
+        System.out.println("InitSha1: " + info.getInitSha1());
+        System.out.println("KernelVersion: " + info.getKernelVersion());
+        System.out.println("MemoryLimit: " + info.isMemoryLimit());
+        System.out.println("NEventsListener: " + info.isnEventsListener());
+        System.out.println("NFd: " + info.getNfd());
+        System.out.println("NGoroutines: " + info.getNgoroutines());
+        System.out.println("SwapLimit: " + info.isSwapLimit());
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/KillCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/KillCommand.java
new file mode 100644
index 0000000000..31fcd82afe
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/KillCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.command.completers.ContainersNameCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.List;
+
+@Command(scope = "docker", name = "kill", description = "Kill one or more running containers")
+@Service
+public class KillCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "container", description = "Name or ID of the containers to kill", required = true, multiValued = true)
+    @Completion(ContainersNameCompleter.class)
+    List<String> containers;
+
+    @Option(name = "--signal", description = "The signal to send to the processes", required = false,  multiValued = false)
+    String signal = "SIGKILL";
+
+    @Override
+    public Object execute() throws Exception {
+        for (String container : containers) {
+            getDockerService().kill(container, signal, url);
+        }
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/LogsCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/LogsCommand.java
new file mode 100644
index 0000000000..e8b0fbf08e
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/LogsCommand.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.command.completers.ContainersNameCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "docker", name = "logs", description = "Fetch the logs of a container")
+@Service
+public class LogsCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "container", description = "Name or ID of the container", required = true, multiValued = false)
+    @Completion(ContainersNameCompleter.class)
+    String container;
+
+    @Option(name = "--stdout", description = "Display stdout", required = false, multiValued = false)
+    boolean stdout = true;
+
+    @Option(name = "--stderr", description = "Display stderr", required = false, multiValued = false)
+    boolean stderr;
+
+    @Option(name = "--timestamps", description = "Show timestamps", required = false, multiValued = false)
+    boolean timestamps;
+
+    @Option(name = "--details", description = "Show extra details provided to logs", required = false, multiValued = false)
+    boolean details;
+
+    @Override
+    public Object execute() throws Exception {
+        if (!stdout && !stderr) {
+            System.err.println("You have at least to choose one stream: stdout or stderr using the corresponding command options");
+        }
+        System.out.println(getDockerService().logs(container, stdout, stderr, timestamps, details, url));
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/PauseCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/PauseCommand.java
new file mode 100644
index 0000000000..c4fe54a900
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/PauseCommand.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.command.completers.ContainersNameCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.List;
+
+@Command(scope = "docker", name = "pause", description = "Pause all processes within one or more containers")
+@Service
+public class PauseCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "container", description = "Name or ID of the containers to pause", required = true, multiValued = true)
+    @Completion(ContainersNameCompleter.class)
+    List<String> containers;
+
+    @Override
+    public Object execute() throws Exception {
+        for (String container : containers) {
+            getDockerService().pause(container, url);
+        }
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/ProvisionCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/ProvisionCommand.java
new file mode 100644
index 0000000000..26332c9f26
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/ProvisionCommand.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "docker", name = "provision", description = "Create a Docker container using the current running Karaf instance")
+@Service
+public class ProvisionCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "name", description = "Name of the Docker container", required = true, multiValued = false)
+    String name;
+
+    @Option(name = "-c", aliases = "--copy", description = "Use directly the current Karaf instance working dir or make a copy", required = false, multiValued = false)
+    boolean copy;
+
+    @Option(name = "--sshPort", description = "Port number used by the Karaf SSH server", required = false, multiValued = false)
+    String sshPort = "8101";
+
+    @Option(name = "--jmxRmiPort", description = "Port number used by the Karaf JMX RMI MBeanServer", required = false, multiValued = false)
+    String jmxRmiPort = "1099";
+
+    @Option(name = "--jmxRmiRegistryPort", description = "Port number used by the Karaf JMX RMI Registry MBeanServer", required = false, multiValued = false)
+    String jmxRmiRegistryPort = "44444";
+
+    @Option(name = "--httpPort", description = "Port number used by the Karaf HTTP service", required = false, multiValued = false)
+    String httpPort = "8181";
+
+    @Override
+    public Object execute() throws Exception {
+        getDockerService().provision(name, sshPort, jmxRmiPort, jmxRmiRegistryPort, httpPort, copy, url);
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/PsCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/PsCommand.java
new file mode 100644
index 0000000000..24f49ea9be
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/PsCommand.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.Container;
+import org.apache.karaf.docker.Port;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+@Command(scope = "docker", name = "ps", description = "List containers")
+@Service
+public class PsCommand extends DockerCommandSupport {
+
+    @Option(name = "-a", aliases = {"--all", "--showAll" }, description = "Display all containers or only running ones", required = false, multiValued = false)
+    boolean showAll;
+
+    @Override
+    public Object execute() throws Exception {
+        ShellTable table = new ShellTable();
+        table.column("Id");
+        table.column("Names");
+        table.column("Command");
+        table.column("Created");
+        table.column("Image");
+        table.column("Image ID");
+        table.column("Status");
+        table.column("State");
+        table.column("Ports");
+        table.column("Size");
+        table.column("Size Root");
+        for (Container container : getDockerService().ps(showAll, url)) {
+            StringBuffer portBuffer = new StringBuffer();
+            for (Port port : container.getPorts()) {
+                portBuffer.append(port.getType()).append(":").append(port.getPrivatePort()).append(":").append(port.getPublicPort()).append(" ");
+            }
+            table.addRow().addContent(
+                    container.getId(),
+                    container.getNames(),
+                    container.getCommand(),
+                    container.getCreated(),
+                    container.getImage(),
+                    container.getImageId(),
+                    container.getStatus(),
+                    container.getState(),
+                    portBuffer.toString(),
+                    container.getSizeRw(),
+                    container.getSizeRootFs()
+            );
+        }
+        table.print(System.out);
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/PullCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/PullCommand.java
new file mode 100644
index 0000000000..f496172343
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/PullCommand.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "docker", name = "pull", description = "Pull an image")
+@Service
+public class PullCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "image", description = "The Docker image to pull", multiValued = false, required = true)
+    String image;
+
+    @Option(name = "-t", aliases = "--tag", description = "Tag to use", multiValued = false, required = false)
+    String tag = "latest";
+
+    @Option(name = "-v", aliases = "--verbose", description = "Display pulling progress on console", multiValued = false, required = false)
+    boolean verbose;
+
+    @Override
+    public Object execute() throws Exception {
+        getDockerService().pull(image, tag, verbose, url);
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/PushCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/PushCommand.java
new file mode 100644
index 0000000000..ec3667896c
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/PushCommand.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.command.completers.ImagesRepoTagsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "docker", name = "push", description = "Push an image or a repository to a registry")
+@Service
+public class PushCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "image", description = "Name or ID of the image", required = true, multiValued = false)
+    @Completion(ImagesRepoTagsCompleter.class)
+    String image;
+
+    @Option(name = "--tag", description = "Push tag",  required = false, multiValued = false)
+    String tag = "latest";
+
+    @Option(name = "-v", aliases = "--verbose", description = "Display push progress on console", required = false, multiValued = false)
+    boolean verbose;
+
+    @Override
+    public Object execute() throws Exception {
+        getDockerService().push(image, tag, verbose, url);
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/RenameCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/RenameCommand.java
new file mode 100644
index 0000000000..6abf80fd8f
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/RenameCommand.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.command.completers.ContainersNameCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "docker", name = "rename", description = "Rename a container")
+@Service
+public class RenameCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "container", description = "Name or ID of the container to rename", required = true, multiValued = false)
+    @Completion(ContainersNameCompleter.class)
+    String container;
+
+    @Argument(index = 1, name = "newName", description = "New name of the container", required = true, multiValued = false)
+    String newName;
+
+    @Override
+    public Object execute() throws Exception {
+        getDockerService().rename(container, newName, url);
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/RestartCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/RestartCommand.java
new file mode 100644
index 0000000000..b996f4b669
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/RestartCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.command.completers.ContainersNameCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.List;
+
+@Command(scope = "docker", name = "restart", description = "Restart one or more containers")
+@Service
+public class RestartCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "container", description = "Name or ID of the containers to restart", required = true, multiValued = true)
+    @Completion(ContainersNameCompleter.class)
+    List<String> containers;
+
+    @Option(name = "-t", aliases = "--time", description = "Seconds to wait for stop before killing it (default 10)", required = false, multiValued = true)
+    int timeToWait = 10;
+
+    @Override
+    public Object execute() throws Exception {
+        for (String container : containers) {
+            getDockerService().restart(container, timeToWait, url);
+        }
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/RmCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/RmCommand.java
new file mode 100644
index 0000000000..b534641602
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/RmCommand.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.command.completers.ContainersNameCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.List;
+
+@Command(scope = "docker", name = "rm", description = "Remove one or more containers")
+@Service
+public class RmCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "container", description = "Name or ID of the containers to remove", required = true, multiValued = true)
+    @Completion(ContainersNameCompleter.class)
+    List<String> containers;
+
+    @Option(name = "--removeVolumes", description = "Remove the container volumes", required = false, multiValued = true)
+    boolean removeVolumes;
+
+    @Option(name = "--force", description = "Force remove container", required = false, multiValued = true)
+    boolean force;
+
+    @Override
+    public Object execute() throws Exception {
+        for (String container : containers) {
+            getDockerService().rm(container, removeVolumes, force, url);
+        }
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/RmiCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/RmiCommand.java
new file mode 100644
index 0000000000..ff25f669bb
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/RmiCommand.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.command.completers.ImagesRepoTagsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "docker", name = "rmi", description = "Remove an image")
+@Service
+public class RmiCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "image", description = "The image to remove", required = true, multiValued = false)
+    @Completion(ImagesRepoTagsCompleter.class)
+    String image;
+
+    @Option(name = "-f", aliases = "--force", description = "Force image remove", required = false, multiValued = false)
+    boolean force;
+
+    @Option(name = "-np", aliases = "--noprune", description = "Don't prune image", required = false, multiValued = false)
+    boolean noprune;
+
+    @Override
+    public Object execute() throws Exception {
+        getDockerService().rmi(image, force, noprune, url);
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/SearchCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/SearchCommand.java
new file mode 100644
index 0000000000..8c5bff91f6
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/SearchCommand.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.ImageSearch;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+@Command(scope = "docker", name = "search", description = "Search the Docker Hub for images")
+@Service
+public class SearchCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "term", description = "Search term", required = true, multiValued = false)
+    String term;
+
+    @Override
+    public Object execute() throws Exception {
+        ShellTable table = new ShellTable();
+        table.column("Name");
+        table.column("Description");
+        table.column("Automated");
+        table.column("Official");
+        table.column("Star Count");
+        for (ImageSearch search : getDockerService().search(term, url)) {
+            table.addRow().addContent(
+                    search.getName(),
+                    search.getDescription(),
+                    search.isAutomated(),
+                    search.isOfficial(),
+                    search.getStarCount()
+            );
+        }
+        table.print(System.out);
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/StartCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/StartCommand.java
new file mode 100644
index 0000000000..5cd0af912c
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/StartCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.command.completers.ContainersNameCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.List;
+
+@Command(scope = "docker", name = "start", description = "Start one or more stopped containers")
+@Service
+public class StartCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "container", description = "Name or ID of the containers to start", required = true, multiValued = true)
+    @Completion(ContainersNameCompleter.class)
+    List<String> containers;
+
+
+    @Override
+    public Object execute() throws Exception {
+        for (String container : containers) {
+            getDockerService().start(container, url);
+        }
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/StopCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/StopCommand.java
new file mode 100644
index 0000000000..474d5bc13b
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/StopCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.command.completers.ContainersNameCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.List;
+
+@Command(scope = "docker", name = "stop", description = "Stop one or more running containers")
+@Service
+public class StopCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "container", description = "Name or ID of the containers to stop", required = true, multiValued = true)
+    @Completion(ContainersNameCompleter.class)
+    List<String> containers;
+
+    @Option(name = "-t", aliases = "--time", description = "Seconds to wait for stop before killing it (default 10)", required = false, multiValued = false)
+    int timeToWait = 10;
+
+    @Override
+    public Object execute() throws Exception {
+        for (String container : containers) {
+            getDockerService().stop(container, timeToWait, url);
+        }
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/TagCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/TagCommand.java
new file mode 100644
index 0000000000..374e593818
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/TagCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.command.completers.ImagesRepoTagsCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "docker", name = "tag", description = "Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE")
+@Service
+public class TagCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "image", description = "ID or name of the image to tag", required = true, multiValued = false)
+    @Completion(ImagesRepoTagsCompleter.class)
+    String image;
+
+    @Argument(index = 1, name = "tag", description = "Tag", required = true, multiValued = false)
+    String tag;
+
+    @Argument(index = 2, name = "repo", description = "Repository where to tag", required = false, multiValued = false)
+    String repo;
+
+    @Override
+    public Object execute() throws Exception {
+        getDockerService().tag(image, repo, tag, url);
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/TopCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/TopCommand.java
new file mode 100644
index 0000000000..283fcecf1a
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/TopCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.Top;
+import org.apache.karaf.docker.command.completers.ContainersNameCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.List;
+
+@Command(scope = "docker", name = "top", description = "Display the running processes of a container")
+@Service
+public class TopCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "container", description = "Name or ID of the container", required = true, multiValued = false)
+    @Completion(ContainersNameCompleter.class)
+    String container;
+
+    @Override
+    public Object execute() throws Exception {
+        Top top = getDockerService().top(container, url);
+        for (List<String> process : top.getProcesses()) {
+            for (String p : process) {
+                System.out.print(p + "\t");
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/UnpauseCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/UnpauseCommand.java
new file mode 100644
index 0000000000..2896ad21ea
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/UnpauseCommand.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.command.completers.ContainersNameCompleter;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.List;
+
+@Command(scope = "docker", name = "unpause", description = "Unpause all processes within one or more containers")
+@Service
+public class UnpauseCommand extends DockerCommandSupport {
+
+    @Argument(index = 0, name = "container", description = "Name or ID of the containers to unpause", required = true, multiValued = true)
+    @Completion(ContainersNameCompleter.class)
+    List<String> containers;
+
+    @Override
+    public Object execute() throws Exception {
+        for (String container : containers) {
+            getDockerService().unpause(container, url);
+        }
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/VersionCommand.java b/docker/src/main/java/org/apache/karaf/docker/command/VersionCommand.java
new file mode 100644
index 0000000000..10f8b51081
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/VersionCommand.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command;
+
+import org.apache.karaf.docker.Version;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "docker", name = "version", description = "Show the Docker version information")
+@Service
+public class VersionCommand extends DockerCommandSupport {
+
+    @Override
+    public Object execute() throws Exception {
+        Version version = getDockerService().version(url);
+        System.out.println("Version: " + version.getVersion());
+        System.out.println("Os: " + version.getOs());
+        System.out.println("Kernel version: " + version.getKernelVersion());
+        System.out.println("Go version: " + version.getGoVersion());
+        System.out.println("Git commit: " + version.getGitCommit());
+        System.out.println("Arch: "  + version.getArch());
+        System.out.println("API version: " + version.getApiVersion());
+        System.out.println("Build time: " + version.getBuildTime());
+        System.out.println("Experimental: " + version.getExperimental());
+        return null;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/completers/ContainersNameCompleter.java b/docker/src/main/java/org/apache/karaf/docker/command/completers/ContainersNameCompleter.java
new file mode 100644
index 0000000000..0f8474d89d
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/completers/ContainersNameCompleter.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command.completers;
+
+import org.apache.karaf.docker.Container;
+import org.apache.karaf.docker.DockerService;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+
+import java.util.List;
+
+@Service
+public class ContainersNameCompleter implements Completer {
+
+    @Reference
+    private DockerService dockerService;
+
+    @Override
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        try {
+            for (Container container : dockerService.ps(true, null)) {
+                for (String name : container.getNames()) {
+                    delegate.getStrings().add(name);
+                }
+            }
+        } catch (Exception e) {
+            // nothing to do
+        }
+        return delegate.complete(session, commandLine, candidates);
+    }
+
+    public void setDockerService(DockerService dockerService) {
+        this.dockerService = dockerService;
+    }
+
+    public DockerService getDockerService() {
+        return this.dockerService;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/command/completers/ImagesRepoTagsCompleter.java b/docker/src/main/java/org/apache/karaf/docker/command/completers/ImagesRepoTagsCompleter.java
new file mode 100644
index 0000000000..967278a80e
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/command/completers/ImagesRepoTagsCompleter.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.command.completers;
+
+import org.apache.karaf.docker.DockerService;
+import org.apache.karaf.docker.Image;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+
+import java.util.List;
+
+@Service
+public class ImagesRepoTagsCompleter implements Completer {
+
+    @Reference
+    private DockerService dockerService;
+
+    @Override
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        try {
+            for (Image image : dockerService.images(null)) {
+                for (String repoTag : image.getRepoTags()) {
+                    delegate.getStrings().add(repoTag);
+                }
+            }
+        } catch (Exception e) {
+            // nothing to do
+        }
+        return delegate.complete(session, commandLine, candidates);
+    }
+
+    public void setDockerService(DockerService dockerService) {
+        this.dockerService = dockerService;
+    }
+
+    public DockerService getDockerService() {
+        return this.dockerService;
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/internal/DockerMBeanImpl.java b/docker/src/main/java/org/apache/karaf/docker/internal/DockerMBeanImpl.java
new file mode 100644
index 0000000000..d485a36e63
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/internal/DockerMBeanImpl.java
@@ -0,0 +1,285 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.internal;
+
+import org.apache.karaf.docker.*;
+
+import javax.management.MBeanException;
+import javax.management.openmbean.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DockerMBeanImpl implements DockerMBean {
+
+    private DockerService dockerService;
+
+    public void setDockerService(DockerService dockerService) {
+        this.dockerService = dockerService;
+    }
+
+    @Override
+    public TabularData ps(boolean showAll, String url) throws MBeanException {
+        try {
+            CompositeType containerType = new CompositeType("container", "Docker Container",
+                    new String[]{"Id", "Names", "Command", "Created", "Image", "Status"},
+                    new String[]{"Container ID", "Container Names", "Command run in the container", "Container creation time", "Image used by the container", "Current container status"},
+                    new OpenType[]{SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.LONG, SimpleType.STRING, SimpleType.STRING});
+            TabularType tableType = new TabularType("containers", "Docker containers", containerType, new String[]{ "Id" });
+            TabularData table = new TabularDataSupport(tableType);
+            for (Container container : dockerService.ps(showAll, url)) {
+                CompositeData data = new CompositeDataSupport(containerType,
+                        new String[]{"Id", "Names", "Command", "Created", "Image", "Status"},
+                        new Object[]{container.getId(), container.getNames(), container.getCommand(), container.getCreated(), container.getImage(), container.getStatus()});
+                table.put(data);
+            }
+            return table;
+        } catch (Exception e) {
+            throw new MBeanException(e);
+        }
+    }
+
+    @Override
+    public Map<String, String> info(String url) throws MBeanException {
+        try {
+            Info info = dockerService.info(url);
+            Map<String, String> infoMap = new HashMap<>();
+            infoMap.put("Containers", new Integer(info.getContainers()).toString());
+            infoMap.put("Debug", new Boolean(info.isDebug()).toString());
+            infoMap.put("Driver", info.getDriver());
+            infoMap.put("ExecutionDriver", info.getExecutionDriver());
+            infoMap.put("IPv4Forwarding", new Boolean(info.isIpv4Forwarding()).toString());
+            infoMap.put("Images", new Integer(info.getImages()).toString());
+            infoMap.put("IndexServerAddress", info.getIndexServerAddress());
+            infoMap.put("InitPath", info.getInitPath());
+            infoMap.put("InitSha1", info.getInitSha1());
+            infoMap.put("KernelVersion", info.getKernelVersion());
+            infoMap.put("MemoryLimit", new Boolean(info.isMemoryLimit()).toString());
+            infoMap.put("NEventsListener", new Boolean(info.isnEventsListener()).toString());
+            infoMap.put("NFd", new Integer(info.getNfd()).toString());
+            infoMap.put("NGoroutines", new Integer(info.getNgoroutines()).toString());
+            infoMap.put("SwapLimit", new Boolean(info.isSwapLimit()).toString());
+            return infoMap;
+        } catch (Exception e) {
+            throw new MBeanException(null, e.getMessage());
+        }
+    }
+
+    @Override
+    public void provision(String name, String sshPort, String jmxRmiPort, String jmxRmiRegistryPort, String httpPort, boolean copy, String url) throws MBeanException {
+        try {
+            dockerService.provision(name, sshPort, jmxRmiPort, jmxRmiRegistryPort, httpPort, copy, url);
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public void rm(String name, boolean removeVolumes, boolean force, String url) throws MBeanException {
+        try {
+            dockerService.rm(name, removeVolumes, force, url);
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public void start(String name, String url) throws MBeanException {
+        try {
+            dockerService.start(name, url);
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public void stop(String name, int timeToWait, String url) throws MBeanException {
+        try {
+            dockerService.stop(name, timeToWait, url);
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public String logs(String name, boolean stdout, boolean stderr, boolean timestamps, boolean details, String url) throws MBeanException {
+        try {
+            if (!stdout && !stderr) {
+                throw new MBeanException(null, "You have to choose at least one stream: stdout or stderr");
+            }
+            return dockerService.logs(name, stdout, stderr, timestamps, details, url);
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public void commit(String name, String repo, String tag, String message, String url) throws MBeanException {
+        try {
+            dockerService.commit(name, repo, url, tag, message);
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public TabularData images(String url) throws MBeanException {
+        try {
+            CompositeType type = new CompositeType("Image", "Docker Image",
+                    new String[]{ "Id", "Created", "RepoTags", "Size"},
+                    new String[]{ "Image Id", "Image Creation Date", "Image repository and tag", "Image size"},
+                    new OpenType[]{ SimpleType.STRING, SimpleType.LONG, SimpleType.STRING, SimpleType.LONG});
+            TabularType tableType = new TabularType("Images", "List of Docker Image", type, new String[]{ "Id" });
+            TabularData table = new TabularDataSupport(tableType);
+            for (Image image : dockerService.images(url)) {
+                CompositeData data = new CompositeDataSupport(type,
+                        new String[]{ "Id", "Created", "RepoTags", "Size"},
+                        new Object[]{ image.getId(), image.getCreated(), image.getRepoTags(), image.getSize() });
+                table.put(data);
+            }
+            return table;
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public void pull(String image, String tag, String url) throws MBeanException {
+        try {
+            dockerService.pull(image, tag, false, url);
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public Map<String, String> version(String url) throws MBeanException {
+        try {
+            Version version = dockerService.version(url);
+            Map<String, String> versionMap = new HashMap<>();
+            versionMap.put("Experimental", version.getExperimental());
+            versionMap.put("ApiVersion", version.getApiVersion());
+            versionMap.put("Arch", version.getArch());
+            versionMap.put("BuildTime", version.getBuildTime());
+            versionMap.put("GitCommit", version.getGitCommit());
+            versionMap.put("GoVersion", version.getGoVersion());
+            versionMap.put("KernelVersion", version.getKernelVersion());
+            versionMap.put("OS", version.getOs());
+            versionMap.put("Version", version.getVersion());
+            return  versionMap;
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public void rename(String container, String newName, String url) throws MBeanException {
+        try {
+            dockerService.rename(container, newName, url);
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public void restart(String container, int timeToWait, String url) throws MBeanException {
+        try {
+            dockerService.restart(container, timeToWait, url);
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public void kill(String container, String signal, String url) throws MBeanException {
+        try {
+            if (signal == null) {
+                signal = "SIGKILL";
+            }
+            dockerService.kill(container, signal, url);
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public void pause(String container, String url) throws MBeanException {
+        try {
+            dockerService.pause(container, url);
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public void unpause(String container, String url) throws MBeanException {
+        try {
+            dockerService.unpause(container, url);
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public TabularData search(String term, String url) throws MBeanException {
+        try {
+            CompositeType imageType = new CompositeType("image", "Image",
+                    new String[]{"Name", "StarCount", "Official", "Automated", "Description"},
+                    new String[]{"Name", "StarCount", "Official", "Automated", "Description"},
+                    new OpenType[]{SimpleType.STRING, SimpleType.INTEGER, SimpleType.BOOLEAN, SimpleType.BOOLEAN, SimpleType.STRING});
+            TabularType tableType = new TabularType("images", "Images", imageType, new String[]{"Name"});
+            TabularData table = new TabularDataSupport(tableType);
+            List<ImageSearch> images = dockerService.search(term, url);
+            for (ImageSearch image : images) {
+                CompositeData data = new CompositeDataSupport(imageType,
+                        new String[]{"Name", "StarCount", "Official", "Automated", "Description"},
+                        new Object[]{image.getName(), image.getStarCount(), image.isOfficial(), image.isAutomated(), image.getDescription()});
+                table.put(data);
+            }
+            return table;
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public void tag(String image, String tag, String repo, String url) throws MBeanException {
+        try {
+            dockerService.tag(image, tag, repo, url);
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public void rmi(String image, boolean force, boolean noprune, String url) throws MBeanException {
+        try {
+            dockerService.rmi(image, force, noprune, url);
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+
+    @Override
+    public void push(String image, String tag, String url) throws MBeanException {
+        try {
+            dockerService.push(image, tag, false, url);
+        } catch (Throwable t) {
+            throw new MBeanException(null, t.getMessage());
+        }
+    }
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/internal/DockerServiceImpl.java b/docker/src/main/java/org/apache/karaf/docker/internal/DockerServiceImpl.java
new file mode 100644
index 0000000000..a212564c0f
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/internal/DockerServiceImpl.java
@@ -0,0 +1,438 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.internal;
+
+import org.apache.karaf.docker.*;
+
+import java.io.*;
+import java.util.*;
+
+public class DockerServiceImpl implements DockerService {
+
+    private File storageLocation;
+
+    public File getStorageLocation() {
+        return storageLocation;
+    }
+
+    public void setStorageLocation(File storageLocation) {
+        this.storageLocation = storageLocation;
+    }
+
+    @Override
+    public List<Container> ps(boolean showAll, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        return dockerClient.ps(showAll);
+    }
+
+    @Override
+    public Info info(String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+
+        return dockerClient.info();
+    }
+
+    @Override
+    public Version version(String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        return dockerClient.version();
+    }
+
+    @Override
+    public void create(String name, String url) throws Exception {
+        // pull the java:8-jre-alpine image
+        // TODO it should be changed to the official karaf image as soon as it's available on Docker HUB
+        pull("java", "8-jre-alpine", true, url);
+
+        // create a default Karaf docker container configuration
+        ContainerConfig config = new ContainerConfig();
+        config.setTty(true);
+        config.setAttachStdin(true);
+        config.setAttachStderr(true);
+        config.setAttachStdout(true);
+        // TODO it should be changed to the official karaf image as soon as it's available on Docker HUB
+        config.setImage("java:8-jre-alpine");
+        config.setHostname("");
+        config.setUser("");
+        config.setCmd(new String[]{ "/opt/apache-karaf/bin/karaf" });
+        config.setWorkingDir("");
+        config.setOpenStdin(true);
+        config.setStdinOnce(true);
+        Map<String, Map<String, String>> exposedPorts = new HashMap<>();
+        exposedPorts.put("8101/tcp", new HashMap<>());
+        exposedPorts.put("1099/tcp", new HashMap<>());
+        exposedPorts.put("44444/tcp", new HashMap<>());
+        exposedPorts.put("8181/tcp", new HashMap<>());
+        config.setExposedPorts(exposedPorts);
+
+        HostConfig hostConfig = new HostConfig();
+        hostConfig.setPrivileged(false);
+        hostConfig.setPublishAllPorts(false);
+
+        // getting the container storage
+        File containerStorage = new File(storageLocation, name);
+        if (containerStorage.exists()) {
+            hostConfig.setBinds(new String[]{ containerStorage.getAbsolutePath() + ":/opt/apache-karaf" });
+        }
+
+        hostConfig.setNetworkMode("bridge");
+        hostConfig.setLxcConf(new String[]{});
+
+        Map<String, List<HostPortBinding>> portBindings = new HashMap<>();
+        // ssh
+        List<HostPortBinding> sshPortBindings = new ArrayList<>();
+        HostPortBinding sshPortBinding = new HostPortBinding();
+        sshPortBinding.setHostIp("");
+        sshPortBinding.setHostPort("8101");
+        sshPortBindings.add(sshPortBinding);
+        portBindings.put("8101/tcp", sshPortBindings);
+        // jmx rmi
+        List<HostPortBinding> jmxPortBindings = new ArrayList<>();
+        HostPortBinding jmxPortBinding = new HostPortBinding();
+        jmxPortBinding.setHostIp("");
+        jmxPortBinding.setHostPort("1099");
+        jmxPortBindings.add(jmxPortBinding);
+        portBindings.put("1099/tcp", jmxPortBindings);
+        // jmx rmi registry
+        List<HostPortBinding> jmxRegistryPortBindings = new ArrayList<>();
+        HostPortBinding jmxRegistryPortBinding = new HostPortBinding();
+        jmxRegistryPortBinding.setHostIp("");
+        jmxRegistryPortBinding.setHostPort("44444");
+        jmxRegistryPortBindings.add(jmxRegistryPortBinding);
+        portBindings.put("44444/tcp", jmxRegistryPortBindings);
+        // http
+        List<HostPortBinding> httpPortBindings = new ArrayList<>();
+        HostPortBinding httpPortBinding = new HostPortBinding();
+        httpPortBinding.setHostIp("");
+        httpPortBinding.setHostPort("8181");
+        httpPortBindings.add(httpPortBinding);
+        portBindings.put("8181/tcp", httpPortBindings);
+
+        hostConfig.setPortBindings(portBindings);
+
+        config.setHostConfig(hostConfig);
+
+        create(name, url, config);
+    }
+
+    @Override
+    public void create(String name, String url, ContainerConfig config) throws Exception {
+        // creating the docker container
+        DockerClient dockerClient = new DockerClient(url);
+
+        dockerClient.create(config, name);
+    }
+
+    @Override
+    public void provision(String name, String sshPort, String jmxRmiPort, String jmxRmiRegistryPort, String httpPort, boolean copy, String url) throws Exception {
+        // pull the java:8-jre-alpine image
+        pull("java", "8-jre-alpine", true, url);
+
+        // use Karaf instance as base
+        File karafBase = new File(System.getProperty("karaf.base"));
+        File containerStorage;
+        if (copy) {
+            containerStorage = new File(storageLocation, name);
+            containerStorage.mkdirs();
+            copy(karafBase, containerStorage);
+            chmod(new File(containerStorage, "bin/karaf"), "a+x");
+            chmod(new File(containerStorage, "bin/client"), "a+x");
+            chmod(new File(containerStorage, "bin/inc"), "a+x");
+            chmod(new File(containerStorage, "bin/instance"), "a+x");
+            chmod(new File(containerStorage, "bin/setenv"), "a+x");
+            chmod(new File(containerStorage, "bin/shell"), "a+x");
+            chmod(new File(containerStorage, "bin/start"), "a+x");
+            chmod(new File(containerStorage, "bin/status"), "a+x");
+            chmod(new File(containerStorage, "bin/stop"), "a+x");
+        } else {
+            containerStorage = karafBase;
+        }
+
+        // creating the Karaf Docker container
+        DockerClient dockerClient = new DockerClient(url);
+        ContainerConfig config = new ContainerConfig();
+        config.setTty(true);
+        config.setAttachStdout(true);
+        config.setAttachStderr(true);
+        config.setAttachStdin(true);
+        config.setImage("java:8-jre-alpine");
+        config.setHostname("");
+        config.setUser("");
+        config.setCmd(new String[]{ "/opt/apache-karaf/bin/karaf" });
+        config.setWorkingDir("");
+        config.setOpenStdin(true);
+        config.setStdinOnce(true);
+        Map<String, Map<String, String>> exposedPorts = new HashMap<>();
+        exposedPorts.put("8101/tcp", new HashMap<>());
+        exposedPorts.put("1099/tcp", new HashMap<>());
+        exposedPorts.put("44444/tcp", new HashMap<>());
+        exposedPorts.put("8181/tcp", new HashMap<>());
+        config.setExposedPorts(exposedPorts);
+
+        HostConfig hostConfig = new HostConfig();
+        hostConfig.setPrivileged(false);
+        hostConfig.setPublishAllPorts(false);
+
+        // binding filesystem
+        hostConfig.setBinds(new String[]{containerStorage.getAbsolutePath() + ":/opt/apache-karaf"});
+
+        hostConfig.setNetworkMode("bridge");
+        hostConfig.setLxcConf(new String[]{});
+
+        Map<String, List<HostPortBinding>> portBindings = new HashMap<>();
+        // ssh
+        List<HostPortBinding> sshPortBindings = new ArrayList<>();
+        HostPortBinding sshPortBinding = new HostPortBinding();
+        sshPortBinding.setHostIp("");
+        sshPortBinding.setHostPort(sshPort);
+        sshPortBindings.add(sshPortBinding);
+        portBindings.put("8101/tcp", sshPortBindings);
+        // jmx rmi
+        List<HostPortBinding> jmxPortBindings = new ArrayList<>();
+        HostPortBinding jmxPortBinding = new HostPortBinding();
+        jmxPortBinding.setHostIp("");
+        jmxPortBinding.setHostPort(jmxRmiPort);
+        jmxPortBindings.add(jmxPortBinding);
+        portBindings.put("1099/tcp", jmxPortBindings);
+        // jmx rmi registry
+        List<HostPortBinding> jmxRegistryPortBindings = new ArrayList<>();
+        HostPortBinding jmxRegistryPortBinding = new HostPortBinding();
+        jmxRegistryPortBinding.setHostIp("");
+        jmxRegistryPortBinding.setHostPort(jmxRmiRegistryPort);
+        jmxRegistryPortBindings.add(jmxRegistryPortBinding);
+        portBindings.put("44444/tcp", jmxRegistryPortBindings);
+        // http
+        List<HostPortBinding> httpPortBindings = new ArrayList<>();
+        HostPortBinding httpPortBinding = new HostPortBinding();
+        httpPortBinding.setHostIp("");
+        httpPortBinding.setHostPort(httpPort);
+        httpPortBindings.add(httpPortBinding);
+        portBindings.put("8181/tcp", httpPortBindings);
+
+        hostConfig.setPortBindings(portBindings);
+
+        config.setHostConfig(hostConfig);
+
+        dockerClient.create(config, name);
+    }
+
+    @Override
+    public void start(String name, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        dockerClient.start(name);
+    }
+
+    @Override
+    public void stop(String name, int timeToWait, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        dockerClient.stop(name, timeToWait);
+    }
+
+    @Override
+    public void restart(String name, int timeToWait, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        dockerClient.restart(name, timeToWait);
+    }
+
+    @Override
+    public void kill(String name, String signal, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        dockerClient.kill(name, signal);
+    }
+
+    @Override
+    public void pause(String name, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        dockerClient.pause(name);
+    }
+
+    @Override
+    public void unpause(String name, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        dockerClient.unpause(name);
+    }
+
+    @Override
+    public void rename(String name, String newName, String url) throws  Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        dockerClient.rename(name, newName);
+    }
+
+    @Override
+    public void rm(String name, boolean removeVolumes, boolean force, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        dockerClient.rm(name, removeVolumes, force);
+        File containerStorage = new File(storageLocation, name);
+        if (containerStorage.exists()) {
+            containerStorage.delete();
+        }
+    }
+
+    @Override
+    public String logs(String name, boolean stdout, boolean stderr, boolean timestamps, boolean details, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        String log = dockerClient.logs(name, stdout, stderr, timestamps, details);
+        return log;
+    }
+
+    @Override
+    public Top top(String name, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        return dockerClient.top(name);
+    }
+
+    @Override
+    public void commit(String name, String repo, String tag, String message, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        dockerClient.commit(name, null, message, repo, tag);
+    }
+
+    @Override
+    public List<Image> images(String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        return dockerClient.images(true);
+    }
+
+    @Override
+    public void pull(String image, String tag, boolean verbose, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        dockerClient.pull(image, tag, verbose);
+    }
+
+    @Override
+    public List<ImageSearch> search(String term, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        return dockerClient.search(term);
+    }
+
+    @Override
+    public Container inspect(String name, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        return dockerClient.inspect(name);
+    }
+
+    @Override
+    public void push(String image, String tag, boolean verbose, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        dockerClient.push(image, tag, verbose);
+    }
+
+    @Override
+    public List<ImageHistory> history(String image, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        return dockerClient.history(image);
+    }
+
+    @Override
+    public void tag(String image, String repo, String tag, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        dockerClient.tag(image, repo, tag);
+    }
+
+    @Override
+    public void rmi(String image, boolean force, boolean noprune, String url) throws Exception {
+        DockerClient dockerClient = new DockerClient(url);
+        dockerClient.rmi(image, force, noprune);
+    }
+
+    private void copy(File source, File destination) throws IOException {
+        if (source.getName().equals("docker")) {
+            // ignore inner docker
+            return;
+        }
+        if (source.getName().equals("cache.lock")) {
+            // ignore cache.lock file
+            return;
+        }
+        if (source.getName().equals("lock")) {
+            // ignore lock file
+            return;
+        }
+        if (source.getName().matches("transaction_\\d+\\.log")) {
+            // ignore active txlog files
+            return;
+        }
+        if (source.isDirectory()) {
+            if (!destination.exists()) {
+                destination.mkdirs();
+            }
+            String[] children = source.list();
+            for (String child : children) {
+                copy(new File(source, child), new File(destination, child));
+            }
+        } else {
+            try (
+                    InputStream in = new FileInputStream(source);
+                    OutputStream out = new FileOutputStream(destination)
+            ) {
+                new StreamUtils().copy(in, out);
+            }
+        }
+    }
+
+    class StreamUtils {
+
+        public StreamUtils() {
+        }
+
+        public void close(Closeable... closeables) {
+            for (Closeable c : closeables) {
+                try {
+                    if (c != null) {
+                        c.close();
+                    }
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+
+        public void close(Iterable<Closeable> closeables) {
+            for (Closeable c : closeables) {
+                try {
+                    if (c != null) {
+                        c.close();
+                    }
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+
+        public void copy(final InputStream input, final OutputStream output) throws IOException {
+            byte[] buffer = new byte[1024 * 16];
+            int n;
+            while ((n = input.read(buffer)) > 0) {
+                output.write(buffer, 0, n);
+            }
+            output.flush();
+        }
+
+    }
+
+    private int chmod(File serviceFile, String mode) throws IOException {
+        java.lang.ProcessBuilder builder = new java.lang.ProcessBuilder();
+        builder.command("chmod", mode, serviceFile.getCanonicalPath());
+        java.lang.Process p = builder.start();
+        try {
+            return p.waitFor();
+        } catch (InterruptedException e) {
+            throw (IOException) new InterruptedIOException().initCause(e);
+        }
+    }
+
+}
diff --git a/docker/src/main/java/org/apache/karaf/docker/internal/osgi/Activator.java b/docker/src/main/java/org/apache/karaf/docker/internal/osgi/Activator.java
new file mode 100644
index 0000000000..5ce1356df2
--- /dev/null
+++ b/docker/src/main/java/org/apache/karaf/docker/internal/osgi/Activator.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.internal.osgi;
+
+import org.apache.karaf.docker.DockerMBean;
+import org.apache.karaf.docker.DockerService;
+import org.apache.karaf.docker.internal.DockerMBeanImpl;
+import org.apache.karaf.docker.internal.DockerServiceImpl;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.Services;
+
+import java.io.File;
+
+@Services(
+        provides = @ProvideService(DockerService.class)
+)
+public class Activator extends BaseActivator {
+
+    @Override
+    protected void doStart() throws Exception {
+        DockerServiceImpl dockerService = new DockerServiceImpl();
+        dockerService.setStorageLocation(new File(System.getProperty("karaf.base"), "docker"));
+        register(DockerService.class, dockerService);
+
+        DockerMBeanImpl mbean = new DockerMBeanImpl();
+        mbean.setDockerService(dockerService);
+        registerMBean(mbean, "type=docker");
+    }
+
+}
diff --git a/docker/src/test/java/org/apache/karaf/docker/DockerClientTest.java b/docker/src/test/java/org/apache/karaf/docker/DockerClientTest.java
new file mode 100644
index 0000000000..b2e051448c
--- /dev/null
+++ b/docker/src/test/java/org/apache/karaf/docker/DockerClientTest.java
@@ -0,0 +1,285 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * DockerClient factory test.
+ */
+public class DockerClientTest {
+
+    private DockerClient dockerClient;
+
+    @Before
+    public void setup() {
+        dockerClient = new DockerClient(null);
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testDockerInfo() throws Exception {
+        Info info = dockerClient.info();
+        System.out.println("Info:");
+        System.out.println("\tDriver: " + info.getDriver());
+        System.out.println("\tDriver Status: " + info.getDriverStatus());
+        System.out.println("\tExecution Driver: " + info.getExecutionDriver());
+        System.out.println("\tIndex Server Address: "  + info.getIndexServerAddress());
+        System.out.println("\tInit Path: " + info.getInitPath());
+        System.out.println("\tInit SHA1: " + info.getInitSha1());
+        System.out.println("\tKernel Version: " + info.getKernelVersion());
+        System.out.println("\tContainers: " + info.getContainers());
+        System.out.println("\tImages: " + info.getImages());
+        System.out.println("\tNFD: " + info.getNfd());
+        System.out.println("\tNGoRoutines: " + info.getNgoroutines());
+        System.out.println("\tMemory Limit enabled: " + info.isMemoryLimit());
+        System.out.println("\tSwap Limit enabled: " + info.isSwapLimit());
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testDockerVersion() throws Exception {
+        Version version = dockerClient.version();
+        System.out.println("Version:");
+        System.out.println("\tAPI version: " + version.getApiVersion());
+        System.out.println("\tArch: " + version.getArch());
+        System.out.println("\tBuild Time: " + version.getBuildTime());
+        System.out.println("\tExperimental: " + version.getExperimental());
+        System.out.println("\tGit Commit: " + version.getGitCommit());
+        System.out.println("\tGo Version: " + version.getGoVersion());
+        System.out.println("\tKernel Version: " + version.getKernelVersion());
+        System.out.println("\tOS: " + version.getOs());
+        System.out.println("\tVersion: " + version.getVersion());
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testListImages() throws Exception {
+        for (Image image : dockerClient.images(true)) {
+            System.out.println("----");
+            System.out.println("Image ID: " + image.getId());
+            System.out.println("Created: " + image.getCreated());
+            System.out.println("Size: " + image.getSize());
+            System.out.println("Virtual size: " + image.getVirtualSize());
+            System.out.println("RepoTags: " + image.getRepoTags());
+            System.out.println("Labels: " + image.getLabels());
+            System.out.println("Containers: " + image.getContainers());
+        }
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testHistoryImage() throws Exception {
+        for (ImageHistory history : dockerClient.history("sha256:f3ea90d50ffd7851cb984764409326b82593c612fe6e6dc7933d9568f735084b")) {
+            System.out.println("----");
+            System.out.println("ID: " + history.getId());
+            System.out.println("Created: " + history.getCreated());
+            System.out.println("Created by: " + history.getCreatedBy());
+            System.out.println("Comment: " + history.getComment());
+            System.out.println("Size: " + history.getSize());
+            System.out.println("Tags: " + history.getTags());
+        }
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testSearchImages() throws Exception {
+        List<ImageSearch> images = dockerClient.search("karaf");
+        for (ImageSearch image : images) {
+            System.out.println("----");
+            System.out.println("Image Name: " + image.getName());
+            System.out.println("Image Star Count: " + image.getStarCount());
+            System.out.println("Image Automated: " + image.isAutomated());
+            System.out.println("Image Official: " + image.isOfficial());
+            System.out.println("Image Description: " + image.getDescription());
+        }
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testPullImage() throws Exception {
+        dockerClient.pull("mkroli/karaf", "latest", true);
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testRemoveImage() throws Exception {
+        dockerClient.rmi("sha256:f3ea90d50ffd7851cb984764409326b82593c612fe6e6dc7933d9568f735084b", true, false);
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testContainers() throws Exception {
+        for (Container container : dockerClient.ps(true)) {
+            System.out.println("----");
+            System.out.println("Container " + container.getId());
+            System.out.println("Command: " + container.getCommand());
+            System.out.println("Created: " + container.getCreated());
+            System.out.println("Image: " + container.getImage());
+            System.out.println("Image ID: " + container.getImageId());
+            System.out.println("Names: " + container.getNames());
+            for (Port port : container.getPorts()) {
+                System.out.println("Port: " + port.getPublicPort() + ":" + port.getPrivatePort() + " (" + port.getType() + ")");
+            }
+            System.out.println("Status: " + container.getStatus());
+            System.out.println("State: " + container.getState());
+            System.out.println("Size: " + container.getSizeRw());
+            System.out.println("Size Root: " + container.getSizeRootFs());
+        }
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testCreateContainer() throws Exception {
+        ContainerConfig config = new ContainerConfig();
+        config.setImage("java:8-jre-alpine");
+        config.setAttachStderr(true);
+        config.setAttachStdin(true);
+        config.setAttachStdout(true);
+        config.setTty(true);
+        dockerClient.create(config, "test");
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testCreateKarafContainer() throws Exception {
+        ContainerConfig config = new ContainerConfig();
+        config.setTty(true);
+        config.setAttachStdout(true);
+        config.setAttachStderr(true);
+        config.setAttachStdin(true);
+        config.setImage("karaf:latest");
+        config.setHostname("docker");
+        config.setUser("");
+        config.setCmd(new String[]{ "/bin/karaf" });
+        config.setWorkingDir("");
+        config.setOpenStdin(true);
+        config.setStdinOnce(true);
+        Map<String, Map<String, String>> exposedPorts = new HashMap<>();
+        Map<String, String> exposedPort = new HashMap<>();
+        exposedPorts.put("8101/tcp", exposedPort);
+        config.setExposedPorts(exposedPorts);
+
+        HostConfig hostConfig = new HostConfig();
+        hostConfig.setPrivileged(false);
+        hostConfig.setPublishAllPorts(false);
+
+        // getting the dock
+        File dock = new File("/tmp/docker", "karaf");
+        if (dock.exists()) {
+            hostConfig.setBinds(new String[]{dock.getAbsolutePath() + ":/opt/apache-karaf"});
+        }
+
+        hostConfig.setNetworkMode("bridge");
+        hostConfig.setLxcConf(new String[]{});
+
+        Map<String, List<HostPortBinding>> portBindings = new HashMap<>();
+        List<HostPortBinding> hostPortBindings = new ArrayList<>();
+        HostPortBinding hostPortBinding = new HostPortBinding();
+        hostPortBinding.setHostIp("");
+        hostPortBinding.setHostPort("8101");
+        hostPortBindings.add(hostPortBinding);
+        portBindings.put("8101/tcp", hostPortBindings);
+        hostConfig.setPortBindings(portBindings);
+
+        config.setHostConfig(hostConfig);
+
+        dockerClient.create(config, "karaf");
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testStartContainer() throws Exception {
+        dockerClient.start("test");
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testStopContainer() throws Exception {
+        dockerClient.stop("test", 30);
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testRestartContainer() throws Exception {
+        dockerClient.restart("test", 30);
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testKillContainer() throws Exception {
+        dockerClient.kill("test", "SIGKILL");
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testRemoveContainer() throws Exception {
+        dockerClient.rm("test", true, true);
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testRenameContainer() throws Exception {
+        dockerClient.rename("test", "karaf-test");
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testPauseContainer() throws Exception {
+        dockerClient.pause("test");
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testUnpauseContainer() throws Exception {
+        dockerClient.unpause("test");
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testContainerLog() throws Exception {
+        System.out.println(dockerClient.logs("test", true, true, true, true));
+    }
+
+    @Test
+    @Ignore("Need a running Docker daemon")
+    public void testContainerTop() throws Exception {
+        Top top = dockerClient.top("test");
+        for (String title : top.getTitles()) {
+            System.out.print(title);
+            System.out.print("\t");
+        }
+        System.out.println();
+        for (List<String> process : top.getProcesses()) {
+            for (String p : process) {
+                System.out.print(p);
+                System.out.print("\t");
+            }
+            System.out.println();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/docker/src/test/java/org/apache/karaf/docker/internal/DockerServiceImplTest.java b/docker/src/test/java/org/apache/karaf/docker/internal/DockerServiceImplTest.java
new file mode 100644
index 0000000000..ad6b7556b8
--- /dev/null
+++ b/docker/src/test/java/org/apache/karaf/docker/internal/DockerServiceImplTest.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.docker.internal;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.File;
+
+public class DockerServiceImplTest {
+
+    private DockerServiceImpl dockerService;
+
+    @Before
+    public void setup() throws Exception {
+        System.setProperty("karaf.base", ".");
+        dockerService = new DockerServiceImpl();
+        File storage = new File("docker");
+        storage.mkdirs();
+        dockerService.setStorageLocation(storage);
+    }
+
+    @Test
+    @Ignore("Require a running docker daemon")
+    public void testProvision() throws Exception {
+        dockerService.provision("test", "8101", "1099", "44444", "8181", false, null);
+    }
+
+}
diff --git a/docker/src/test/resources/Dockerfile b/docker/src/test/resources/Dockerfile
new file mode 100644
index 0000000000..a56eda5b23
--- /dev/null
+++ b/docker/src/test/resources/Dockerfile
@@ -0,0 +1,17 @@
+FROM java:8-jre
+
+MAINTAINER dev@karaf.apache.org
+
+ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64
+
+ENV KARAF_VERSION=4.2.0
+
+RUN cd /opt; \
+	wget http://www-us.apache.org/dist/karaf/${KARAF_VERSION}/apache-karaf-${KARAF_VERSION}.tar.gz; \
+	tar -xzf apache-karaf-${KARAF_VERSION}.tar.gz; \
+	mv apache-karaf-${KARAF_VERSION} apache-karaf; \
+	rm -f apache-karaf-${KARAF_VERSION}.tar.gz
+
+EXPOSE 1099 8101 44444
+
+ENTRYPOINT ["/opt/apache-karaf/bin/karaf"]
\ No newline at end of file
diff --git a/docker/src/test/resources/docker-build.sh b/docker/src/test/resources/docker-build.sh
new file mode 100644
index 0000000000..42f5dd8457
--- /dev/null
+++ b/docker/src/test/resources/docker-build.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+docker build --cache-from openjdk:8-jre --tag karaf/karaf-docker:4.2.0 .
\ No newline at end of file
diff --git a/itests/test/src/test/java/org/apache/karaf/itests/features/EnterpriseFeaturesTest.java b/itests/test/src/test/java/org/apache/karaf/itests/features/EnterpriseFeaturesTest.java
index 12bb094a40..84c5e493ca 100644
--- a/itests/test/src/test/java/org/apache/karaf/itests/features/EnterpriseFeaturesTest.java
+++ b/itests/test/src/test/java/org/apache/karaf/itests/features/EnterpriseFeaturesTest.java
@@ -147,4 +147,9 @@ public void installSubsystems() throws Exception {
         installAssertAndUninstallFeatures("subsystems");
     }
 
+    @Test
+    public void installDocker() throws Exception {
+        installAssertAndUninstallFeatures("docker");
+    }
+
 }
diff --git a/manual/src/main/asciidoc/index.adoc b/manual/src/main/asciidoc/index.adoc
index 2bafd87951..14a49ec55c 100644
--- a/manual/src/main/asciidoc/index.adoc
+++ b/manual/src/main/asciidoc/index.adoc
@@ -58,6 +58,8 @@ include::user-guide/instances.adoc[]
 
 include::user-guide/security.adoc[]
 
+include::user-guide/dockerClient.adoc[]
+
 include::user-guide/obr.adoc[]
 
 === Enterprise
diff --git a/manual/src/main/asciidoc/overview.adoc b/manual/src/main/asciidoc/overview.adoc
index ff3bbcef55..6e219269ee 100644
--- a/manual/src/main/asciidoc/overview.adoc
+++ b/manual/src/main/asciidoc/overview.adoc
@@ -17,7 +17,7 @@
 Apache Karaf is a modern and polymorphic container.
 
 Karaf can be used standalone as a container, supporting a wide range of applications and technologies.
-It also supports the "run anywhere" (on any machine with Java, cloud, docker images, ...) using the embedded mode.
+It also supports the "run anywhere" (on any machine with Java, cloud, dockerClient images, ...) using the embedded mode.
 
 It's a lightweight, powerful, and enterprise ready platform.
 
diff --git a/manual/src/main/asciidoc/user-guide/docker.adoc b/manual/src/main/asciidoc/user-guide/docker.adoc
new file mode 100644
index 0000000000..559f6274af
--- /dev/null
+++ b/manual/src/main/asciidoc/user-guide/docker.adoc
@@ -0,0 +1,381 @@
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+=== Docker
+
+Apache Karaf provides Docker resources allowing you to easily create your own image and container.
+
+Official Karaf docker image are also available on Docker Hub.
+
+But, Apache Karaf also provides a docker feature allows you to:
+
+- manipulate Docker containers directly from Apache Karaf
+- create a Docker container based on the current running Apache Karaf instance (named provisioning)
+
+==== Docker images
+
+You can find the docker resource at http://github.com/apache/karaf/assemblies/docker.
+
+As prerequisites, you have:
+
+* to install the most recent stable version of docker (https://docs.docker.com/installation/)
+* to install the most recent stable version of docker-compose (https://docs.docker.com/compose/install/)
+
+===== Official images
+
+Apache Karaf official docker images are available on Docker HUB: https://hub.docker.com/_/karaf/
+
+You can directly pull the official image:
+
+----
+docker pull karaf
+----
+
+===== Build your own
+
+You can create your own docker image. The images are based on the official Java Alpine (OpenJDK 8) image. If you
+want to build the Karaf image run:
+
+----
+sh build.sh
+----
+
+or
+
+----
+docker build -t karaf .
+----
+
+If you want to build the container for a specific version of Karaf you can configure it with the KARAF_VERSION arg:
+
+----
+docker build --build-arg KARAF_VERSION=4.2.0 -t "karaf:4.2.0" karaf
+----
+
+===== Run
+
+* Run Karaf in interactive mode
+
+----
+docker-compose run karaf
+----
+
+* Run Karaf as a daemon (without interaction)
+
+----
+docker-compose up
+----
+
+* Kill Karaf
+
+----
+docker-compose kill
+----
+
+====== Ports
+
+* The Karaf SSH server is on 8101
+* The Karaf WebContainer is on 8888
+* The Karaf JMX MBean server is on 1099 (default, not exposed to host) and 44444 (default, not exposed to host)
+
+Edit the `docker-compose.yml` file to edit port settings.
+
+==== Docker feature
+
+Docker is an optional feature from the Karaf Enterprise features repository.
+
+It means that you have to install the `docker` feature first:
+
+----
+karaf@root()> feature:install docker
+----
+
+The Karaf Docker feature uses the Docker HTTP layer to communicate with the Docker backend. It could be on the same local
+machine where Apache Karaf instance is running or a remote Docker machine.
+
+The location of the Docker backend (URL) can be specified as an option to the `docker:*` commands. By default, Karaf Docker
+feature uses `http://localhost:2375`. Please, take a look on the Docker documentation how to enable remote API using HTTP
+for Docker daemon. As short notice, you just have to enable `tcp` transport connector enabled for the docker daemon.
+You have to do it using the `-H` option on `dockerd`:
+
+----
+/usr/bin/dockerd -H fd:// -H tcp://localhost:2375
+----
+
+Apache Karaf Docker feature exposes `DockerService` OSGi service that you can use programmatically (the `dockerClient:*` commands
+and the `DockerMBean` use the `DockerService` service).
+
+==== System-wide information
+
+The `docker:info` command provides some details about the docker backend:
+
+----
+karaf@root()> docker:info
+Containers: 0
+Debug: false
+Driver: overlay2
+ExecutionDriver: null
+IPv4Forwarding: true
+Images: 1
+IndexServerAddress: https://index.docker.io/v1/
+InitPath: null
+InitSha1: null
+KernelVersion: 4.15.0-29-generic
+MemoryLimit: true
+NEventsListener: false
+NFd: 20
+NGoroutines: 34
+SwapLimit: false
+----
+
+==== Show the Docker version information
+
+The `docker:version` command provides details about the docker version:
+
+----
+karaf@root()> docker:version
+Version: 17.12.1-ce
+Os: linux
+Kernel version: 4.15.0-29-generic
+Go version: go1.10.1
+Git commit: 7390fc6
+Arch: amd64
+API version: 1.35
+Build time: 2018-02-28T17:46:05.000000000+00:00
+Experimental: null
+----
+
+==== Search image
+
+The `docker:search` command (or `search()` operation on the `DockerMBean`) searches for a image on Docker HUB:
+
+----
+karaf@root()> docker:search java
+Name                                       │ Description                                                                                          │ Automated │ Official │ Star Count
+───────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┼───────────┼──────────┼───────────
+java                                       │ Java is a concurrent, class-based, and object-oriented programming language.                         │ false     │ true     │ 1774
+anapsix/alpine-java                        │ Oracle Java 8 (and 7) with GLIBC 2.23 over AlpineLinux                                               │ true      │ false    │ 332
+node                                       │ Node.js is a JavaScript-based platform for server-side and networking applications.                  │ false     │ true     │ 5893
+tomcat                                     │ Apache Tomcat is an open source implementation of the Java Servlet and JavaServer Pages technologies │ false     │ true     │ 1950
+openjdk                                    │ OpenJDK is an open-source implementation of the Java Platform, Standard Edition                      │ false     │ true     │ 1097
+frekele/java                               │ docker run --rm --name java frekele/java                                                             │ true      │ false    │ 10
+ghost                                      │ Ghost is a free and open source blogging platform written in JavaScript                              │ false     │ true     │ 799
+appuio/s2i-maven-java                      │ S2I Builder with Maven and Java                                                                      │ true      │ false    │ 1
+zabbix/zabbix-java-gateway                 │ Zabbix Java Gateway                                                                                  │ true      │ false    │ 13
+jetty                                      │ Jetty provides a Web server and javax.servlet container.                                             │ false     │ true     │ 260
+fabric8/s2i-java                           │ S2I Builder Image for plain Java applications                                                        │ false     │ false    │ 5
+appuio/s2i-gradle-java                     │ S2I Builder with Gradle and Java                                                                     │ true      │ false    │ 1
+cloudbees/jnlp-slave-with-java-build-tools │ Extends cloudbees/java-build-tools docker image to make it a JNLP slave                              │ true      │ false    │ 18
+blacklabelops/java                         │ Java Base Images.                                                                                    │ true      │ false    │ 8
+groovy                                     │ Apache Groovy is a multi-faceted language for the Java platform.                                     │ false     │ true     │ 47
+lwieske/java-8                             │ Oracle Java 8 Container - Full + Slim - Based off Alpine + CentOS (8u00 - 8u172)                     │ true      │ false    │ 39
+davidcaste/alpine-java-unlimited-jce       │ Oracle Java 8 (and 7) with GLIBC 2.21 over AlpineLinux with unlimited JCE patch applied              │ true      │ false    │ 11
+cfje/java-test-applications                │ Java Test Applications CI Image                                                                      │ false     │ false    │ 0
+thingswise/java-docker                     │ Java + dcd                                                                                           │ true      │ false    │ 0
+rightctrl/java                             │ Oracle Java                                                                                          │ true      │ false    │ 2
+cfje/java-resource                         │ Java Concourse Resource                                                                              │ false     │ false    │ 0
+cfje/java-buildpack                        │ Java Buildpack CI Image                                                                              │ false     │ false    │ 0
+tomee                                      │ Apache TomEE is an all-Apache Java EE certified stack where Apache Tomcat is top dog.                │ false     │ true     │ 53
+couchdb                                    │ CouchDB is a database that uses JSON for documents, an HTTP API, & JavaScript/declarative indexing.  │ false     │ true     │ 218
+dwolla/java                                │ Dwolla’s custom Java image                                                                           │ true      │ false    │ 1
+----
+
+==== Pull image
+
+The `docker:pull` command (or `pull()` operation on the `DockerMBean`) pull a image from Docker HUB:
+
+----
+karaf@root()> docker:pull -v java:8-jre-alpine
+{"status":"Pulling from library/java","id":"latest"}
+{"status":"Pulling fs layer","progressDetail":{},"id":"5040bd298390"}
+{"status":"Pulling fs layer","progressDetail":{},"id":"fce5728aad85"}
+{"status":"Pulling fs layer","progressDetail":{},"id":"76610ec20bf5"}
+{"status":"Pulling fs layer","progressDetail":{},"id":"60170fec2151"}
+{"status":"Pulling fs layer","progressDetail":{},"id":"e98f73de8f0d"}
+{"status":"Pulling fs layer","progressDetail":{},"id":"11f7af24ed9c"}
+{"status":"Pulling fs layer","progressDetail":{},"id":"49e2d6393f32"}
+{"status":"Pulling fs layer","progressDetail":{},"id":"bb9cdec9c7f3"}
+{"status":"Waiting","progressDetail":{},"id":"11f7af24ed9c"}
+{"status":"Waiting","progressDetail":{},"id":"49e2d6393f32"}
+{"status":"Waiting","progressDetail":{},"id":"bb9cdec9c7f3"}
+{"status":"Waiting","progressDetail":{},"id":"60170fec2151"}
+{"status":"Waiting","progressDetail":{},"id":"e98f73de8f0d"}
+----
+
+==== Listing images
+
+The `docker:images` command (or `images()` operation on the `DockerMBean`) lists the available images on docker:
+
+----
+karaf@root()> docker:images
+Id                                                                      │ RepoTags            │ Created    │ Labels │ Size      │ Virtual Size
+────────────────────────────────────────────────────────────────────────┼─────────────────────┼────────────┼────────┼───────────┼─────────────
+sha256:fdc893b19a147681ee764b2edab6c494d60fe99d83b14b8794bbcbc040ec7aa7 │ [java:8-jre-alpine] │ 1488578492 │ {}     │ 107854045 │ 107854045
+sha256:d23bdf5b1b1b1afce5f1d0fd33e7ed8afbc084b594b9ccf742a5b27080d8a4a8 │ [java:latest]       │ 1484614374 │ {}     │ 643195347 │ 643195347
+----
+
+==== Remove image
+
+The `docker:rmi` command (or `rmi()` operation on the `DockerMBean`) removes an image from docker:
+
+----
+karaf@root()> docker:rmi --force sha256:d23bdf5b1b1b1afce5f1d0fd33e7ed8afbc084b594b9ccf742a5b27080d8a4a8
+----
+
+==== Image history
+
+The `docker:history` command displays the complete history for a given image:
+
+----
+karaf@root()> docker:history sha256:fdc893b19a147681ee764b2edab6c494d60fe99d83b14b8794bbcbc040ec7aa7
+ID                                                                      │ Created    │ Created By                                                                                                                                                                                                                 │ Tags │ Size
+────────────────────────────────────────────────────────────────────────┼────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼──────┼────────────────────
+sha256:fdc893b19a147681ee764b2edab6c494d60fe99d83b14b8794bbcbc040ec7aa7 │ 1488578492 │ /bin/sh -c set -x        && apk add --no-cache           openjdk8-jre="$JAVA_ALPINE_VERSION"     && [ "$JAVA_HOME" = "$(docker-java-home)" ]                                                                                                │      │ [java:8-jre-alpine]
+<missing>                                                               │ 1488578488 │ /bin/sh -c #(nop)  ENV JAVA_ALPINE_VERSION=8.111.14-r0                                                                                                                                                                     │      │
+<missing>                                                               │ 1488578488 │ /bin/sh -c #(nop)  ENV JAVA_VERSION=8u111                                                                                                                                                                                  │      │
+<missing>                                                               │ 1488578487 │ /bin/sh -c #(nop)  ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/jvm/java-1.8-openjdk/jre/bin:/usr/lib/jvm/java-1.8-openjdk/bin                                                           │      │
+<missing>                                                               │ 1488578487 │ /bin/sh -c #(nop)  ENV JAVA_HOME=/usr/lib/jvm/java-1.8-openjdk/jre                                                                                                                                                         │      │
+<missing>                                                               │ 1488578458 │ /bin/sh -c {             echo '#!/bin/sh';               echo 'set -e';          echo;           echo 'dirname "$(dirname "$(readlink -f "$(which javac || which java)")")"';    } > /usr/local/bin/docker-java-home       && chmod +x /usr/local/bin/docker-java-home │      │
+<missing>                                                               │ 1488578457 │ /bin/sh -c #(nop)  ENV LANG=C.UTF-8                                                                                                                                                                                        │      │
+<missing>                                                               │ 1488573141 │ /bin/sh -c #(nop) ADD file:3df55c321c1c8d73f22bc69240c0764290d6cb293da46ba8f94ed25473fb5853 in /                                                                                                                           │      │
+----
+
+==== Pushing and tagging image
+
+The `docker:push` command allows you to push an image on a given repository.
+
+The `docker:tag` command create a new tag for a given image.
+
+==== Create container
+
+The Karaf Docker feature can create a Docker container based on a given image.
+
+You can use either the `docker:create` shell command or the `create()` operation on the JMX `DockerMBean`.
+
+For instance, here's an example of the `docker:bootstrap` to create a Docker container based on `elasticsearch` instance:
+
+----
+karaf@root()> docker:create --image fdc893b19a147681ee764b2edab6c494d60fe99d83b14b8794bbcbc040ec7aa7 --cmd /bin/bash test
+----
+
+==== Listing containers
+
+You can list the containers:
+
+----
+karaf@root()> docker:ps -a
+Id                                                               │ Names   │ Command   │ Created    │ Image                                                            │ Image ID                                                                │ Status  │ State   │ Ports │ Size │ Size Root
+─────────────────────────────────────────────────────────────────┼─────────┼───────────┼────────────┼──────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────┼─────────┼─────────┼───────┼──────┼──────────
+92f8b280a7fa69c9ff673ed9678b7040a56c16c9c4aa403498a538cf0f501e9e │ [/test] │ /bin/bash │ 1532809485 │ fdc893b19a147681ee764b2edab6c494d60fe99d83b14b8794bbcbc040ec7aa7 │ sha256:fdc893b19a147681ee764b2edab6c494d60fe99d83b14b8794bbcbc040ec7aa7 │ Created │ created │       │ 0    │ 0
+----
+
+You can also use the containers attribute on the `DockerMBean` JMX MBean or the `containers()` method on the `DockerService` service.
+
+==== Provision Docker container
+
+Provisioning is a specific way of creating container based on the current running Karaf instance: it creates a Docker container using the current running Apache Karaf instance `karaf.base`.
+
+You can then reuse this container to create a Docker image and to duplicate the container on another Docker backend via dockerhub.
+
+----
+karaf@root()> docker:provision my-karaf
+----
+
+You can also use the `provision()` method on the `DockerMBean` JMX MBean or the `DockerService` service.
+
+==== Start container
+
+You can start a container using the `docker:start` command:
+
+----
+karaf@root()> docker:start my-container
+----
+
+You can also use the `start()` method on the `DockerMBean` JMX MBean or the `DockerService` service.
+
+==== Stop container
+
+You can stop a container using the `docker:stop` command:
+
+----
+karaf@root()> docker:stop my-container
+----
+
+You can also use the `stop()` method on the `DockerMBean` JMX MBean or the `DockerService` service.
+
+==== Restart container
+
+You can restart a container using the `docker:restart` command:
+
+----
+karaf@root()> docker:restart my-container
+----
+
+==== Delete container
+
+You can delete an existing Docker container using the `docker:rm` commmand:
+
+----
+karaf@root()> docker:rm my-container
+----
+
+You can also use the `rm()` method on the `DockerMBean` JMX MBean or the `DockerService` service.
+
+==== Pause container
+
+The `docker:pause` command pauses all processes within one or more containers:
+
+----
+karaf@root()> docker:pause my-container
+----
+
+==== Unpause container
+
+The `docker:unpause` command unpauses all processes within one or more containers:
+
+----
+karaf@root()> docker:unpause my-container
+----
+
+==== Kill container
+
+The `docker:kill` command kills a running container:
+
+----
+karaf@root()> docker:kill my-container
+----
+
+==== Rename container
+
+The `docker:rename` command renames an existing container:
+
+----
+karaf@root()> docker:rename my-container new-container
+----
+
+==== Logs
+
+The `docker:logs` command displays the log on an existing container:
+
+----
+karaf@root()> docker:logs --timestamps --details --stdout --stderr my-container
+----
+
+==== Top
+
+The `docker:top` command displays the current running processes in an existing container:
+
+----
+karaf@root()> docker:top my-container
+----
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index be9054e576..7bf91a10bd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -60,6 +60,7 @@
         <module>scr</module>
         <module>diagnostic</module>
         <module>obr</module>
+        <module>docker</module>
         <module>jndi</module>
         <module>jdbc</module>
         <module>jms</module>


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


> Add docker feature
> ------------------
>
>                 Key: KARAF-5644
>                 URL: https://issues.apache.org/jira/browse/KARAF-5644
>             Project: Karaf
>          Issue Type: Improvement
>          Components: karaf-core
>            Reporter: Jean-Baptiste Onofré
>            Assignee: Jean-Baptiste Onofré
>            Priority: Major
>             Fix For: 4.2.1
>
>
> As discussed on the mailing list, we gonna add the karaf-docker feature right now on PoC:
> https://github.com/jbonofre/karaf-docker



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

Mime
View raw message