Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -1587,6 +1587,11 @@ The following settings are available:
:::
: Forcefully apply compression option to all layers, including already existing layers (default: `false`).

`wave.build.conda.baseImage`
: :::{versionadded} 25.12.0-edge
:::
: 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`.

`wave.build.conda.basePackages`
: One or more Conda packages to be always added in the resulting container (default: `conda-forge::procps-ng`).

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

`wave.build.template`
: :::{versionadded} 25.12.0-edge
:::
: The build template to use for container builds (default: `conda/micromamba:v1`). Supported values:
: - `conda/micromamba:v1`: Standard Micromamba 1.x single-stage build. Default when unspecified.
: - `conda/micromamba:v2`: Micromamba 2.x with multi-stage builds.
: - `conda/pixi:v1`: Pixi package manager with multi-stage builds for optimized image sizes.
: - `cran/installr:v1`: R/CRAN packages using installr.
: Multi-stage templates produce smaller images by excluding build tools from the final image.

`wave.httpClient.connectTimeout`
: :::{versionadded} 22.06.0-edge
:::
Expand Down
34 changes: 34 additions & 0 deletions docs/wave.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,40 @@ conda.channels = 'conda-forge,bioconda'

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.

(wave-build-templates)=

### Build templates

:::{versionadded} 25.12.0-edge
:::

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.

Multi-stage build templates (`conda/pixi:v1` and `conda/micromamba:v2`) offer several advantages:

- **Smaller images**: Build tools and package managers are excluded from the final image (30-50% size reduction typical).
- **Reproducibility**: Lock files are generated for exact package version tracking.
- **Security**: Fewer binaries in the final image reduces the attack surface.

To use a specific build template, add the following to your configuration:

```groovy
wave.enabled = true
wave.strategy = ['conda']
wave.build.template = 'conda/pixi:v1'
```

Available templates:

| Template | Description |
|----------|-------------|
| `conda/micromamba:v1` | Standard Micromamba 1.x single-stage build. The final image includes the package manager. Default when unspecified. |
| `conda/micromamba:v2` | Multi-stage build using Micromamba 2.x. Produces smaller images without the package manager. |
| `conda/pixi:v1` | Multi-stage build using [Pixi](https://pixi.sh/) package manager. Produces smaller images with faster dependency resolution. |
| `cran/installr:v1` | Build template for R/CRAN packages using installr. |

When `wave.build.template` is not specified, Wave uses the standard `conda/micromamba:v1` template.

(wave-singularity)=

### Build Singularity native images
Expand Down
4 changes: 2 additions & 2 deletions plugins/nf-wave/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ dependencies {
api 'org.apache.commons:commons-compress:1.26.1'
api 'com.google.code.gson:gson:2.13.1'
api 'org.yaml:snakeyaml:2.2'
api 'io.seqera:wave-api:0.16.0'
api 'io.seqera:wave-utils:0.15.1'
api 'io.seqera:wave-api:1.31.1'
api 'io.seqera:wave-utils:1.31.1'

testImplementation(testFixtures(project(":nextflow")))
testImplementation "org.apache.groovy:groovy:4.0.29"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,9 @@ class SubmitContainerTokenRequest {
*/
BuildCompression buildCompression

/**
* The build template to use for container builds
*/
String buildTemplate

}
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,8 @@ class WaveClient {
mirror: config.mirrorMode(),
scanMode: config.scanMode(),
scanLevels: config.scanAllowedLevels(),
buildCompression: config.buildCompression()
buildCompression: config.buildCompression(),
buildTemplate: config.buildTemplate()
)
}

Expand Down Expand Up @@ -254,7 +255,8 @@ class WaveClient {
mirror: config.mirrorMode(),
scanMode: config.scanMode(),
scanLevels: config.scanAllowedLevels(),
buildCompression: config.buildCompression()
buildCompression: config.buildCompression(),
buildTemplate: config.buildTemplate()
)
return sendRequest(request)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ class WaveConfig implements ConfigScope {

String cacheRepository() { build.cacheRepository }

String buildTemplate() { build.template }

Duration buildMaxDuration() { build.maxDuration }

BuildCompression buildCompression() { build.compression }
Expand Down Expand Up @@ -275,6 +277,12 @@ class BuildOpts implements ConfigScope {
""")
final String cacheRepository

@ConfigOption
@Description("""
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.
""")
final String template

final CondaOpts conda

final BuildCompression compression
Expand All @@ -287,6 +295,7 @@ class BuildOpts implements ConfigScope {
BuildOpts(Map opts) {
repository = opts.repository
cacheRepository = opts.cacheRepository
template = opts.template
conda = new CondaOpts(opts.conda as Map ?: Collections.emptyMap())
compression = parseCompression(opts.compression as Map)
maxDuration = opts.maxDuration as Duration ?: Duration.of('40m')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,21 @@ class WaveConfigTest extends Specification {
def opts = new WaveConfig([:])
then:
opts.condaOpts().mambaImage == 'mambaorg/micromamba:1.5.10-noble'
opts.condaOpts().baseImage == 'ubuntu:24.04'
opts.condaOpts().commands == null

when:
opts = new WaveConfig([build:[conda:[mambaImage:'mambaorg/foo:1', commands:['USER hola']]]])
then:
opts.condaOpts().mambaImage == 'mambaorg/foo:1'
opts.condaOpts().commands == ['USER hola']


when:
opts = new WaveConfig([build:[conda:[baseImage:'debian:12', mambaImage:'mambaorg/micromamba:2-amazon2023']]])
then:
opts.condaOpts().baseImage == 'debian:12'
opts.condaOpts().mambaImage == 'mambaorg/micromamba:2-amazon2023'

}

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

def 'should get build template' () {
when:
def opts = new WaveConfig([:])
then:
opts.buildTemplate() == null

when:
opts = new WaveConfig([build:[template:'conda/pixi:v1']])
then:
opts.buildTemplate() == 'conda/pixi:v1'
}

@Unroll
def 'should set strategy' () {
when:
Expand Down Expand Up @@ -185,7 +204,7 @@ class WaveConfigTest extends Specification {
given:
def config = new WaveConfig([enabled: true])
expect:
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)'
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)'
}

def 'should not allow invalid setting' () {
Expand Down
4 changes: 4 additions & 0 deletions validation/wave-tests/example6/nextflow.config
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ fusion {
enabled = true
}

wave {
build.template = 'conda/pixi:v1'
}

Loading