You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2023/07/27 22:22:47 UTC

[camel-karavan] 02/03: devservices #817

This is an automated email from the ASF dual-hosted git repository.

marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git

commit 44a0674263bcf7ffb282e783cfac6a5572cc35cc
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Thu Jul 27 16:39:12 2023 -0400

    devservices #817
---
 .../apache/camel/karavan/api/DevModeResource.java  |  16 +-
 .../camel/karavan/api/InfrastructureResource.java  |  48 ++++-
 .../apache/camel/karavan/docker/DockerService.java | 205 ++++++------------
 .../camel/karavan/docker/DockerServiceUtils.java   | 229 +++++++++++++++++++++
 .../camel/karavan/docker/model/DevService.java     | 103 +++++++++
 .../karavan/docker/model/HealthCheckConfig.java    |  55 +++++
 .../apache/camel/karavan/service/CodeService.java  |   5 +-
 .../camel/karavan/service/ProjectService.java      |  11 +-
 .../camel/karavan/service/ScheduledService.java    |  11 +-
 .../src/main/webui/src/api/KaravanApi.tsx          |   8 +-
 .../src/main/webui/src/api/ProjectModels.ts        |   2 +-
 .../src/main/webui/src/api/ProjectService.ts       |  66 +++---
 .../src/main/webui/src/api/ServiceModels.ts        |  13 +-
 .../webui/src/containers/ContainerTableRow.tsx     |  12 +-
 .../main/webui/src/containers/ContainersPage.tsx   |  32 +--
 .../webui/src/project/pipeline/ProjectStatus.tsx   |   2 +-
 .../src/main/webui/src/services/ServicesPage.tsx   |  33 ++-
 .../main/webui/src/services/ServicesTableRow.tsx   |  91 ++++++--
 .../karavan/infinispan/InfinispanService.java      |   6 -
 .../karavan/infinispan/model/ContainerStatus.java  |   4 +
 20 files changed, 680 insertions(+), 272 deletions(-)

diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java
index d6d647c9..8466fe42 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java
@@ -35,6 +35,8 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.util.Objects;
 
+import static org.apache.camel.karavan.shared.EventType.CONTAINER_STATUS;
+
 @Path("/api/devmode")
 public class DevModeResource {
 
@@ -74,7 +76,8 @@ public class DevModeResource {
             if (ConfigService.inKubernetes()) {
                 kubernetesService.runDevModeContainer(project, jBangOptions);
             } else {
-                dockerService.runDevmodeContainer(project, jBangOptions);
+                dockerService.createDevmodeContainer(project.getProjectId(), jBangOptions);
+                dockerService.runContainer(project.getProjectId());
             }
             return Response.ok(containerName).build();
         }
@@ -105,7 +108,7 @@ public class DevModeResource {
     @Consumes(MediaType.APPLICATION_JSON)
     @Path("/{projectId}/{deletePVC}")
     public Response deleteDevMode(@PathParam("projectId") String projectId, @PathParam("deletePVC") boolean deletePVC) {
-        infinispanService.setContainerStatusTransit(projectId, environment, projectId);
+        setContainerStatusTransit(projectId, ContainerStatus.ContainerType.devmode.name());
         if (ConfigService.inKubernetes()) {
             kubernetesService.deleteDevModePod(projectId, deletePVC);
         } else {
@@ -114,6 +117,15 @@ public class DevModeResource {
         return Response.accepted().build();
     }
 
+    private void setContainerStatusTransit(String name, String type){
+        ContainerStatus status = infinispanService.getContainerStatus(name, environment, name);
+        if (status == null) {
+            status = ContainerStatus.createByType(name, environment, ContainerStatus.ContainerType.valueOf(type));
+        }
+        status.setInTransit(true);
+        eventBus.send(CONTAINER_STATUS, JsonObject.mapFrom(status));
+    }
+
     @GET
     @Produces(MediaType.APPLICATION_JSON)
     @Path("/container/{projectId}")
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java
index 8291c7eb..74ffd6a8 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java
@@ -21,12 +21,14 @@ import io.vertx.core.json.JsonObject;
 import io.vertx.mutiny.core.eventbus.EventBus;
 import io.vertx.mutiny.core.eventbus.Message;
 import org.apache.camel.karavan.docker.DockerService;
+import org.apache.camel.karavan.docker.model.DevService;
 import org.apache.camel.karavan.infinispan.InfinispanService;
 import org.apache.camel.karavan.infinispan.model.ContainerStatus;
 import org.apache.camel.karavan.infinispan.model.DeploymentStatus;
 import org.apache.camel.karavan.infinispan.model.Project;
 import org.apache.camel.karavan.infinispan.model.ServiceStatus;
 import org.apache.camel.karavan.kubernetes.KubernetesService;
+import org.apache.camel.karavan.service.ProjectService;
 import org.apache.camel.karavan.shared.ConfigService;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
@@ -40,6 +42,8 @@ import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
 
+import static org.apache.camel.karavan.shared.EventType.CONTAINER_STATUS;
+
 @Path("/api/infrastructure")
 public class InfrastructureResource {
 
@@ -55,6 +59,9 @@ public class InfrastructureResource {
     @Inject
     DockerService dockerService;
 
+    @Inject
+    ProjectService projectService;
+
     @ConfigProperty(name = "karavan.environment")
     String environment;
 
@@ -166,23 +173,47 @@ public class InfrastructureResource {
     @POST
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
-    @Path("/container/{env}/{name}")
-    public Response startContainer(@PathParam("env") String env, @PathParam("name") String name, JsonObject command) throws Exception {
+    @Path("/container/{env}/{type}/{name}")
+    public Response startContainer(@PathParam("env") String env, @PathParam("type") String type, @PathParam("name") String name, JsonObject command) throws Exception {
         if (infinispanService.isReady()) {
-            infinispanService.setContainerStatusTransit(name, env, name);
+            // set container statuses
+            setContainerStatusTransit(name, type);
+            // exec docker commands
             if (command.containsKey("command")) {
-                if (command.getString("command").equalsIgnoreCase("start")) {
-                    dockerService.startContainer(name);
+                if (command.getString("command").equalsIgnoreCase("run")) {
+                    if (Objects.equals(type, ContainerStatus.ContainerType.devservice.name())) {
+                        String code = projectService.getDevServiceCode();
+                        DevService devService = dockerService.getDevService(code, name);
+                        if (devService != null) {
+                            dockerService.createDevserviceContainer(devService);
+                            dockerService.runContainer(devService.getContainer_name());
+                        }
+                    } else if (Objects.equals(type, ContainerStatus.ContainerType.devmode.name())) {
+                        dockerService.createDevmodeContainer(name, "");
+                        dockerService.runContainer(name);
+                    }
                     return Response.ok().build();
                 } else if (command.getString("command").equalsIgnoreCase("stop")) {
                     dockerService.stopContainer(name);
                     return Response.ok().build();
+                } else if (command.getString("command").equalsIgnoreCase("pause")) {
+                    dockerService.pauseContainer(name);
+                    return Response.ok().build();
                 }
             }
         }
         return Response.notModified().build();
     }
 
+    private void setContainerStatusTransit(String name, String type){
+        ContainerStatus status = infinispanService.getContainerStatus(name, environment, name);
+        if (status == null) {
+            status = ContainerStatus.createByType(name, environment, ContainerStatus.ContainerType.valueOf(type));
+        }
+        status.setInTransit(true);
+        eventBus.send(CONTAINER_STATUS, JsonObject.mapFrom(status));
+    }
+
     @GET
     @Produces(MediaType.APPLICATION_JSON)
     @Path("/container/{env}")
@@ -205,10 +236,11 @@ public class InfrastructureResource {
     @DELETE
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
-    @Path("/container/{env}/{name}")
-    public Response deleteContainer(@PathParam("env") String env, @PathParam("name") String name) {
+    @Path("/container/{env}/{type}/{name}")
+    public Response deleteContainer(@PathParam("env") String env, @PathParam("type") String type, @PathParam("name") String name) {
         if (infinispanService.isReady()) {
-            infinispanService.setContainerStatusTransit(name, env, name);
+            // set container statuses
+            setContainerStatusTransit(name, type);
             try {
                 if (ConfigService.inKubernetes()) {
                     kubernetesService.deletePod(name, kubernetesService.getNamespace());
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
index af1d67f6..03e8b378 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
@@ -18,6 +18,7 @@ package org.apache.camel.karavan.docker;
 
 import com.github.dockerjava.api.DockerClient;
 import com.github.dockerjava.api.async.ResultCallback;
+import com.github.dockerjava.api.command.CreateContainerCmd;
 import com.github.dockerjava.api.command.CreateContainerResponse;
 import com.github.dockerjava.api.command.CreateNetworkResponse;
 import com.github.dockerjava.api.command.HealthState;
@@ -30,11 +31,14 @@ import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
 import com.github.dockerjava.transport.DockerHttpClient;
 import io.smallrye.mutiny.tuples.Tuple2;
 import io.vertx.core.eventbus.EventBus;
-import org.apache.camel.karavan.infinispan.InfinispanService;
+import io.vertx.core.json.JsonObject;
+import org.apache.camel.karavan.docker.model.DevService;
 import org.apache.camel.karavan.infinispan.model.ContainerStatus;
 import org.apache.camel.karavan.infinispan.model.Project;
+import org.apache.camel.karavan.service.CodeService;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
+import org.yaml.snakeyaml.Yaml;
 
 import javax.enterprise.context.ApplicationScoped;
 import javax.inject.Inject;
@@ -49,7 +53,7 @@ import static org.apache.camel.karavan.shared.Constants.*;
 import static org.apache.camel.karavan.shared.EventType.*;
 
 @ApplicationScoped
-public class DockerService {
+public class DockerService extends DockerServiceUtils {
 
     private static final Logger LOGGER = Logger.getLogger(DockerService.class.getName());
 
@@ -57,10 +61,7 @@ public class DockerService {
     protected static final String KARAVAN_CONTAINER_NAME = "karavan-headless";
 
     protected static final String NETWORK_NAME = "karavan";
-    private static final DecimalFormat formatCpu = new DecimalFormat("0.00");
-    private static final DecimalFormat formatMiB = new DecimalFormat("0.0");
-    private static final DecimalFormat formatGiB = new DecimalFormat("0.00");
-    private static final Map<String, Tuple2<Long, Long>> previousStats = new ConcurrentHashMap<>();
+
     private static final List<String> infinispanHealthCheckCMD = List.of("CMD", "curl", "-f", "http://localhost:11222/rest/v2/cache-managers/default/health/status");
 
     @ConfigProperty(name = "karavan.environment")
@@ -100,25 +101,37 @@ public class DockerService {
         }
     }
 
-    public void runDevmodeContainer(Project project, String jBangOptions) throws InterruptedException {
-        String projectId = project.getProjectId();
+    public void createDevmodeContainer(String projectId, String jBangOptions) throws InterruptedException {
         LOGGER.infof("DevMode starting for %s with JBANG_OPTIONS=%s", projectId, jBangOptions);
 
         HealthCheck healthCheck = new HealthCheck().withTest(List.of("CMD", "curl", "-f", "http://localhost:8080/q/dev/health"))
                 .withInterval(10000000000L).withTimeout(10000000000L).withStartPeriod(10000000000L).withRetries(30);
 
-        List<String> env = jBangOptions !=null && !jBangOptions.trim().isEmpty()
+        List<String> env = jBangOptions != null && !jBangOptions.trim().isEmpty()
                 ? List.of(ENV_VAR_JBANG_OPTIONS + "=" + jBangOptions)
                 : List.of();
 
         createContainer(projectId, devmodeImage,
-                env, null, false, false, healthCheck,
+                env, null, false, List.of(), healthCheck,
                 Map.of(LABEL_TYPE, ContainerStatus.ContainerType.devmode.name(), LABEL_PROJECT_ID, projectId));
 
-        startContainer(projectId);
         LOGGER.infof("DevMode started for %s", projectId);
     }
 
+    public void createDevserviceContainer(DevService devService) throws InterruptedException {
+        LOGGER.infof("DevService starting for ", devService.getContainer_name());
+
+        HealthCheck healthCheck = getHealthCheck(devService.getHealthcheck());
+        List<String> env = devService.getEnvironment() != null ? devService.getEnvironmentList() : List.of();
+        String ports = String.join(",", devService.getPorts());
+
+        createContainer(devService.getContainer_name(), devService.getImage(),
+                env, ports, false, devService.getExpose(), healthCheck,
+                Map.of(LABEL_TYPE, ContainerStatus.ContainerType.devservice.name()));
+
+        LOGGER.infof("DevService started for %s", devService.getContainer_name());
+    }
+
     public void startInfinispan() {
         try {
             LOGGER.info("Infinispan is starting...");
@@ -126,12 +139,14 @@ public class DockerService {
             HealthCheck healthCheck = new HealthCheck().withTest(infinispanHealthCheckCMD)
                     .withInterval(10000000000L).withTimeout(10000000000L).withStartPeriod(10000000000L).withRetries(30);
 
+            List<String> exposedPorts = List.of(infinispanPort.split(":")[0]);
+
             createContainer(INFINISPAN_CONTAINER_NAME, infinispanImage,
                     List.of("USER=" + infinispanUsername, "PASS=" + infinispanPassword),
-                    infinispanPort, false, true, healthCheck,
+                    infinispanPort, false, exposedPorts, healthCheck,
                     Map.of(LABEL_TYPE, ContainerStatus.ContainerType.internal.name()));
 
-            startContainer(INFINISPAN_CONTAINER_NAME);
+            runContainer(INFINISPAN_CONTAINER_NAME);
             LOGGER.info("Infinispan is started");
         } catch (Exception e) {
             LOGGER.error(e.getMessage());
@@ -148,10 +163,10 @@ public class DockerService {
                             "INFINISPAN_USERNAME=" + infinispanUsername,
                             "INFINISPAN_PASSWORD=" + infinispanPassword
                     ),
-                    null, false, false, new HealthCheck(),
+                    null, false, List.of(), new HealthCheck(),
                     Map.of(LABEL_TYPE, ContainerStatus.ContainerType.internal.name()));
 
-            startContainer(KARAVAN_CONTAINER_NAME);
+            runContainer(KARAVAN_CONTAINER_NAME);
             LOGGER.info("Karavan headless is started");
         } catch (Exception e) {
             LOGGER.error(e.getMessage());
@@ -171,7 +186,8 @@ public class DockerService {
         List<ContainerStatus> result = new ArrayList<>();
         getDockerClient().listContainersCmd().withShowAll(true).exec().forEach(container -> {
             ContainerStatus containerStatus = getContainerStatus(container);
-            updateStatistics(containerStatus, container);
+            Statistics stats = getContainerStats(container.getId());
+            updateStatistics(containerStatus, container, stats);
             result.add(containerStatus);
         });
         return result;
@@ -186,16 +202,6 @@ public class DockerService {
         return ContainerStatus.createWithId(name, environment, container.getId(), container.getImage(), ports, type, commands, container.getState(), created);
     }
 
-    private void updateStatistics(ContainerStatus containerStatus, Container container) {
-        Statistics stats = getContainerStats(container.getId());
-        if (stats != null && stats.getMemoryStats() != null) {
-            String memoryUsage = formatMemory(stats.getMemoryStats().getUsage());
-            String memoryLimit = formatMemory(stats.getMemoryStats().getLimit());
-            containerStatus.setMemoryInfo(memoryUsage + " / " + memoryLimit);
-            containerStatus.setCpuInfo(formatCpu(containerStatus.getContainerName(), stats));
-        }
-    }
-
     public void startListeners() {
         getDockerClient().eventsCmd().exec(dockerEventListener);
     }
@@ -254,22 +260,23 @@ public class DockerService {
     }
 
     public Container createContainer(String name, String image, List<String> env, String ports, boolean inRange,
-                                     boolean exposedPort, HealthCheck healthCheck, Map<String, String> labels) throws InterruptedException {
+                                     List<String> exposed, HealthCheck healthCheck, Map<String, String> labels) throws InterruptedException {
         List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
         if (containers.size() == 0) {
             pullImage(image);
 
-            List<ExposedPort> exposedPorts = getPortsFromString(ports).values().stream().map(i -> ExposedPort.tcp(i)).collect(Collectors.toList());
-
-            CreateContainerResponse response = getDockerClient().createContainerCmd(image)
-                    .withName(name)
-                    .withLabels(labels)
-                    .withEnv(env)
-                    .withExposedPorts(exposedPorts)
-                    .withHostName(name)
-                    .withHostConfig(getHostConfig(ports, exposedPort, inRange))
-                    .withHealthcheck(healthCheck)
-                    .exec();
+            CreateContainerCmd createContainerCmd = getDockerClient().createContainerCmd(image)
+                    .withName(name).withLabels(labels).withEnv(env).withHostName(name).withHealthcheck(healthCheck);
+
+            if (exposed != null) {
+                List<ExposedPort> exposedPorts = exposed.stream().map(i -> ExposedPort.tcp(Integer.parseInt(i))).collect(Collectors.toList());
+                createContainerCmd.withExposedPorts(exposedPorts);
+                createContainerCmd.withHostConfig(getHostConfig(ports, exposedPorts, inRange, NETWORK_NAME));
+            } else {
+                createContainerCmd.withHostConfig(getHostConfig(ports, List.of(), inRange, NETWORK_NAME));
+            }
+
+            CreateContainerResponse response = createContainerCmd.exec();
             LOGGER.info("Container created: " + response.getId());
             return getDockerClient().listContainersCmd().withShowAll(true)
                     .withIdFilter(Collections.singleton(response.getId())).exec().get(0);
@@ -279,21 +286,18 @@ public class DockerService {
         }
     }
 
-    public void startContainer(String name) throws InterruptedException {
+    public void runContainer(String name) {
         List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
         if (containers.size() == 1) {
             Container container = containers.get(0);
-            if (!container.getState().equals("running")) {
+            if (container.getState().equals("paused")) {
+                getDockerClient().unpauseContainerCmd(container.getId()).exec();
+            } else if (!container.getState().equals("running")) {
                 getDockerClient().startContainerCmd(container.getId()).exec();
             }
         }
     }
 
-    public void restartContainer(String name) throws InterruptedException {
-        stopContainer(name);
-        startContainer(name);
-    }
-
     public void logContainer(String containerName, LogCallback callback) {
         try {
             Container container = getContainerByName(containerName);
@@ -312,6 +316,16 @@ public class DockerService {
         }
     }
 
+    public void pauseContainer(String name) {
+        List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
+        if (containers.size() == 1) {
+            Container container = containers.get(0);
+            if (container.getState().equals("running")) {
+                getDockerClient().pauseContainerCmd(container.getId()).exec();
+            }
+        }
+    }
+
     public void stopContainer(String name) {
         List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
         if (containers.size() == 1) {
@@ -342,31 +356,6 @@ public class DockerService {
         }
     }
 
-    private HostConfig getHostConfig(String ports, boolean exposedPort, boolean inRange) {
-        Ports portBindings = new Ports();
-
-        getPortsFromString(ports).forEach((hostPort, containerPort) -> {
-            Ports.Binding binding = exposedPort
-                    ? (inRange ? Ports.Binding.bindPortRange(hostPort, hostPort + 1000) : Ports.Binding.bindPort(hostPort))
-                    : Ports.Binding.bindPort(hostPort);
-            portBindings.bind(ExposedPort.tcp(containerPort), binding);
-        });
-        return new HostConfig()
-                .withPortBindings(portBindings)
-                .withNetworkMode(NETWORK_NAME);
-    }
-
-    private Map<Integer, Integer> getPortsFromString(String ports) {
-        Map<Integer, Integer> p = new HashMap<>();
-        if (ports != null && !ports.isEmpty()) {
-            Arrays.stream(ports.split(",")).forEach(s -> {
-                String[] values = s.split(":");
-                p.put(Integer.parseInt(values[0]), Integer.parseInt(values[1]));
-            });
-        }
-        return p;
-    }
-
     private DockerClientConfig getDockerClientConfig() {
         return DefaultDockerClientConfig.createDefaultConfigBuilder().build();
     }
@@ -387,80 +376,4 @@ public class DockerService {
         }
         return dockerClient;
     }
-
-    private String formatMemory(Long memory) {
-        try {
-            if (memory < (1073741824)) {
-                return formatMiB.format(memory.doubleValue() / 1048576) + "MiB";
-            } else {
-                return formatGiB.format(memory.doubleValue() / 1073741824) + "GiB";
-            }
-        } catch (Exception e) {
-            return "";
-        }
-    }
-
-    private ContainerStatus.ContainerType getContainerType(Map<String, String> labels) {
-        String type = labels.get(LABEL_TYPE);
-        if (Objects.equals(type, ContainerStatus.ContainerType.devmode.name())) {
-            return ContainerStatus.ContainerType.devmode;
-        } else if (Objects.equals(type, ContainerStatus.ContainerType.devservice.name())) {
-            return ContainerStatus.ContainerType.devservice;
-        } else if (Objects.equals(type, ContainerStatus.ContainerType.project.name())) {
-            return ContainerStatus.ContainerType.project;
-        } else if (Objects.equals(type, ContainerStatus.ContainerType.internal.name())) {
-            return ContainerStatus.ContainerType.internal;
-        }
-        return ContainerStatus.ContainerType.unknown;
-    }
-
-    private List<ContainerStatus.Command> getContainerCommand(String state) {
-        List<ContainerStatus.Command> result = new ArrayList<>();
-        if (Objects.equals(state, ContainerStatus.State.created.name())) {
-            result.add(ContainerStatus.Command.run);
-            result.add(ContainerStatus.Command.delete);
-        } else if (Objects.equals(state, ContainerStatus.State.exited.name())) {
-            result.add(ContainerStatus.Command.run);
-            result.add(ContainerStatus.Command.delete);
-        } else if (Objects.equals(state, ContainerStatus.State.running.name())) {
-            result.add(ContainerStatus.Command.pause);
-            result.add(ContainerStatus.Command.stop);
-            result.add(ContainerStatus.Command.delete);
-        } else if (Objects.equals(state, ContainerStatus.State.paused.name())) {
-            result.add(ContainerStatus.Command.run);
-            result.add(ContainerStatus.Command.stop);
-            result.add(ContainerStatus.Command.delete);
-        } else if (Objects.equals(state, ContainerStatus.State.dead.name())) {
-            result.add(ContainerStatus.Command.delete);
-        }
-        return result;
-    }
-
-    private String formatCpu(String containerName, Statistics stats) {
-        try {
-            double cpuUsage = 0;
-            long previousCpu = previousStats.containsKey(containerName) ? previousStats.get(containerName).getItem1() : -1;
-            long previousSystem = previousStats.containsKey(containerName) ? previousStats.get(containerName).getItem2() : -1;
-
-            CpuStatsConfig cpuStats = stats.getCpuStats();
-            if (cpuStats != null) {
-                CpuUsageConfig cpuUsageConfig = cpuStats.getCpuUsage();
-                long systemUsage = cpuStats.getSystemCpuUsage();
-                long totalUsage = cpuUsageConfig.getTotalUsage();
-
-                if (previousCpu != -1 && previousSystem != -1) {
-                    float cpuDelta = totalUsage - previousCpu;
-                    float systemDelta = systemUsage - previousSystem;
-
-                    if (cpuDelta > 0 && systemDelta > 0) {
-                        cpuUsage = cpuDelta / systemDelta * cpuStats.getOnlineCpus() * 100;
-                    }
-                }
-                previousStats.put(containerName, Tuple2.of(totalUsage, systemUsage));
-            }
-            return formatCpu.format(cpuUsage) + "%";
-        } catch (Exception e) {
-            return "";
-        }
-    }
 }
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerServiceUtils.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerServiceUtils.java
new file mode 100644
index 00000000..748c17bc
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerServiceUtils.java
@@ -0,0 +1,229 @@
+/*
+ * 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.camel.karavan.docker;
+
+import com.github.dockerjava.api.model.*;
+import io.smallrye.mutiny.tuples.Tuple2;
+import io.vertx.core.json.JsonObject;
+import org.apache.camel.karavan.api.KameletResources;
+import org.apache.camel.karavan.docker.model.DevService;
+import org.apache.camel.karavan.docker.model.HealthCheckConfig;
+import org.apache.camel.karavan.infinispan.model.ContainerStatus;
+import org.apache.camel.karavan.service.CodeService;
+import org.yaml.snakeyaml.Yaml;
+
+import javax.inject.Inject;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.text.DecimalFormat;
+import java.time.Instant;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import static org.apache.camel.karavan.shared.Constants.LABEL_TYPE;
+
+public class DockerServiceUtils {
+
+    protected static final DecimalFormat formatCpu = new DecimalFormat("0.00");
+    protected static final DecimalFormat formatMiB = new DecimalFormat("0.0");
+    protected static final DecimalFormat formatGiB = new DecimalFormat("0.00");
+    protected static final Map<String, Tuple2<Long, Long>> previousStats = new ConcurrentHashMap<>();
+
+    @Inject
+    CodeService codeService;
+
+
+    protected ContainerStatus getContainerStatus(Container container, String environment) {
+        String name = container.getNames()[0].replace("/", "");
+        List<Integer> ports = Arrays.stream(container.getPorts()).map(ContainerPort::getPrivatePort).filter(Objects::nonNull).collect(Collectors.toList());
+        List<ContainerStatus.Command> commands = getContainerCommand(container.getState());
+        ContainerStatus.ContainerType type = getContainerType(container.getLabels());
+        String created = Instant.ofEpochSecond(container.getCreated()).toString();
+        return ContainerStatus.createWithId(name, environment, container.getId(), container.getImage(), ports, type, commands, container.getState(), created);
+    }
+
+    protected void updateStatistics(ContainerStatus containerStatus, Container container, Statistics stats) {
+        if (stats != null && stats.getMemoryStats() != null) {
+            String memoryUsage = formatMemory(stats.getMemoryStats().getUsage());
+            String memoryLimit = formatMemory(stats.getMemoryStats().getLimit());
+            containerStatus.setMemoryInfo(memoryUsage + " / " + memoryLimit);
+            containerStatus.setCpuInfo(formatCpu(containerStatus.getContainerName(), stats));
+        }
+    }
+
+    public DevService getDevService(String code, String name) {
+        Yaml yaml = new Yaml();
+        Map<String, Object> obj = yaml.load(code);
+        JsonObject json = JsonObject.mapFrom(obj);
+        JsonObject services = json.getJsonObject("services");
+        if (services.containsKey(name)) {
+            DevService ds = services.getJsonObject(name).mapTo(DevService.class);
+            if (ds.getContainer_name() == null) {
+                ds.setContainer_name(name);
+            }
+            return ds;
+        } else {
+            Optional<JsonObject> j = services.fieldNames().stream()
+                    .map(services::getJsonObject)
+                    .filter(s -> {
+                        s.getJsonObject("container_name");
+                        return false;
+                    }).findFirst();
+            if (j.isPresent()) {
+                return j.get().mapTo(DevService.class);
+            }
+        }
+        return null;
+    }
+
+    protected HealthCheck getHealthCheck(HealthCheckConfig config) {
+        if (config != null) {
+            HealthCheck healthCheck = new HealthCheck().withTest(config.getTest());
+            if (config.getInterval() != null) {
+                healthCheck.withInterval(convertDuration(config.getInterval()));
+            }
+            if (config.getTimeout() != null) {
+                healthCheck.withTimeout(convertDuration(config.getTimeout()));
+            }
+            if (config.getStart_period() != null) {
+                healthCheck.withStartPeriod(convertDuration(config.getStart_period()));
+            }
+            if (config.getRetries() != null) {
+                healthCheck.withRetries(config.getRetries());
+            }
+            return healthCheck;
+        }
+        return new HealthCheck();
+    }
+
+    protected Long convertDuration(String value) {
+        return Long.parseLong(value.replace("s", "")) * 1000000000L;
+    }
+
+    protected String getResourceFile(String path) {
+        try {
+            InputStream inputStream = KameletResources.class.getResourceAsStream(path);
+            return new BufferedReader(new InputStreamReader(inputStream))
+                    .lines().collect(Collectors.joining(System.getProperty("line.separator")));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    protected HostConfig getHostConfig(String ports, List<ExposedPort> exposedPorts, boolean inRange, String network) {
+        Ports portBindings = new Ports();
+
+        getPortsFromString(ports).forEach((hostPort, containerPort) -> {
+            Ports.Binding binding = (exposedPorts.stream().anyMatch(e -> e.getPort() == containerPort))
+                    ? (inRange ? Ports.Binding.bindPortRange(hostPort, hostPort + 1000) : Ports.Binding.bindPort(hostPort))
+                    : Ports.Binding.bindPort(hostPort);
+            portBindings.bind(ExposedPort.tcp(containerPort), binding);
+        });
+        return new HostConfig()
+                .withPortBindings(portBindings)
+                .withNetworkMode(network);
+    }
+
+    protected Map<Integer, Integer> getPortsFromString(String ports) {
+        Map<Integer, Integer> p = new HashMap<>();
+        if (ports != null && !ports.isEmpty()) {
+            Arrays.stream(ports.split(",")).forEach(s -> {
+                String[] values = s.split(":");
+                p.put(Integer.parseInt(values[0]), Integer.parseInt(values[1]));
+            });
+        }
+        return p;
+    }
+
+    protected String formatMemory(Long memory) {
+        try {
+            if (memory < (1073741824)) {
+                return formatMiB.format(memory.doubleValue() / 1048576) + "MiB";
+            } else {
+                return formatGiB.format(memory.doubleValue() / 1073741824) + "GiB";
+            }
+        } catch (Exception e) {
+            return "";
+        }
+    }
+
+    protected ContainerStatus.ContainerType getContainerType(Map<String, String> labels) {
+        String type = labels.get(LABEL_TYPE);
+        if (Objects.equals(type, ContainerStatus.ContainerType.devmode.name())) {
+            return ContainerStatus.ContainerType.devmode;
+        } else if (Objects.equals(type, ContainerStatus.ContainerType.devservice.name())) {
+            return ContainerStatus.ContainerType.devservice;
+        } else if (Objects.equals(type, ContainerStatus.ContainerType.project.name())) {
+            return ContainerStatus.ContainerType.project;
+        } else if (Objects.equals(type, ContainerStatus.ContainerType.internal.name())) {
+            return ContainerStatus.ContainerType.internal;
+        }
+        return ContainerStatus.ContainerType.unknown;
+    }
+
+    protected List<ContainerStatus.Command> getContainerCommand(String state) {
+        List<ContainerStatus.Command> result = new ArrayList<>();
+        if (Objects.equals(state, ContainerStatus.State.created.name())) {
+            result.add(ContainerStatus.Command.run);
+            result.add(ContainerStatus.Command.delete);
+        } else if (Objects.equals(state, ContainerStatus.State.exited.name())) {
+            result.add(ContainerStatus.Command.run);
+            result.add(ContainerStatus.Command.delete);
+        } else if (Objects.equals(state, ContainerStatus.State.running.name())) {
+            result.add(ContainerStatus.Command.pause);
+            result.add(ContainerStatus.Command.stop);
+            result.add(ContainerStatus.Command.delete);
+        } else if (Objects.equals(state, ContainerStatus.State.paused.name())) {
+            result.add(ContainerStatus.Command.run);
+            result.add(ContainerStatus.Command.stop);
+            result.add(ContainerStatus.Command.delete);
+        } else if (Objects.equals(state, ContainerStatus.State.dead.name())) {
+            result.add(ContainerStatus.Command.delete);
+        }
+        return result;
+    }
+
+    protected String formatCpu(String containerName, Statistics stats) {
+        try {
+            double cpuUsage = 0;
+            long previousCpu = previousStats.containsKey(containerName) ? previousStats.get(containerName).getItem1() : -1;
+            long previousSystem = previousStats.containsKey(containerName) ? previousStats.get(containerName).getItem2() : -1;
+
+            CpuStatsConfig cpuStats = stats.getCpuStats();
+            if (cpuStats != null) {
+                CpuUsageConfig cpuUsageConfig = cpuStats.getCpuUsage();
+                long systemUsage = cpuStats.getSystemCpuUsage();
+                long totalUsage = cpuUsageConfig.getTotalUsage();
+
+                if (previousCpu != -1 && previousSystem != -1) {
+                    float cpuDelta = totalUsage - previousCpu;
+                    float systemDelta = systemUsage - previousSystem;
+
+                    if (cpuDelta > 0 && systemDelta > 0) {
+                        cpuUsage = cpuDelta / systemDelta * cpuStats.getOnlineCpus() * 100;
+                    }
+                }
+                previousStats.put(containerName, Tuple2.of(totalUsage, systemUsage));
+            }
+            return formatCpu.format(cpuUsage) + "%";
+        } catch (Exception e) {
+            return "";
+        }
+    }
+}
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/DevService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/DevService.java
new file mode 100644
index 00000000..2c0b4e30
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/DevService.java
@@ -0,0 +1,103 @@
+package org.apache.camel.karavan.docker.model;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class DevService {
+
+    private String container_name;
+    private String image;
+    private String restart;
+    private List<String> ports;
+    private List<String> expose;
+    private String depends_on;
+    private Map<String,String> environment;
+    private HealthCheckConfig healthcheck;
+
+    public DevService() {
+    }
+
+    public String getContainer_name() {
+        return container_name;
+    }
+
+    public void setContainer_name(String container_name) {
+        this.container_name = container_name;
+    }
+
+    public String getImage() {
+        return image;
+    }
+
+    public void setImage(String image) {
+        this.image = image;
+    }
+
+    public String getRestart() {
+        return restart;
+    }
+
+    public void setRestart(String restart) {
+        this.restart = restart;
+    }
+
+    public List<String> getPorts() {
+        return ports;
+    }
+
+    public void setPorts(List<String> ports) {
+        this.ports = ports;
+    }
+
+    public List<String> getExpose() {
+        return expose;
+    }
+
+    public void setExpose(List<String> expose) {
+        this.expose = expose;
+    }
+
+    public String getDepends_on() {
+        return depends_on;
+    }
+
+    public void setDepends_on(String depends_on) {
+        this.depends_on = depends_on;
+    }
+
+    public Map<String, String> getEnvironment() {
+        return environment;
+    }
+
+    public List<String> getEnvironmentList() {
+        return environment.entrySet().stream()
+                .map(e -> e.getKey().concat("=").concat(e.getValue())).collect(Collectors.toList());
+    }
+
+    public void setEnvironment(Map<String, String> environment) {
+        this.environment = environment;
+    }
+
+    public HealthCheckConfig getHealthcheck() {
+        return healthcheck;
+    }
+
+    public void setHealthcheck(HealthCheckConfig healthcheck) {
+        this.healthcheck = healthcheck;
+    }
+
+    @Override
+    public String toString() {
+        return "DevService{" +
+                "container_name='" + container_name + '\'' +
+                ", image='" + image + '\'' +
+                ", restart='" + restart + '\'' +
+                ", ports=" + ports +
+                ", expose=" + expose +
+                ", depends_on='" + depends_on + '\'' +
+                ", environment=" + environment +
+                ", healthcheck=" + healthcheck +
+                '}';
+    }
+}
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/HealthCheckConfig.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/HealthCheckConfig.java
new file mode 100644
index 00000000..46f766df
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/HealthCheckConfig.java
@@ -0,0 +1,55 @@
+package org.apache.camel.karavan.docker.model;
+
+import java.util.List;
+
+public class HealthCheckConfig {
+
+    private String interval;
+    private Integer retries;
+    private String timeout;
+    private String start_period;
+    private List<String> test;
+
+    public HealthCheckConfig() {
+    }
+
+    public String getInterval() {
+        return interval;
+    }
+
+    public void setInterval(String interval) {
+        this.interval = interval;
+    }
+
+    public Integer getRetries() {
+        return retries;
+    }
+
+    public void setRetries(Integer retries) {
+        this.retries = retries;
+    }
+
+    public String getTimeout() {
+        return timeout;
+    }
+
+    public void setTimeout(String timeout) {
+        this.timeout = timeout;
+    }
+
+    public List<String> getTest() {
+        return test;
+    }
+
+    public void setTest(List<String> test) {
+        this.test = test;
+    }
+
+    public String getStart_period() {
+        return start_period;
+    }
+
+    public void setStart_period(String start_period) {
+        this.start_period = start_period;
+    }
+}
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
index a23d93d0..73505065 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
@@ -51,6 +51,7 @@ public class CodeService {
 
     private static final Logger LOGGER = Logger.getLogger(CodeService.class.getName());
     public static final String APPLICATION_PROPERTIES_FILENAME = "application.properties";
+    public static final String DEV_SERVICES_FILENAME = "dev-services.yaml";
 
     @Inject
     KubernetesService kubernetesService;
@@ -133,8 +134,8 @@ public class CodeService {
 
     public Map<String, String> getServices() {
         Map<String, String> result = new HashMap<>();
-        String templateText = getResourceFile("/services/dev-services.yaml");
-        result.put("dev-services.yaml", templateText);
+        String templateText = getResourceFile("/services/" + DEV_SERVICES_FILENAME);
+        result.put(DEV_SERVICES_FILENAME, templateText);
         return result;
     }
 
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
index 7c05cea0..93ec231f 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
@@ -16,8 +16,6 @@
  */
 package org.apache.camel.karavan.service;
 
-import io.quarkus.scheduler.Scheduled;
-import io.quarkus.vertx.ConsumeEvent;
 import io.smallrye.mutiny.tuples.Tuple2;
 import org.apache.camel.karavan.infinispan.InfinispanService;
 import org.apache.camel.karavan.infinispan.model.GitRepo;
@@ -37,9 +35,10 @@ import javax.inject.Inject;
 import java.time.Instant;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import static org.apache.camel.karavan.shared.EventType.IMPORT_PROJECTS;
+import static org.apache.camel.karavan.service.CodeService.DEV_SERVICES_FILENAME;
 
 @Default
 @Readiness
@@ -263,4 +262,10 @@ public class ProjectService implements HealthCheck{
             LOGGER.error("Error during pipelines project creation", e);
         }
     }
+
+    public String getDevServiceCode() {
+        List <ProjectFile> files = infinispanService.getProjectFiles(Project.Type.services.name());
+        Optional<ProjectFile> file = files.stream().filter(f -> f.getName().equals(DEV_SERVICES_FILENAME)).findFirst();
+        return file.orElse(new ProjectFile()).getCode();
+    }
 }
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java
index 16fcbeb6..a4ba7878 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java
@@ -62,10 +62,13 @@ public class ScheduledService {
             List<String> namesInDocker = statusesInDocker.stream().map(ContainerStatus::getContainerName).collect(Collectors.toList());
             List<ContainerStatus> statusesInInfinispan = infinispanService.getContainerStatuses(environment);
             // clean deleted
-            statusesInInfinispan.stream().filter(cs -> !namesInDocker.contains(cs.getContainerName())).forEach(containerStatus -> {
-                infinispanService.deleteContainerStatus(containerStatus);
-                infinispanService.deleteCamelStatuses(containerStatus.getProjectId(), containerStatus.getEnv());
-            });
+            statusesInInfinispan.stream()
+                    .filter(cs -> !(cs.getContainerId() == null && cs.getInTransit()))
+                    .filter(cs -> !namesInDocker.contains(cs.getContainerName()))
+                    .forEach(containerStatus -> {
+                        infinispanService.deleteContainerStatus(containerStatus);
+                        infinispanService.deleteCamelStatuses(containerStatus.getProjectId(), containerStatus.getEnv());
+                    });
             // send statuses to save
             statusesInDocker.forEach(containerStatus -> {
                 eventBus.send(EventType.CONTAINER_STATUS, JsonObject.mapFrom(containerStatus));
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index fc0ed2e8..4c94484a 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -463,8 +463,8 @@ export class KaravanApi {
         });
     }
 
-    static async manageContainer(environment: string, name: string, command: 'run' | 'pause' | 'stop', after: (res: AxiosResponse<any>) => void) {
-        instance.post('/api/infrastructure/container/' + environment + '/' + name, {command: command})
+    static async manageContainer(environment: string, type: 'devmove' | 'devservice' | 'project' | 'internal' | 'unknown',  name: string, command: 'run' | 'pause' | 'stop', after: (res: AxiosResponse<any>) => void) {
+        instance.post('/api/infrastructure/container/' + environment + '/' + type + "/" + name, {command: command})
             .then(res => {
                 after(res);
             }).catch(err => {
@@ -472,8 +472,8 @@ export class KaravanApi {
         });
     }
 
-    static async deleteContainer(environment: string, name: string, after: (res: AxiosResponse<any>) => void) {
-        instance.delete('/api/infrastructure/container/' + environment + '/' + name)
+    static async deleteContainer(environment: string, type: 'devmove' | 'devservice' | 'project' | 'internal' | 'unknown', name: string, after: (res: AxiosResponse<any>) => void) {
+        instance.delete('/api/infrastructure/container/' + environment + '/' + type + "/" + name)
             .then(res => {
                 after(res);
             }).catch(err => {
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts
index 3a4ced02..82a89413 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts
@@ -75,7 +75,7 @@ export class ContainerStatus {
     deployment: string = '';
     projectId: string = '';
     env: string = '';
-    type: string = '';
+    type: 'devmove' | 'devservice' | 'project' | 'internal' | 'unknown' = 'unknown';
     memoryInfo: string = '';
     cpuInfo: string = '';
     created: string = '';
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
index 8b043d61..cdfada6f 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
@@ -1,7 +1,7 @@
-import {KaravanApi} from "./KaravanApi";
-import {DeploymentStatus, ContainerStatus, Project, ProjectFile, ToastMessage} from "./ProjectModels";
-import {TemplateApi} from "karavan-core/lib/api/TemplateApi";
-import {InfrastructureAPI} from "../designer/utils/InfrastructureAPI";
+import {KaravanApi} from './KaravanApi';
+import {DeploymentStatus, ContainerStatus, Project, ProjectFile, ToastMessage} from './ProjectModels';
+import {TemplateApi} from 'karavan-core/lib/api/TemplateApi';
+import {InfrastructureAPI} from '../designer/utils/InfrastructureAPI';
 import {unstable_batchedUpdates} from 'react-dom'
 import {
     useFilesStore,
@@ -9,17 +9,17 @@ import {
     useFileStore, useLogStore,
     useProjectsStore,
     useProjectStore, useDevModeStore
-} from "./ProjectStore";
-import {ProjectEventBus} from "./ProjectEventBus";
+} from './ProjectStore';
+import {ProjectEventBus} from './ProjectEventBus';
 
 export class ProjectService {
 
     public static startDevModeContainer(project: Project, verbose: boolean) {
-        useDevModeStore.setState({status: "wip"})
+        useDevModeStore.setState({status: 'wip'})
         KaravanApi.startDevModeContainer(project, verbose, res => {
-            useDevModeStore.setState({status: "none"})
+            useDevModeStore.setState({status: 'none'})
             if (res.status === 200 || res.status === 201) {
-                ProjectEventBus.sendLog("set", '');
+                ProjectEventBus.sendLog('set', '');
                 useLogStore.setState({showLog: true, type: 'container', podName: res.data})
             } else {
                 // Todo notification
@@ -28,9 +28,9 @@ export class ProjectService {
     }
 
     public static reloadDevModeCode(project: Project) {
-        useDevModeStore.setState({status: "wip"})
+        useDevModeStore.setState({status: 'wip'})
         KaravanApi.reloadDevModeCode(project.projectId, res => {
-            useDevModeStore.setState({status: "none"})
+            useDevModeStore.setState({status: 'none'})
             if (res.status === 200 || res.status === 201) {
                 // setIsReloadingPod(false);
             } else {
@@ -41,38 +41,38 @@ export class ProjectService {
     }
 
     public static stopDevModeContainer(project: Project) {
-        useDevModeStore.setState({status: "wip"})
-        KaravanApi.manageContainer("dev", project.projectId, 'stop', res => {
-            useDevModeStore.setState({status: "none"})
+        useDevModeStore.setState({status: 'wip'})
+        KaravanApi.manageContainer('dev', 'devmove', project.projectId, 'stop', res => {
+            useDevModeStore.setState({status: 'none'})
             if (res.status === 200) {
                 useLogStore.setState({showLog: false, type: 'container', isRunning: false})
             } else {
-                ProjectEventBus.sendAlert(new ToastMessage("Error stopping DevMode container", res.statusText, 'warning'))
+                ProjectEventBus.sendAlert(new ToastMessage('Error stopping DevMode container', res.statusText, 'warning'))
             }
         });
     }
 
     public static pauseDevModeContainer(project: Project) {
-        useDevModeStore.setState({status: "wip"})
-        KaravanApi.manageContainer("dev", project.projectId, 'pause', res => {
-            useDevModeStore.setState({status: "none"})
+        useDevModeStore.setState({status: 'wip'})
+        KaravanApi.manageContainer('dev', 'devmove', project.projectId, 'pause', res => {
+            useDevModeStore.setState({status: 'none'})
             if (res.status === 200) {
                 useLogStore.setState({showLog: false, type: 'container', isRunning: false})
             } else {
-                ProjectEventBus.sendAlert(new ToastMessage("Error stopping DevMode container", res.statusText, 'warning'))
+                ProjectEventBus.sendAlert(new ToastMessage('Error stopping DevMode container', res.statusText, 'warning'))
             }
         });
     }
 
     public static deleteDevModeContainer(project: Project) {
-        useDevModeStore.setState({status: "wip"})
-        ProjectEventBus.sendLog("set", '');
+        useDevModeStore.setState({status: 'wip'})
+        ProjectEventBus.sendLog('set', '');
         KaravanApi.deleteDevModeContainer(project.projectId, false, res => {
-            useDevModeStore.setState({status: "none"})
+            useDevModeStore.setState({status: 'none'})
             if (res.status === 202) {
                 useLogStore.setState({showLog: false, type: 'container', isRunning: false})
             } else {
-                ProjectEventBus.sendAlert(new ToastMessage("Error delete runner", res.statusText, 'warning'))
+                ProjectEventBus.sendAlert(new ToastMessage('Error delete runner', res.statusText, 'warning'))
             }
         });
     }
@@ -86,14 +86,14 @@ export class ProjectService {
                     if (useDevModeStore.getState().podName !== containerStatus.containerName){
                         useDevModeStore.setState({podName: containerStatus.containerName})
                     }
-                    if (useDevModeStore.getState().status !== "wip"){
+                    if (useDevModeStore.getState().status !== 'wip'){
                         useLogStore.setState({isRunning: true})
                     }
                     useProjectStore.setState({containerStatus: containerStatus});
                 })
             } else {
                 unstable_batchedUpdates(() => {
-                    useDevModeStore.setState({status: "none", podName: undefined})
+                    useDevModeStore.setState({status: 'none', podName: undefined})
                     useProjectStore.setState({containerStatus: new ContainerStatus({})});
                 })
             }
@@ -103,8 +103,8 @@ export class ProjectService {
     public static pushProject(project: Project, commitMessage: string) {
         useProjectStore.setState({isPushing: true})
         const params = {
-            "projectId": project.projectId,
-            "message": commitMessage
+            'projectId': project.projectId,
+            'message': commitMessage
         };
         KaravanApi.push(params, res => {
             if (res.status === 200 || res.status === 201) {
@@ -167,10 +167,10 @@ export class ProjectService {
     public static deleteProject(project: Project) {
         KaravanApi.deleteProject(project, res => {
             if (res.status === 204) {
-                // this.props.toast?.call(this, "Success", "Project deleted", "success");
+                // this.props.toast?.call(this, 'Success', 'Project deleted', 'success');
                 ProjectService.refreshProjectData();
             } else {
-                // this.props.toast?.call(this, "Error", res.statusText, "danger");
+                // this.props.toast?.call(this, 'Error', res.statusText, 'danger');
             }
         });
     }
@@ -179,9 +179,9 @@ export class ProjectService {
         KaravanApi.postProject(project, res => {
             if (res.status === 200 || res.status === 201) {
                 ProjectService.refreshProjectData();
-                // this.props.toast?.call(this, "Success", "Project created", "success");
+                // this.props.toast?.call(this, 'Success', 'Project created', 'success');
             } else {
-                // this.props.toast?.call(this, "Error", res.status + ", " + res.statusText, "danger");
+                // this.props.toast?.call(this, 'Error', res.status + ', ' + res.statusText, 'danger');
             }
         });
     }
@@ -211,10 +211,10 @@ export class ProjectService {
         KaravanApi.getProject(project.projectId, (project: Project) => {
             // ProjectEventBus.selectProject(project);
             KaravanApi.getTemplatesFiles((files: ProjectFile[]) => {
-                files.filter(f => f.name.endsWith("java"))
+                files.filter(f => f.name.endsWith('java'))
                     .filter(f => f.name.startsWith(project.runtime))
                     .forEach(f => {
-                        const name = f.name.replace(project.runtime + "-", '').replace(".java", '');
+                        const name = f.name.replace(project.runtime + '-', '').replace('.java', '');
                         TemplateApi.saveTemplate(name, f.code);
                     })
             });
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ServiceModels.ts b/karavan-web/karavan-app/src/main/webui/src/api/ServiceModels.ts
index a24f473d..506301d7 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ServiceModels.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ServiceModels.ts
@@ -11,8 +11,7 @@ export class Healthcheck {
     }
 }
 
-export class Service {
-    name: string = '';
+export class DevService {
     container_name: string = '';
     image: string = '';
     restart: string = '';
@@ -21,14 +20,14 @@ export class Service {
     environment: any = {};
     healthcheck?: Healthcheck;
 
-    public constructor(init?: Partial<Service>) {
+    public constructor(init?: Partial<DevService>) {
         Object.assign(this, init);
     }
 }
 
 export class Services {
     version: string = '';
-    services: Service[] = [];
+    services: DevService[] = [];
 
     public constructor(init?: Partial<Services>) {
         Object.assign(this, init);
@@ -43,8 +42,10 @@ export class ServicesYaml {
         const result: Services = new Services({version: fromYaml.version});
         Object.keys(fromYaml.services).forEach(key => {
             const o = fromYaml.services[key];
-            const service = new Service(o);
-            service.name = key;
+            const service = new DevService(o);
+            if (!service.container_name) {
+                service.container_name = key;
+            }
             result.services.push(service);
         })
         return result;
diff --git a/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx b/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
index 13d702d2..0e2a1091 100644
--- a/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
@@ -59,15 +59,15 @@ export const ContainerTableRow = (props: Props) => {
                     {!inTransit && <Label color={color}>{container.state}</Label>}
                     {inTransit && <Spinner isSVG size="lg" aria-label="spinner"/>}
                 </Td>
-                <Td className="project-action-buttons">
+                <Td>
                     {container.type !== 'internal' &&
-                        <Flex direction={{default: "row"}} justifyContent={{default: "justifyContentFlexEnd"}}
+                        <Flex direction={{default: "row"}} flexWrap={{default: "nowrap"}}
                               spaceItems={{default: 'spaceItemsNone'}}>
                             <FlexItem>
                                 <Tooltip content={"Start container"} position={"bottom"}>
                                     <Button variant={"plain"} icon={<PlayIcon/>} isDisabled={!commands.includes('run') || inTransit}
                                             onClick={e => {
-                                                KaravanApi.manageContainer(container.env, container.containerName, 'run', res => {});
+                                                KaravanApi.manageContainer(container.env, container.type, container.containerName, 'run', res => {});
                                             }}></Button>
                                 </Tooltip>
                             </FlexItem>
@@ -75,7 +75,7 @@ export const ContainerTableRow = (props: Props) => {
                                 <Tooltip content={"Pause container"} position={"bottom"}>
                                     <Button variant={"plain"} icon={<PauseIcon/>} isDisabled={!commands.includes('pause') || inTransit}
                                             onClick={e => {
-                                                KaravanApi.manageContainer(container.env, container.containerName, 'pause', res => {});
+                                                KaravanApi.manageContainer(container.env, container.type, container.containerName, 'pause', res => {});
                                             }}></Button>
                                 </Tooltip>
                             </FlexItem>
@@ -83,7 +83,7 @@ export const ContainerTableRow = (props: Props) => {
                                 <Tooltip content={"Stop container"} position={"bottom"}>
                                     <Button variant={"plain"} icon={<StopIcon/>} isDisabled={!commands.includes('stop') || inTransit}
                                             onClick={e => {
-                                                KaravanApi.manageContainer(container.env, container.containerName, 'stop', res => {});
+                                                KaravanApi.manageContainer(container.env, container.type, container.containerName, 'stop', res => {});
                                             }}></Button>
                                 </Tooltip>
                             </FlexItem>
@@ -91,7 +91,7 @@ export const ContainerTableRow = (props: Props) => {
                                 <Tooltip content={"Delete container"} position={"bottom"}>
                                     <Button variant={"plain"} icon={<DeleteIcon/>} isDisabled={!commands.includes('delete') || inTransit}
                                             onClick={e => {
-                                                KaravanApi.deleteContainer(container.env, container.containerName, res => {});
+                                                KaravanApi.deleteContainer(container.env, container.type, container.containerName, res => {});
                                             }}></Button>
                                 </Tooltip>
                             </FlexItem>
diff --git a/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx b/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx
index 9d1f6607..08539413 100644
--- a/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx
@@ -1,32 +1,24 @@
 import React, {useEffect, useState} from 'react';
 import {
-    Badge, Bullseye,
+    Bullseye,
     Button, EmptyState, EmptyStateIcon, EmptyStateVariant,
-    Flex,
-    FlexItem, HelperText, HelperTextItem, Label, LabelGroup,
     PageSection, Spinner,
     Text,
     TextContent,
     TextInput, Title, ToggleGroup, ToggleGroupItem,
     Toolbar,
     ToolbarContent,
-    ToolbarItem, Tooltip
+    ToolbarItem
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
-import {CamelStatus, ContainerStatus, DeploymentStatus, Project, ServiceStatus} from "../api/ProjectModels";
+import {ContainerStatus} from "../api/ProjectModels";
 import {TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
-import {camelIcon, CamelUi} from "../designer/utils/CamelUi";
 import {KaravanApi} from "../api/KaravanApi";
-import Icon from "../Logo";
-import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
-import DownIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
 import RefreshIcon from "@patternfly/react-icons/dist/esm/icons/sync-alt-icon";
 import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon";
 import {MainToolbar} from "../designer/MainToolbar";
-import {useAppConfigStore, useProjectsStore, useStatusesStore} from "../api/ProjectStore";
+import {useAppConfigStore, useStatusesStore} from "../api/ProjectStore";
 import {shallow} from "zustand/shallow";
-import {Service} from "../api/ServiceModels";
-import {ServicesTableRow} from "../services/ServicesTableRow";
 import {ContainerTableRow} from "./ContainerTableRow";
 
 export const ContainersPage = () => {
@@ -39,19 +31,17 @@ export const ContainersPage = () => {
 
     useEffect(() => {
         const interval = setInterval(() => {
-            onGetProjects()
+            updateContainerStatuses()
         }, 700);
         return () => {
             clearInterval(interval)
         };
     }, []);
 
-    function onGetProjects() {
-        KaravanApi.getConfiguration((config: any) => {
-            KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => {
-                setContainers(statuses);
-                setLoading(false);
-            });
+    function updateContainerStatuses() {
+        KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => {
+            setContainers(statuses);
+            setLoading(false);
         });
     }
 
@@ -72,7 +62,7 @@ export const ContainersPage = () => {
         return (<Toolbar id="toolbar-group-types">
             <ToolbarContent>
                 <ToolbarItem>
-                    <Button variant="link" icon={<RefreshIcon/>} onClick={e => onGetProjects()}/>
+                    <Button variant="link" icon={<RefreshIcon/>} onClick={e => updateContainerStatuses()}/>
                 </ToolbarItem>
                 <ToolbarItem>
                     <ToggleGroup aria-label="Default with single selectable">
@@ -148,7 +138,7 @@ export const ContainersPage = () => {
                             <Th key='cpuInfo'>CPU</Th>
                             <Th key='memoryInfo'>Memory</Th>
                             <Th key='state'>State</Th>
-                            <Th key='action'></Th>
+                            <Th  key='action'></Th>
                         </Tr>
                     </Thead>
                     {conts?.map((container: ContainerStatus, index: number) => (
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
index add844b9..14dc1eaa 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
@@ -84,7 +84,7 @@ export class ProjectStatus extends React.Component<Props, State> {
                 });
                 break;
             case "pod":
-                KaravanApi.deleteContainer(environment, name, (res: any) => {
+                KaravanApi.deleteContainer(environment, 'project', name, (res: any) => {
                     // if (Array.isArray(res) && Array.from(res).length > 0)
                     // this.onRefresh();
                 });
diff --git a/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx b/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx
index 54a101be..87606ac9 100644
--- a/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx
@@ -3,7 +3,6 @@ import {
     Toolbar,
     ToolbarContent,
     ToolbarItem,
-    TextInput,
     PageSection,
     TextContent,
     Text,
@@ -17,28 +16,43 @@ import {
 import '../designer/karavan.css';
 import RefreshIcon from '@patternfly/react-icons/dist/esm/icons/sync-alt-icon';
 import PlusIcon from '@patternfly/react-icons/dist/esm/icons/plus-icon';
-import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
+import {TableComposable, Td, Th, Thead, Tr} from "@patternfly/react-table";
 import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
 import {ServicesTableRow} from "./ServicesTableRow";
 import {DeleteServiceModal} from "./DeleteServiceModal";
 import {CreateServiceModal} from "./CreateServiceModal";
-import {useProjectStore} from "../api/ProjectStore";
+import {useProjectStore, useStatusesStore} from "../api/ProjectStore";
 import {MainToolbar} from "../designer/MainToolbar";
-import {Project, ProjectFile, ProjectType} from "../api/ProjectModels";
+import {ContainerStatus, Project, ProjectType} from "../api/ProjectModels";
 import {KaravanApi} from "../api/KaravanApi";
-import {Service, Services, ServicesYaml} from "../api/ServiceModels";
+import {DevService, Services, ServicesYaml} from "../api/ServiceModels";
+import {shallow} from "zustand/shallow";
 
 
 export const ServicesPage = () => {
 
     const [services, setServices] = useState<Services>();
+    const [containers, setContainers] = useStatusesStore((state) => [state.containers, state.setContainers], shallow);
     const [operation, setOperation] = useState<'create' | 'delete' | 'none'>('none');
     const [loading, setLoading] = useState<boolean>(false);
 
     useEffect(() => {
         getServices();
+        const interval = setInterval(() => {
+            updateContainerStatuses()
+        }, 700);
+        return () => {
+            clearInterval(interval)
+        };
     }, []);
 
+    function updateContainerStatuses() {
+        KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => {
+            setContainers(statuses);
+            setLoading(false);
+        });
+    }
+
     function getServices() {
         KaravanApi.getFiles(ProjectType.services, files => {
             const file = files.at(0);
@@ -92,6 +106,10 @@ export const ServicesPage = () => {
         )
     }
 
+    function getContainer(name: string) {
+        return containers.filter(c => c.containerName === name).at(0);
+    }
+
     function getServicesTable() {
         return (
             <TableComposable aria-label="Services" variant={"compact"}>
@@ -102,11 +120,12 @@ export const ServicesPage = () => {
                         <Th key='container_name'>Container Name</Th>
                         <Th key='image'>Image</Th>
                         <Th key='ports'>Ports</Th>
+                        <Th key='state'>State</Th>
                         <Th key='action'></Th>
                     </Tr>
                 </Thead>
-                {services?.services.map((service: Service, index: number) => (
-                    <ServicesTableRow key={service.name} index={index} service={service}/>
+                {services?.services.map((service: DevService, index: number) => (
+                    <ServicesTableRow key={service.container_name} index={index} service={service} container={getContainer(service.container_name)}/>
                 ))}
                 {services?.services.length === 0 && getEmptyState()}
             </TableComposable>
diff --git a/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx b/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx
index f6affafe..fab5456d 100644
--- a/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx
@@ -2,35 +2,90 @@ import React, {useState} from 'react';
 import {
     Button,
     Tooltip,
-    Flex, FlexItem, Label
+    Flex, FlexItem, Label, ToolbarContent, Toolbar, ToolbarItem, Spinner
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
-import {ExpandableRowContent, Tbody, Td, Tr} from "@patternfly/react-table";
+import {ActionsColumn, ExpandableRowContent, Tbody, Td, Tr} from "@patternfly/react-table";
 import StopIcon from "@patternfly/react-icons/dist/js/icons/stop-icon";
 import PlayIcon from "@patternfly/react-icons/dist/esm/icons/play-icon";
-import {Service} from "../api/ServiceModels";
+import {DevService} from "../api/ServiceModels";
+import {ContainerStatus} from "../api/ProjectModels";
+import PauseIcon from "@patternfly/react-icons/dist/esm/icons/pause-icon";
+import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
+import {useAppConfigStore} from "../api/ProjectStore";
+import {shallow} from "zustand/shallow";
+import {KaravanApi} from "../api/KaravanApi";
 
 interface Props {
     index: number
-    service: Service
+    service: DevService
+    container?: ContainerStatus
 }
 
 export const ServicesTableRow = (props: Props) => {
 
+    const [config] = useAppConfigStore((state) => [state.config], shallow)
     const [isExpanded, setIsExpanded] = useState<boolean>(false);
-    const [running, setRunning] = useState<boolean>(false);
+
+
+    function getButtons() {
+        const container = props.container;
+        const commands = container?.commands || ['run'];
+        const inTransit = container?.inTransit;
+        return (
+            <Td noPadding className="project-action-buttons">
+                <Flex direction={{default: "row"}} flexWrap={{default: "nowrap"}}
+                      spaceItems={{default: 'spaceItemsNone'}}>
+                    <FlexItem>
+                        <Tooltip content={"Start container"} position={"bottom"}>
+                            <Button variant={"plain"} icon={<PlayIcon/>} isDisabled={!commands.includes('run') || inTransit}
+                                    onClick={e => {
+                                        KaravanApi.manageContainer(config.environment, 'devservice', service.container_name, 'run', res => {});
+                                    }}></Button>
+                        </Tooltip>
+                    </FlexItem>
+                    <FlexItem>
+                        <Tooltip content={"Pause container"} position={"bottom"}>
+                            <Button variant={"plain"} icon={<PauseIcon/>} isDisabled={!commands.includes('pause') || inTransit}
+                                    onClick={e => {
+                                        // KaravanApi.manageContainer(container.env, container.containerName, 'pause', res => {});
+                                    }}></Button>
+                        </Tooltip>
+                    </FlexItem>
+                    <FlexItem>
+                        <Tooltip content={"Stop container"} position={"bottom"}>
+                            <Button variant={"plain"} icon={<StopIcon/>} isDisabled={!commands.includes('stop') || inTransit}
+                                    onClick={e => {
+                                        KaravanApi.manageContainer(config.environment, 'devservice', service.container_name, 'stop', res => {});
+                                    }}></Button>
+                        </Tooltip>
+                    </FlexItem>
+                    <FlexItem>
+                        <Tooltip content={"Delete container"} position={"bottom"}>
+                            <Button variant={"plain"} icon={<DeleteIcon/>} isDisabled={!commands.includes('delete') || inTransit}
+                                    onClick={e => {
+                                        KaravanApi.deleteContainer(config.environment, 'devservice', service.container_name, res => {});
+                                    }}></Button>
+                        </Tooltip>
+                    </FlexItem>
+                </Flex>
+            </Td>
+        )
+    }
 
     const service = props.service;
     const healthcheck = service.healthcheck;
     const env = service.environment;
     const keys = Object.keys(env);
-    const icon = running ? <StopIcon/> : <PlayIcon/>;
-    const tooltip = running ? "Stop container" : "Start container";
+    const container = props.container;
+    const isRunning = container?.state === 'running';
+    const inTransit = container?.inTransit;
+    const color = container?.state === 'running' ? "green" : "grey";
     return (
         <Tbody isExpanded={isExpanded}>
-            <Tr key={service.name}>
+            <Tr key={service.container_name}>
                 <Td expand={
-                    service.name
+                    service.container_name
                         ? {
                             rowIndex: props.index,
                             isExpanded: isExpanded,
@@ -41,7 +96,7 @@ export const ServicesTableRow = (props: Props) => {
                     modifier={"fitContent"}>
                 </Td>
                 <Td>
-                    <Label color={"grey"}>{service.name}</Label>
+                    <Label color={color}>{service.container_name}</Label>
                 </Td>
                 <Td>{service.container_name}</Td>
                 <Td>{service.image}</Td>
@@ -50,19 +105,11 @@ export const ServicesTableRow = (props: Props) => {
                         {service.ports.map(port => <FlexItem key={port}>{port}</FlexItem>)}
                     </Flex>
                 </Td>
-                {/*<Td>{service.environment}</Td>*/}
-                <Td className="project-action-buttons">
-                    <Flex direction={{default: "row"}} justifyContent={{default: "justifyContentFlexEnd"}}
-                          spaceItems={{default: 'spaceItemsNone'}}>
-                        <FlexItem>
-                            <Tooltip content={tooltip} position={"bottom"}>
-                                <Button variant={"plain"} icon={icon} onClick={e => {
-                                    // setProject(project, "delete");
-                                }}></Button>
-                            </Tooltip>
-                        </FlexItem>
-                    </Flex>
+                <Td>
+                    {!inTransit && container?.state && <Label color={color}>{container?.state}</Label>}
+                    {inTransit && <Spinner isSVG size="lg" aria-label="spinner"/>}
                 </Td>
+                {getButtons()}
             </Tr>
             {keys.length > 0 && <Tr isExpanded={isExpanded}>
                 <Td></Td>
diff --git a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
index ac905f64..839de24c 100644
--- a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
+++ b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
@@ -276,12 +276,6 @@ public class InfinispanService {
                 .execute().list();
     }
 
-    public void setContainerStatusTransit(String projectId, String env, String containerName) {
-        ContainerStatus cs = getContainerStatus(projectId, env, containerName);
-        cs.setInTransit(true);
-        saveContainerStatus(cs);
-    }
-
     public ContainerStatus getContainerStatus(String projectId, String env, String containerName) {
         return containerStatuses.get(GroupedKey.create(projectId, env, containerName));
     }
diff --git a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
index ef7ca779..c9336b8e 100644
--- a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
+++ b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
@@ -105,6 +105,10 @@ public class ContainerStatus {
         return new ContainerStatus(projectId, projectId, null, null, null, env, ContainerType.devmode, null, null, null, List.of(Command.run), null, false, false);
     }
 
+    public static ContainerStatus createByType(String name, String env, ContainerType type) {
+        return new ContainerStatus(name, name, null, null, null, env, type, null, null, null, List.of(Command.run), null, false, false);
+    }
+
     public static ContainerStatus createWithId(String name, String env, String containerId, String image, List<Integer> ports, ContainerType type, List<Command> commands, String status, String created) {
         return new ContainerStatus(name, name, containerId, image, ports, env, type,
                 null, null, created,  commands, status, false, false);