1515 generate_hooks_cmd ,
1616 generate_install_cmd ,
1717 generate_upgrade_cmd ,
18+ jinja_env ,
1819)
1920
2021if TYPE_CHECKING :
2122 from ..container import ContainerManager
2223
24+ # Containerfile template - stages are conditionally included
25+ _CONTAINERFILE_TEMPLATE = jinja_env .from_string ("""\
26+ FROM {{ base_image }} AS init
2327
24- def get_boost_image_name ( name : str ) -> str :
25- """Get the name for a boosted image.
28+ # Marker for boosted image - distrobox-init will skip package setup
29+ RUN touch /.distrobox-boost
2630
27- Args:
28- name: Base container name
31+ # Upgrade existing packages
32+ RUN {{ upgrade_cmd }}
2933
30- Returns:
31- Image tag with distrobox-plus suffix
32- """
33- # Sanitize name to be a valid image tag
34+ # Install distrobox dependencies (conditional per-package check)
35+ RUN {{ install_cmd }}
36+
37+ {% for stage in stages %}
38+ FROM {{ stage.from }} AS {{ stage.name }}
39+
40+ # {{ stage.comment }}
41+ RUN {{ stage.run }}
42+
43+ {% endfor %}
44+ """ )
45+
46+
47+ def get_boost_image_name (name : str ) -> str :
48+ """Get the name for a boosted image."""
3449 safe_name = name .lower ().replace ("/" , "-" ).replace (":" , "-" )
3550 return f"{ safe_name } :distrobox-plus"
3651
@@ -41,24 +56,9 @@ def get_boost_image_tag(
4156 init_hooks : str = "" ,
4257 pre_init_hooks : str = "" ,
4358) -> str :
44- """Generate a unique image tag based on inputs.
45-
46- Creates a hash-based tag to enable caching of different configurations.
47-
48- Args:
49- base_image: Base image name
50- additional_packages: Space-separated additional packages
51- init_hooks: Init hooks command
52- pre_init_hooks: Pre-init hooks command
53-
54- Returns:
55- Unique image tag
56- """
57- # Create a hash of the configuration
59+ """Generate a unique image tag based on inputs."""
5860 content = f"{ base_image } |{ additional_packages } |{ init_hooks } |{ pre_init_hooks } "
5961 config_hash = hashlib .sha256 (content .encode ()).hexdigest ()[:12 ]
60-
61- # Sanitize base image name
6262 safe_name = base_image .lower ().replace ("/" , "-" ).replace (":" , "-" )
6363 return f"{ safe_name } -boost:{ config_hash } "
6464
@@ -71,83 +71,50 @@ def generate_containerfile(
7171) -> str :
7272 """Generate multi-stage Containerfile content for a boosted image.
7373
74- Creates a multi-stage build with conditional stages:
75- - Stage 1: init - Always present, installs base dependencies
76- - Stage 2: pre-hooks - Only if pre_init_hooks provided
77- - Stage 3: packages - Only if additional_packages provided
78- - Stage 4: runner - Only if init_hooks provided
79-
80- Each stage inherits from the previous existing stage, enabling
81- efficient layer caching by Docker/Podman.
82-
83- Args:
84- base_image: Base image to build from
85- additional_packages: Space-separated additional packages to install
86- init_hooks: Commands to run at end of init
87- pre_init_hooks: Commands to run at start of init
88-
89- Returns:
90- Containerfile content as string
74+ Creates a multi-stage build with conditional stages that chain together.
9175 """
92- lines : list [str ] = []
93- current_stage = "init"
94-
95- # Stage 1: init (always present)
96- lines .extend (
97- [
98- f"FROM { base_image } AS init" ,
99- "" ,
100- "# Marker for boosted image - distrobox-init will skip package setup" ,
101- "RUN touch /.distrobox-boost" ,
102- "" ,
103- "# Upgrade existing packages" ,
104- f"RUN { generate_upgrade_cmd ()} " ,
105- "" ,
106- "# Install distrobox dependencies (conditional per-package check)" ,
107- f"RUN { generate_install_cmd ()} " ,
108- "" ,
109- ]
110- )
76+ # Build stages list dynamically - each stage depends on the previous
77+ stages : list [dict [str , str ]] = []
78+ prev_stage = "init"
11179
112- # Stage 2: pre-hooks (conditional)
11380 if pre_init_hooks :
114- lines .extend (
115- [
116- f"FROM { current_stage } AS pre-hooks" ,
117- "" ,
118- "# Pre-init hooks" ,
119- f"RUN { generate_hooks_cmd (pre_init_hooks )} " ,
120- "" ,
121- ]
81+ stages .append (
82+ {
83+ "from" : prev_stage ,
84+ "name" : "pre-hooks" ,
85+ "comment" : "Pre-init hooks" ,
86+ "run" : generate_hooks_cmd (pre_init_hooks ),
87+ }
12288 )
123- current_stage = "pre-hooks"
89+ prev_stage = "pre-hooks"
12490
125- # Stage 3: packages (conditional)
12691 if additional_packages :
127- lines .extend (
128- [
129- f"FROM { current_stage } AS packages" ,
130- "" ,
131- "# Install additional packages" ,
132- f"RUN { generate_additional_packages_cmd (additional_packages )} " ,
133- "" ,
134- ]
92+ stages .append (
93+ {
94+ "from" : prev_stage ,
95+ "name" : "packages" ,
96+ "comment" : "Install additional packages" ,
97+ "run" : generate_additional_packages_cmd (additional_packages ),
98+ }
13599 )
136- current_stage = "packages"
100+ prev_stage = "packages"
137101
138- # Stage 4: runner (conditional)
139102 if init_hooks :
140- lines .extend (
141- [
142- f"FROM { current_stage } AS runner" ,
143- "" ,
144- "# Init hooks" ,
145- f"RUN { generate_hooks_cmd (init_hooks )} " ,
146- "" ,
147- ]
103+ stages .append (
104+ {
105+ "from" : prev_stage ,
106+ "name" : "runner" ,
107+ "comment" : "Init hooks" ,
108+ "run" : generate_hooks_cmd (init_hooks ),
109+ }
148110 )
149111
150- return "\n " .join (lines )
112+ return _CONTAINERFILE_TEMPLATE .render (
113+ base_image = base_image ,
114+ upgrade_cmd = generate_upgrade_cmd (),
115+ install_cmd = generate_install_cmd (),
116+ stages = stages ,
117+ )
151118
152119
153120def build_image (
0 commit comments