Repository: kurtosis-tech/kurtosis
Pull request #943 (2023-07-19 11:03:37)

feat: Add ability to update a running service
## Description:
<!-- Describe this change, how it works, and the motivation behind it. -->

## Is this change user facing?
NO
<!-- If yes, please add the "user facing" label to the PR -->
<!-- If yes, don't forget to include docs changes where relevant -->

## References (if applicable):
<!-- Add relevant Github Issues, Discord threads, or other helpful information. -->



Patch:
diff --git a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend.go b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend.go
index 6b1180907..9f1330b48 100644
--- a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend.go
+++ b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/docker_kurtosis_backend.go
@@ -227,6 +227,28 @@ func (backend *DockerKurtosisBackend) StartRegisteredUserServices(ctx context.Co
 	return successfullyStartedService, failedService, nil
 }
 
+func (backend *DockerKurtosisBackend) RemoveRegisteredUserServiceProcesses(ctx context.Context, enclaveUuid enclave.EnclaveUUID, services map[service.ServiceUUID]bool) (map[service.ServiceUUID]bool, map[service.ServiceUUID]error, error) {
+	serviceRegistrationsForEnclave, found := backend.serviceRegistrations[enclaveUuid]
+	if !found {
+		return nil, nil, stacktrace.NewError(
+			"No service registrations are being tracked for enclave '%v'; this likely means that the registration "+
+				"request is being called where it shouldn't be (i.e. outside the API container)",
+			enclaveUuid,
+		)
+	}
+
+	successfullyStartedService, failedService, err := user_service_functions.RemoveRegisteredUserServiceProcesses(
+		ctx,
+		enclaveUuid,
+		services,
+		serviceRegistrationsForEnclave,
+		backend.dockerManager)
+	if err != nil {
+		return nil, nil, stacktrace.Propagate(err, "Unexpected error while updating user services")
+	}
+	return successfullyStartedService, failedService, nil
+}
+
 func (backend *DockerKurtosisBackend) GetUserServices(
 	ctx context.Context,
 	enclaveUuid enclave.EnclaveUUID,
diff --git a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/user_services_functions/start_user_services.go b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/user_services_functions/start_user_services.go
index ea13005f5..cdf00ff09 100644
--- a/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/user_services_functions/start_user_services.go
+++ b/container-engine-lib/lib/backend_impls/docker/docker_kurtosis_backend/user_services_functions/start_user_services.go
@@ -196,6 +196,88 @@ func StartRegisteredUserServices(
 	return successfulServicesPool, failedServicesPool, nil
 }
 
+func RemoveRegisteredUserServiceProcesses(
+	ctx context.Context,
+	enclaveUuid enclave.EnclaveUUID,
+	services map[service.ServiceUUID]bool,
+	serviceRegistrations map[service.ServiceUUID]*service.ServiceRegistration,
+	dockerManager *docker_manager.DockerManager,
+) (
+	map[service.ServiceUUID]bool,
+	map[service.ServiceUUID]error,
+	error,
+) {
+	successfullyRemovedService := map[service.ServiceUUID]bool{}
+	failedServicesPool := map[service.ServiceUUID]error{}
+
+	serviceUuidsToUpdate := map[service.ServiceUUID]bool{}
+	for serviceUuid := range services {
+		if _, found := serviceRegistrations[serviceUuid]; found {
+			serviceUuidsToUpdate[serviceUuid] = true
+		} else {
+			failedServicesPool[serviceUuid] = stacktrace.NewError("Unable to update service '%s' that is not "+
+				"registered inside this enclave", serviceUuid)
+		}
+	}
+
+	removeServiceFilters := &service.ServiceFilters{
+		Names:    nil,
+		UUIDs:    serviceUuidsToUpdate,
+		Statuses: nil,
+	}
+
+	allServiceObjs, allDockerResources, err := shared_helpers.GetMatchingUserServiceObjsAndDockerResourcesNoMutex(ctx, enclaveUuid, removeServiceFilters, dockerManager)
+	if err != nil {
+		return nil, nil, stacktrace.Propagate(err, "An error occurred getting user services matching filters '%+v'", removeServiceFilters)
+	}
+
+	servicesToStartByContainerId := map[string]*service.Service{}
+	for uuid, serviceResources := range allDockerResources {
+		serviceObj, found := allServiceObjs[uuid]
+		if !found {
+			// Should never happen; there should be a 1:1 mapping between service_objects:docker_resources by GUID
+			return nil, nil, stacktrace.NewError("No service object found for service '%v' that had Docker resources; this is a bug in Kurtosis", uuid)
+		}
+		servicesToStartByContainerId[serviceResources.ServiceContainer.GetId()] = serviceObj
+	}
+
+	var dockerOperation docker_operation_parallelizer.DockerOperation = func(
+		ctx context.Context,
+		dockerManager *docker_manager.DockerManager,
+		dockerObjectId string,
+	) error {
+		if err := dockerManager.RemoveContainer(ctx, dockerObjectId); err != nil {
+			return stacktrace.Propagate(err, "An error occurred removing user service processes container with ID '%v'", dockerObjectId)
+		}
+		return nil
+	}
+
+	successfulUuidStrs, erroredUuidStrs, err := docker_operation_parallelizer.RunDockerOperationInParallelForKurtosisObjects(
+		ctx,
+		servicesToStartByContainerId,
+		dockerManager,
+		extractServiceUUIDFromService,
+		dockerOperation,
+	)
+	if err != nil {
+		return nil, nil, stacktrace.Propagate(err, "An error occurred removing user service processes containers matching filters '%+v'", removeServiceFilters)
+	}
+
+	for failedServiceUuid, failedServiceErr := range erroredUuidStrs {
+		failedServicesPool[service.ServiceUUID(failedServiceUuid)] = failedServiceErr
+	}
+
+	for serviceUuidStr := range successfulUuidStrs {
+		serviceUuid := service.ServiceUUID(serviceUuidStr)
+		serviceConfig, found := services[serviceUuid]
+		if !found {
+			failedServicesPool[serviceUuid] = stacktrace.NewError("An error occurred removing user service processes for service with UUID '%s'", serviceUuid)
+		}
+		successfullyRemovedService[serviceUuid] = serviceConfig
+	}
+	return successfullyRemovedService, failedServicesPool, nil
+}
+
 // ====================================================================================================
 //
 //	Private helper functions
diff --git a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend.go b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend.go
index b2dc04164..70db959b3 100644
--- a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend.go
+++ b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/kubernetes_kurtosis_backend.go
@@ -263,6 +263,33 @@ func (backend *KubernetesKurtosisBackend) StartRegisteredUserServices(
 	return successfullyStartedServices, failedServices, nil
 }
 
+func (backend *KubernetesKurtosisBackend) RemoveRegisteredUserServiceProcesses(
+	ctx context.Context,
+	enclaveUuid enclave.EnclaveUUID,
+	services map[service.ServiceUUID]bool,
+) (
+	map[service.ServiceUUID]bool,
+	map[service.ServiceUUID]error,
+	error,
+) {
+	successfullyStartedServices, failedServices, err := user_services_functions.RemoveRegisteredUserServiceProcesses(
+		ctx,
+		enclaveUuid,
+		services,
+		backend.cliModeArgs,
+		backend.apiContainerModeArgs,
+		backend.engineServerModeArgs,
+		backend.kubernetesManager)
+	if err != nil {
+		var serviceUuids []service.ServiceUUID
+		for serviceUuid := range services {
+			serviceUuids = append(serviceUuids, serviceUuid)
+		}
+		return nil, nil, stacktrace.Propagate(err, "Unexpected error removing services with GUIDs '%v' in enclave '%s'", serviceUuids, enclaveUuid)
+	}
+	return successfullyStartedServices, failedServices, nil
+}
+
 func (backend *KubernetesKurtosisBackend) GetUserServices(
 	ctx context.Context,
 	enclaveUuid enclave.EnclaveUUID,
diff --git a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/user_services_functions/start_user_services.go b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/user_services_functions/start_user_services.go
index d45513b7f..3182cef66 100644
--- a/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/user_services_functions/start_user_services.go
+++ b/container-engine-lib/lib/backend_impls/kubernetes/kubernetes_kurtosis_backend/user_services_functions/start_user_services.go
@@ -193,6 +193,39 @@ func StartRegisteredUserServices(
 	return successfulServicesPool, failedServicesPool, nil
 }
 
+func RemoveRegisteredUserServiceProcesses(
+	ctx context.Context,
+	enclaveUuid enclave.EnclaveUUID,
+	services map[service.ServiceUUID]bool,
+	cliModeArgs *shared_helpers.CliModeArgs,
+	apiContainerModeArgs *shared_helpers.ApiContainerModeArgs,
+	engineServerModeArgs *shared_helpers.EngineServerModeArgs,
+	kubernetesManager *kubernetes_manager.KubernetesManager,
+) (
+	map[service.ServiceUUID]bool,
+	map[service.ServiceUUID]error,
+	error,
+) {
+	// in Kubernetes, removing service process is equivalent to stopping it. It sets its number of pod to zero
+	removeServiceProcessesFilters := &service.ServiceFilters{
+		Names:    nil,
+		UUIDs:    services,
+		Statuses: nil,
+	}
+	successfullyRemovedService, failedRemovedService, err := StopUserServices(
+		ctx,
+		enclaveUuid,
+		removeServiceProcessesFilters,
+		cliModeArgs,
+		apiContainerModeArgs,
+		engineServerModeArgs,
+		kubernetesManager)
+	if err != nil {
+		return nil, nil, stacktrace.Propagate(err, "Unexpected error removing service processes")
+	}
+	return successfullyRemovedService, failedRemovedService, nil
+}
+
 // ====================================================================================================
 //
 //	Private helper functions
diff --git a/container-engine-lib/lib/backend_impls/metrics_reporting/metrics_reporting_kurtosis_backend.go b/container-engine-lib/lib/backend_impls/metrics_reporting/metrics_reporting_kurtosis_backend.go
index 72e45829b..1ea6f3fd3 100644
--- a/container-engine-lib/lib/backend_impls/metrics_reporting/metrics_reporting_kurtosis_backend.go
+++ b/container-engine-lib/lib/backend_impls/metrics_reporting/metrics_reporting_kurtosis_backend.go
@@ -257,6 +257,14 @@ func (backend *MetricsReportingKurtosisBackend) StartRegisteredUserServices(ctx
 	return successes, failures, nil
 }
 
+func (backend *MetricsReportingKurtosisBackend) RemoveRegisteredUserServiceProcesses(ctx context.Context, enclaveUuid enclave.EnclaveUUID, services map[service.ServiceUUID]bool) (map[service.ServiceUUID]bool, map[service.ServiceUUID]error, error) {
+	successes, failures, err := backend.underlying.RemoveRegisteredUserServiceProcesses(ctx, enclaveUuid, services)
+	if err != nil {
+		return nil, nil, stacktrace.Propagate(err, "An error occurred removing service processes in enclave '%v' with the following service ids: %+v", enclaveUuid, services)
+	}
+	return successes, failures, nil
+}
+
 func (backend *MetricsReportingKurtosisBackend) GetUserServices(
 	ctx context.Context,
 	enclaveUuid enclave.EnclaveUUID,
diff --git a/container-engine-lib/lib/backend_impls/remote_context_backend/remote_context_kurtosis_backend.go b/container-engine-lib/lib/backend_impls/remote_context_backend/remote_context_kurtosis_backend.go
index 74877a79a..3bd5f6f4c 100644
--- a/container-engine-lib/lib/backend_impls/remote_context_backend/remote_context_kurtosis_backend.go
+++ b/container-engine-lib/lib/backend_impls/remote_context_backend/remote_context_kurtosis_backend.go
@@ -136,6 +136,10 @@ func (backend *RemoteContextKurtosisBackend) StartRegisteredUserServices(ctx con
 	return backend.remoteKurtosisBackend.StartRegisteredUserServices(ctx, enclaveUuid, services)
 }
 
+func (backend *RemoteContextKurtosisBackend) RemoveRegisteredUserServiceProcesses(ctx context.Context, enclaveUuid enclave.EnclaveUUID, services map[service.ServiceUUID]bool) (map[service.ServiceUUID]bool, map[service.ServiceUUID]error, error) {
+	return backend.remoteKurtosisBackend.RemoveRegisteredUserServiceProcesses(ctx, enclaveUuid, services)
+}
+
 func (backend *RemoteContextKurtosisBackend) GetUserServices(ctx context.Context, enclaveUuid enclave.EnclaveUUID, filters *service.ServiceFilters) (map[service.ServiceUUID]*service.Service, error) {
 	return backend.remoteKurtosisBackend.GetUserServices(ctx, enclaveUuid, filters)
 }
diff --git a/container-engine-lib/lib/backend_interface/kurtosis_backend.go b/container-engine-lib/lib/backend_interface/kurtosis_backend.go
index 77f46133d..7dc8119f3 100644
--- a/container-engine-lib/lib/backend_interface/kurtosis_backend.go
+++ b/container-engine-lib/lib/backend_interface/kurtosis_backend.go
@@ -223,6 +223,19 @@ type KurtosisBackend interface {
 		error, // represents an error with the function itself, rather than the user services
 	)
 
+	// RemoveRegisteredUserServiceProcesses removes the running user service process but keeps the service registration
+	// TODO: As we don't persist user service logs anywhere, removing the container/pod will also remove all its
+	//  logs. We need a persistent log storage to address this issue.
+	RemoveRegisteredUserServiceProcesses(
+		ctx context.Context,
+		enclaveUuid enclave.EnclaveUUID,
+		services map[service.ServiceUUID]bool,
+	) (
+		map[service.ServiceUUID]bool, // user service UUIDs that were successfully removed
+		map[service.ServiceUUID]error, // user service UUIDs that failed to be removed, with the error
+		error, // represents an error with the function itself, rather than the user services
+	)
+
 	// Gets user services using the given filters, returning a map of matched user services identified by their UUID
 	GetUserServices(
 		ctx context.Context,
diff --git a/container-engine-lib/lib/backend_interface/mock_kurtosis_backend.go b/container-engine-lib/lib/backend_interface/mock_kurtosis_backend.go
index 6e8c548d9..223d4e73a 100644
--- a/container-engine-lib/lib/backend_interface/mock_kurtosis_backend.go
+++ b/container-engine-lib/lib/backend_interface/mock_kurtosis_backend.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.29.0. DO NOT EDIT.
+// Code generated by mockery v2.31.1. DO NOT EDIT.
 
 package backend_interface
 
@@ -1611,6 +1611,71 @@ func (_c *MockKurtosisBackend_RegisterUserServices_Call) RunAndReturn(run func(c
 	return _c
 }
 
+// RemoveRegisteredUserServiceProcesses provides a mock function with given fields: ctx, enclaveUuid, services
+func (_m *MockKurtosisBackend) RemoveRegisteredUserServiceProcesses(ctx context.Context, enclaveUuid enclave.EnclaveUUID, services map[service.ServiceUUID]bool) (map[service.ServiceUUID]bool, map[service.ServiceUUID]error, error) {
+	ret := _m.Called(ctx, enclaveUuid, services)
+
+	var r0 map[service.ServiceUUID]bool
+	var r1 map[service.ServiceUUID]error
+	var r2 error
+	if rf, ok := ret.Get(0).(func(context.Context, enclave.EnclaveUUID, map[service.ServiceUUID]bool) (map[service.ServiceUUID]bool, map[service.ServiceUUID]error, error)); ok {
+		return rf(ctx, enclaveUuid, services)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, enclave.EnclaveUUID, map[service.ServiceUUID]bool) map[service.ServiceUUID]bool); ok {
+		r0 = rf(ctx, enclaveUuid, services)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(map[service.ServiceUUID]bool)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, enclave.EnclaveUUID, map[service.ServiceUUID]bool) map[service.ServiceUUID]error); ok {
+		r1 = rf(ctx, enclaveUuid, services)
+	} else {
+		if ret.Get(1) != nil {
+			r1 = ret.Get(1).(map[service.ServiceUUID]error)
+		}
+	}
+
+	if rf, ok := ret.Get(2).(func(context.Context, enclave.EnclaveUUID, map[service.ServiceUUID]bool) error); ok {
+		r2 = rf(ctx, enclaveUuid, services)
+	} else {
+		r2 = ret.Error(2)
+	}
+
+	return r0, r1, r2
+}
+
+// MockKurtosisBackend_RemoveRegisteredUserServiceProcesses_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveRegisteredUserServiceProcesses'
+type MockKurtosisBackend_RemoveRegisteredUserServiceProcesses_Call struct {
+	*mock.Call
+}
+
+// RemoveRegisteredUserServiceProcesses is a helper method to define mock.On call
+//   - ctx context.Context
+//   - enclaveUuid enclave.EnclaveUUID
+//   - services map[service.ServiceUUID]bool
+func (_e *MockKurtosisBackend_Expecter) RemoveRegisteredUserServiceProcesses(ctx interface{}, enclaveUuid interface{}, services interface{}) *MockKurtosisBackend_RemoveRegisteredUserServiceProcesses_Call {
+	return &MockKurtosisBackend_RemoveRegisteredUserServiceProcesses_Call{Call: _e.mock.On("RemoveRegisteredUserServiceProcesses", ctx, enclaveUuid, services)}
+}
+
+func (_c *MockKurtosisBackend_RemoveRegisteredUserServiceProcesses_Call) Run(run func(ctx context.Context, enclaveUuid enclave.EnclaveUUID, services map[service.ServiceUUID]bool)) *MockKurtosisBackend_RemoveRegisteredUserServiceProcesses_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(enclave.EnclaveUUID), args[2].(map[service.ServiceUUID]bool))
+	})
+	return _c
+}
+
+func (_c *MockKurtosisBackend_RemoveRegisteredUserServiceProcesses_Call) Return(_a0 map[service.ServiceUUID]bool, _a1 map[service.ServiceUUID]error, _a2 error) *MockKurtosisBackend_RemoveRegisteredUserServiceProcesses_Call {
+	_c.Call.Return(_a0, _a1, _a2)
+	return _c
+}
+
+func (_c *MockKurtosisBackend_RemoveRegisteredUserServiceProcesses_Call) RunAndReturn(run func(context.Context, enclave.EnclaveUUID, map[service.ServiceUUID]bool) (map[service.ServiceUUID]bool, map[service.ServiceUUID]error, error)) *MockKurtosisBackend_RemoveRegisteredUserServiceProcesses_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
 // RunNetworkingSidecarExecCommands provides a mock function with given fields: ctx, enclaveUuid, networkingSidecarsCommands
 func (_m *MockKurtosisBackend) RunNetworkingSidecarExecCommands(ctx context.Context, enclaveUuid enclave.EnclaveUUID, networkingSidecarsCommands map[service.ServiceUUID][]string) (map[service.ServiceUUID]*exec_result.ExecResult, map[service.ServiceUUID]error, error) {
 	ret := _m.Called(ctx, enclaveUuid, networkingSidecarsCommands)
@@ -2237,13 +2302,12 @@ func (_c *MockKurtosisBackend_UpdateEnclave_Call) RunAndReturn(run func(context.
 	return _c
 }
 
-type mockConstructorTestingTNewMockKurtosisBackend interface {
+// NewMockKurtosisBackend creates a new instance of MockKurtosisBackend. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewMockKurtosisBackend(t interface {
 	mock.TestingT
 	Cleanup(func())
-}
-
-// NewMockKurtosisBackend creates a new instance of MockKurtosisBackend. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-func NewMockKurtosisBackend(t mockConstructorTestingTNewMockKurtosisBackend) *MockKurtosisBackend {
+}) *MockKurtosisBackend {
 	mock := &MockKurtosisBackend{}
 	mock.Mock.Test(t)
 
diff --git a/core/server/api_container/server/service_network/default_service_network.go b/core/server/api_container/server/service_network/default_service_network.go
index 4f1fe3a2a..aae89f1c5 100644
--- a/core/server/api_container/server/service_network/default_service_network.go
+++ b/core/server/api_container/server/service_network/default_service_network.go
@@ -460,8 +460,127 @@ func (network *DefaultServiceNetwork) AddServices(
 	return startedServices, map[service.ServiceName]error{}, nil
 }
 
-// UpdateService This is purely called from a Starlark context so this only works with Names
-func (network *DefaultServiceNetwork) UpdateService(
+// UpdateService updates a service currently running inside an enclave. See UpdateServices for more details
+func (network *DefaultServiceNetwork) UpdateService(ctx context.Context, serviceName service.ServiceName, serviceConfig *service.ServiceConfig) (*service.Service, error) {
+	serviceConfigMap := map[service.ServiceName]*service.ServiceConfig{
+		serviceName: serviceConfig,
+	}
+
+	startedServices, serviceFailed, err := network.UpdateServices(ctx, serviceConfigMap, singleServiceStartupBatch)
+	if err != nil {
+		return nil, err
+	}
+	if failure, found := serviceFailed[serviceName]; found {
+		return nil, failure
+	}
+	if startedService, found := startedServices[serviceName]; found {
+		return startedService, nil
+	}
+	return nil, stacktrace.NewError("Service '%s' could not be updated properly, and its state is unknown. This is a Kurtosis internal bug", serviceName)
+}
+
+// UpdateServices updates the service by removing the current container and re-creating it, keeping the registration
+// identical. Note this function does not handle any kind of rollback if it fails halfway. This is because we have no
+// way to do soft-delete for containers. Once it's deleted, it's gone, so if Kurtosis fails at re-creating it, it
+// doesn't rollback to the state previous to calling this function
+func (network *DefaultServiceNetwork) UpdateServices(ctx context.Context, updateServiceConfigs map[service.ServiceName]*service.ServiceConfig, batchSize int) (map[service.ServiceName]*service.Service, map[service.ServiceName]error, error) {
+	network.mutex.Lock()
+	defer network.mutex.Unlock()
+	failedServicesPool := map[service.ServiceName]error{}
+	successfullyUpdatedService := map[service.ServiceName]*service.Service{}
+
+	// First, remove the service
+	serviceUuidToNameMap := map[service.ServiceUUID]service.ServiceName{}
+	serviceUuidsToRemove := map[service.ServiceUUID]bool{}
+	for serviceName := range updateServiceConfigs {
+		serviceRegistration, found := network.registeredServiceInfo[serviceName]
+		if !found {
+			failedServicesPool[serviceName] = stacktrace.NewError("Unable to update service that is not registered "+
+				"inside this enclave: '%s'", serviceName)
+		} else {
+			serviceUuid := serviceRegistration.GetUUID()
+			serviceUuidsToRemove[serviceUuid] = true
+			serviceUuidToNameMap[serviceUuid] = serviceName
+		}
+	}
+	successfullyRemovedServices, failedRemovedServices, err := network.kurtosisBackend.RemoveRegisteredUserServiceProcesses(ctx, network.enclaveUuid, serviceUuidsToRemove)
+	if err != nil {
+		return nil, nil, stacktrace.Propagate(err, "Unexpected error happened updating services")
+	}
+	for serviceUuid, serviceErr := range failedRemovedServices {
+		if serviceName, found := serviceUuidToNameMap[serviceUuid]; found {
+			failedServicesPool[serviceName] = serviceErr
+		} else {
+			return nil, nil, stacktrace.NewError("Error mapping service UUID to service name. This is a bug in Kurtosis")
+		}
+	}
+
+	// Set service status back to registered and remove its currently saved service config
+	successfullyRemovedServicesIncludingSidecars := map[service.ServiceUUID]bool{}
+	for serviceUuid := range successfullyRemovedServices {
+		if serviceName, found := serviceUuidToNameMap[serviceUuid]; found {
+			if network.isPartitioningEnabled {
+				if sidecar, found := network.networkingSidecars[serviceName]; found {
+					if err = network.networkingSidecarManager.Remove(ctx, sidecar); err != nil {
+						failedServicesPool[serviceName] = stacktrace.Propagate(err, "An error occurred destroying the sidecar for service with name '%v'", serviceName)
+					} else {
+						successfullyRemovedServicesIncludingSidecars[serviceUuid] = true
+						delete(network.networkingSidecars, serviceName)
+					}
+				}
+			} else {
+				successfullyRemovedServicesIncludingSidecars[serviceUuid] = true
+			}
+
+			network.registeredServiceInfo[serviceName].SetStatus(service.ServiceStatus_Registered)
+			network.registeredServiceInfo[serviceName].SetConfig(nil)
+		} else {
+			return nil, nil, stacktrace.NewError("Error mapping service UUID to service name. This is a bug in Kurtosis")
+		}
+	}
+
+	// Re-create service with the new service config
+	serviceToRecreate := map[service.ServiceUUID]*service.ServiceConfig{}
+	for serviceUuid := range successfullyRemovedServicesIncludingSidecars {
+		serviceName, found := serviceUuidToNameMap[serviceUuid]
+		if !found {
+			failedServicesPool[serviceName] = stacktrace.NewError("Unable to update service that is not registered "+
+				"inside this enclave: '%s'", serviceName)
+			continue
+		}
+		newServiceConfig, found := updateServiceConfigs[serviceName]
+		if !found {
+			failedServicesPool[serviceName] = stacktrace.NewError("Unable to update service '%s' because no new "+
+				"service config could be found. This is a bug in Kurtosis", serviceName)
+			continue
+		}
+		serviceToRecreate[serviceUuid] = newServiceConfig
+	}
+	recreatedService, failedToRecreateService := network.startRegisteredServices(ctx, serviceToRecreate, batchSize)
+	for serviceUuid, failedToRecreateServiceErr := range failedToRecreateService {
+		serviceName, found := serviceUuidToNameMap[serviceUuid]
+		if !found {
+			failedServicesPool[serviceName] = stacktrace.NewError("Unable to update service that is not registered "+
+				"inside this enclave: '%s'", serviceName)
+			continue
+		}
+		failedServicesPool[serviceName] = failedToRecreateServiceErr
+	}
+	for serviceUuid, newServiceObj := range recreatedService {
+		serviceName, found := serviceUuidToNameMap[serviceUuid]
+		if !found {
+			failedServicesPool[serviceName] = stacktrace.NewError("Unable to update service that is not registered "+
+				"inside this enclave: '%s'", serviceName)
+			continue
+		}
+		successfullyUpdatedService[serviceName] = newServiceObj
+		network.registeredServiceInfo[serviceName].SetStatus(service.ServiceStatus_Started)
+	}
+	return successfullyUpdatedService, failedServicesPool, nil
+}
+
+// UpdateServiceSubnetwork This is purely called from a Starlark context so this only works with Names
+func (network *DefaultServiceNetwork) UpdateServiceSubnetwork(
 	ctx context.Context,
 	updateServiceConfigs map[service.ServiceName]*kurtosis_core_rpc_api_bindings.UpdateServiceConfig,
 ) (
diff --git a/core/server/api_container/server/service_network/mock_service_network.go b/core/server/api_container/server/service_network/mock_service_network.go
index 59415d1d6..de096899b 100644
--- a/core/server/api_container/server/service_network/mock_service_network.go
+++ b/core/server/api_container/server/service_network/mock_service_network.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.28.1. DO NOT EDIT.
+// Code generated by mockery v2.31.1. DO NOT EDIT.
 
 package service_network
 
@@ -1174,8 +1174,64 @@ func (_c *MockServiceNetwork_UnsetConnection_Call) RunAndReturn(run func(context
 	return _c
 }
 
-// UpdateService provides a mock function with given fields: ctx, updateServiceConfigs
-func (_m *MockServiceNetwork) UpdateService(ctx context.Context, updateServiceConfigs map[service.ServiceName]*kurtosis_core_rpc_api_bindings.UpdateServiceConfig) (map[service.ServiceName]bool, map[service.ServiceName]error, error) {
+// UpdateService provides a mock function with given fields: ctx, serviceName, serviceConfig
+func (_m *MockServiceNetwork) UpdateService(ctx context.Context, serviceName service.ServiceName, serviceConfig *service.ServiceConfig) (*service.Service, error) {
+	ret := _m.Called(ctx, serviceName, serviceConfig)
+
+	var r0 *service.Service
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, service.ServiceName, *service.ServiceConfig) (*service.Service, error)); ok {
+		return rf(ctx, serviceName, serviceConfig)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, service.ServiceName, *service.ServiceConfig) *service.Service); ok {
+		r0 = rf(ctx, serviceName, serviceConfig)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*service.Service)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, service.ServiceName, *service.ServiceConfig) error); ok {
+		r1 = rf(ctx, serviceName, serviceConfig)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MockServiceNetwork_UpdateService_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateService'
+type MockServiceNetwork_UpdateService_Call struct {
+	*mock.Call
+}
+
+// UpdateService is a helper method to define mock.On call
+//   - ctx context.Context
+//   - serviceName service.ServiceName
+//   - serviceConfig *service.ServiceConfig
+func (_e *MockServiceNetwork_Expecter) UpdateService(ctx interface{}, serviceName interface{}, serviceConfig interface{}) *MockServiceNetwork_UpdateService_Call {
+	return &MockServiceNetwork_UpdateService_Call{Call: _e.mock.On("UpdateService", ctx, serviceName, serviceConfig)}
+}
+
+func (_c *MockServiceNetwork_UpdateService_Call) Run(run func(ctx context.Context, serviceName service.ServiceName, serviceConfig *service.ServiceConfig)) *MockServiceNetwork_UpdateService_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(service.ServiceName), args[2].(*service.ServiceConfig))
+	})
+	return _c
+}
+
+func (_c *MockServiceNetwork_UpdateService_Call) Return(_a0 *service.Service, _a1 error) *MockServiceNetwork_UpdateService_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *MockServiceNetwork_UpdateService_Call) RunAndReturn(run func(context.Context, service.ServiceName, *service.ServiceConfig) (*service.Service, error)) *MockServiceNetwork_UpdateService_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// UpdateServiceSubnetwork provides a mock function with given fields: ctx, updateServiceConfigs
+func (_m *MockServiceNetwork) UpdateServiceSubnetwork(ctx context.Context, updateServiceConfigs map[service.ServiceName]*kurtosis_core_rpc_api_bindings.UpdateServiceConfig) (map[service.ServiceName]bool, map[service.ServiceName]error, error) {
 	ret := _m.Called(ctx, updateServiceConfigs)
 
 	var r0 map[service.ServiceName]bool
@@ -1209,31 +1265,96 @@ func (_m *MockServiceNetwork) UpdateService(ctx context.Context, updateServiceCo
 	return r0, r1, r2
 }
 
-// MockServiceNetwork_UpdateService_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateService'
-type MockServiceNetwork_UpdateService_Call struct {
+// MockServiceNetwork_UpdateServiceSubnetwork_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateServiceSubnetwork'
+type MockServiceNetwork_UpdateServiceSubnetwork_Call struct {
 	*mock.Call
 }
 
-// UpdateService is a helper method to define mock.On call
+// UpdateServiceSubnetwork is a helper method to define mock.On call
 //   - ctx context.Context
 //   - updateServiceConfigs map[service.ServiceName]*kurtosis_core_rpc_api_bindings.UpdateServiceConfig
-func (_e *MockServiceNetwork_Expecter) UpdateService(ctx interface{}, updateServiceConfigs interface{}) *MockServiceNetwork_UpdateService_Call {
-	return &MockServiceNetwork_UpdateService_Call{Call: _e.mock.On("UpdateService", ctx, updateServiceConfigs)}
+func (_e *MockServiceNetwork_Expecter) UpdateServiceSubnetwork(ctx interface{}, updateServiceConfigs interface{}) *MockServiceNetwork_UpdateServiceSubnetwork_Call {
+	return &MockServiceNetwork_UpdateServiceSubnetwork_Call{Call: _e.mock.On("UpdateServiceSubnetwork", ctx, updateServiceConfigs)}
 }
 
-func (_c *MockServiceNetwork_UpdateService_Call) Run(run func(ctx context.Context, updateServiceConfigs map[service.ServiceName]*kurtosis_core_rpc_api_bindings.UpdateServiceConfig)) *MockServiceNetwork_UpdateService_Call {
+func (_c *MockServiceNetwork_UpdateServiceSubnetwork_Call) Run(run func(ctx context.Context, updateServiceConfigs map[service.ServiceName]*kurtosis_core_rpc_api_bindings.UpdateServiceConfig)) *MockServiceNetwork_UpdateServiceSubnetwork_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 		run(args[0].(context.Context), args[1].(map[service.ServiceName]*kurtosis_core_rpc_api_bindings.UpdateServiceConfig))
 	})
 	return _c
 }
 
-func (_c *MockServiceNetwork_UpdateService_Call) Return(_a0 map[service.ServiceName]bool, _a1 map[service.ServiceName]error, _a2 error) *MockServiceNetwork_UpdateService_Call {
+func (_c *MockServiceNetwork_UpdateServiceSubnetwork_Call) Return(_a0 map[service.ServiceName]bool, _a1 map[service.ServiceName]error, _a2 error) *MockServiceNetwork_UpdateServiceSubnetwork_Call {
 	_c.Call.Return(_a0, _a1, _a2)
 	return _c
 }
 
-func (_c *MockServiceNetwork_UpdateService_Call) RunAndReturn(run func(context.Context, map[service.ServiceName]*kurtosis_core_rpc_api_bindings.UpdateServiceConfig) (map[service.ServiceName]bool, map[service.ServiceName]error, error)) *MockServiceNetwork_UpdateService_Call {
+func (_c *MockServiceNetwork_UpdateServiceSubnetwork_Call) RunAndReturn(run func(context.Context, map[service.ServiceName]*kurtosis_core_rpc_api_bindings.UpdateServiceConfig) (map[service.ServiceName]bool, map[service.ServiceName]error, error)) *MockServiceNetwork_UpdateServiceSubnetwork_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// UpdateServices provides a mock function with given fields: ctx, updateServiceConfigs, batchSize
+func (_m *MockServiceNetwork) UpdateServices(ctx context.Context, updateServiceConfigs map[service.ServiceName]*service.ServiceConfig, batchSize int) (map[service.ServiceName]*service.Service, map[service.ServiceName]error, error) {
+	ret := _m.Called(ctx, updateServiceConfigs, batchSize)
+
+	var r0 map[service.ServiceName]*service.Service
+	var r1 map[service.ServiceName]error
+	var r2 error
+	if rf, ok := ret.Get(0).(func(context.Context, map[service.ServiceName]*service.ServiceConfig, int) (map[service.ServiceName]*service.Service, map[service.ServiceName]error, error)); ok {
+		return rf(ctx, updateServiceConfigs, batchSize)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, map[service.ServiceName]*service.ServiceConfig, int) map[service.ServiceName]*service.Service); ok {
+		r0 = rf(ctx, updateServiceConfigs, batchSize)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(map[service.ServiceName]*service.Service)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, map[service.ServiceName]*service.ServiceConfig, int) map[service.ServiceName]error); ok {
+		r1 = rf(ctx, updateServiceConfigs, batchSize)
+	} else {
+		if ret.Get(1) != nil {
+			r1 = ret.Get(1).(map[service.ServiceName]error)
+		}
+	}
+
+	if rf, ok := ret.Get(2).(func(context.Context, map[service.ServiceName]*service.ServiceConfig, int) error); ok {
+		r2 = rf(ctx, updateServiceConfigs, batchSize)
+	} else {
+		r2 = ret.Error(2)
+	}
+
+	return r0, r1, r2
+}
+
+// MockServiceNetwork_UpdateServices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateServices'
+type MockServiceNetwork_UpdateServices_Call struct {
+	*mock.Call
+}
+
+// UpdateServices is a helper method to define mock.On call
+//   - ctx context.Context
+//   - updateServiceConfigs map[service.ServiceName]*service.ServiceConfig
+//   - batchSize int
+func (_e *MockServiceNetwork_Expecter) UpdateServices(ctx interface{}, updateServiceConfigs interface{}, batchSize interface{}) *MockServiceNetwork_UpdateServices_Call {
+	return &MockServiceNetwork_UpdateServices_Call{Call: _e.mock.On("UpdateServices", ctx, updateServiceConfigs, batchSize)}
+}
+
+func (_c *MockServiceNetwork_UpdateServices_Call) Run(run func(ctx context.Context, updateServiceConfigs map[service.ServiceName]*service.ServiceConfig, batchSize int)) *MockServiceNetwork_UpdateServices_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(map[service.ServiceName]*service.ServiceConfig), args[2].(int))
+	})
+	return _c
+}
+
+func (_c *MockServiceNetwork_UpdateServices_Call) Return(_a0 map[service.ServiceName]*service.Service, _a1 map[service.ServiceName]error, _a2 error) *MockServiceNetwork_UpdateServices_Call {
+	_c.Call.Return(_a0, _a1, _a2)
+	return _c
+}
+
+func (_c *MockServiceNetwork_UpdateServices_Call) RunAndReturn(run func(context.Context, map[service.ServiceName]*service.ServiceConfig, int) (map[service.ServiceName]*service.Service, map[service.ServiceName]error, error)) *MockServiceNetwork_UpdateServices_Call {
 	_c.Call.Return(run)
 	return _c
 }
@@ -1291,13 +1412,12 @@ func (_c *MockServiceNetwork_UploadFilesArtifact_Call) RunAndReturn(run func([]b
 	return _c
 }
 
-type mockConstructorTestingTNewMockServiceNetwork interface {
+// NewMockServiceNetwork creates a new instance of MockServiceNetwork. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewMockServiceNetwork(t interface {
 	mock.TestingT
 	Cleanup(func())
-}
-
-// NewMockServiceNetwork creates a new instance of MockServiceNetwork. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-func NewMockServiceNetwork(t mockConstructorTestingTNewMockServiceNetwork) *MockServiceNetwork {
+}) *MockServiceNetwork {
 	mock := &MockServiceNetwork{}
 	mock.Mock.Test(t)
 
diff --git a/core/server/api_container/server/service_network/service_network.go b/core/server/api_container/server/service_network/service_network.go
index dc683ed9b..4b076a228 100644
--- a/core/server/api_container/server/service_network/service_network.go
+++ b/core/server/api_container/server/service_network/service_network.go
@@ -51,6 +51,25 @@ type ServiceNetwork interface {
 	)
 
 	UpdateService(
+		ctx context.Context,
+		serviceName service.ServiceName,
+		serviceConfig *service.ServiceConfig,
+	) (
+		*service.Service,
+		error,
+	)
+
+	UpdateServices(
+		ctx context.Context,
+		updateServiceConfigs map[service.ServiceName]*service.ServiceConfig,
+		batchSize int,
+	) (
+		map[service.ServiceName]*service.Service,
+		map[service.ServiceName]error,
+		error,
+	)
+
+	UpdateServiceSubnetwork(
 		ctx context.Context,
 		updateServiceConfigs map[service.ServiceName]*kurtosis_core_rpc_api_bindings.UpdateServiceConfig,
 	) (
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_instruction/update_service/update_service.go b/core/server/api_container/server/startosis_engine/kurtosis_instruction/update_service/update_service.go
index 019349e36..9f8ade15f 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_instruction/update_service/update_service.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_instruction/update_service/update_service.go
@@ -120,7 +120,7 @@ func (builtin *UpdateServiceCapabilities) Execute(ctx context.Context, _ *builti
 		builtin.serviceName: builtin.updateServiceConfig,
 	}
 
-	serviceSuccessful, serviceFailed, err := builtin.serviceNetwork.UpdateService(ctx, updateServiceConfigMap)
+	serviceSuccessful, serviceFailed, err := builtin.serviceNetwork.UpdateServiceSubnetwork(ctx, updateServiceConfigMap)
 	if err != nil {
 		return "", stacktrace.Propagate(err, "Failed updating service '%s' with an unexpected error", builtin.serviceName)
 	}


Test patch:
diff --git a/core/server/api_container/server/service_network/default_service_network_test.go b/core/server/api_container/server/service_network/default_service_network_test.go
index 9f5b5115e..c5a781f8d 100644
--- a/core/server/api_container/server/service_network/default_service_network_test.go
+++ b/core/server/api_container/server/service_network/default_service_network_test.go
@@ -1203,6 +1203,164 @@ func TestUpdateService(t *testing.T) {
 	defer db.Close()
 	enclaveDb := &enclave_db.EnclaveDB{DB: db}
 
+	network, err := NewDefaultServiceNetwork(
+		enclaveName,
+		apiContainerInfo,
+		!partitioningEnabled,
+		backend,
+		unusedEnclaveDataDir,
+		networking_sidecar.NewStandardNetworkingSidecarManager(backend, enclaveName),
+		enclaveDb,
+	)
+	require.Nil(t, err)
+
+	initialServiceConfig := testServiceConfig(testContainerImageName, defaultSubnetwork)
+	updatedServiceConfig := testServiceConfig("kurtosistech/new-service-image", defaultSubnetwork)
+
+	// service that will be successfully updated
+	existingServiceIndex := 1
+	existingServiceIp := testIpFromInt(existingServiceIndex)
+	existingServiceRegistration := service.NewServiceRegistration(
+		testServiceNameFromInt(existingServiceIndex),
+		testServiceUuidFromInt(existingServiceIndex),
+		enclaveName,
+		existingServiceIp,
+		testServiceHostnameFromInt(existingServiceIndex))
+	existingServiceRegistration.SetConfig(initialServiceConfig)
+	existingServiceRegistration.SetStatus(service.ServiceStatus_Started)
+	network.registeredServiceInfo[existingServiceRegistration.GetName()] = existingServiceRegistration
+
+	// service that will fail because it's not registered in the enclave prior to update
+	unknownServiceIndex := 2
+	unknownServiceIp := testIpFromInt(unknownServiceIndex)
+	unknownServiceRegistration := service.NewServiceRegistration(
+		testServiceNameFromInt(unknownServiceIndex),
+		testServiceUuidFromInt(unknownServiceIndex),
+		enclaveName,
+		unknownServiceIp,
+		testServiceHostnameFromInt(unknownServiceIndex))
+
+	// service that will fail to be removed from the enclave
+	failedToBeRemovedServiceIndex := 3
+	failedToBeRemovedServiceIp := testIpFromInt(unknownServiceIndex)
+	failedToBeRemovedServiceRegistration := service.NewServiceRegistration(
+		testServiceNameFromInt(failedToBeRemovedServiceIndex),
+		testServiceUuidFromInt(failedToBeRemovedServiceIndex),
+		enclaveName,
+		failedToBeRemovedServiceIp,
+		testServiceHostnameFromInt(failedToBeRemovedServiceIndex))
+	failedToBeRemovedServiceRegistration.SetConfig(initialServiceConfig)
+	failedToBeRemovedServiceRegistration.SetStatus(service.ServiceStatus_Started)
+	network.registeredServiceInfo[failedToBeRemovedServiceRegistration.GetName()] = failedToBeRemovedServiceRegistration
+
+	// service that will fail to be re-created once it has been removed
+	failedToBeRecreatedServiceIndex := 4
+	failedToBeRecreatedServiceIp := testIpFromInt(unknownServiceIndex)
+	failedToBeRecreatedServiceRegistration := service.NewServiceRegistration(
+		testServiceNameFromInt(failedToBeRecreatedServiceIndex),
+		testServiceUuidFromInt(failedToBeRecreatedServiceIndex),
+		enclaveName,
+		failedToBeRecreatedServiceIp,
+		testServiceHostnameFromInt(failedToBeRecreatedServiceIndex))
+	failedToBeRecreatedServiceRegistration.SetConfig(initialServiceConfig)
+	failedToBeRecreatedServiceRegistration.SetStatus(service.ServiceStatus_Started)
+	network.registeredServiceInfo[failedToBeRecreatedServiceRegistration.GetName()] = failedToBeRecreatedServiceRegistration
+
+	// The service will be removed first
+	backend.EXPECT().RemoveRegisteredUserServiceProcesses(
+		ctx,
+		enclaveName,
+		map[service.ServiceUUID]bool{
+			existingServiceRegistration.GetUUID():            true,
+			failedToBeRemovedServiceRegistration.GetUUID():   true,
+			failedToBeRecreatedServiceRegistration.GetUUID(): true,
+		},
+	).Times(1).Return(
+		map[service.ServiceUUID]bool{
+			existingServiceRegistration.GetUUID():            true,
+			failedToBeRecreatedServiceRegistration.GetUUID(): true,
+		},
+		map[service.ServiceUUID]error{
+			failedToBeRemovedServiceRegistration.GetUUID(): stacktrace.NewError("Unable to remove service"),
+		},
+		nil,
+	)
+
+	// The services will then be re-created
+	serviceObj := service.NewService(existingServiceRegistration, container_status.ContainerStatus_Running, map[string]*port_spec.PortSpec{}, existingServiceIp, map[string]*port_spec.PortSpec{})
+	backend.EXPECT().StartRegisteredUserServices(
+		ctx,
+		enclaveName,
+		map[service.ServiceUUID]*service.ServiceConfig{
+			existingServiceRegistration.GetUUID(): updatedServiceConfig,
+		},
+	).Times(1).Return(
+		map[service.ServiceUUID]*service.Service{
+			existingServiceRegistration.GetUUID(): serviceObj,
+		},
+		map[service.ServiceUUID]error{},
+		nil,
+	)
+	backend.EXPECT().StartRegisteredUserServices(
+		ctx,
+		enclaveName,
+		map[service.ServiceUUID]*service.ServiceConfig{
+			failedToBeRecreatedServiceRegistration.GetUUID(): updatedServiceConfig,
+		},
+	).Times(1).Return(
+		map[service.ServiceUUID]*service.Service{},
+		map[service.ServiceUUID]error{
+			failedToBeRecreatedServiceRegistration.GetUUID(): stacktrace.NewError("Unable to re-create service"),
+		},
+		nil,
+	)
+
+	success, failure, err := network.UpdateServices(ctx, map[service.ServiceName]*service.ServiceConfig{
+		existingServiceRegistration.GetName():            updatedServiceConfig,
+		unknownServiceRegistration.GetName():             updatedServiceConfig,
+		failedToBeRemovedServiceRegistration.GetName():   updatedServiceConfig,
+		failedToBeRecreatedServiceRegistration.GetName(): updatedServiceConfig,
+	}, 1)
+	require.Nil(t, err)
+	require.Len(t, success, 1)
+	require.Contains(t, success, existingServiceRegistration.GetName())
+
+	require.Len(t, failure, 3)
+	require.Contains(t, failure, unknownServiceRegistration.GetName())
+	require.Contains(t, failure, failedToBeRemovedServiceRegistration.GetName())
+	require.Contains(t, failure, failedToBeRecreatedServiceRegistration.GetName())
+
+	newExistingServiceRegistration, found := network.registeredServiceInfo[existingServiceRegistration.GetName()]
+	require.True(t, found)
+	require.Equal(t, newExistingServiceRegistration.GetStatus(), service.ServiceStatus_Started)
+	require.Equal(t, newExistingServiceRegistration.GetConfig(), updatedServiceConfig)
+
+	_, found = network.registeredServiceInfo[unknownServiceRegistration.GetName()]
+	require.False(t, found)
+
+	newFailedToBeRemovedServiceRegistration, found := network.registeredServiceInfo[failedToBeRemovedServiceRegistration.GetName()]
+	require.True(t, found)
+	require.Equal(t, newFailedToBeRemovedServiceRegistration.GetStatus(), service.ServiceStatus_Started)
+	require.Equal(t, newFailedToBeRemovedServiceRegistration.GetConfig(), initialServiceConfig)
+
+	newFailedToBeRecreatedServiceRegistration, found := network.registeredServiceInfo[failedToBeRecreatedServiceRegistration.GetName()]
+	require.True(t, found)
+	require.Equal(t, newFailedToBeRecreatedServiceRegistration.GetStatus(), service.ServiceStatus_Registered)
+	require.Nil(t, newFailedToBeRecreatedServiceRegistration.GetConfig())
+}
+
+func TestUpdateServiceSubnetwork(t *testing.T) {
+	ctx := context.Background()
+	backend := backend_interface.NewMockKurtosisBackend(t)
+
+	file, err := os.CreateTemp("/tmp", "*.db")
+	defer os.Remove(file.Name())
+	require.Nil(t, err)
+	db, err := bolt.Open(file.Name(), 0666, nil)
+	require.Nil(t, err)
+	defer db.Close()
+	enclaveDb := &enclave_db.EnclaveDB{DB: db}
+
 	network, err := NewDefaultServiceNetwork(
 		enclaveName,
 		apiContainerInfo,
@@ -1269,7 +1427,7 @@ func TestUpdateService(t *testing.T) {
 	network.networkingSidecars[serviceAlreadyInPartition.GetName()] = networking_sidecar.NewMockNetworkingSidecarWrapper()
 	network.networkingSidecars[serviceToMoveOutOfDefaultPartition.GetName()] = networking_sidecar.NewMockNetworkingSidecarWrapper()
 
-	success, failure, err := network.UpdateService(ctx, map[service.ServiceName]*kurtosis_core_rpc_api_bindings.UpdateServiceConfig{
+	success, failure, err := network.UpdateServiceSubnetwork(ctx, map[service.ServiceName]*kurtosis_core_rpc_api_bindings.UpdateServiceConfig{
 		successfulService.GetName():                  binding_constructors.NewUpdateServiceConfig(string(partition2)),
 		serviceAlreadyInPartition.GetName():          binding_constructors.NewUpdateServiceConfig(string(partition0)),
 		nonExistentService.GetName():                 binding_constructors.NewUpdateServiceConfig(string(partition2)),
@@ -1300,7 +1458,7 @@ func TestUpdateService(t *testing.T) {
 	require.Equal(t, expectedPartitions, partitionServices)
 }
 
-func TestUpdateService_FullBatchFailureRollBack(t *testing.T) {
+func TestUpdateServiceSubnetwork_FullBatchFailureRollBack(t *testing.T) {
 	ctx := context.Background()
 	backend := backend_interface.NewMockKurtosisBackend(t)
 
@@ -1351,7 +1509,7 @@ func TestUpdateService_FullBatchFailureRollBack(t *testing.T) {
 	// do not add sidecar for failingService so that it fails updating the connections
 	network.networkingSidecars[successfulService.GetName()] = networking_sidecar.NewMockNetworkingSidecarWrapper()
 
-	success, failure, err := network.UpdateService(ctx, map[service.ServiceName]*kurtosis_core_rpc_api_bindings.UpdateServiceConfig{
+	success, failure, err := network.UpdateServiceSubnetwork(ctx, map[service.ServiceName]*kurtosis_core_rpc_api_bindings.UpdateServiceConfig{
 		failingService.GetName():    binding_constructors.NewUpdateServiceConfig(string(partition2)),
 		successfulService.GetName(): binding_constructors.NewUpdateServiceConfig(string(partition2)),
 	})
diff --git a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/update_service_framework_test.go b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/update_service_framework_test.go
index f8c9566e5..a2b75bebb 100644
--- a/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/update_service_framework_test.go
+++ b/core/server/api_container/server/startosis_engine/kurtosis_starlark_framework/test_engine/update_service_framework_test.go
@@ -40,7 +40,7 @@ func (t *updateServiceTestCase) GetInstruction() *kurtosis_plan_instruction.Kurt
 		nil,
 	)
 
-	serviceNetwork.EXPECT().UpdateService(
+	serviceNetwork.EXPECT().UpdateServiceSubnetwork(
 		mock.Anything,
 		map[service.ServiceName]*kurtosis_core_rpc_api_bindings.UpdateServiceConfig{
 			TestServiceName: binding_constructors.NewUpdateServiceConfig(string(TestSubnetwork)),