Add Portainer-style container name replace on deploy.
Release rolling / release (push) Has been cancelled
Release rolling / release (push) Has been cancelled
When deploying a container whose name already exists, prompt to Replace (stop and remove the old container) or Cancel. Server accepts replace:true to perform the swap; clients pre-check names and retry on conflict races.
This commit is contained in:
@@ -26,13 +26,52 @@ export function registerDeployHandlers(session) {
|
||||
args.image = image
|
||||
|
||||
const existingContainers = await docker.listContainers({ all: true })
|
||||
if (existingContainers.some((c) => c.Names.includes(`/${args.containerName}`))) {
|
||||
throw formatDeployError(
|
||||
new Error(
|
||||
`Conflict. The container name "/${args.containerName}" is already in use by container. You have to remove (or rename) that container to be able to reuse that name.`
|
||||
),
|
||||
{ stage: 'create', containerName: args.containerName, image: args.image }
|
||||
)
|
||||
const existing = existingContainers.find((c) =>
|
||||
(c.Names || []).some((n) => {
|
||||
const bare = String(n || '').replace(/^\//, '')
|
||||
return bare === args.containerName || n === `/${args.containerName}`
|
||||
})
|
||||
)
|
||||
|
||||
// Portainer-style replace: stop+remove the name holder, then create new
|
||||
if (existing) {
|
||||
if (args.replace === true) {
|
||||
logger.info('Replacing existing container', {
|
||||
name: args.containerName,
|
||||
id: String(existing.Id || '').slice(0, 12),
|
||||
state: existing.State,
|
||||
})
|
||||
const old = docker.getContainer(existing.Id)
|
||||
try {
|
||||
await old.stop({ t: 10 })
|
||||
} catch {
|
||||
// already stopped / gone
|
||||
}
|
||||
try {
|
||||
await old.remove({ force: true })
|
||||
} catch (rmErr) {
|
||||
throw formatDeployError(rmErr, {
|
||||
stage: 'replace',
|
||||
containerName: args.containerName,
|
||||
image: args.image,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const err = new Error(
|
||||
`A container named "${args.containerName}" already exists` +
|
||||
(existing.State ? ` (${existing.State})` : '') +
|
||||
(existing.Image ? ` · image ${existing.Image}` : '') +
|
||||
'. Deploying will stop and remove it, then create a new container with your settings.'
|
||||
)
|
||||
err.code = 'CONTAINER_NAME_CONFLICT'
|
||||
err.existing = {
|
||||
id: existing.Id,
|
||||
name: args.containerName,
|
||||
state: existing.State || 'unknown',
|
||||
image: existing.Image || '',
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Skip pull when client already pulled (job stepper) unless force
|
||||
@@ -278,13 +317,18 @@ export function registerDeployHandlers(session) {
|
||||
logger.info('Container deployed successfully', {
|
||||
name: args.containerName,
|
||||
image: args.image,
|
||||
replaced: args.replace === true,
|
||||
})
|
||||
|
||||
await broadcastContainers()
|
||||
return {
|
||||
success: true,
|
||||
message: `Container "${args.containerName}" deployed successfully from image "${args.image}"`,
|
||||
message:
|
||||
args.replace === true
|
||||
? `Container "${args.containerName}" replaced successfully from image "${args.image}"`
|
||||
: `Container "${args.containerName}" deployed successfully from image "${args.image}"`,
|
||||
id: container.id,
|
||||
replaced: args.replace === true,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user