Skip to content

Commit d08a895

Browse files
pditommasoclaudechristopher-hakkaart
authored
Add wave.build.template config option (#6639) [ci fast]
* Add wave.build.template config option Add support for specifying build templates in Wave container builds. This allows users to select optimized multi-stage build templates like `conda/pixi/v1` or `conda/micromamba/v2` for smaller container images. Supported templates: - conda/pixi/v1: Pixi package manager with multi-stage builds - conda/micromamba/v2: Micromamba 2.x with multi-stage builds - cran/installr/v1: R/CRAN packages using installr 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Clarify default build template is conda/micromamba/v1 [ci skip] * Update docs/reference/config.md [ci skip] Co-authored-by: Chris Hakkaart <chris.hakkaart@seqera.io> Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com> * Update docs/wave.md [ci skip] Co-authored-by: Chris Hakkaart <chris.hakkaart@seqera.io> Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com> * Fix wave.build.template format to use colon separator Update build template syntax from `<scope>/<name>/<version>` to `<scope>/<name>:<version>` to match the Wave API specification. Examples: - conda/pixi:v1 - conda/micromamba:v1 - conda/micromamba:v2 - cran/installr:v1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com> * Add wave.build.conda.baseImage config option Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com> --------- Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Chris Hakkaart <chris.hakkaart@seqera.io>
1 parent 59c4f4e commit d08a895

File tree

8 files changed

+94
-6
lines changed

8 files changed

+94
-6
lines changed

docs/reference/config.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1587,6 +1587,11 @@ The following settings are available:
15871587
:::
15881588
: Forcefully apply compression option to all layers, including already existing layers (default: `false`).
15891589

1590+
`wave.build.conda.baseImage`
1591+
: :::{versionadded} 25.12.0-edge
1592+
:::
1593+
: The base image for the final stage in multi-stage Conda container builds (default: `ubuntu:24.04`). This option only applies when using `wave.build.template` set to `conda/micromamba:v2` or `conda/pixi:v1`.
1594+
15901595
`wave.build.conda.basePackages`
15911596
: One or more Conda packages to be always added in the resulting container (default: `conda-forge::procps-ng`).
15921597

@@ -1600,6 +1605,16 @@ The following settings are available:
16001605
: The container repository where images built by Wave are uploaded.
16011606
: The corresponding credentials must be provided in your Seqera Platform account.
16021607

1608+
`wave.build.template`
1609+
: :::{versionadded} 25.12.0-edge
1610+
:::
1611+
: The build template to use for container builds (default: `conda/micromamba:v1`). Supported values:
1612+
: - `conda/micromamba:v1`: Standard Micromamba 1.x single-stage build. Default when unspecified.
1613+
: - `conda/micromamba:v2`: Micromamba 2.x with multi-stage builds.
1614+
: - `conda/pixi:v1`: Pixi package manager with multi-stage builds for optimized image sizes.
1615+
: - `cran/installr:v1`: R/CRAN packages using installr.
1616+
: Multi-stage templates produce smaller images by excluding build tools from the final image.
1617+
16031618
`wave.httpClient.connectTimeout`
16041619
: :::{versionadded} 22.06.0-edge
16051620
:::

docs/wave.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,40 @@ conda.channels = 'conda-forge,bioconda'
9292

9393
Packages from the [Python Package Index](https://pypi.org/) can also be added to a Conda `environment.yml` file. See {ref}`Conda and PyPI <conda-pypi>` for more information.
9494

95+
(wave-build-templates)=
96+
97+
### Build templates
98+
99+
:::{versionadded} 25.12.0-edge
100+
:::
101+
102+
Wave supports different build templates for creating container images from Conda packages. Build templates control how packages are installed and how the final container image is structured.
103+
104+
Multi-stage build templates (`conda/pixi:v1` and `conda/micromamba:v2`) offer several advantages:
105+
106+
- **Smaller images**: Build tools and package managers are excluded from the final image (30-50% size reduction typical).
107+
- **Reproducibility**: Lock files are generated for exact package version tracking.
108+
- **Security**: Fewer binaries in the final image reduces the attack surface.
109+
110+
To use a specific build template, add the following to your configuration:
111+
112+
```groovy
113+
wave.enabled = true
114+
wave.strategy = ['conda']
115+
wave.build.template = 'conda/pixi:v1'
116+
```
117+
118+
Available templates:
119+
120+
| Template | Description |
121+
|----------|-------------|
122+
| `conda/micromamba:v1` | Standard Micromamba 1.x single-stage build. The final image includes the package manager. Default when unspecified. |
123+
| `conda/micromamba:v2` | Multi-stage build using Micromamba 2.x. Produces smaller images without the package manager. |
124+
| `conda/pixi:v1` | Multi-stage build using [Pixi](https://pixi.sh/) package manager. Produces smaller images with faster dependency resolution. |
125+
| `cran/installr:v1` | Build template for R/CRAN packages using installr. |
126+
127+
When `wave.build.template` is not specified, Wave uses the standard `conda/micromamba:v1` template.
128+
95129
(wave-singularity)=
96130

97131
### Build Singularity native images

plugins/nf-wave/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ dependencies {
5656
api 'org.apache.commons:commons-compress:1.26.1'
5757
api 'com.google.code.gson:gson:2.13.1'
5858
api 'org.yaml:snakeyaml:2.2'
59-
api 'io.seqera:wave-api:0.16.0'
60-
api 'io.seqera:wave-utils:0.15.1'
59+
api 'io.seqera:wave-api:1.31.1'
60+
api 'io.seqera:wave-utils:1.31.1'
6161

6262
testImplementation(testFixtures(project(":nextflow")))
6363
testImplementation "org.apache.groovy:groovy:4.0.29"

plugins/nf-wave/src/main/io/seqera/wave/plugin/SubmitContainerTokenRequest.groovy

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,9 @@ class SubmitContainerTokenRequest {
154154
*/
155155
BuildCompression buildCompression
156156

157+
/**
158+
* The build template to use for container builds
159+
*/
160+
String buildTemplate
161+
157162
}

plugins/nf-wave/src/main/io/seqera/wave/plugin/WaveClient.groovy

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,8 @@ class WaveClient {
226226
mirror: config.mirrorMode(),
227227
scanMode: config.scanMode(),
228228
scanLevels: config.scanAllowedLevels(),
229-
buildCompression: config.buildCompression()
229+
buildCompression: config.buildCompression(),
230+
buildTemplate: config.buildTemplate()
230231
)
231232
}
232233

@@ -254,7 +255,8 @@ class WaveClient {
254255
mirror: config.mirrorMode(),
255256
scanMode: config.scanMode(),
256257
scanLevels: config.scanAllowedLevels(),
257-
buildCompression: config.buildCompression()
258+
buildCompression: config.buildCompression(),
259+
buildTemplate: config.buildTemplate()
258260
)
259261
return sendRequest(request)
260262
}

plugins/nf-wave/src/main/io/seqera/wave/plugin/config/WaveConfig.groovy

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ class WaveConfig implements ConfigScope {
139139

140140
String cacheRepository() { build.cacheRepository }
141141

142+
String buildTemplate() { build.template }
143+
142144
Duration buildMaxDuration() { build.maxDuration }
143145

144146
BuildCompression buildCompression() { build.compression }
@@ -275,6 +277,12 @@ class BuildOpts implements ConfigScope {
275277
""")
276278
final String cacheRepository
277279

280+
@ConfigOption
281+
@Description("""
282+
The build template to use for container builds. Supported values: `conda/pixi:v1` (Pixi with multi-stage builds), `conda/micromamba:v2` (Micromamba 2.x with multi-stage builds), `cran/installr:v1` (R/CRAN packages). Default: standard conda/micromamba:v1 template.
283+
""")
284+
final String template
285+
278286
final CondaOpts conda
279287

280288
final BuildCompression compression
@@ -287,6 +295,7 @@ class BuildOpts implements ConfigScope {
287295
BuildOpts(Map opts) {
288296
repository = opts.repository
289297
cacheRepository = opts.cacheRepository
298+
template = opts.template
290299
conda = new CondaOpts(opts.conda as Map ?: Collections.emptyMap())
291300
compression = parseCompression(opts.compression as Map)
292301
maxDuration = opts.maxDuration as Duration ?: Duration.of('40m')

plugins/nf-wave/src/test/io/seqera/wave/plugin/config/WaveConfigTest.groovy

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,21 @@ class WaveConfigTest extends Specification {
8989
def opts = new WaveConfig([:])
9090
then:
9191
opts.condaOpts().mambaImage == 'mambaorg/micromamba:1.5.10-noble'
92+
opts.condaOpts().baseImage == 'ubuntu:24.04'
9293
opts.condaOpts().commands == null
9394

9495
when:
9596
opts = new WaveConfig([build:[conda:[mambaImage:'mambaorg/foo:1', commands:['USER hola']]]])
9697
then:
9798
opts.condaOpts().mambaImage == 'mambaorg/foo:1'
9899
opts.condaOpts().commands == ['USER hola']
99-
100+
101+
when:
102+
opts = new WaveConfig([build:[conda:[baseImage:'debian:12', mambaImage:'mambaorg/micromamba:2-amazon2023']]])
103+
then:
104+
opts.condaOpts().baseImage == 'debian:12'
105+
opts.condaOpts().mambaImage == 'mambaorg/micromamba:2-amazon2023'
106+
100107
}
101108

102109
def 'should get build and cache repos' () {
@@ -113,6 +120,18 @@ class WaveConfigTest extends Specification {
113120
opts.cacheRepository() == 'some/cache'
114121
}
115122

123+
def 'should get build template' () {
124+
when:
125+
def opts = new WaveConfig([:])
126+
then:
127+
opts.buildTemplate() == null
128+
129+
when:
130+
opts = new WaveConfig([build:[template:'conda/pixi:v1']])
131+
then:
132+
opts.buildTemplate() == 'conda/pixi:v1'
133+
}
134+
116135
@Unroll
117136
def 'should set strategy' () {
118137
when:
@@ -185,7 +204,7 @@ class WaveConfigTest extends Specification {
185204
given:
186205
def config = new WaveConfig([enabled: true])
187206
expect:
188-
config.toString() == 'WaveConfig(build:BuildOpts(repository:null, cacheRepository:null, conda:CondaOpts(mambaImage=mambaorg/micromamba:1.5.10-noble; basePackages=conda-forge::procps-ng, commands=null), compression:null, maxDuration:40m), enabled:true, endpoint:https://wave.seqera.io, freeze:false, httpClient:HttpOpts(), mirror:false, retryPolicy:RetryOpts(delay:450ms, maxDelay:1m 30s, maxAttempts:5, jitter:0.25, multiplier:2.0, delayAsDuration:PT0.45S, maxDelayAsDuration:PT1M30S), scan:ScanOpts(allowedLevels:null, mode:null), strategy:[container, dockerfile, conda], bundleProjectResources:null, containerConfigUrl:[], preserveFileTimestamp:null, tokensCacheMaxDuration:30m)'
207+
config.toString() == 'WaveConfig(build:BuildOpts(repository:null, cacheRepository:null, template:null, conda:CondaOpts(mambaImage=mambaorg/micromamba:1.5.10-noble; basePackages=conda-forge::procps-ng, commands=null, baseImage=ubuntu:24.04), compression:null, maxDuration:40m), enabled:true, endpoint:https://wave.seqera.io, freeze:false, httpClient:HttpOpts(), mirror:false, retryPolicy:RetryOpts(delay:450ms, maxDelay:1m 30s, maxAttempts:5, jitter:0.25, multiplier:2.0, delayAsDuration:PT0.45S, maxDelayAsDuration:PT1M30S), scan:ScanOpts(allowedLevels:null, mode:null), strategy:[container, dockerfile, conda], bundleProjectResources:null, containerConfigUrl:[], preserveFileTimestamp:null, tokensCacheMaxDuration:30m)'
189208
}
190209

191210
def 'should not allow invalid setting' () {

validation/wave-tests/example6/nextflow.config

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ fusion {
77
enabled = true
88
}
99

10+
wave {
11+
build.template = 'conda/pixi:v1'
12+
}
13+

0 commit comments

Comments
 (0)