@@ -32,56 +32,23 @@ func BuildDistrolessContainer(ctx context.Context, input BuildDistrolessContaine
3232
3333 withRepos := input .Config .RepoMounts (repos , input .SOpt , opts ... )
3434
35- debug := llb .Scratch ().File (llb .Mkfile ("debug" , 0o644 , []byte (`debug=2` )), opts ... )
36- // This file makes dpkg give more verbose output which can be useful when things go awry.
37- debugOpt := llb .AddMount ("/etc/dpkg/dpkg.cfg.d/99-dalec-debug" , debug , llb .SourcePath ("debug" ), llb .Readonly )
38-
39- // Distroless images are built from scratch.
35+ // Step 1: Bootstrap base image structure from scratch
4036 baseImg := llb .Scratch ().
41- File (llb .Mkfile ("/apt.conf" , 0o644 , []byte (`#Apt::Architecture "amd64";
42- #Apt::Architectures "amd64";
43- Dir "/tmp/rootfs";
44- Dir::Etc::TrustedParts "/etc/apt/trusted.gpg.d/";
45- Dir::Etc::sourcelist "/etc/apt/sources.list";
46- Dir::Etc::sourceparts "/etc/apt/sources.list.d/";
47- ` )), opts ... ).
4837 File (llb .Mkdir ("/etc" , 0o755 ), opts ... ).
4938 File (llb .Mkdir ("/etc/apt" , 0o755 ), opts ... ).
5039 File (llb .Mkdir ("/etc/apt/apt.conf.d" , 0o755 ), opts ... ).
5140 File (llb .Mkdir ("/etc/apt/preferences.d" , 0o755 ), opts ... ).
41+ File (llb .Mkdir ("/etc/apt/sources.list.d" , 0o755 ), opts ... ).
5242 File (llb .Mkdir ("/var" , 0o755 ), opts ... ).
5343 File (llb .Mkdir ("/var/cache" , 0o755 ), opts ... ).
44+ File (llb .Mkdir ("/var/cache/apt" , 0o755 ), opts ... ).
45+ File (llb .Mkdir ("/var/cache/apt/archives" , 0o755 ), opts ... ).
5446 File (llb .Mkdir ("/var/lib" , 0o755 ), opts ... ).
5547 File (llb .Mkdir ("/var/lib/dpkg" , 0o755 ), opts ... ).
56- File (llb .Mkfile ("/var/lib/dpkg/status" , 0o755 , []byte {}), opts ... )
57-
58- baseImg = input .Worker .Run (
59- dalec .WithConstraints (opts ... ),
60- withRepos ,
61- debugOpt ,
62- llb .AddEnv ("DEBIAN_FRONTEND" , "noninteractive" ),
63- llb .AddEnv ("APT_CONFIG" , "/tmp/rootfs/apt.conf" ),
64- dalec .ShArgs ("set -x; cp -r /etc/apt/sources.list* /tmp/rootfs/etc/apt/ && apt-get update && apt-get --yes --download-only install $(dpkg-query -Wf '${Package} ${Essential}\n ' | awk '$2 == \" yes\" {print $1}') && for f in /tmp/rootfs/var/cache/apt/archives/*.deb; do dpkg-deb --extract \" $f\" /tmp/rootfs; done" ),
65- frontend .IgnoreCache (input .Client , targets .IgnoreCacheKeyContainer ),
66- ).AddMount ("/tmp/rootfs" , baseImg ).Run (
67- dalec .WithConstraints (opts ... ),
68- debugOpt ,
69- llb .AddEnv ("DEBIAN_FRONTEND" , "noninteractive" ),
70- dalec .ShArgs ("dpkg --install --force-depends /var/cache/apt/archives/*.deb && rm -rf /var/cache/apt/archives/*.deb" ),
71- frontend .IgnoreCache (input .Client , targets .IgnoreCacheKeyContainer ),
72- ).Root ()
73-
74- bi , err := input .Spec .GetSingleBase (input .Target )
75- if err != nil {
76- return dalec .ErrorState (llb .Scratch (), err )
77- }
78- if bi != nil {
79- baseImg = bi .ToState (input .SOpt , opts ... )
80- }
81-
82- opts = append (opts , dalec .ProgressGroup ("Install spec package" ))
48+ File (llb .Mkfile ("/var/lib/dpkg/status" , 0o644 , []byte {}), opts ... )
8349
84- // If we have base packages to install, create a meta-package to install them.
50+ // Step 2: Build base packages if configured
51+ var basePkg llb.State
8552 if len (input .Config .BasePackages ) > 0 {
8653 runtimePkgs := make (dalec.PackageDependencyList , len (input .Config .BasePackages ))
8754 for _ , pkgName := range input .Config .BasePackages {
@@ -97,30 +64,79 @@ Dir::Etc::sourceparts "/etc/apt/sources.list.d/";
9764 Runtime : runtimePkgs ,
9865 },
9966 }
67+ basePkg = input .Config .BuildPkg (ctx , input .Client , input .SOpt , basePkgSpec , input .Target , opts ... )
68+ } else {
69+ basePkg = llb .Scratch ()
70+ }
10071
101- basePkg := input .Config .BuildPkg (ctx , input .Client , input .SOpt , basePkgSpec , input .Target , opts ... )
72+ // Step 3: Use worker to download all packages + deps and install into baseImg
73+ // Worker has apt-get, dpkg, etc. while baseImg is just empty directories
74+ const installScript = `#!/bin/sh
75+ set -ex
10276
103- // Update the base image to include the base packages.
104- // This may include things that are necessary to even install the debSt package.
105- // So this must be done separately from the debSt package.
106- opts := append (opts , dalec .ProgressGroup ("Install base image packages" ))
107- baseImg = baseImg .Run (
108- dalec .WithConstraints (opts ... ),
109- debugOpt ,
110- withRepos ,
111- InstallLocalPkg (basePkg , true , opts ... ),
112- dalec .WithMountedAptCache (input .Config .AptCachePrefix , opts ... ),
113- ).Root ()
114- }
77+ # Ensure apt cache directory exists
78+ mkdir -p /var/cache/apt/archives
11579
116- return baseImg .Run (
80+ # Copy local packages (base + spec) to apt cache
81+ cp /base-packages/*.deb /var/cache/apt/archives/
82+ cp /spec-packages/*.deb /var/cache/apt/archives/
83+
84+ apt-get update
85+
86+ # Download essential packages and all dependencies for our local packages
87+ # Get full recursive dependency tree (including already-installed packages)
88+ # Extract dependencies directly from local .deb files (they aren't in apt repo)
89+ local_deps=$(for f in /var/cache/apt/archives/*.deb; do dpkg-deb -f "$f" Depends 2>/dev/null; done | tr ',' '\n' | sed 's/([^)]*)//g' | tr -d ' ' | grep -v '^$' | sort -u)
90+ all_deps=$(apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances \
91+ $(dpkg-query -Wf '${Package} ${Essential}\n' | awk '$2 == "yes" {print $1}') \
92+ $local_deps \
93+ | grep "^\w" | sort -u)
94+ apt-get --yes --download-only --reinstall install $all_deps
95+
96+ # Extract all packages into the target rootfs
97+ for f in /var/cache/apt/archives/*.deb; do
98+ dpkg-deb --extract "$f" /tmp/rootfs
99+ done
100+
101+ cp /var/cache/apt/archives/*.deb /tmp/rootfs/var/cache/apt/archives/
102+ `
103+
104+ script := llb .Scratch ().File (llb .Mkfile ("install.sh" , 0o755 , []byte (installScript )), opts ... )
105+
106+ baseImg = input .Worker .Run (
117107 dalec .WithConstraints (opts ... ),
108+ llb .AddMount ("/tmp/install.sh" , script , llb .SourcePath ("install.sh" )),
109+ llb .AddMount ("/base-packages" , basePkg , llb .Readonly ),
110+ llb .AddMount ("/spec-packages" , input .DebSt , llb .Readonly ),
118111 withRepos ,
112+ llb .AddEnv ("DEBIAN_FRONTEND" , "noninteractive" ),
113+ dalec .ShArgs ("/tmp/install.sh" ),
114+ frontend .IgnoreCache (input .Client , targets .IgnoreCacheKeyContainer ),
115+ ).AddMount ("/tmp/rootfs" , baseImg )
116+
117+ // Allow spec to override the base image entirely
118+ bi , err := input .Spec .GetSingleBase (input .Target )
119+ if err != nil {
120+ return dalec .ErrorState (llb .Scratch (), err )
121+ }
122+ if bi != nil {
123+ baseImg = bi .ToState (input .SOpt , opts ... )
124+ }
125+
126+ debug := llb .Scratch ().File (llb .Mkfile ("debug" , 0o644 , []byte (`debug=2` )), opts ... )
127+ debugOpt := llb .AddMount ("/etc/dpkg/dpkg.cfg.d/99-dalec-debug" , debug , llb .SourcePath ("debug" ), llb .Readonly )
128+
129+ // Run dpkg --install to properly configure the packages
130+ // Use /usr/bin/dash explicitly since /bin/sh symlink doesn't exist yet
131+ baseImg = baseImg .Run (
132+ dalec .WithConstraints (opts ... ),
119133 debugOpt ,
120- InstallLocalPkg (input .DebSt , true , opts ... ),
134+ llb .AddEnv ("DEBIAN_FRONTEND" , "noninteractive" ),
135+ llb .Args ([]string {"/usr/bin/sh" , "-c" , "dpkg --install --force-depends /var/cache/apt/archives/*.deb && rm -rf /var/cache/apt/archives/*.deb" }),
121136 frontend .IgnoreCache (input .Client , targets .IgnoreCacheKeyContainer ),
122- ).Root ().
123- With (dalec .InstallPostSymlinks (input .Spec .GetImagePost (input .Target ), input .Worker , opts ... ))
137+ ).Root ()
138+
139+ return baseImg .With (dalec .InstallPostSymlinks (input .Spec .GetImagePost (input .Target ), input .Worker , opts ... ))
124140}
125141
126142func (c * Config ) BuildContainer (ctx context.Context , client gwclient.Client , sOpt dalec.SourceOpts , spec * dalec.Spec , targetKey string , debSt llb.State , opts ... llb.ConstraintsOpt ) llb.State {
0 commit comments