feat(docker): start temporary local registry for buildx dependency resolution and ensure buildx builder uses host network

This commit is contained in:
2026-02-06 15:53:32 +00:00
parent d1785aab86
commit 37dfde005e
4 changed files with 160 additions and 62 deletions

View File

@@ -10,6 +10,10 @@ const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash',
});
const LOCAL_REGISTRY_PORT = 5234;
const LOCAL_REGISTRY_HOST = `localhost:${LOCAL_REGISTRY_PORT}`;
const LOCAL_REGISTRY_CONTAINER = 'tsdocker-local-registry';
/**
* Class Dockerfile represents a Dockerfile on disk
*/
@@ -135,6 +139,53 @@ export class Dockerfile {
return sortedDockerfileArray;
}
/** Determines if a local registry is needed for buildx dependency resolution. */
public static needsLocalRegistry(
dockerfiles: Dockerfile[],
options?: { platform?: string },
): boolean {
const hasLocalDeps = dockerfiles.some(df => df.localBaseImageDependent);
if (!hasLocalDeps) return false;
const config = dockerfiles[0]?.managerRef?.config;
return !!options?.platform || !!(config?.platforms && config.platforms.length > 1);
}
/** Starts a temporary registry:2 container on port 5234. */
public static async startLocalRegistry(): Promise<void> {
await smartshellInstance.execSilent(
`docker rm -f ${LOCAL_REGISTRY_CONTAINER} 2>/dev/null || true`
);
const result = await smartshellInstance.execSilent(
`docker run -d --name ${LOCAL_REGISTRY_CONTAINER} -p ${LOCAL_REGISTRY_PORT}:5000 registry:2`
);
if (result.exitCode !== 0) {
throw new Error(`Failed to start local registry: ${result.stderr || result.stdout}`);
}
// registry:2 starts near-instantly; brief wait for readiness
await new Promise(resolve => setTimeout(resolve, 1000));
logger.log('info', `Started local registry at ${LOCAL_REGISTRY_HOST}`);
}
/** Stops and removes the temporary local registry container. */
public static async stopLocalRegistry(): Promise<void> {
await smartshellInstance.execSilent(
`docker rm -f ${LOCAL_REGISTRY_CONTAINER} 2>/dev/null || true`
);
logger.log('info', 'Stopped local registry');
}
/** Pushes a built image to the local registry for buildx consumption. */
public static async pushToLocalRegistry(dockerfile: Dockerfile): Promise<void> {
const registryTag = `${LOCAL_REGISTRY_HOST}/${dockerfile.buildTag}`;
await smartshellInstance.execSilent(`docker tag ${dockerfile.buildTag} ${registryTag}`);
const result = await smartshellInstance.execSilent(`docker push ${registryTag}`);
if (result.exitCode !== 0) {
throw new Error(`Failed to push to local registry: ${result.stderr || result.stdout}`);
}
dockerfile.localRegistryTag = registryTag;
logger.log('info', `Pushed ${dockerfile.buildTag} to local registry as ${registryTag}`);
}
/**
* Builds the corresponding real docker image for each Dockerfile class instance
*/
@@ -144,26 +195,41 @@ export class Dockerfile {
): Promise<Dockerfile[]> {
const total = sortedArrayArg.length;
const overallStart = Date.now();
const useRegistry = Dockerfile.needsLocalRegistry(sortedArrayArg, options);
for (let i = 0; i < total; i++) {
const dockerfileArg = sortedArrayArg[i];
const progress = `(${i + 1}/${total})`;
logger.log('info', `${progress} Building ${dockerfileArg.cleanTag}...`);
if (useRegistry) {
await Dockerfile.startLocalRegistry();
}
const elapsed = await dockerfileArg.build(options);
logger.log('ok', `${progress} Built ${dockerfileArg.cleanTag} in ${formatDuration(elapsed)}`);
try {
for (let i = 0; i < total; i++) {
const dockerfileArg = sortedArrayArg[i];
const progress = `(${i + 1}/${total})`;
logger.log('info', `${progress} Building ${dockerfileArg.cleanTag}...`);
// Tag the built image with the full base image references used by dependent Dockerfiles,
// so their FROM lines resolve to the locally-built image instead of pulling from a registry.
const dependentBaseImages = new Set<string>();
for (const other of sortedArrayArg) {
if (other.localBaseDockerfile === dockerfileArg && other.baseImage !== dockerfileArg.buildTag) {
dependentBaseImages.add(other.baseImage);
const elapsed = await dockerfileArg.build(options);
logger.log('ok', `${progress} Built ${dockerfileArg.cleanTag} in ${formatDuration(elapsed)}`);
// Tag in host daemon for standard docker build compatibility
const dependentBaseImages = new Set<string>();
for (const other of sortedArrayArg) {
if (other.localBaseDockerfile === dockerfileArg && other.baseImage !== dockerfileArg.buildTag) {
dependentBaseImages.add(other.baseImage);
}
}
for (const fullTag of dependentBaseImages) {
logger.log('info', `Tagging ${dockerfileArg.buildTag} as ${fullTag} for local dependency resolution`);
await smartshellInstance.exec(`docker tag ${dockerfileArg.buildTag} ${fullTag}`);
}
// Push to local registry for buildx dependency resolution
if (useRegistry && sortedArrayArg.some(other => other.localBaseDockerfile === dockerfileArg)) {
await Dockerfile.pushToLocalRegistry(dockerfileArg);
}
}
for (const fullTag of dependentBaseImages) {
logger.log('info', `Tagging ${dockerfileArg.buildTag} as ${fullTag} for local dependency resolution`);
await smartshellInstance.exec(`docker tag ${dockerfileArg.buildTag} ${fullTag}`);
} finally {
if (useRegistry) {
await Dockerfile.stopLocalRegistry();
}
}
@@ -366,6 +432,7 @@ export class Dockerfile {
public baseImage: string;
public localBaseImageDependent: boolean;
public localBaseDockerfile!: Dockerfile;
public localRegistryTag?: string;
constructor(managerRefArg: TsDockerManager, options: IDockerfileOptions) {
this.managerRef = managerRefArg;
@@ -412,9 +479,12 @@ export class Dockerfile {
let buildContextFlag = '';
if (this.localBaseImageDependent && this.localBaseDockerfile) {
const fromImage = this.baseImage;
const localTag = this.localBaseDockerfile.buildTag;
buildContextFlag = ` --build-context "${fromImage}=docker-image://${localTag}"`;
logger.log('info', `Using local build context: ${fromImage} -> docker-image://${localTag}`);
if (this.localBaseDockerfile.localRegistryTag) {
// BuildKit pulls from the local registry (reachable via host network)
const registryTag = this.localBaseDockerfile.localRegistryTag;
buildContextFlag = ` --build-context "${fromImage}=docker-image://${registryTag}"`;
logger.log('info', `Using local registry build context: ${fromImage} -> docker-image://${registryTag}`);
}
}
let buildCommand: string;