Add very basic initial implementation for local docker usage.

Should work on Linux and on systems with docker-machine
diff --git a/containers-api/src/main/java/org/apache/aries/containers/Container.java b/containers-api/src/main/java/org/apache/aries/containers/Container.java
new file mode 100644
index 0000000..9c4d569
--- /dev/null
+++ b/containers-api/src/main/java/org/apache/aries/containers/Container.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 WARRANTIESOR 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.aries.containers;
+
+import java.util.Map;
+
+public interface Container extends Comparable<Container> {
+    @Override
+    default int compareTo(Container other) {
+        return getID().compareTo(other.getID());
+    }
+
+    void destroy();
+    String getID();
+    String getIPAddress();
+
+    /* The maps of ports exposed to the outside by the container.
+     * It maps an internal port to an outside port.
+     * @return a map of exposed ports.
+     */
+    Map<Integer, Integer> getExposedPorts();
+
+    Service getService();
+}
diff --git a/containers-api/src/main/java/org/apache/aries/containers/ContainerFactory.java b/containers-api/src/main/java/org/apache/aries/containers/ContainerFactory.java
index 3bdaf8d..ec69264 100644
--- a/containers-api/src/main/java/org/apache/aries/containers/ContainerFactory.java
+++ b/containers-api/src/main/java/org/apache/aries/containers/ContainerFactory.java
@@ -4,5 +4,5 @@
 
 @ProviderType
 public interface ContainerFactory {
-    Service getService(ServiceConfig config);
+    Service getService(ServiceConfig config) throws Exception;
 }
diff --git a/containers-api/src/main/java/org/apache/aries/containers/Service.java b/containers-api/src/main/java/org/apache/aries/containers/Service.java
index 0132ac4..6b334d0 100644
--- a/containers-api/src/main/java/org/apache/aries/containers/Service.java
+++ b/containers-api/src/main/java/org/apache/aries/containers/Service.java
@@ -32,4 +32,6 @@
      * @return The instance count.
      */
     int getActualInstanceCount();
+
+    void setInstanceCount(int count);
 }
\ No newline at end of file
diff --git a/containers-api/src/main/java/org/apache/aries/containers/ServiceConfig.java b/containers-api/src/main/java/org/apache/aries/containers/ServiceConfig.java
index 85c28b2..8523fca 100644
--- a/containers-api/src/main/java/org/apache/aries/containers/ServiceConfig.java
+++ b/containers-api/src/main/java/org/apache/aries/containers/ServiceConfig.java
@@ -33,7 +33,6 @@
     private final String entryPoint;
     private final Map<String, String> envVars;
 //    private final List<HealthCheck> healthChecks;
-    private final int mainPort;
     private final double requestedCPUunits;
     private final int requestedInstances;
     private final double requestedMemory; // in MiB
@@ -41,14 +40,13 @@
 
 
     private ServiceConfig(String[] commandLine, String containerImage, List<Integer> containerPorts, String entryPoint,
-            Map<String, String> envVars, int mainPort, double requestedCPUunits, int requestedInstances, double requestedMemory,
+            Map<String, String> envVars, double requestedCPUunits, int requestedInstances, double requestedMemory,
             String serviceName) {
         this.commandLine = commandLine;
         this.containerImage = containerImage;
         this.containerPorts = containerPorts;
         this.entryPoint = entryPoint;
         this.envVars = envVars;
-        this.mainPort = mainPort;
         this.requestedCPUunits = requestedCPUunits;
         this.requestedInstances = requestedInstances;
         this.requestedMemory = requestedMemory;
@@ -75,10 +73,6 @@
         return envVars;
     }
 
-    public int getMainPort() {
-        return mainPort;
-    }
-
     public double getRequestedCpuUnits() {
         return requestedCPUunits;
     }
@@ -91,6 +85,10 @@
         return requestedMemory;
     }
 
+    /**
+     * The name of the service deployment. This has to be unique in the system.
+     * @return The name of the service.
+     */
     public String getServiceName() {
         return serviceName;
     }
@@ -108,7 +106,6 @@
         private double requestedCpuUnits = 0.5;
         private int requestedInstances = 1;
         private double requestedMemory = 64;
-        private int mainPort = -1;
         private List<Integer> ports = new ArrayList<>();
         private String serviceName;
 
@@ -154,23 +151,13 @@
         }
 
         public Builder port(int port) {
-            return port(port, false);
-        }
-
-        public Builder port(int port, boolean main) {
             this.ports.add(port);
-            if (main) {
-                if (this.mainPort != -1)
-                    throw new IllegalStateException("A main port has already been set: " + mainPort);
-
-                this.mainPort = port;
-            }
             return this;
         }
 
         public ServiceConfig build() {
             return new ServiceConfig(commandLine, containerImage, ports, entryPoint,
-                    envMap, mainPort, requestedCpuUnits, requestedInstances, requestedMemory,
+                    envMap, requestedCpuUnits, requestedInstances, requestedMemory,
                     serviceName);
         }
     }
diff --git a/containers-api/src/test/java/org/apache/aries/containers/api/ServiceConfigTest.java b/containers-api/src/test/java/org/apache/aries/containers/api/ServiceConfigTest.java
index d8b7ef2..752872d 100644
--- a/containers-api/src/test/java/org/apache/aries/containers/api/ServiceConfigTest.java
+++ b/containers-api/src/test/java/org/apache/aries/containers/api/ServiceConfigTest.java
@@ -28,7 +28,7 @@
                 env("a", "b c d").
                 instances(17).
                 memory(5.5).
-                port(8080, true).
+                port(8080).
                 port(9090).
                 build();
 
@@ -42,7 +42,6 @@
         assertEquals(expectedEnv, sc.getEnvVars());
         assertEquals(17, sc.getRequestedInstances());
         assertEquals(5.5, sc.getRequestedMemory(), 0.01);
-        assertEquals(8080, sc.getMainPort());
         assertEquals(Arrays.asList(8080, 9090), sc.getContainerPorts());
     }
 
diff --git a/containers-docker-local/pom.xml b/containers-docker-local/pom.xml
new file mode 100644
index 0000000..ae7cada
--- /dev/null
+++ b/containers-docker-local/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<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/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.aries.containers</groupId>
+        <artifactId>org.apache.aries.containers.parent</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+        <relativePath>../containers-parent</relativePath>
+    </parent>
+
+    <artifactId>org.apache.aries.containers.docker.local</artifactId>
+    <packaging>jar</packaging>
+    <name>Apache Aries Containers impl for local Docker use</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>org.apache.aries.containers.api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
+
diff --git a/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ContainerImpl.java b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ContainerImpl.java
new file mode 100644
index 0000000..66f3f83
--- /dev/null
+++ b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ContainerImpl.java
@@ -0,0 +1,68 @@
+/*
+ * 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 WARRANTIESOR 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.aries.containers.docker.local.impl;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.aries.containers.Container;
+import org.apache.aries.containers.Service;
+
+public class ContainerImpl implements Container {
+    private final String id;
+    private final String ip;
+    private final Map<Integer, Integer> ports;
+    private ServiceImpl service;
+
+    ContainerImpl(String id, String ip, Map<Integer, Integer> ports) {
+        this.id = id;
+        this.ip = ip;
+        this.ports = Collections.unmodifiableMap(new HashMap<>(ports));
+    }
+
+    @Override
+    public void destroy() {
+        service.killAndReplaceContainer(this);
+    }
+
+    @Override
+    public Map<Integer, Integer> getExposedPorts() {
+        return ports;
+    }
+
+    @Override
+    public String getID() {
+        return id;
+    }
+
+    @Override
+    public String getIPAddress() {
+        return ip;
+    }
+
+    @Override
+    public Service getService() {
+        return service;
+    }
+
+    void setService(ServiceImpl svc) {
+        service = svc;
+    }
+}
diff --git a/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/DockerContainerInfo.java b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/DockerContainerInfo.java
new file mode 100644
index 0000000..8caa6af
--- /dev/null
+++ b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/DockerContainerInfo.java
@@ -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 WARRANTIESOR 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.aries.containers.docker.local.impl;
+
+class DockerContainerInfo {
+    private final String id;
+    private final String ip;
+
+    public DockerContainerInfo(String id, String ipAddress) {
+        this.id = id;
+        this.ip = ipAddress;
+    }
+
+    public String getID() {
+        return id;
+    }
+
+    public String getIP() {
+        return ip;
+    }
+}
diff --git a/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/LocalDockerContainerFactory.java b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/LocalDockerContainerFactory.java
new file mode 100644
index 0000000..a7c68d1
--- /dev/null
+++ b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/LocalDockerContainerFactory.java
@@ -0,0 +1,162 @@
+/*
+ * 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 WARRANTIESOR 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.aries.containers.docker.local.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import org.apache.aries.containers.ContainerFactory;
+import org.apache.aries.containers.Service;
+import org.apache.aries.containers.ServiceConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LocalDockerContainerFactory implements ContainerFactory {
+    static final Logger LOG = LoggerFactory.getLogger(LocalDockerContainerFactory.class);
+    private static final String SERVICE_NAME = "service.name";
+
+    private static final String DOCKER_MACHINE_VM_NAME = System.getenv("DOCKER_MACHINE_NAME");
+    private static final boolean CHECK_DOCKER_MACHINE = Stream
+            .of(System.getenv("PATH").split(Pattern.quote(File.pathSeparator)))
+            .map(Paths::get)
+            .anyMatch(path -> Files.exists(path.resolve("docker-machine")));
+
+    private static final boolean USE_DOCKER_MACHINE = (DOCKER_MACHINE_VM_NAME != null) && CHECK_DOCKER_MACHINE;
+    private static final String CONTAINER_HOST = USE_DOCKER_MACHINE
+            ? ProcessRunner.waitFor(ProcessRunner.run("docker-machine", "ip", DOCKER_MACHINE_VM_NAME))
+            : "localhost";
+
+
+    private volatile LocalDockerController docker;
+    private final AtomicBoolean initialized = new AtomicBoolean(false);
+    private final ConcurrentMap<String, Service> services =
+            new ConcurrentHashMap<>();
+
+    private void init() {
+        if (!initialized.compareAndSet(false, true))
+            return;
+
+        if (docker == null)
+            docker = new LocalDockerController();
+
+        // TODO discover any running docker containers.
+    }
+
+    @Override
+    public Service getService(ServiceConfig config) throws Exception {
+        init();
+
+        Service existingService = services.get(config.getServiceName());
+        if (existingService != null)
+            return existingService;
+
+        // TODO return discovered containers if it contains the requested one.
+
+        List<ContainerImpl> containers = createContainers(config);
+        ServiceImpl svc = new ServiceImpl(config, this, containers);
+        for (ContainerImpl c : containers) {
+            c.setService(svc);
+        }
+
+        services.put(config.getServiceName(), svc);
+
+        return svc;
+    }
+
+    private List<ContainerImpl> createContainers(ServiceConfig config) throws Exception {
+        List<ContainerImpl> containers = new ArrayList<>();
+
+        for (int i=0; i<config.getRequestedInstances(); i++) {
+            containers.add(createDockerContainer(config));
+        }
+
+        return containers;
+    }
+
+    private ContainerImpl createDockerContainer(ServiceConfig config) throws Exception {
+        List<String> command = new ArrayList<>();
+        command.add("-d");
+        command.add("-l");
+        command.add(SERVICE_NAME + "=" + config.getServiceName());
+
+        String ep = config.getEntryPoint();
+        if (ep != null) {
+            command.add("--entrypoint");
+            command.add(ep);
+        }
+
+        Map<Integer, Integer> ports = new HashMap<>();
+        for (Integer p : config.getContainerPorts()) {
+            command.add("-p");
+            int freePort = getFreePort();
+            command.add(freePort + ":" + p);
+            ports.put(p, freePort);
+
+        }
+
+        for(Map.Entry<String, String> entry : config.getEnvVars().entrySet()) {
+            command.add("-e");
+            command.add(entry.getKey() + '=' + entry.getValue());
+        }
+
+        command.add("--cpus");
+        command.add("" + config.getRequestedCpuUnits() + "");
+
+        command.add("-m");
+        command.add("" + ((int) config.getRequestedMemory()) + "m");
+
+        command.add(config.getContainerImage());
+        command.addAll(Arrays.asList(config.getCommandLine()));
+
+        DockerContainerInfo info = docker.run(command);
+
+        return new ContainerImpl(info.getID(), info.getIP(), ports);
+    }
+
+    public void destroyDockerContainer(String id, boolean remove) throws Exception {
+        if (remove) {
+            docker.remove(id);
+        } else {
+            docker.kill(id);
+        }
+    }
+
+    private int getFreePort() throws IOException {
+        try (ServerSocket ss = new ServerSocket(0)) {
+            return ss.getLocalPort();
+        }
+    }
+
+    public static String getContainerHost() {
+        return CONTAINER_HOST;
+    }
+}
diff --git a/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/LocalDockerController.java b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/LocalDockerController.java
new file mode 100644
index 0000000..2fecfa7
--- /dev/null
+++ b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/LocalDockerController.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 WARRANTIESOR 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.aries.containers.docker.local.impl;
+
+import java.io.LineNumberReader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class LocalDockerController {
+
+    public String kill(String id) throws Exception {
+        return kill(id, "KILL");
+    }
+
+    private String kill(String id, String signal) throws Exception {
+        return runCommandExpectSingleID("docker", "kill", "-s", signal, id);
+    }
+
+    public String remove(String id) throws Exception {
+        // Kill the docker container if its still running
+        return runCommandExpectSingleID("docker", "rm", "-f", id);
+    }
+
+    public DockerContainerInfo run(List<String> command) throws Exception {
+        List<String> execCmd = new ArrayList<>();
+        execCmd.add("docker");
+        execCmd.add("run");
+        execCmd.addAll(command);
+
+        String id = runCommandExpectSingleID(execCmd.toArray(new String [] {}));
+        return new DockerContainerInfo(id, LocalDockerContainerFactory.getContainerHost());
+    }
+
+    String runCommandExpectSingleID(String ... command) throws Exception {
+        String res = ProcessRunner.waitFor(ProcessRunner.run(command));
+        if (res != null) {
+            res = res.trim();
+            String lastLine = res;
+            try ( final LineNumberReader lnr = new LineNumberReader(new StringReader(res)) ) {
+                String line;
+                while ( ( line = lnr.readLine()) != null ) {
+                    lastLine = line;
+                }
+            }
+            if ( lastLine.indexOf(' ') != -1 ) {
+                 throw new Exception("Unable to execute docker command: " + res);
+            }
+            res = lastLine;
+        }
+
+        return res;
+    }
+}
diff --git a/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ProcessRunner.java b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ProcessRunner.java
new file mode 100644
index 0000000..b145026
--- /dev/null
+++ b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ProcessRunner.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 WARRANTIESOR 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.aries.containers.docker.local.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ProcessRunner {
+    // For testing purposes
+    public static final String SKIP_RUN = ProcessRunner.class.getName() + ".skiprun";
+
+    private static final Logger LOG = LoggerFactory.getLogger(ProcessRunner.class);
+
+    private ProcessRunner() {
+        // Util class do not instantiate
+    }
+
+    public static Process run(String ... args) {
+        try {
+            return run(Collections.emptyMap(), args);
+        } catch (IOException | InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static Process run(Map<String, String> envVars, String ... args) throws IOException, InterruptedException {
+        return run(envVars, null, args);
+    }
+
+    public static Process run(Map<String, String> envVars, final File dir, String ... args) throws IOException {
+        if ("true".equals(System.getProperty(SKIP_RUN))) {
+            LOG.debug("Skipping the external command run as configured.");
+            return null;
+        }
+
+        LOG.info("Executing shell command: {} with environment {}", args, envVars);
+
+        try {
+            ProcessBuilder builder = new ProcessBuilder(args);
+            if ( dir != null ) {
+                builder.directory(dir);
+            }
+            builder.redirectErrorStream(true);
+            Map<String, String> environ = builder.environment();
+            environ.putAll(envVars);
+
+            Process process = builder.start();
+
+            return process;
+        } catch (IOException e) {
+            LOG.error("Problem executing command: " + Arrays.toString(args), e);
+            throw e;
+        }
+    }
+
+    public static String waitFor(final Process process) {
+        try {
+            process.waitFor();
+            String res = new String(Streams.suck(process.getInputStream())).trim();
+            LOG.debug("Result: {}", res);
+            return res;
+        } catch (IOException | InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ServiceImpl.java b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ServiceImpl.java
new file mode 100644
index 0000000..4577f70
--- /dev/null
+++ b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/ServiceImpl.java
@@ -0,0 +1,74 @@
+/*
+ * 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 WARRANTIESOR 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.aries.containers.docker.local.impl;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.aries.containers.Service;
+import org.apache.aries.containers.ServiceConfig;
+
+public class ServiceImpl implements Service {
+    private final ServiceConfig config;
+    private final List<ContainerImpl> containers;
+    private final LocalDockerContainerFactory factory;
+
+    public ServiceImpl(ServiceConfig config,
+            LocalDockerContainerFactory factory,
+            List<ContainerImpl> containers) {
+        this.config = config;
+        this.factory = factory;
+        this.containers = new CopyOnWriteArrayList<>(containers);
+    }
+
+    @Override
+    public void destroy() {
+        setInstanceCount(0);
+    }
+
+    @Override
+    public int getActualInstanceCount() {
+        return containers.size(); // TODO obtain live
+    }
+
+    @Override
+    public void setInstanceCount(int count) {
+        try {
+            int curSize = containers.size();
+            if (count < curSize) {
+                for (int i=0 ; i < curSize - count; i++) {
+                    killContainer(containers.remove(0));
+                }
+            } else {
+                // TODO implement scaling up
+            }
+        } catch (Exception e) {
+            LocalDockerContainerFactory.LOG.error("Problem changing instance count of service {} to {}",
+                    config.getServiceName(), count, e);
+        }
+    }
+
+    private void killContainer(ContainerImpl container) throws Exception {
+        factory.destroyDockerContainer(container.getID(), true);
+    }
+
+    public void killAndReplaceContainer(ContainerImpl containerImpl) {
+        // TODO implement
+    }
+}
diff --git a/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/Streams.java b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/Streams.java
new file mode 100644
index 0000000..37a4860
--- /dev/null
+++ b/containers-docker-local/src/main/java/org/apache/aries/containers/docker/local/impl/Streams.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 WARRANTIESOR 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.aries.containers.docker.local.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public final class Streams {
+    private Streams() {
+        // Util class do not instantiate
+    }
+
+    public static void pump(InputStream is, OutputStream os) throws IOException {
+        byte[] bytes = new byte[16384];
+
+        int length = 0;
+        int offset = 0;
+
+        while ((length = is.read(bytes, offset, bytes.length - offset)) != -1) {
+            offset += length;
+
+            if (offset == bytes.length) {
+                os.write(bytes, 0, bytes.length);
+                offset = 0;
+            }
+        }
+        if (offset != 0) {
+            os.write(bytes, 0, offset);
+        }
+    }
+
+    public static byte [] suck(InputStream is) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try {
+            pump(is, baos);
+            return baos.toByteArray();
+        } finally {
+            is.close();
+        }
+    }
+}
diff --git a/pom.xml b/pom.xml
index 752b467..881f9c4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,6 +39,7 @@
     <modules>
         <module>containers-parent</module>
         <module>containers-api</module>
+        <module>containers-docker-local</module>
     </modules>
 </project>