Fix container name conflict on redeployment

Remove old container before creating new one instead of trying to keep
it for rollback. Rollback isn't safe anyway because database migrations
may have been applied by the new container.

The old stop-then-rollback approach failed because Docker doesn't allow
two containers with the same name, even if one is stopped.
This commit is contained in:
Jeffrey Paul 2025-12-31 14:48:16 -08:00
parent d2f2747ae6
commit 83986626a4

View File

@ -307,7 +307,7 @@ func (svc *Service) buildImageWithTimeout(
}
// deployContainerWithTimeout runs the deploy phase with a timeout.
// It stops the old container, starts the new one, and handles rollback on failure.
// It removes the old container and starts the new one.
func (svc *Service) deployContainerWithTimeout(
ctx context.Context,
app *models.App,
@ -322,17 +322,12 @@ func (svc *Service) deployContainerWithTimeout(
return err
}
// Stop old container (but don't remove yet - keep for potential rollback)
oldContainerID := svc.stopOldContainer(deployCtx, app, deployment)
// Remove old container first to free up the name
svc.removeOldContainer(deployCtx, app, deployment)
// Try to create and start the new container
// Create and start the new container
_, err = svc.createAndStartContainer(deployCtx, app, deployment, imageID)
if err != nil {
// Rollback: restart the old container if we have one
if oldContainerID != "" {
svc.rollbackContainer(ctx, oldContainerID, deployment)
}
if errors.Is(deployCtx.Err(), context.DeadlineExceeded) {
timeoutErr := fmt.Errorf("%w after %v", ErrDeployTimeout, deployTimeout)
svc.failDeployment(ctx, app, deployment, timeoutErr)
@ -343,11 +338,6 @@ func (svc *Service) deployContainerWithTimeout(
return err
}
// Success: remove the old container
if oldContainerID != "" {
svc.removeContainer(ctx, oldContainerID, deployment)
}
return nil
}
@ -627,19 +617,18 @@ func (svc *Service) updateDeploymentDeploying(
return nil
}
// stopOldContainer stops the old container but keeps it for potential rollback.
// Returns the container ID if found, empty string otherwise.
func (svc *Service) stopOldContainer(
// removeOldContainer stops and removes the old container before deploying a new one.
func (svc *Service) removeOldContainer(
ctx context.Context,
app *models.App,
deployment *models.Deployment,
) string {
) {
containerInfo, err := svc.docker.FindContainerByAppID(ctx, app.ID)
if err != nil || containerInfo == nil {
return ""
return
}
svc.log.Info("stopping old container", "id", containerInfo.ID)
svc.log.Info("removing old container", "id", containerInfo.ID)
if containerInfo.Running {
stopErr := svc.docker.StopContainer(ctx, containerInfo.ID)
@ -648,47 +637,12 @@ func (svc *Service) stopOldContainer(
}
}
_ = deployment.AppendLog(ctx, "Old container stopped: "+containerInfo.ID[:12])
return containerInfo.ID
}
// rollbackContainer restarts the old container after a failed deployment.
func (svc *Service) rollbackContainer(
ctx context.Context,
containerID string,
deployment *models.Deployment,
) {
svc.log.Info("rolling back to old container", "id", containerID)
_ = deployment.AppendLog(ctx, "Rolling back to previous container: "+containerID[:12])
startErr := svc.docker.StartContainer(ctx, containerID)
if startErr != nil {
svc.log.Error("failed to restart old container during rollback", "error", startErr)
_ = deployment.AppendLog(ctx, "ERROR: Failed to rollback: "+startErr.Error())
return
}
_ = deployment.AppendLog(ctx, "Rollback successful - previous container restarted")
}
// removeContainer removes a container after successful deployment.
func (svc *Service) removeContainer(
ctx context.Context,
containerID string,
deployment *models.Deployment,
) {
svc.log.Info("removing old container", "id", containerID)
removeErr := svc.docker.RemoveContainer(ctx, containerID, true)
removeErr := svc.docker.RemoveContainer(ctx, containerInfo.ID, true)
if removeErr != nil {
svc.log.Warn("failed to remove old container", "error", removeErr)
return
}
_ = deployment.AppendLog(ctx, "Old container removed: "+containerID[:12])
_ = deployment.AppendLog(ctx, "Old container removed: "+containerInfo.ID[:12])
}
func (svc *Service) createAndStartContainer(