Add container create page from the containers list with always-pull.
Release rolling / release (push) Successful in 8m24s
Release rolling / release (push) Successful in 8m24s
Adds a dedicated blank create form (name, image, always pull, ports, auto-remove, advanced settings) separate from template Deploy, reuses deployContainer with pull-if-missing and publish-all-ports support.
This commit is contained in:
+45
-14
@@ -7,6 +7,20 @@ import { broadcastContainers } from './containers.js'
|
||||
import logger from '../utils/logger.js'
|
||||
import { formatDeployError } from '../utils/dockerErrors.js'
|
||||
|
||||
/**
|
||||
* @param {string} imageRef
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async function imageExistsLocally(imageRef) {
|
||||
if (!imageRef) return false
|
||||
try {
|
||||
await docker.getImage(imageRef).inspect()
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function registerDeployHandlers(session) {
|
||||
session.respond('deployContainer', async (args) => {
|
||||
const containerName = validation.sanitizeString(args.containerName, 63)
|
||||
@@ -33,7 +47,7 @@ export function registerDeployHandlers(session) {
|
||||
})
|
||||
)
|
||||
|
||||
// Portainer-style replace: stop+remove the name holder, then create new
|
||||
// Optional replace: stop+remove the name holder, then create new
|
||||
if (existing) {
|
||||
if (args.replace === true) {
|
||||
logger.info('Replacing existing container', {
|
||||
@@ -74,20 +88,33 @@ export function registerDeployHandlers(session) {
|
||||
}
|
||||
}
|
||||
|
||||
// Skip pull when client already pulled (job stepper) unless force
|
||||
// Image pull policy:
|
||||
// - skipPull: client already pulled successfully — do nothing
|
||||
// - alwaysPull === false: use local image if present; pull only when missing
|
||||
// - alwaysPull true/undefined: force pull (legacy deploy / template behavior)
|
||||
if (!args.skipPull) {
|
||||
logger.info(`Pulling Docker image: ${args.image}`)
|
||||
try {
|
||||
const pullStream = await docker.pull(args.image)
|
||||
await new Promise((resolve, reject) => {
|
||||
docker.modem.followProgress(pullStream, (err) => (err ? reject(err) : resolve()))
|
||||
})
|
||||
} catch (pullErr) {
|
||||
throw formatDeployError(pullErr, {
|
||||
stage: 'pull',
|
||||
containerName: args.containerName,
|
||||
image: args.image,
|
||||
})
|
||||
const forcePull = args.alwaysPull !== false
|
||||
let needPull = forcePull
|
||||
if (!forcePull) {
|
||||
needPull = !(await imageExistsLocally(args.image))
|
||||
if (!needPull) {
|
||||
logger.info('Using local image (alwaysPull=false)', { image: args.image })
|
||||
}
|
||||
}
|
||||
if (needPull) {
|
||||
logger.info(`Pulling Docker image: ${args.image}`, { force: forcePull })
|
||||
try {
|
||||
const pullStream = await docker.pull(args.image)
|
||||
await new Promise((resolve, reject) => {
|
||||
docker.modem.followProgress(pullStream, (err) => (err ? reject(err) : resolve()))
|
||||
})
|
||||
} catch (pullErr) {
|
||||
throw formatDeployError(pullErr, {
|
||||
stage: 'pull',
|
||||
containerName: args.containerName,
|
||||
image: args.image,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +182,10 @@ export function registerDeployHandlers(session) {
|
||||
NetworkMode: args.networkMode || 'bridge',
|
||||
}
|
||||
|
||||
if (args.publishAllPorts === true) {
|
||||
hostConfig.PublishAllPorts = true
|
||||
}
|
||||
|
||||
if (args.ports && Array.isArray(args.ports)) {
|
||||
hostConfig.PortBindings = {}
|
||||
for (const portStr of args.ports) {
|
||||
|
||||
Reference in New Issue
Block a user