@@ -6,9 +6,12 @@ import { networkInterfaces } from "os";
66import { join } from "path" ;
77import { safeReadJSONFile } from "../utilities/fileSystem.js" ;
88import { readFileSync } from "fs" ;
9+
910import { isLinux } from "std-env" ;
1011import { z } from "zod" ;
1112import { assertExhaustive } from "../utilities/assertExhaustive.js" ;
13+ import { tryCatch } from "@trigger.dev/core" ;
14+ import { CliApiClient } from "../apiClient.js" ;
1215
1316export interface BuildImageOptions {
1417 // Common options
@@ -19,6 +22,7 @@ export interface BuildImageOptions {
1922
2023 // Local build options
2124 push ?: boolean ;
25+ authenticateToRegistry ?: boolean ;
2226 network ?: string ;
2327 builder : string ;
2428
@@ -37,6 +41,7 @@ export interface BuildImageOptions {
3741 extraCACerts ?: string ;
3842 apiUrl : string ;
3943 apiKey : string ;
44+ apiClient : CliApiClient ;
4045 branchName ?: string ;
4146 buildEnvVars ?: Record < string , string | undefined > ;
4247 onLog ?: ( log : string ) => void ;
@@ -51,6 +56,7 @@ export async function buildImage(options: BuildImageOptions): Promise<BuildImage
5156 imagePlatform,
5257 noCache,
5358 push,
59+ authenticateToRegistry,
5460 load,
5561 authAccessToken,
5662 imageTag,
@@ -66,6 +72,7 @@ export async function buildImage(options: BuildImageOptions): Promise<BuildImage
6672 extraCACerts,
6773 apiUrl,
6874 apiKey,
75+ apiClient,
6976 branchName,
7077 buildEnvVars,
7178 network,
@@ -84,11 +91,13 @@ export async function buildImage(options: BuildImageOptions): Promise<BuildImage
8491 contentHash,
8592 projectRef,
8693 push,
94+ authenticateToRegistry,
8795 load,
8896 noCache,
8997 extraCACerts,
9098 apiUrl,
9199 apiKey,
100+ apiClient,
92101 branchName,
93102 buildEnvVars,
94103 network,
@@ -292,8 +301,10 @@ interface SelfHostedBuildImageOptions {
292301 projectRef : string ;
293302 imagePlatform : string ;
294303 push ?: boolean ;
304+ authenticateToRegistry ?: boolean ;
295305 apiUrl : string ;
296306 apiKey : string ;
307+ apiClient : CliApiClient ;
297308 branchName ?: string ;
298309 noCache ?: boolean ;
299310 extraCACerts ?: string ;
@@ -305,7 +316,7 @@ interface SelfHostedBuildImageOptions {
305316}
306317
307318async function localBuildImage ( options : SelfHostedBuildImageOptions ) : Promise < BuildImageResults > {
308- const { builder, imageTag } = options ;
319+ const { builder, imageTag, deploymentId , apiClient } = options ;
309320
310321 // Ensure multi-platform build is supported on the local machine
311322 let builderExists = false ;
@@ -414,6 +425,64 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
414425
415426 await ensureQemuRegistered ( options . imagePlatform ) ;
416427
428+ const errors : string [ ] = [ ] ;
429+
430+ let registryHost : string | undefined ;
431+ if ( push && options . authenticateToRegistry ) {
432+ registryHost = process . env . TRIGGER_DOCKER_REGISTRY ?? extractRegistryHostFromImageTag ( imageTag ) ;
433+
434+ if ( ! registryHost ) {
435+ return {
436+ ok : false as const ,
437+ error : "Failed to extract registry host from image tag" ,
438+ logs : "" ,
439+ } ;
440+ }
441+
442+ const [ credentialsError , credentials ] = await tryCatch (
443+ getDockerUsernameAndPassword ( apiClient , deploymentId )
444+ ) ;
445+
446+ if ( credentialsError ) {
447+ return {
448+ ok : false as const ,
449+ error : `Failed to get docker credentials: ${ credentialsError . message } ` ,
450+ logs : "" ,
451+ } ;
452+ }
453+
454+ logger . debug ( `Logging in to docker registry: ${ registryHost } ` ) ;
455+
456+ const loginProcess = x (
457+ "docker" ,
458+ [ "login" , "--username" , credentials . username , "--password-stdin" , registryHost ] ,
459+ {
460+ nodeOptions : {
461+ cwd : options . cwd ,
462+ } ,
463+ }
464+ ) ;
465+
466+ loginProcess . process ?. stdin ?. write ( credentials . password ) ;
467+ loginProcess . process ?. stdin ?. end ( ) ;
468+
469+ for await ( const line of loginProcess ) {
470+ errors . push ( line ) ;
471+ logger . debug ( line ) ;
472+ options . onLog ?.( line ) ;
473+ }
474+
475+ if ( loginProcess . exitCode !== 0 ) {
476+ return {
477+ ok : false as const ,
478+ error : `Failed to login to registry: ${ registryHost } ` ,
479+ logs : extractLogs ( errors ) ,
480+ } ;
481+ }
482+
483+ options . onLog ?.( `Successfully logged in to ${ registryHost } ` ) ;
484+ }
485+
417486 const args = [
418487 "buildx" ,
419488 "build" ,
@@ -453,17 +522,16 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
453522 "--progress" ,
454523 "plain" ,
455524 "-t" ,
456- options . imageTag ,
525+ imageTag ,
457526 "." , // The build context
458527 ] . filter ( Boolean ) as string [ ] ;
459528
460529 logger . debug ( `docker ${ args . join ( " " ) } ` , { cwd : options . cwd } ) ;
461530
462- const errors : string [ ] = [ ] ;
463-
464- // Build the image
465531 const buildProcess = x ( "docker" , args , {
466- nodeOptions : { cwd : options . cwd } ,
532+ nodeOptions : {
533+ cwd : options . cwd ,
534+ } ,
467535 } ) ;
468536
469537 for await ( const line of buildProcess ) {
@@ -474,6 +542,11 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
474542 }
475543
476544 if ( buildProcess . exitCode !== 0 ) {
545+ if ( registryHost ) {
546+ logger . debug ( `Logging out from docker registry: ${ registryHost } ` ) ;
547+ await x ( "docker" , [ "logout" , registryHost ] ) ;
548+ }
549+
477550 return {
478551 ok : false as const ,
479552 error : "Error building image" ,
@@ -519,6 +592,11 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
519592 options . onLog ?.( `Image size: ${ ( imageSizeBytes / ( 1024 * 1024 ) ) . toFixed ( 2 ) } MB` ) ;
520593 }
521594
595+ if ( registryHost ) {
596+ logger . debug ( `Logging out from docker registry: ${ registryHost } ` ) ;
597+ await x ( "docker" , [ "logout" , registryHost ] ) ;
598+ }
599+
522600 return {
523601 ok : true as const ,
524602 imageSizeBytes,
@@ -840,6 +918,43 @@ function getAddHost(apiUrl: string) {
840918 return ;
841919}
842920
921+ function extractRegistryHostFromImageTag ( imageTag : string ) : string | undefined {
922+ const host = imageTag . split ( "/" ) [ 0 ] ;
923+
924+ if ( ! host || ! host . includes ( "." ) ) {
925+ return undefined ;
926+ }
927+
928+ return host ;
929+ }
930+
931+ async function getDockerUsernameAndPassword (
932+ apiClient : CliApiClient ,
933+ deploymentId : string
934+ ) : Promise < { username : string ; password : string } > {
935+ if ( process . env . TRIGGER_DOCKER_USERNAME && process . env . TRIGGER_DOCKER_PASSWORD ) {
936+ return {
937+ username : process . env . TRIGGER_DOCKER_USERNAME ,
938+ password : process . env . TRIGGER_DOCKER_PASSWORD ,
939+ } ;
940+ }
941+
942+ const result = await apiClient . generateRegistryCredentials ( deploymentId ) ;
943+
944+ if ( ! result . success ) {
945+ logger . debug ( "Failed to generate registry credentials" , {
946+ error : result . error ,
947+ deploymentId,
948+ } ) ;
949+ throw new Error ( "Failed to generate registry credentials" ) ;
950+ }
951+
952+ return {
953+ username : result . data . username ,
954+ password : result . data . password ,
955+ } ;
956+ }
957+
843958function isQemuRegistered ( ) {
844959 try {
845960 // Check a single QEMU handler
0 commit comments