@@ -204,7 +204,7 @@ func (s *Spec) Compile(fs billy.Filesystem, devcontainerDir, scratchDir string,
204204 // We should make a best-effort attempt to find the user.
205205 // Features must be executed as root, so we need to swap back
206206 // to the running user afterwards.
207- params .User , err = UserFromDockerfile (params .DockerfileContent )
207+ params .User , err = UserFromDockerfile (params .DockerfileContent , params . BuildArgs )
208208 if err != nil {
209209 return nil , fmt .Errorf ("user from dockerfile: %w" , err )
210210 }
@@ -308,12 +308,42 @@ func (s *Spec) compileFeatures(fs billy.Filesystem, devcontainerDir, scratchDir
308308
309309// UserFromDockerfile inspects the contents of a provided Dockerfile
310310// and returns the user that will be used to run the container.
311- func UserFromDockerfile (dockerfileContent string ) (user string , err error ) {
311+ func UserFromDockerfile (dockerfileContent string , buildArgs [] string ) (user string , err error ) {
312312 res , err := parser .Parse (strings .NewReader (dockerfileContent ))
313313 if err != nil {
314314 return "" , fmt .Errorf ("parse dockerfile: %w" , err )
315315 }
316316
317+ // Collect ARG values (defaults + overrides from buildArgs) for
318+ // substitution into FROM image refs.
319+ lexer := shell .NewLex ('\\' )
320+ var argEnvs []string
321+ for _ , child := range res .AST .Children {
322+ if strings .EqualFold (child .Value , "arg" ) {
323+ arg := strings .TrimSpace (child .Original [len ("ARG " ):])
324+ if strings .Contains (arg , "=" ) {
325+ parts := strings .SplitN (arg , "=" , 2 )
326+ key := parts [0 ]
327+ val := parts [1 ]
328+ // Allow buildArgs to override.
329+ for _ , ba := range buildArgs {
330+ if k , v , ok := strings .Cut (ba , "=" ); ok && k == key {
331+ val = v
332+ break
333+ }
334+ }
335+ argEnvs = append (argEnvs , key + "=" + val )
336+ } else {
337+ for _ , ba := range buildArgs {
338+ if k , v , ok := strings .Cut (ba , "=" ); ok && k == arg {
339+ argEnvs = append (argEnvs , k + "=" + v )
340+ break
341+ }
342+ }
343+ }
344+ }
345+ }
346+
317347 // Parse stages and user commands to determine the relevant user
318348 // from the final stage.
319349 var (
@@ -330,6 +360,11 @@ func UserFromDockerfile(dockerfileContent string) (user string, err error) {
330360
331361 switch i := inst .(type ) {
332362 case * instructions.Stage :
363+ // Substitute ARG values in the base image name.
364+ baseName , _ , err := lexer .ProcessWord (i .BaseName , shell .EnvsFromSlice (argEnvs ))
365+ if err == nil {
366+ i .BaseName = baseName
367+ }
333368 stages = append (stages , i )
334369 if i .Name != "" {
335370 stageNames [i .Name ] = i
@@ -388,7 +423,7 @@ func UserFromDockerfile(dockerfileContent string) (user string, err error) {
388423
389424// ImageFromDockerfile inspects the contents of a provided Dockerfile
390425// and returns the image that will be used to run the container.
391- func ImageFromDockerfile (dockerfileContent string ) (name.Reference , error ) {
426+ func ImageFromDockerfile (dockerfileContent string , buildArgs [] string ) (name.Reference , error ) {
392427 lexer := shell .NewLex ('\\' )
393428 var args []string
394429 var imageRef string
@@ -408,7 +443,22 @@ func ImageFromDockerfile(dockerfileContent string) (name.Reference, error) {
408443 if err != nil {
409444 return nil , fmt .Errorf ("processing %q: %w" , line , err )
410445 }
446+ // Allow buildArgs to override Dockerfile ARG defaults.
447+ for _ , ba := range buildArgs {
448+ if k , v , ok := strings .Cut (ba , "=" ); ok && k == key {
449+ val = v
450+ break
451+ }
452+ }
411453 args = append (args , key + "=" + val )
454+ } else {
455+ // ARG without a default — look up in buildArgs.
456+ for _ , ba := range buildArgs {
457+ if k , v , ok := strings .Cut (ba , "=" ); ok && k == arg {
458+ args = append (args , k + "=" + v )
459+ break
460+ }
461+ }
412462 }
413463 continue
414464 }
0 commit comments