Skip to content

Commit 5280dda

Browse files
authored
Fix environment-only Dockerfile builds (#47)
* Fix environment-only Dockerfile builds * extract marker path to a constant
1 parent 657020d commit 5280dda

File tree

2 files changed

+117
-2
lines changed

2 files changed

+117
-2
lines changed

pkg/build/frontend.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,37 @@ func solvePlatform(ctx context.Context, bopts *BOpts, pl ocispecs.Platform, c ga
378378
return nil, nil, err
379379
}
380380

381-
// This only happens when the dockerfile is just `FROM scratch`
381+
// Handle metadata-only builds (ENV/ARG/LABEL without RUN/COPY/ADD)
382382
if ref == nil {
383-
return nil, nil, ErrNoBuildDirectives
383+
if img == nil {
384+
return nil, nil, ErrNoBuildDirectives
385+
}
386+
387+
// OCI manifests require a layers array (cannot be null)
388+
const metadataOnlyMarker = "/.container-metadata-only"
389+
stateWithLayer := llb.Scratch().
390+
File(llb.Mkfile(metadataOnlyMarker, 0644, []byte("# This image contains only metadata (ENV/ARG/LABEL)\n"))).
391+
Platform(pl)
392+
393+
layerDef, err := stateWithLayer.Marshal(ctx)
394+
if err != nil {
395+
return nil, nil, err
396+
}
397+
398+
layerResult, err := c.Solve(ctx, gateway.SolveRequest{
399+
Evaluate: false,
400+
Definition: layerDef.ToPB(),
401+
FrontendOpt: frontendOpt,
402+
FrontendInputs: frontendInputs,
403+
})
404+
if err != nil {
405+
return nil, nil, err
406+
}
407+
408+
ref, err = layerResult.SingleRef()
409+
if err != nil {
410+
return nil, nil, err
411+
}
384412
}
385413

386414
_, err = ref.ToState()

pkg/build/frontend_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,93 @@ COPY --from=builder /app /app`,
621621
expectedStates: 2,
622622
wantErr: false,
623623
},
624+
// Environment-only Dockerfile tests
625+
{
626+
name: "FROM scratch with only ENV directives",
627+
dockerfile: `FROM scratch
628+
629+
ARG BUILD_DATE
630+
ENV TERM=xterm \
631+
BUILD_DATE=${BUILD_DATE}`,
632+
buildPlatforms: []ocispecs.Platform{{OS: "linux", Architecture: "arm64"}},
633+
targetPlatform: ocispecs.Platform{OS: "linux", Architecture: "arm64"},
634+
buildArgs: map[string]string{"BUILD_DATE": "2025-01-01"},
635+
target: "",
636+
expectedResolverCalls: []testResolverCall{}, // No resolver calls for scratch
637+
expectedStates: 0,
638+
wantErr: false,
639+
errContains: "",
640+
},
641+
{
642+
name: "FROM alpine with only ENV and LABEL",
643+
dockerfile: `FROM alpine:latest
644+
ENV APP_VERSION=1.0.0 \
645+
APP_NAME=myapp
646+
LABEL maintainer="[email protected]" \
647+
version="1.0.0"`,
648+
buildPlatforms: []ocispecs.Platform{{OS: "linux", Architecture: "arm64"}},
649+
targetPlatform: ocispecs.Platform{OS: "linux", Architecture: "arm64"},
650+
buildArgs: map[string]string{},
651+
target: "",
652+
expectedResolverCalls: []testResolverCall{
653+
{ref: "alpine:latest", platform: &ocispecs.Platform{OS: "linux", Architecture: "arm64"}},
654+
},
655+
expectedStates: 1,
656+
wantErr: false,
657+
errContains: "",
658+
},
659+
{
660+
name: "Complex ARG and ENV combinations",
661+
dockerfile: `FROM scratch
662+
ARG JOBS=6
663+
ARG ARCH=amd64
664+
ENV MAKEOPTS="-j${JOBS}" \
665+
ARCH="${ARCH}" \
666+
PATH="/usr/local/bin:/usr/bin"`,
667+
buildPlatforms: []ocispecs.Platform{{OS: "linux", Architecture: "arm64"}},
668+
targetPlatform: ocispecs.Platform{OS: "linux", Architecture: "arm64"},
669+
buildArgs: map[string]string{"JOBS": "8", "ARCH": "arm64"},
670+
target: "",
671+
expectedResolverCalls: []testResolverCall{},
672+
expectedStates: 0,
673+
wantErr: false,
674+
errContains: "",
675+
},
676+
{
677+
name: "FROM scratch with only LABEL directives",
678+
dockerfile: `FROM scratch
679+
LABEL maintainer="[email protected]" \
680+
version="1.0.0" \
681+
description="Test image"`,
682+
buildPlatforms: []ocispecs.Platform{{OS: "linux", Architecture: "arm64"}},
683+
targetPlatform: ocispecs.Platform{OS: "linux", Architecture: "arm64"},
684+
buildArgs: map[string]string{},
685+
target: "",
686+
expectedResolverCalls: []testResolverCall{},
687+
expectedStates: 0,
688+
wantErr: false,
689+
errContains: "",
690+
},
691+
{
692+
name: "FROM debian with ENV ARG and LABEL mix",
693+
dockerfile: `FROM debian:bullseye
694+
ARG VERSION=1.0.0
695+
696+
ENV APP_VERSION=${VERSION} \
697+
PATH=/app/bin:$PATH
698+
LABEL maintainer="${MAINTAINER}" \
699+
version="${VERSION}"`,
700+
buildPlatforms: []ocispecs.Platform{{OS: "linux", Architecture: "amd64"}},
701+
targetPlatform: ocispecs.Platform{OS: "linux", Architecture: "amd64"},
702+
buildArgs: map[string]string{"VERSION": "2.0.0", "MAINTAINER": "[email protected]"},
703+
target: "",
704+
expectedResolverCalls: []testResolverCall{
705+
{ref: "debian:bullseye", platform: &ocispecs.Platform{OS: "linux", Architecture: "amd64"}},
706+
},
707+
expectedStates: 1,
708+
wantErr: false,
709+
errContains: "",
710+
},
624711
}
625712

626713
for _, tt := range tests {

0 commit comments

Comments
 (0)