diff --git a/docs/api.md b/docs/api.md index 07f44c392..5b36b40a7 100644 --- a/docs/api.md +++ b/docs/api.md @@ -4,7 +4,7 @@ title: API This page summarizes the API provided by the Wave container service. -**API limits** +## API limits The Wave service implements API rate limits for API calls. Authenticated users have higher rate limits than anonymous users. @@ -18,19 +18,42 @@ If an access token isn't provided, the following rate limits apply: - 25 container builds per day - 100 container pulls per hour -## POST `/container-token` +### How Wave pull rate limits work -Deprecated endpoint allows you to submit a request to access a private container registry via Wave, or build a container image on-the-fly with a Dockerfile or Conda recipe file. +When you pull a container image: -The endpoint returns the name of the container request made available by Wave. +1. The Docker service downloads the container manifest (a JSON file detailing each layer of the final image). +1. The Docker service downloads each layer listed in the manifest file. -:::important +Wave defines a pull as downloading both the container manifest and all layers. Therefore: -This API endpoint is deprecated in current versions of Wave. +- The manifest request to Wave counts as one pull against your rate limit +- Layer and blob requests don't count against rate limits +- A container image with 100 layers counts as 1 pull + +Rate limits affect pipelines with high concurrency. The following example demonstrates this issue: + +- 50 concurrent pipeline runs +- Each run spawns 10,000 tasks +- Each task runs on it's own VM +- Each task pulls a container image +- 500,000 manifest requests are made + +This volume exceeds the 2,000 container pulls per minute limit and causes failed tasks and pipeline errors. +## General API + +### POST `/container-token` + +:::warning +This API endpoint is deprecated in current versions of Wave. ::: -### Request body +Submit a request to access a private container registry via Wave, or build a container image on-the-fly with a Dockerfile or Conda recipe file. + +The endpoint returns the name of the container request made available by Wave. + +#### Request body ```json { @@ -65,7 +88,7 @@ This API endpoint is deprecated in current versions of Wave. } ``` -#### Container token request attributes +**Container token request attributes** | Attribute | Description | | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -90,7 +113,7 @@ This API endpoint is deprecated in current versions of Wave. | `towerAccessToken` | Access token of the user account granting access to the Seqera Platform service specified via `towerEndpoint` (optional). | | `towerWorkspaceId` | ID of the Seqera Platform workspace from where the container registry credentials are retrieved (optional). When omitted the personal workspace is used. | -### Response +#### Response ```json { @@ -100,19 +123,21 @@ This API endpoint is deprecated in current versions of Wave. } ``` +**Container token response attributes** + | Attribute | Description | | ---------------- | ---------------------------------------------------------------------------------------- | | `containerToken` | The unique token identifying the Wave container request, e.g., `0123456789`. | | `targetImage` | The Wave container image name, e.g., `wave.seqera.io/wt/0123456789/library/ubuntu:latest`. | | `expiration` | The expiration timestamp of the Wave container using ISO-8601 format. | -## POST `/v1alpha2/container` +### POST `/v1alpha2/container` -This endpoint allows you to submit a request to access a private container registry via Wave, or build a container image on-the-fly with a Dockerfile or Conda recipe file. +Submit a request to access a private container registry via Wave, or build a container image on-the-fly with a Dockerfile or Conda recipe file. -The endpoint returns the name of the container request made available by Wave. +Returns the name of the container request made available by Wave. -### Request body +#### Request body ```json { @@ -160,7 +185,7 @@ The endpoint returns the name of the container request made available by Wave. } ``` -#### Container token request attributes +**Container token request attributes** | Attribute | Description | | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -197,7 +222,7 @@ The endpoint returns the name of the container request made available by Wave. | `rImage` | Name of the R Docker image used to build CRAN containers (e.g., `rocker/r-ver:4.4.1`). | | `nameStrategy` | The name strategy to be used to create the name of the container built by Wave. Its values can be `none`, `tagPrefix`, or `imageSuffix`. | | -### Response +#### Response ```json { @@ -209,7 +234,7 @@ The endpoint returns the name of the container request made available by Wave. } ``` -#### Container token response attributes +**Container token response attributes** | Attribute | Description | | ---------------- | ---------------------------------------------------------------------------------------- | @@ -218,156 +243,156 @@ The endpoint returns the name of the container request made available by Wave. | `expiration` | The expiration timestamp of the Wave container using ISO-8601 format. | | `cached` | Indicates if the requested image is built or in progress. | -### Examples +#### Examples -1. Create Docker image with Conda packages: +- Create Docker image with Conda packages: -##### Request + **Request:** -```shell -curl --location 'http://localhost:9090/v1alpha2/container' \ ---header 'Content-Type: application/json' \ ---data '{ - "packages":{ - "type": "CONDA", - "entries": ["salmon", "bwa"], - "channels": ["conda-forge", "bioconda"] + ```shell + curl --location 'http://localhost:9090/v1alpha2/container' \ + --header 'Content-Type: application/json' \ + --data '{ + "packages":{ + "type": "CONDA", + "entries": ["salmon", "bwa"], + "channels": ["conda-forge", "bioconda"] + } + }' + ``` + + **Response** + + ```json + { + "containerToken":"732b73aa17c8", + "targetImage":"0625dce899da.ngrok.app/wt/732b73aa17c8/hrma017/dev:salmon_bwa--5e49881e6ad74121", + "expiration":"2024-04-09T21:19:01.715321Z", + "buildId":"5e49881e6ad74121_1", + "cached":false, + "freeze":false } -}' -``` - -#### Response - -```json -{ - "containerToken":"732b73aa17c8", - "targetImage":"0625dce899da.ngrok.app/wt/732b73aa17c8/hrma017/dev:salmon_bwa--5e49881e6ad74121", - "expiration":"2024-04-09T21:19:01.715321Z", - "buildId":"5e49881e6ad74121_1", - "cached":false, - "freeze":false -} -``` - -2. Create Singularity image with Conda packages: - -##### Request - -```shell -curl --location 'http://localhost:9090/v1alpha2/container' \ ---header 'Content-Type: application/json' \ ---data '{ - "format": "sif", - "containerPlatform": "arm64", - "packages":{ - "type": "CONDA", - "entries": ["salmon"], - "channels": ["conda-forge", "bioconda"] - }, - "freeze": true, - "buildRepository": , - "towerAccessToken":, - "towerEndpoint": "http://localhost:8008/api" -}' -``` - -#### Response - -```json -{ - "targetImage":"oras://:salmon--6c084f2e43f86a78", - "buildId":"6c084f2e43f86a78_1", - "cached":false, - "freeze":true -} -``` - -:::note -You must add your container registry credentials in Seqera Platform to use the freeze feature. This is a requirement for Singularity. -::: - -3. Create Docker image with CRAN packages: - -##### Request - -```shell -curl --location 'http://localhost:9090/v1alpha2/container' \ ---header 'Content-Type: application/json' \ ---data '{ - "packages":{ - "type": "CRAN", - "entries": ["dplyr", "ggplot2"], - "channels": ["cran"], - "cranOpts": { - "rImage": "rocker/r-ver:4.4.1", - "basePackages": "littler r-cran-docopt" + ``` + +- Create Singularity image with Conda packages: + + **Request** + + ```shell + curl --location 'http://localhost:9090/v1alpha2/container' \ + --header 'Content-Type: application/json' \ + --data '{ + "format": "sif", + "containerPlatform": "arm64", + "packages":{ + "type": "CONDA", + "entries": ["salmon"], + "channels": ["conda-forge", "bioconda"] + }, + "freeze": true, + "buildRepository": , + "towerAccessToken":, + "towerEndpoint": "http://localhost:8008/api" + }' + ``` + + **Response** + + ```json + { + "targetImage":"oras://:salmon--6c084f2e43f86a78", + "buildId":"6c084f2e43f86a78_1", + "cached":false, + "freeze":true + } + ``` + + :::note + To create Singularity images with the freeze feature, you must add your container registry credentials in Seqera Platform. + ::: + +- Create Docker image with CRAN packages: + + **Request** + + ```shell + curl --location 'http://localhost:9090/v1alpha2/container' \ + --header 'Content-Type: application/json' \ + --data '{ + "packages":{ + "type": "CRAN", + "entries": ["dplyr", "ggplot2"], + "channels": ["cran"], + "cranOpts": { + "rImage": "rocker/r-ver:4.4.1", + "basePackages": "littler r-cran-docopt" + } } + }' + ``` + + **Response** + + ```json + { + "requestId": "22d3c6c1cb06", + "containerToken": "22d3c6c1cb06", + "targetImage": "wave.seqera.io/wt/22d3c6c1cb06/wave/build:49b26ca0c3a07b1b", + "expiration": "2025-11-09T02:50:23.254497148Z", + "containerImage": "private.cr.seqera.io/wave/build:49b26ca0c3a07b1b", + "buildId": "bd-49b26ca0c3a07b1b_1", + "cached": false, + "freeze": false, + "mirror": false, + "scanId": "sc-a6acedfe6969f4bf_1" } -}' -``` - -#### Response - -```json -{ - "requestId": "22d3c6c1cb06", - "containerToken": "22d3c6c1cb06", - "targetImage": "wave.seqera.io/wt/22d3c6c1cb06/wave/build:49b26ca0c3a07b1b", - "expiration": "2025-11-09T02:50:23.254497148Z", - "containerImage": "private.cr.seqera.io/wave/build:49b26ca0c3a07b1b", - "buildId": "bd-49b26ca0c3a07b1b_1", - "cached": false, - "freeze": false, - "mirror": false, - "scanId": "sc-a6acedfe6969f4bf_1" -} -``` - -4. Create Singularity image with CRAN packages: + ``` + +- Create Singularity image with CRAN packages: + + **Request** + + ```shell + curl --location 'https://wave.seqera.io/v1alpha2/container' \ + --header 'Content-Type: application/json' \ + --data '{ + "format": "sif", + "containerPlatform": "linux/amd64", + "packages":{ + "type": "CRAN", + "entries": ["tidyverse", "data.table"], + "channels": ["cran"], + "cranOpts": { + "rImage": "rocker/r-ver:4.4.1", + "basePackages": "build-essential" + } + }, + "freeze": true, + "buildRepository": "", # hrma017/test + "towerAccessToken": "" + }' + ``` + + **Response** + + ```json + { + "requestId": "6706d70da258", + "targetImage": "oras://hrma017/test:a4fd48144607aaa7", + "containerImage": "oras://hrma017/test:a4fd48144607aaa7", + "buildId": "bd-a4fd48144607aaa7_1", + "freeze": true, + "mirror": false, + "succeeded": true + } + ``` -##### Request +### GET `/v1alpha1/builds/{buildId}/status` -```shell -curl --location 'https://wave.seqera.io/v1alpha2/container' \ ---header 'Content-Type: application/json' \ ---data '{ - "format": "sif", - "containerPlatform": "linux/amd64", - "packages":{ - "type": "CRAN", - "entries": ["tidyverse", "data.table"], - "channels": ["cran"], - "cranOpts": { - "rImage": "rocker/r-ver:4.4.1", - "basePackages": "build-essential" - } - }, - "freeze": true, - "buildRepository": "", # hrma017/test - "towerAccessToken": "" -}' -``` +Get status of build against `buildId` passed as path variable #### Response -```json -{ - "requestId": "6706d70da258", - "targetImage": "oras://hrma017/test:a4fd48144607aaa7", - "containerImage": "oras://hrma017/test:a4fd48144607aaa7", - "buildId": "bd-a4fd48144607aaa7_1", - "freeze": true, - "mirror": false, - "succeeded": true -} -``` - -## GET `/v1alpha1/builds/{buildId}/status` - -Provides status of build against buildId passed as path variable - -### Response - ```json { id: string, @@ -382,7 +407,7 @@ Provides status of build against buildId passed as path variable Status can only be `PENDING` or `COMPLETED`. ::: -### Example +#### Examples ```shell % curl --location 'http://localhost:9090/v1alpha1/builds/6c084f2e43f86a78_1/status' @@ -395,19 +420,19 @@ Status can only be `PENDING` or `COMPLETED`. } ``` -## GET `/v1alpha1/builds/{buildId}/logs` +### GET `/v1alpha1/builds/{buildId}/logs` Supply logs corresponding to the specified buildId within the API request. -### Response +#### Response ```text string ``` -### Example +#### Examples -``` +```shell % curl --location 'http://localhost:9090/v1alpha1/builds//logs' INFO[0001] Retrieving image manifest alpine:latest INFO[0001] Retrieving image alpine:latest from registry index.docker.io @@ -425,11 +450,11 @@ INFO[0002] Pushing image to / INFO[0005] Pushed index.docker.io// ``` -## GET `/service-info` +### GET `/service-info` -Provides basic information about the service status. +Get basic information about the service status. -### Response +#### Response ```json { @@ -440,11 +465,11 @@ Provides basic information about the service status. } ``` -## POST `/v1alpha1/inspect` +### POST `/v1alpha1/inspect` -This endpoint returns the metadata about provided container image +Returns the metadata about provided container image -### Request +#### Request ```json { @@ -455,7 +480,7 @@ This endpoint returns the metadata about provided container image } ``` -#### Container inspect request attributes +**Container inspect request attributes** | Attribute | Description | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -464,7 +489,7 @@ This endpoint returns the metadata about provided container image | `towerAccessToken` | Access token of the user account granting the access to the Seqera Platform service specified via `towerEndpoint` (optional). | | `towerWorkspaceId` | ID of the Seqera Platform workspace from where the container registry credentials are retrieved (optional). When omitted the personal workspace is used.| -### Response +#### Response ```json { @@ -519,94 +544,92 @@ This endpoint returns the metadata about provided container image You can find the explanation of the response attributes (here)[https://github.com/opencontainers/image-spec/blob/main/spec.md] ::: -### Example - -#### API call - -```shell -curl --location 'http://localhost:9090/v1alpha1/inspect' \ ---header 'Content-Type: application/json' \ ---data '{ - "containerImage": "docker.io//", - "towerAccessToken": "", - "towerEndpoint": "http://localhost:8000/api" -}' -``` - -##### Response +#### Examples -```json -{ - "container": { - "registry": "docker.io", - "hostName": "https://registry-1.docker.io", - "imageName": "//", - "reference": "9b80535d04eceefd", - "digest": "sha256:1fcabdb850dc7c46646b3796fca01aca5721330252b586058e0d326705374dd5", - "config": { - "architecture": "amd64", +- API call + + **Request** + + ```shell + curl --location 'http://localhost:9090/v1alpha1/inspect' \ + --header 'Content-Type: application/json' \ + --data '{ + "containerImage": "docker.io//", + "towerAccessToken": "", + "towerEndpoint": "http://localhost:8000/api" + }' + ``` + + **Response** + + ```json + { + "container": { + "registry": "docker.io", + "hostName": "https://registry-1.docker.io", + "imageName": "//", + "reference": "9b80535d04eceefd", + "digest": "sha256:1fcabdb850dc7c46646b3796fca01aca5721330252b586058e0d326705374dd5", "config": { - "env": [ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - ], - "cmd": [ - "/bin/sh" - ], - "image": "sha256:9a5ce069f40cfe0f2270eafbff0a0f2fa08f1add73571af9f78209e96bb8a5e9" + "architecture": "amd64", + "config": { + "env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "cmd": [ + "/bin/sh" + ], + "image": "sha256:9a5ce069f40cfe0f2270eafbff0a0f2fa08f1add73571af9f78209e96bb8a5e9" + }, + "container": "4189cbc534955765760c227f328ec1cdd52e8550681c2bf9f8f990b27b644f9c", + "created": "2024-04-19T14:38:17.047396956Z", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:d4fc045c9e3a848011de66f34b81f052d4f2c15a17bb196d637e526349601820" + ] + } }, - "container": "4189cbc534955765760c227f328ec1cdd52e8550681c2bf9f8f990b27b644f9c", - "created": "2024-04-19T14:38:17.047396956Z", - "rootfs": { - "type": "layers", - "diff_ids": [ - "sha256:d4fc045c9e3a848011de66f34b81f052d4f2c15a17bb196d637e526349601820" + "manifest": { + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "digest": "sha256:639823e18eb8b62cf43e92bac114ae35c03c07449e4ee5c10f8ebf8d033877d6", + "size": 774 + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "digest": "sha256:4abcf20661432fb2d719aaf90656f55c287f8ca915dc1c92ec14ff61e67fbaf8", + "size": 3408729 + } ] - } - }, - "manifest": { - "schemaVersion": 2, - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "config": { - "mediaType": "application/vnd.docker.container.image.v1+json", - "digest": "sha256:639823e18eb8b62cf43e92bac114ae35c03c07449e4ee5c10f8ebf8d033877d6", - "size": 774 }, - "layers": [ - { - "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "digest": "sha256:4abcf20661432fb2d719aaf90656f55c287f8ca915dc1c92ec14ff61e67fbaf8", - "size": 3408729 - } - ] - }, - "v1": false, - "v2": true, - "oci": false + "v1": false, + "v2": true, + "oci": false + } } -} -``` + ``` -## Metrics APIs based on Redis +## Metrics API -These APIs provide usage (builds and pulls) metrics of Wave for a specific date and/or a specific organization. -These APIs require basic authentication, so you must provide a username and password while calling these APIs. +These APIs provide usage (builds and pulls) metrics of Wave for a specific date and/or a organization. +They require basic authentication (i.e., a username and password). All Metrics API endpoints use these query parameters: | Name | Description | sample Value | |------|-----------------------------------------------------------------------|--------------| | date | Format: `yyyy-mm-dd`, The date of the required metrics. | 2024-04-08 | -| org | Domain of the organization used in emails, e.g., `org=seqera.io` | seqera.io | - -### Build Metrics API - -These APIs are used to retrieve metrics about container builds performed by Wave. +| org | Domain of the organization used in emails, e.g., `org=seqera.io` | seqera.io | ### GET `/v1alpha2/metrics/builds` -This endpoint is used to retrieve the builds performed by Wave. +Get the builds performed by Wave. -### Response +#### Response ```json { @@ -622,54 +645,53 @@ This endpoint is used to retrieve the builds performed by Wave. #### Examples -```shell -curl -u foo:bar "http://localhost:9090/v1alpha2/metrics/builds" -{ - "metric": "builds", - "count": 18, - "orgs": { - "seqera.io": 13, - "gmail.com": 5 - } -} -``` - -```shell -curl -u foo:bar "http://localhost:9090/v1alpha2/metrics/builds?date=2024-04-08&org=seqera.io" -{"count":4} -``` + **Requests** -```shell -curl -u foo:bar "http://localhost:9090/v1alpha2/metrics/builds?date=2024-04-08" -{ - "metric": "builds", - "count": 8, - "orgs": { - "gmail.com": 4, - "seqera.io": 4 + ```shell + curl -u foo:bar "http://localhost:9090/v1alpha2/metrics/builds" + { + "metric": "builds", + "count": 18, + "orgs": { + "seqera.io": 13, + "gmail.com": 5 + } } -} -``` - -```shell -curl -u foo:bar "http://localhost:9090/v1alpha2/metrics/builds?org=seqera.io" -{ - "metric": "builds", - "count": 13, - "orgs": { - "seqera.io": 13 + ``` + + ```shell + curl -u foo:bar "http://localhost:9090/v1alpha2/metrics/builds?date=2024-04-08&org=seqera.io" + {"count":4} + ``` + + ```shell + curl -u foo:bar "http://localhost:9090/v1alpha2/metrics/builds?date=2024-04-08" + { + "metric": "builds", + "count": 8, + "orgs": { + "gmail.com": 4, + "seqera.io": 4 + } } -} -``` -### Pull Metrics API - -These APIs are used to get the metrics about the container pulls through Wave. + ``` + + ```shell + curl -u foo:bar "http://localhost:9090/v1alpha2/metrics/builds?org=seqera.io" + { + "metric": "builds", + "count": 13, + "orgs": { + "seqera.io": 13 + } + } + ``` ### GET `/v1alpha2/metrics/pulls` -This endpoint is used to get the pulls performed through Wave. +Get the pulls performed through Wave. -### Response +#### Response ```json { @@ -725,15 +747,11 @@ curl -u foo:bar "http://localhost:9090/v1alpha2/metrics/pulls?org=seqera.io" } ``` -### Fusion Pull Metrics API - -These APIs are used to get the metrics about the Fusion-based container pulls through Wave. - ### GET `/v1alpha2/metrics/fusion/pulls` -This endpoint is used to get the pulls of Fusion-based containers performed through Wave. +Get the pulls of Fusion-based containers performed through Wave. -### Response +#### Response ```json { diff --git a/docs/nextflow/reduce-api-calls.md b/docs/nextflow/reduce-api-calls.md new file mode 100644 index 000000000..554b90d44 --- /dev/null +++ b/docs/nextflow/reduce-api-calls.md @@ -0,0 +1,118 @@ +--- +title: Reducing Wave API calls +description: Learn how to configure Nextflow to freeze containers and reduce API calls +date created: 2025-09-30 +date edited: 2025-10-31 +tags: [nextflow, wave, rate limits, guides] +--- + +Large-scale pipelines that pull container images across thousands of concurrent tasks can encounter Wave rate limits. Wave freeze solves this by building your container once and storing it in your registry, so your head job communicates with Wave only once per container. Without freeze, all tasks communicate with Wave for every container used within your pipeline. Depending on your specific pipeline configuration, this may result in a large volume of concurrent requests that trigger rate limits. + +:::note +Wave applies rate limits to container builds and pulls (manifest requests). Authenticated users have higher rate limits than anonymous users. See [API limits](../api.md#api-limits) for more information. +::: + +## How Wave freeze avoids rate limits + +Wave freeze provisions container images on-demand with the following characteristics: + +- Containers are built on-demand from a user-provided Dockerfile or Conda packages +- They have stable (non-ephemeral) container names +- They are stored in the container repository specified by `wave.build.repository` +- Build cache layers are stored in the repository specified by `wave.build.cacheRepository` + +After the initial build, Wave redirects the container manifest and layers to your private registry, so subsequent requests pull directly from your registry instead of making repeated Wave API calls. + +### Building without Wave freeze + +When you run your pipeline without Wave freeze: + +1. Each task requests a manifest from Wave +1. Wave performs one of the following actions: + - Retrieves the base image manifest from the source registry + - Builds the image from a Dockerfile + - Builds the image from a Conda definition +1. Wave injects the Fusion layer to the container image manifest +1. Wave stores the final manifest on Seqera infrastructure +1. Wave returns the modified manifest + +This approach exceeds rate limits with thousands of concurrent tasks. + +### Building with Wave freeze + +When you run your pipeline with Wave freeze for the first time: + +1. The Nextflow head job sends your container request to Wave +1. Wave checks whether the requested image already exist + - The content hash does not match +1. Wave builds of the container +1. Wave stores the container in your destination container registry +1. Wave returns the final registry URLs +1. Your compute tasks pull images directly from your registry + +When you run your pipeline with Wave freeze again: + +1. The Nextflow head job sends your build request to Wave +1. Wave checks whether the requested image already exist + - The content hash matches the previous build +1. Wave returns the container URLs in the destination container registry without rebuilding +1. Nextflow tasks pull the image directly from your registry + +With freeze enabled, only the first API call to Wave counts toward your quota. +Wave reuses frozen images as long as the image and its configuration remain the same. +This prevents rate limit issues because manifest requests happen at the registry level, not through Wave. + +:::note +For pipelines with stable containers, you can prevent Wave API calls by pre-resolving URLs with [`nextflow inspect`](https://nextflow.io/docs/latest/reference/cli.html#inspect) or [Wave CLI](../cli/index.md), then using the resolved registry URLs directly in your configuration. Keep Wave enabled during active development or when using dynamic container features to build container images at runtime. +::: + +## Configure Wave freeze + +To configure Wave freeze, add the following configuration to your Nextflow pipeline: + +```groovy +fusion.enabled = true // Recommended (optimizes frozen images for cloud storage) +tower.accessToken = '' // Required +wave.enabled = true // Required +wave.freeze = true // Required +wave.build.repository = '' // Required +wave.build.cacheRepository = '' // Recommended (accelerates builds by reusing unchanged layers) +``` + +Replace the following: + +- ``: Your [Platform access token](../tutorials/nextflow-wave.mdx#create-your-seqera-access-token) +- ``: The container registry URL where Wave uploads built images +- ``: The container registry URL for caching image layers built by the Wave service + +## Container image tags + +**Recommended**: Use specific version tags (such as `ubuntu:22.04`) or SHA256 digests with Wave freeze. + +Specific tags enable Wave to match content hashes and reuse frozen images. +This ensures reproducibility and eliminates unnecessary rebuilds. +Avoid using the `latest` tag because it points to different image versions over time. + +## Container registry selection + +**Recommended**: Use your cloud provider's native container registry for the simplest setup and integration. + +Native cloud registries have the following benefits: + +- Automatic authentication through cloud IAM roles +- Low latency for workloads in the same cloud region +- Simple setup and configuration +- Native integration with your cloud platform + +Examples of native registries by cloud provider: + +- **AWS**: Amazon Elastic Container Registry (ECR) +- **Azure**: Azure Container Registry (ACR) +- **Google Cloud**: Google Artifact Registry + +**Alternative option**: Third-party container registries. + +Third-party registries (e.g., Docker Hub or Quay.io) require additional setup and have the following requirements: + +- Manual credential configuration on each compute instance +- Public endpoints for Wave to connect to