From b23595a455f971c90084b4029a3e7ef8fc98aa31 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 28 Jan 2026 17:34:05 +0100 Subject: [PATCH 001/180] shared: update Mise --- shared/mise.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/mise.md b/shared/mise.md index 25d285479..5fd574ea9 100644 --- a/shared/mise.md +++ b/shared/mise.md @@ -1,7 +1,7 @@ ### Install tools with Mise package manager -Mise package manager is a powerful tool to install and manage dependencies on Clever Cloud. Just add a `mise.toml` file at the root of your project or set the `CC_MISE_FILE_PATH` environment variable. All tools will be installed at the defined version before the build phase and available for your scripts. You can also use Mise to define [environment variables](https://mise.jdx.dev/environments/) and alias ([tasks](https://mise.jdx.dev/tasks/)) in a declarative way. +[Mise](https://mise.jdx.dev) is available on all Clever Cloud runtimes to install and manage tools and dependencies. Add a `mise.toml` file at the root of your project and all defined tools are installed at the specified version before the build phase. To place this file in a subdirectory, set the `CC_MISE_FILE_PATH` environment variable to its relative path (e.g. `config/mise.toml`). You can also use Mise to define [environment variables](https://mise.jdx.dev/environments/) and [tasks](https://mise.jdx.dev/tasks/) in a declarative way. -To disable Mise, set `CC_DISABLE_MISE` environment variable to `true`. +To disable Mise tool installation, set `CC_DISABLE_MISE` environment variable to `true`. -- [Learn more about Mise package manager](https://mise.jdx.dev/configuration.html) +- [Learn more about Mise configuration](https://mise.jdx.dev/configuration.html) From f05fb1d992dca365fa7084477c0ac872b934d2f9 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 28 Jan 2026 17:34:40 +0100 Subject: [PATCH 002/180] shared: add Request Flow --- shared/request-flow.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 shared/request-flow.md diff --git a/shared/request-flow.md b/shared/request-flow.md new file mode 100644 index 000000000..145239f86 --- /dev/null +++ b/shared/request-flow.md @@ -0,0 +1,14 @@ +## Request Flow: Varnish, Redirection.io, custom proxy + +Request Flow automatically chains reverse proxies between port `8080` (public) and your application, managing port allocation with no manual configuration. Supported services are activated by their presence in your project: + +- **Varnish**: add a `clevercloud/varnish.vcl` file or set `CC_VARNISH_FILE` +- **Redirection.io**: set `CC_REDIRECTIONIO_PROJECT_KEY` + +Both can be active simultaneously. To control the order, set `CC_REQUEST_FLOW` (e.g. `redirectionio,varnish`). To add a custom middleware, include `custom` in the chain and define `CC_REQUEST_FLOW_CUSTOM` with `@@LISTEN_PORT@@` and `@@FORWARD_PORT@@` placeholders. To block public access, set `CC_REQUEST_FLOW=block`. + +When at least one middleware is active, your application must listen on port `9000` instead of `8080`. + +- [Learn more about Request Flow](/doc/develop/request-flow/) +- [Learn more about Varnish on Clever Cloud](/doc/administrate/cache/) +- [Learn more about Redirection.io](https://redirection.io/) From 815dee3b9243ecd2624065036c74f4d0361e52fd Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 28 Jan 2026 17:36:44 +0100 Subject: [PATCH 003/180] develop: add Request Flow --- content/doc/develop/request-flow.md | 102 ++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 content/doc/develop/request-flow.md diff --git a/content/doc/develop/request-flow.md b/content/doc/develop/request-flow.md new file mode 100644 index 000000000..57bccd695 --- /dev/null +++ b/content/doc/develop/request-flow.md @@ -0,0 +1,102 @@ +--- +type: docs +linkTitle: Request Flow +title: Request Flow +description: Automatically chain reverse proxies and middleware (Varnish, Redirection.io, custom) in front of your application with Request Flow on Clever Cloud +keywords: +- request flow +- reverse proxy +- varnish +- redirection.io +- middleware +- port configuration +aliases: +- /doc/request-flow +--- + +## Overview + +Request Flow is Clever Cloud's automatic middleware chaining mechanism. It configures reverse proxies and services between the public port (`8080`) and your application, managing port allocation automatically. There is no need to manually configure listening ports for each service. + +Request Flow is available in the following runtimes: + +- [FrankenPHP](/doc/applications/frankenphp/) +- [Linux](/doc/applications/linux/) +- [Python with uv](/doc/applications/python/uv/) +- [Static](/doc/applications/static/) +- [V (Vlang)](/doc/applications/v/) + +## Supported services + +| Service | Activation | Description | +|---------|-----------|-------------| +| `varnish` | `clevercloud/varnish.vcl` file or `CC_VARNISH_FILE` | HTTP cache accelerator | +| `redirectionio` | `CC_REDIRECTIONIO_PROJECT_KEY` | HTTP redirects, rewrites, SEO | +| `custom` | `CC_REQUEST_FLOW_CUSTOM` | Any custom reverse proxy | + +## Automatic detection + +When no `CC_REQUEST_FLOW` is set, Clever Cloud detects and activates services automatically: + +- If a `clevercloud/varnish.vcl` file exists (or `CC_VARNISH_FILE` is set), Varnish is activated +- If `CC_REDIRECTIONIO_PROJECT_KEY` is set, Redirection.io is activated + +Both can be active simultaneously. Default order: Varnish first, then Redirection.io. + +## Port management + +Request Flow allocates ports in a chain from port `8080` (public) down to the application: + +- With no middleware: your application listens directly on port `8080` +- With one middleware: the middleware listens on `8080`, forwards to your application on port `9000` +- With two middleware: first listens on `8080`, forwards to second on `8081`, which forwards to the application on `9000` + +Your application must listen on port `8080` when no middleware is active, or on port `9000` when at least one middleware is configured. + +> [!NOTE] +> In runtimes where Clever Cloud manages the port configuration (FrankenPHP, Static), port allocation is handled transparently with no additional configuration. + +## Explicit configuration with CC_REQUEST_FLOW + +To control the order or selection of middleware, set `CC_REQUEST_FLOW` to a comma-separated list of services: + +```bash +CC_REQUEST_FLOW="redirectionio,varnish" +``` + +This inverts the default order: Redirection.io listens on `8080`, forwards to Varnish on `8081`, which forwards to the application on `9000`. + +### Disable Request Flow + +To disable Request Flow entirely and have your application listen directly on port `8080`: + +```bash +CC_REQUEST_FLOW="disable" +``` + +## Custom middleware + +To insert a custom reverse proxy in the chain, add `custom` to `CC_REQUEST_FLOW` and define the command with `CC_REQUEST_FLOW_CUSTOM`. The deployment process replaces `@@LISTEN_PORT@@` and `@@FORWARD_PORT@@` placeholders with the actual allocated ports: + +```bash +CC_REQUEST_FLOW="redirectionio,custom,varnish" +CC_REQUEST_FLOW_CUSTOM="./my-proxy --listen @@LISTEN_PORT@@ --forward @@FORWARD_PORT@@" +``` + +In this example: +- Redirection.io listens on `8080`, forwards to custom middleware on `8081` +- Custom middleware listens on `8081`, forwards to Varnish on `8082` +- Varnish listens on `8082`, forwards to the application on `9000` + +## Environment variables reference + +| Name | Description | +|------|-------------| +| `CC_REQUEST_FLOW` | Comma-separated list of middleware to chain (e.g. `varnish,redirectionio`). Special values: `disable`, `block` | +| `CC_REQUEST_FLOW_CUSTOM` | Command to start a custom middleware. Must contain `@@LISTEN_PORT@@` and `@@FORWARD_PORT@@` placeholders | +| `CC_REDIRECTIONIO_PROJECT_KEY` | Redirection.io project key. Activates Redirection.io in the request flow | +| `CC_VARNISH_FILE` | Path to a custom Varnish VCL file (default: `clevercloud/varnish.vcl`) | + +- [Learn more about Varnish on Clever Cloud](/doc/administrate/cache/) +- [Learn more about Redirection.io](https://redirection.io/) +- [Learn more about Network Groups](/doc/develop/network-groups/) From e814eda1ae5992d97ff6a55ec45b55ea690ce9a5 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 28 Jan 2026 17:45:24 +0100 Subject: [PATCH 004/180] data(runtime_versions): add more tools --- data/runtime_versions.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/data/runtime_versions.yml b/data/runtime_versions.yml index 7e8aeb3cd..e7be6456b 100644 --- a/data/runtime_versions.yml +++ b/data/runtime_versions.yml @@ -3,6 +3,11 @@ bun: default: - 1.3.7 +caddy: + eol_source: https://github.com/caddyserver/caddy/releases + default: + - "2.10.2" + dotnet: eol_source: https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core default: @@ -25,6 +30,15 @@ elixir: - 1.18 - 1.19 +frankenphp: + eol_source: https://github.com/dunglas/frankenphp/releases + default: + - "1.9.1" + caddy: + - "2.10.2" + php: + - "8.4.12" + java: eol_source: https://adoptium.net/fr/support/ default: @@ -64,6 +78,16 @@ php: - 8.4 - 8.5 +sws: + eol_source: https://github.com/static-web-server/static-web-server/releases + default: + - "2.40.1" + +v: + eol_source: https://github.com/vlang/v/releases + default: + - "0.5" + python: eol_source: https://devguide.python.org/versions/#python-release-cycle default: From 6bbe198cfca5544231ce18379f960d039c6ffb3f Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 28 Jan 2026 17:46:53 +0100 Subject: [PATCH 005/180] aplications(linux): add Makefile, Request Flow --- content/doc/applications/linux.md | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/content/doc/applications/linux.md b/content/doc/applications/linux.md index 647644ddd..36dbfec9b 100644 --- a/content/doc/applications/linux.md +++ b/content/doc/applications/linux.md @@ -30,25 +30,28 @@ To create a new Linux application, use the [Clever Cloud Console](https://consol ```bash clever create --type linux ``` -* [Learn more about Clever Tools](/doc/cli/) -* [Learn more about Clever Cloud application deployment](/doc/quickstart/#create-an-application-step-by-step) +- [Learn more about Clever Tools](/doc/cli/) +- [Learn more about Clever Cloud application deployment](/doc/quickstart/#create-an-application-step-by-step) ## Configure your Linux application ### Mandatory needs -Linux runtime only requires a `CC_RUN_COMMAND` to execute, with a working web application listening on `0.0.0.0:8080`. +Linux runtime requires a run command (through `CC_RUN_COMMAND`, a Mise `run` task, or a Makefile `run:` target) and a working web application listening on `0.0.0.0:8080`. -* [Learn more about environment variables on Clever Cloud](/doc/reference/reference-environment-variables/) +- [Learn more about environment variables on Clever Cloud](/doc/reference/reference-environment-variables/) -### Build phase +### Build and run commands -During the build phase, Clever Cloud will run the `CC_BUILD_COMMAND` if provided. You can use it to install dependencies, compile your code, or any other task you need to perform before running your application. +Build and run commands are resolved in this priority order: -- [Learn more about Deployment hooks](/doc/develop/build-hooks/) +1. `CC_BUILD_COMMAND` and `CC_RUN_COMMAND` environment variables +2. [Mise](https://mise.jdx.dev/tasks/) `build` and `run` tasks (from `mise.toml` or [File Tasks](https://mise.jdx.dev/tasks/#tasks-in-mise-toml-files)) +3. Makefile `build:` and `run:` targets (searches `GNUmakefile`, `Makefile`, `makefile` or the file defined in `CC_MAKEFILE`) + +Each level fills in only the commands not already defined by a higher priority source. For example, you can define `CC_BUILD_COMMAND` and let Mise or a Makefile provide the `run` command. -> [!TIP] Use Mise package manager to define build/run commands -> If you define `build` and `run` tasks in the `mise.toml` file [or as File Tasks](https://mise.jdx.dev/tasks/#tasks-in-mise-toml-files), Clever Cloud will automatically use them. `CC_BUILD_COMMAND` and `CC_RUN_COMMAND` have precedence over the `build` and `run` tasks defined by Mise. +- [Learn more about Deployment hooks](/doc/develop/build-hooks/) ## Clever Task and Multi-runtime approach @@ -71,5 +74,5 @@ clever deploy # or clever restart if there is no code change - [Deploy a Swift application with Mise](https://github.com/CleverCloud/swift-hello-world-example) - [Deploy a Zig application with Mise](https://github.com/CleverCloud/zig-with-mise-example) -{{% content "redirectionio" %}} -{{% content "varnish" %}} +{{% content "url_healthcheck" %}} +{{% content "request-flow" %}} From e9466697d88c450262c14bc67fe95bfea75fcdba Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 28 Jan 2026 17:47:30 +0100 Subject: [PATCH 006/180] aplications(v): add dependencies, Request Flow --- content/doc/applications/v.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/content/doc/applications/v.md b/content/doc/applications/v.md index 88e192813..4d4816d49 100644 --- a/content/doc/applications/v.md +++ b/content/doc/applications/v.md @@ -26,8 +26,8 @@ To create a new V (Vlang) application, use the [Clever Cloud Console](https://co ```bash clever create --type v ``` -* [Learn more about Clever Tools](/doc/cli/) -* [Learn more about Clever Cloud application deployment](/doc/quickstart/#create-an-application-step-by-step) +- [Learn more about Clever Tools](/doc/cli/) +- [Learn more about Clever Cloud application deployment](/doc/quickstart/#create-an-application-step-by-step) ## Configure your V (Vlang) application @@ -35,17 +35,26 @@ clever create --type v V (Vlang) runtime only requires a working application listening on `0.0.0.0:8080`. -* [Learn more about environment variables on Clever Cloud](/doc/reference/reference-environment-variables/) +- [Learn more about environment variables on Clever Cloud](/doc/reference/reference-environment-variables/) + +### Dependencies + +If a `v.mod` file exists at the root of your project, dependencies are installed with `v install` before compilation. Modules are stored in the `VMODULES` directory (default: `/home/bas/.vmodules/`) and cached between deployments. ### Build phase During the build phase, the V (Vlang) application is built with the `v . -prod` command. To compile without the `-prod` flag, set `ENVIRONMENT=development`. You can choose a custom output binary name with the `CC_V_BINARY` environment variable, default is `${APP_HOME}/v_bin_${APP_ID}`. - [Deploy an example V application on Clever Cloud](https://github.com/CleverCloud/v-example) +- [Learn more about Deployment hooks](/doc/develop/build-hooks/) + +### Custom run command + +By default, the compiled binary is executed directly. To override this behavior, set `CC_RUN_COMMAND`. ### V (Vlang) version and tools -The currently deployed version of V (Vlang) on Clever Cloud is `0.4.11`. +The currently deployed version of V (Vlang) on Clever Cloud is `{{< runtime_version v >}}`. ## V scripts (.vsh), Clever Tasks @@ -58,5 +67,5 @@ clever deploy # or clever restart if there is no code change - [Learn more about Clever Tasks](/doc/develop/tasks/) -{{% content "redirectionio" %}} -{{% content "varnish" %}} +{{% content "url_healthcheck" %}} +{{% content "request-flow" %}} From 7c489990416382f72bdf9cd6f14528ff7bc10787 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 28 Jan 2026 17:49:51 +0100 Subject: [PATCH 007/180] aplications(static): add details, Request Flow --- content/doc/applications/static.md | 63 +++++++++++++++++------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/content/doc/applications/static.md b/content/doc/applications/static.md index 7cc1c98f8..35b7ef2cd 100644 --- a/content/doc/applications/static.md +++ b/content/doc/applications/static.md @@ -41,8 +41,8 @@ To create a new Static application, use the [Clever Cloud Console](https://conso ```bash clever create --type static ``` -* [Learn more about Clever Tools](/doc/cli/) -* [Learn more about Clever Cloud application deployment](/doc/quickstart/#create-an-application-step-by-step) +- [Learn more about Clever Tools](/doc/cli/) +- [Learn more about Clever Cloud application deployment](/doc/quickstart/#create-an-application-step-by-step) ## Configure your Static application @@ -50,7 +50,7 @@ clever create --type static Static runtime only requires a working web application, with an `index.htm` or `index.html` file. If you need to serve files from a specific directory, set the `CC_WEBROOT` environment variable, relative to the root of your project (for example `/public`, default is `/`). -* [Learn more about environment variables on Clever Cloud](/doc/reference/reference-environment-variables/) +- [Learn more about environment variables on Clever Cloud](/doc/reference/reference-environment-variables/) ### Build phase @@ -63,13 +63,17 @@ During the build phase, Clever Cloud will run the `CC_BUILD_COMMAND` if provided When [auto-build](#static-site-generators-ssg-auto-build) activates, or if you define `CC_WEBROOT`, the build cache contains only some configuration files and the served directory to optimize size, reduce archive time. When neither option applies, the system caches the entire application root directory instead. -To override this behavior, set the `CC_OVERRIDE_BUILD_CACHE` environment variable with a colon-separated list of directories and files, relative to the application root. For example: `CC_OVERRIDE_BUILD_CACHE=myScript.sh:/myBuildDir`. +To override this behavior, set the `CC_OVERRIDE_BUILDCACHE` environment variable with a colon-separated list of directories and files, relative to the application root. For example: `CC_OVERRIDE_BUILDCACHE=myScript.sh:/myBuildDir`. ## Supported web servers -By default, the Rust-based [Static Web Server (SWS)](https://static-web-server.net) serves your website. If a valid Caddyfile is present at the root of your project, it will be used with [Caddy](https://caddyserver.com) and the `caddy run` command, you can also set its location with `CC_STATIC_CADDYFILE` (Default is `./Caddyfile`). +By default, [Static Web Server (SWS)](https://static-web-server.net) `{{< runtime_version sws >}}` serves your website. If a valid Caddyfile is present at the root of your project, it will be used with [Caddy](https://caddyserver.com) `{{< runtime_version caddy >}}` and the `caddy run` command, you can also set its location with `CC_STATIC_CADDYFILE` (Default is `./Caddyfile`). -You can force the use of Caddy by setting the `CC_STATIC_SERVER` environment variable to `caddy`. It configures your application to serve the website with the `caddy file-server` command which don't rely on a Caddyfile. +You can force the use of Caddy by setting the `CC_STATIC_SERVER` environment variable to `caddy`. It configures your application to serve the website with the `caddy file-server` command which doesn't rely on a Caddyfile. + +## Custom run command + +To override the default web server behavior, set the `CC_RUN_COMMAND` environment variable. When defined, it takes priority over the static server command. This is useful to run a custom server or a script before serving files. ## Custom configuration and port @@ -86,55 +90,60 @@ Caddy and SWS can be configured with a configuration file or through environment ## Static Site Generators (SSG) Auto-build -If you don't set a `CC_BUILD_COMMAND`, Clever Cloud try to detect and configure the Static Site Generator (SSG) through the presence of specific files in the project root. If detected the static website is built in the `cc_static_autobuilt` folder (or `CC_STATIC_AUTOBUILD_OUTDIR`), used as `CC_WEBROOT` and build cache content. If you defined a `CC_WEBROOT`, it will be used instead of `cc_static_autobuilt`. +If you don't set a `CC_BUILD_COMMAND`, Clever Cloud tries to detect and configure the Static Site Generator (SSG) through the presence of specific files in the project root. If detected, the static website is built in the `cc_static_autobuilt` folder (or `CC_STATIC_AUTOBUILD_OUTDIR`), used as `CC_WEBROOT` and build cache content. If you defined a `CC_WEBROOT`, it will be used instead of `cc_static_autobuilt`. Supported Static Site Generators (SSG) are: ### Astro -* Build command: `npm i && npm run astro build -- --outDir ` -* Detected file: `astro.config.mjs`, `astro.config.ts`, `astro.config.js`, `astro.config.cjs` +- Build command: `npm i && npm run astro build -- --outDir ` +- Detected file: `astro.config.mjs`, `astro.config.ts`, `astro.config.js`, `astro.config.cjs` ### Docusaurus -* Build command: `npm i && npm run docusaurus build -- --out-dir ` -* Detected file: `docusaurus.config.js`, `docusaurus.config.ts` +- Build command: `npm i && npm run docusaurus build -- --out-dir ` +- Detected file: `docusaurus.config.js`, `docusaurus.config.ts` ### Hugo -* Build command: `hugo --gc --minify --destination ` -* Detected file: `hugo.json`, `hugo.toml`, `hugo.yaml` +- Build command: `hugo --gc --minify --destination ` +- Detected file: `hugo.toml`, `hugo.yaml`, `hugo.json` > [!TIP] Set the Hugo version >Use a specific Hugo version by setting the `CC_HUGO_VERSION` environment variable to `0.147`, `0.148`, `0.149` (default), `0.150`, `0.151` or `0.152` ### mdBook -* Build command: `mdbook build --dest-dir ` -* Detected file: `book.toml` +- Build command: `mdbook build --dest-dir ` +- Detected file: `book.toml` -### Mkdocs +### MkDocs -* Build command: `uvx mkdocs build --site-dir ` -* Detected file: `mkdocs.yml` +- Build command: `uvx mkdocs build --site-dir ` +- Detected file: `mkdocs.yml` ### Nuxt.js -* Build command: `npm i && npm run generate && mv .output/public ` -* Detected file: `nuxt.config.ts` +- Build command: `npm i && npm run generate && mv .output/public ` +- Detected file: `nuxt.config.ts` + +### Storybook + +- Build command: `npm i && npm run build-storybook -- --output-dir ` +- Detected file: `.storybook/main.js`, `.storybook/main.ts` -### Vitepress +### VitePress -* Build command: `npm i && npm run docs:build -- --outDir ` -* Detected file: `vitepress.config.js`, `vitepress.config.ts`, `vitepress.config.mjs`, `vitepress.config.mts` +- Build command: `npm i && npm run docs:build -- --outDir ` +- Detected file: `.vitepress/config.js`, `.vitepress/config.ts`, `.vitepress/config.mjs`, `.vitepress/config.mts` ### Zola -* Build command: `zola build --minify --output-dir ` -* Detected file: `config.toml` +- Build command: `zola build --minify --output-dir ` +- Detected file: `config.toml` ## 🎓 Static Site Generators (SSG) guides {{% content-raw "static-guides" %}} -{{% content "redirectionio" %}} -{{% content "varnish" %}} +{{% content "url_healthcheck" %}} +{{% content "request-flow" %}} From 6d45c21944a9979ac6030f62374c3bdbcee1b8a1 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 28 Jan 2026 17:53:13 +0100 Subject: [PATCH 008/180] aplications(docker): add details, reorganize --- content/doc/applications/docker.md | 100 +++++++++++++++-------------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/content/doc/applications/docker.md b/content/doc/applications/docker.md index c65035dc8..0cd0b2a84 100644 --- a/content/doc/applications/docker.md +++ b/content/doc/applications/docker.md @@ -34,31 +34,23 @@ aliases: Docker containers can encapsulate any payload, and will run consistently on and between virtually any server. The same container that a developer builds and tests on a laptop will run at scale, in production, on VMs, bare-metal servers, public instances, or combinations of the above. -Clever Cloud allows you to deploy any application running inside a Docker container. This page will explain how to set up your application to run it on our service. +Clever Cloud allows you to deploy any application running inside a Docker container. This page explains how to set up your application to run it on our service. -{{< callout type="info" >}} - Clever Cloud supports many languages, but some users have specific application needs. With Docker, they can create custom stacks without relying on Clever Cloud's specific support. -{{< /callout >}} +> [!NOTE] +> Clever Cloud supports many languages, but some users have specific application needs. With Docker, they can create custom stacks without relying on Clever Cloud's specific support. -{{< callout type="warning" >}} -[FS Buckets](/doc/best-practices/cloud-storage/#what-is-fs-bucket) access, Dockerfile validation, and Docker Compose functionalities are not supported. -{{< /callout >}} +> [!WARNING] +> [FS Buckets](/doc/best-practices/cloud-storage/#what-is-fs-bucket) access, Dockerfile validation, and Docker Compose functionalities are not supported. ### How it works When you create a Docker application on Clever Cloud, the deployment process involves the following steps: -1. **Install/Login:** - - The system checks for a Dockerfile and an entrypoint. - - It logs into the docker registry that you configured in the Dockerfile, if any, to find the necessary image (**note:** the name of the Docker registry may vary depending on the provider. It's called "container registry" in GitHub, for instance) -2. **Build:** - - The application pulls the specified image and execute commands you specified in Dockerfile. - - **Note:** This step focuses on executing commands in your Dockerfile and doesn't require build instructions if you are using a pre-compiled image. -3. **Run:** - - The application starts in a Docker container and exposes the service on port 8080 by default. - - If you need to expose your application on a different port, you can specify this using the environment variable `CC_DOCKER_EXPOSED_HTTP_PORT`. +1. **Login:** The system checks for a Dockerfile and logs into the Docker registry you configured, if any, to find the necessary image. +2. **Build:** The application pulls the specified image and executes the commands specified in your Dockerfile. +3. **Run:** The application starts in a Docker container and exposes the service on port 8080 by default. - {{% content "set-env-vars" %}} +{{% content "set-env-vars" %}} ## Configure your Docker application @@ -66,30 +58,31 @@ When you create a Docker application on Clever Cloud, the deployment process inv Be sure that you: -* push on the **master branch**. -* have and commit a file named **Dockerfile** or use the **CC_DOCKERFILE** [environment variable](/doc/reference/reference-environment-variables#docker) if your Dockerfile has a different name, [Here is what it will look like](https://docs.docker.com/develop/develop-images/dockerfile_best-practices "Dockerfile"). -* run the application with `CMD` or `ENTRYPOINT` in your Dockerfile. -* listen on HTTP **port 8080** by default (you can set your own port using `CC_DOCKER_EXPOSED_HTTP_PORT=` environment variable). +- Have and commit a file named **Dockerfile**, or use the `CC_DOCKERFILE` [environment variable](/doc/reference/reference-environment-variables#docker) if your Dockerfile has a different name. [Here is what it will look like](https://docs.docker.com/develop/develop-images/dockerfile_best-practices "Dockerfile"). +- Run the application with `CMD` or `ENTRYPOINT` in your Dockerfile. +- Listen on HTTP **port 8080** by default (you can set your own port using `CC_DOCKER_EXPOSED_HTTP_PORT`). + +- [Learn more about environment variables on Clever Cloud](/doc/reference/reference-environment-variables/) ### Dockerfile contents -You can virtually put everything you want in your Dockerfile. The only mandatory (for us) instruction to put in it is: +You can virtually put everything you want in your Dockerfile. The only mandatory instruction is: -```bash +```dockerfile CMD ``` -**command to run**: this is the command that starts your application. Your application **must** listen on port 8080. It can be easier for you to put a script in your docker image and call it with the CMD instruction. +**command to run**: this is the command that starts your application. Your application **must** listen on the port defined by `CC_DOCKER_EXPOSED_HTTP_PORT` (default: `8080`). It can be easier for you to put a script in your Docker image and call it with the CMD instruction. ### Docker Buildx -We still use `docker build` command for legacy reasons, but you can use `docker buildx` instead, setting `CC_DOCKER_BUILDX` to `true`. +The default build uses `docker build` with BuildKit disabled. To use Docker Buildx instead, set `CC_DOCKER_BUILDX` to `true`. Buildx uses the `--load` flag to make the image available locally. -### Memory usage during building +### Memory management -If the building step of your app crashes because it uses more memory that it's available, you'll have to split the building and running steps and enable [Dedicated build instance](/doc/administrate/apps-management#edit-application-configuration) +The Docker container runs with a memory limit equal to the instance's available memory, with swap disabled. If the building step of your application crashes due to memory usage, split the building and running steps and enable [Dedicated build instance](/doc/administrate/apps-management#edit-application-configuration): -```bash +```dockerfile # The base image FROM outlinewiki/outline:version-0.44.0 @@ -102,48 +95,59 @@ CMD yarn start ### Login to registry -As Docker Hub limits the number of image pulls and actions without authentication, use you own account to get higher limits. You can also use a private registry where you store your images. This feature launch `docker login` command before the build phase. +As Docker Hub limits the number of image pulls without authentication, use your own account to get higher limits. You can also use a private registry where you store your images. This runs `docker login` before the build phase. + +- `CC_DOCKER_LOGIN_USERNAME`: the username to use +- `CC_DOCKER_LOGIN_PASSWORD`: the password for your username +- `CC_DOCKER_LOGIN_SERVER` (optional): the server of your private registry, default is Docker Hub + +> [!NOTE] +> The name of the Docker registry may vary depending on the provider (e.g. "container registry" in GitHub). If login fails, the build continues without authentication. + +### Network mode -* `CC_DOCKER_LOGIN_USERNAME`: the username to use to login -* `CC_DOCKER_LOGIN_PASSWORD`: the password of your username -* `CC_DOCKER_LOGIN_SERVER` (optional): the server of your private registry, default is Docker Hub +When using default ports (`CC_DOCKER_EXPOSED_HTTP_PORT=8080` and `CC_DOCKER_EXPOSED_TCP_PORT=4040`), the container runs with `--net host` for direct network access. When custom ports are specified, Docker's port mapping is used instead (`-p 8080: -p 4040:`). ### TCP support -Clever Cloud enables you to use TCP over Docker applications using the environment variable `CC_DOCKER_EXPOSED_TCP_PORT=`. +Clever Cloud enables you to use TCP over Docker applications using the environment variable `CC_DOCKER_EXPOSED_TCP_PORT` (default: `4040`). -* [Learn more about TCP redirections](/doc/administrate/tcp-redirections) +- [Learn more about TCP redirections](/doc/administrate/tcp-redirections) ### Docker socket access -Some containers require access to the docker socket, to spawn sibling containers for instance. +Some containers require access to the Docker socket, to spawn sibling containers for instance. -{{< callout type="warning" >}} -Giving access to the docker socket breaks all isolation provided by docker. **DO NOT** give socket access to untrusted code. -{{< /callout >}} +> [!WARNING] +> Giving access to the Docker socket breaks all isolation provided by Docker. **DO NOT** give socket access to untrusted code. -You can make the docker socket available from inside the container by adding the `CC_MOUNT_DOCKER_SOCKET=true` environment variable. In that case, docker is started in the namespaced mode, and in bridge network mode. +You can make the Docker socket available from inside the container by adding the `CC_MOUNT_DOCKER_SOCKET=true` environment variable. In that case, Docker is started in the namespaced mode (user namespace remapping), and the container uses port mapping instead of host network mode. ### Enable IPv6 networking -You can activate the support of IPv6 with a IPv6 subnet in the docker daemon by adding the `CC_DOCKER_FIXED-CIDR-V6=` environment variable. +You can activate the support of IPv6 with an IPv6 subnet in the Docker daemon by adding the `CC_DOCKER_FIXED_CIDR_V6=` environment variable (e.g. `fd00::/80`). ### Build-time variables You can use the [ARG](https://docs.docker.com/engine/reference/builder/#arg) instruction to define build-time environment variables. -Every environment variable defined for your application will be passed as a build environment variable using the `--build-arg=` parameter during the `docker build` phase. +Every environment variable defined for your application is passed as a build environment variable using the `--build-arg` parameter during the `docker build` phase. -### Sample dockerized applications +### Deployment hooks + +Docker applications support all [deployment hooks](/doc/develop/build-hooks/). The `CC_PRE_BUILD_HOOK`, `CC_POST_BUILD_HOOK`, and `CC_PRE_RUN_HOOK` run on the host. The `CC_RUN_SUCCEEDED_HOOK` runs inside the container after successful start. -We provide a few examples of dockerized applications on Clever Cloud. +### Docker applications as Clever Tasks -* [Elixir App](https://github.com/CleverCloud/demo-docker-elixir/blob/master/Dockerfile) -* [Seaside / Smalltalk App](https://github.com/CleverCloud/demo-seaside) -* [Rust App](https://github.com/CleverCloud/demo-rust) +Docker containers can run as on-demand workloads on Clever Cloud. Configure an application as Tasks from the `Information` panel in [the Console](https://console.clever-cloud.com) or with [Clever Tools](/doc/cli/applications/#tasks). The container runs, executes its command, and stops. -You might need to use the `CC_DOCKERFILE = ` variable. +- [Learn more about Clever Tasks](/doc/develop/tasks/) + +### Sample dockerized applications +- [Elixir App](https://github.com/CleverCloud/demo-docker-elixir/blob/master/Dockerfile) +- [Seaside / Smalltalk App](https://github.com/CleverCloud/demo-seaside) +- [Rust App](https://github.com/CleverCloud/demo-rust) {{% content "env-injection" %}} @@ -153,4 +157,4 @@ You might need to use the `CC_DOCKERFILE = ` variable. {{% content "more-config" %}} -{{% content "url_healthcheck" %}} \ No newline at end of file +{{% content "url_healthcheck" %}} From 42c4f16ff371f09f5b9b5cd8e45f8a0d3e4ace08 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 28 Jan 2026 17:55:51 +0100 Subject: [PATCH 009/180] aplications(docker): add details, Request Flow --- content/doc/applications/frankenphp.md | 35 ++++++++++++++++---------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/content/doc/applications/frankenphp.md b/content/doc/applications/frankenphp.md index a8536d202..ed3f704d6 100644 --- a/content/doc/applications/frankenphp.md +++ b/content/doc/applications/frankenphp.md @@ -32,8 +32,8 @@ To create a new FrankenPHP application, use the [Clever Cloud Console](https://c ```bash clever create --type frankenphp ``` -* [Learn more about Clever Tools](/doc/cli/) -* [Learn more about Clever Cloud application deployment](/doc/quickstart/#create-an-application-step-by-step) +- [Learn more about Clever Tools](/doc/cli/) +- [Learn more about Clever Cloud application deployment](/doc/quickstart/#create-an-application-step-by-step) > [!NOTE] FrankenPHP applications can't be deployed on a pico instance, XS is the default instance type @@ -43,19 +43,19 @@ clever create --type frankenphp FrankenPHP runtime only requires a working web application, with an `index.php` or `index.html` file. If you need to serve files from a specific directory, set the `CC_WEBROOT` environment variable, relative to the root of your project (default: `/`). -* [Learn more about environment variables on Clever Cloud](/doc/reference/reference-environment-variables/) +- [Learn more about environment variables on Clever Cloud](/doc/reference/reference-environment-variables/) ### FrankenPHP version and tools -FrankenPHP currently deployed version on Clever Cloud is `1.9.1` based on PHP `8.4.12` and Caddy server `2.10.2`. Virtual machine image includes multiple tools from the PHP ecosystem such as Composer or Symfony CLI. +FrankenPHP currently deployed version on Clever Cloud is `{{< runtime_version frankenphp >}}` based on PHP `{{< runtime_version frankenphp php >}}` and Caddy server `{{< runtime_version frankenphp caddy >}}`. Virtual machine image includes multiple tools from the PHP ecosystem such as Composer or Symfony CLI. The `php` command available in hooks and scripts uses `frankenphp php-cli` under the hood. - [FrankenPHP PHP info](https://frankenphpinfo.cleverapps.io/) ### Composer native support -If a `composer.json` file is detected at the root of your project, it will be used to install dependencies during building phase with `--no-interaction --no-progress --no-scripts --no-dev` flags. To use your own, set the `CC_PHP_COMPOSER_FLAGS`environment variable. +If a `composer.json` file is detected at the root of your project, it will be used to install dependencies during building phase with `--no-interaction --no-progress --no-scripts --no-dev` flags. To override the base flags (`--no-interaction --no-progress --no-scripts`), set the `CC_PHP_COMPOSER_FLAGS` environment variable. -To install development dependencies, set the `CC_PHP_DEV_DEPENDENCIES` environment variable to `install`. +To install development dependencies, set the `CC_PHP_DEV_DEPENDENCIES` environment variable to `install`. This removes the `--no-dev` flag independently of `CC_PHP_COMPOSER_FLAGS`. > [!TIP] Use a local Composer version > If you put a `composer.phar` file at the root of your project, it will be used to install dependencies. @@ -73,13 +73,13 @@ To manage Materia KV data with FrankenPHP, use the included `redis` extension in - [Learn more about Materia KV](/doc/addons/materia-kv) - [Materia KV and FrankenPHP demo](https://github.com/CleverCloud/frankenphp-kv-json-example) -### Worker mode +## Worker mode -With FrankenPHP worker mode, a script of your project is kept in memory to handle incoming requests in a few milliseconds. Define the path to this script, relative to the root of your project, with the `CC_FRANKENPHP_WORKER` environment variable (e.g. `/worker/script.php`). It's supported by design by Laravel Octane and Symfony Runtime projects. +With FrankenPHP worker mode, a script of your project is kept in memory to handle incoming requests in a few milliseconds. Define the path to this script, relative to the root of your project, with the `CC_FRANKENPHP_WORKER` environment variable (e.g. `/public/worker.php`). The worker script must be located within the webroot directory. It's supported by design by Laravel Octane and Symfony Runtime projects. -* [Learn more about FrankenPHP worker mode](https://frankenphp.dev/docs/worker/#standalone-binary) -* [Learn more about Laravel Octane](https://laravel.com/docs/master/octane#frankenphp) -* [Learn more about Symfony Runtime](https://symfony.com/doc/current/components/runtime.html) +- [Learn more about FrankenPHP worker mode](https://frankenphp.dev/docs/worker/#standalone-binary) +- [Learn more about Laravel Octane](https://laravel.com/docs/master/octane#frankenphp) +- [Learn more about Symfony Runtime](https://symfony.com/doc/current/components/runtime.html) ## Configurable port @@ -87,9 +87,17 @@ By default, FrankenPHP listens on port `8080`. If you want to change it, set the ## Custom FrankenPHP run command -Use your own command to run your FrankenPHP application to define flags such as `--debug`, `--mercure` or `--no-compress`. To do so, set the `CC_RUN_COMMAND` environment variable, starting with `frankenphp php-server --listen 0.0.0.0:8080`. +To override the default server behavior, set the `CC_RUN_COMMAND` environment variable. When defined, it completely replaces the default `frankenphp php-server` command. Use it to define flags such as `--debug`, `--mercure` or `--no-compress`: -You can also use this to load [a custom Caddyfile](https://frankenphp.dev/docs/config/#caddyfile-config), starting `CC_RUN_COMMAND` with `frankenphp run --config /path/to/Caddyfile`. +```bash +CC_RUN_COMMAND="frankenphp php-server --listen 0.0.0.0:8080 --debug --mercure" +``` + +You can also use it to load [a custom Caddyfile](https://frankenphp.dev/docs/config/#caddyfile-config): + +```bash +CC_RUN_COMMAND="frankenphp run --config /path/to/Caddyfile" +``` ## Use FrankenPHP to execute PHP scripts as Clever Tasks @@ -107,3 +115,4 @@ clever deploy # or clever restart if there is no code change FrankenPHP on Clever Cloud comes with a set included PHP extensions: `amqp`,`apcu`,`ast`,`bcmath`,`brotli`,`bz2`,`calendar`,`ctype`,`curl`,`dba`,`dom`,`exif`,`fileinfo`,`filter`,`ftp`,`gd`,`gmp`,`gettext`,`iconv`,`igbinary`,`imagick`,`intl`,`ldap`,`lz4`,`mbregex`,`mbstring`,`memcache`,`memcached`,`mysqli`,`mysqlnd`,`opcache`,`openssl`,`password-argon2`,`parallel`,`pcntl`,`pdo`,`pdo_mysql`,`pdo_pgsql`,`pdo_sqlite`,`pdo_sqlsrv`,`pgsql`,`phar`,`posix`,`protobuf`,`readline`,`redis`,`session`,`shmop`,`simplexml`,`soap`,`sockets`,`sodium`,`sqlite3`,`ssh2`,`sysvmsg`,`sysvsem`,`sysvshm`,`tidy`,`tokenizer`,`xlswriter`,`xml`,`xmlreader`,`xmlwriter`,`xz`,`zip`,`zlib`,`yaml`,`zstd` {{% content "url_healthcheck" %}} +{{% content "request-flow" %}} From 0562657be6c3ff65e9db2dfa8a9625e8e7142be0 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 28 Jan 2026 18:00:12 +0100 Subject: [PATCH 010/180] applications(go): add details --- content/doc/applications/golang.md | 80 +++++++++++++++--------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/content/doc/applications/golang.md b/content/doc/applications/golang.md index 217308265..d9a7d224a 100644 --- a/content/doc/applications/golang.md +++ b/content/doc/applications/golang.md @@ -27,7 +27,7 @@ aliases: ## Overview -Clever Cloud allows you to deploy any Go application. This page explains how to set up your project to run it on our service. You won't need to change a lot, the *requirements* will help you configure your applications with some mandatory files to add, and properties to set up. +Clever Cloud allows you to deploy any Go application. This page explains how to set up your project to run it on our service. {{% content "create-application" %}} @@ -35,29 +35,29 @@ Clever Cloud allows you to deploy any Go application. This page explains how to ## Configure your Go application -### Mandatory needs +### Mandatory configuration -By default, we consider that your repository contains a single application. Be sure that: -* It listens to the wild network `0.0.0.0`, not only `localhost` or `127.0.0.1` -* It listens on port `8080` -* You follow our build/run instructions +Be sure that your application: -In most cases you won't need to change anything to your application, except host/port and some configuration variables. +- Listens on `0.0.0.0`, not only `localhost` or `127.0.0.1` +- Listens on port `8080` +- Contains a valid build configuration (see below) ### Complementary runtime -If you need a runtime environment such as [Node.js](/doc/applications/nodejs) or tools to build a frontend for example, some are available in our Go instances. You can use them through scripts launched by [deployments hooks](/doc/develop/build-hooks) and [Environment variables](/doc/reference/reference-environment-variables) sometimes allow you to configure them. So if you need a specific version of Node.js, set `CC_NODE_VERSION` (it could be `node` (latest), `lts/*`, `20` or `21.5.0`). +Go instances include additional runtime environments such as [Node.js](/doc/applications/nodejs) that you can use through scripts launched by [deployment hooks](/doc/develop/build-hooks). Some of these runtimes can be configured through environment variables. For example, to use a specific version of Node.js, set `CC_NODE_VERSION` (it could be `node` (latest), `lts/*`, `24` or `24.13.0`). ### Modern Go project structure There are multiple ways to build/run a Go application, and this has evolved over the years. In its modern form a Go project can be a: -- `Package`: one or more `.go` files you can `build` or `run`. `main` package and `main()` function are the default entry point -- `Module`: one or more packages you can `install`, defined in a `go.mod` file (`go.sum` for checksums) -- `Workspace`: one or more modules seamlessly combined, defined in a `go.work` file + +- **Package**: one or more `.go` files you can `build` or `run`. The `main` package and `main()` function are the default entry point +- **Module**: one or more packages you can `install`, defined in a `go.mod` file (`go.sum` for checksums) +- **Workspace**: one or more modules seamlessly combined, defined in a `go.work` file Install any module locally or from a remote repository by passing its URL to the `install` command. A `Makefile` is sometimes used to define how to build, run and/or clean a Go project. The lightest form of a Go project is a `main.go` file to build. The `src/` folder was often used for source code, but using the `cmd/` folder instead is now a common practice. -If you want to limit from where a package can be imported, [place it](https://docs.google.com/document/d/1e8kOo3r51b2BWtTs_1uADIA5djfXhPT36s6eHVRIvaU/edit) in a folder named `ìnternal/`. Access to functions in `.go` files is defined depending [on their name](https://go.dev/tour/basics/3): if it starts with a capital letter it's a public function, if not it's a private function. +If you want to limit from where a package can be imported, [place it](https://docs.google.com/document/d/1e8kOo3r51b2BWtTs_1uADIA5djfXhPT36s6eHVRIvaU/edit) in a folder named `internal/`. Access to functions in `.go` files is defined depending [on their name](https://go.dev/tour/basics/3): if it starts with a capital letter it's a public (exported) function, if not it's a private (unexported) function. For a complete project, a common files/folders organisation can be: {{< filetree/container >}} @@ -86,40 +86,35 @@ For a complete project, a common files/folders organisation can be: ### Go build and deploy on Clever Cloud -In such a situation, our strategy is to let the user choose how to build/run its application and make the deployment easy anyway. At first, we used the `goget` method, which is now deprecated. Thus, you can now use `gobuild` (for packages), `gomod` (for modules) or `makefile`. The latter will allow you to define custom build steps and a `main` executable to start the application. +Clever Cloud supports multiple ways to build and run a Go application. The build tool is determined by the `CC_GO_BUILD_TOOL` environment variable or by auto-detection. Available methods are `gomod` (for modules), `gobuild` (for packages), or `makefile` (for custom build steps). The `goget` method still exists but is deprecated. -{{< callout type="info" >}} - If the required Go version declared in the `go.mod` is superior to the version built in the instance, it will be automatically updated. -{{< /callout >}} +> [!NOTE] +> If the Go version declared in your `go.mod` file is newer than the one installed on the instance, the Go toolchain downloads the required version automatically. ### Environment variables -If you don't want to add a file to your project, you can set one of these environment variables: - | Name | Description | -| :------- | :---- | -| `CC_GO_BUILD_TOOL` | Available values: `gomod`, `gobuild`, `makefile`. Build and install your application. `goget` still exists but is deprecated. | -| `CC_GO_BINARY` | Mandatory for a `Makefile` build, path to the built binary, used to launch your application. | -| `CC_GO_PKG` | Tell the `CC_GO_BUILD_TOOL` which file contains the `main()` function, default `main.go`. | -| `CC_GO_RUNDIR` | Run the application from the specified path, relative to `$GOPATH/src/`, now deprecated. | - -{{< callout type="info" >}} - The default `GO_PATH` is `${HOME}/go_home`. - The command executed to launch the application is `go install $CC_GO_PKG`. \ - Your project may include vendored dependencies (in the `vendor/` folder). -{{< /callout >}} +| :--- | :---------- | +| `CC_GO_BUILD_TOOL` | Available values: `gomod`, `gobuild`, `makefile`. Determines how to build and install your application. `goget` still exists but is deprecated. | +| `CC_GO_BINARY` | Required when using the `makefile` build tool. Path to the built binary, used to launch your application. | +| `CC_GO_PKG` | Package path passed to `go install`. Overrides auto-detection from `go.mod`. Default is `main.go` when no `go.mod` is present. | +| `CC_GO_RUNDIR` | Run the application from the specified path, relative to `$GOPATH/src/`. Deprecated. | -#### gobuild +The default `GOPATH` is `${HOME}/go_home`. The build command is `go install ` for all non-Makefile methods. If a `vendor/` directory is present, it is included in the build cache. -To build a Go package. `CC_GO_PKG` can be set to define the main file of your application (default `main.go`). +The resulting binary is placed at `$GOPATH/bin/`, where `` is the basename of the package path without the `.go` extension. For example, `CC_GO_PKG=cmd/server.go` produces a binary named `server`. #### gomod -To build a Go module, be sure that the `go.mod` file is in your git tree and at the root of your application. Your project's entry point should be in the same folder as the `go.mod` file and be named `main.go`. If it isn't, you have to set `CC_GO_PKG=path/to/entrypoint.go`. +Builds a Go module. Requires a `go.mod` file at the root of your application. The module name is read from `go.mod` and passed to `go install`. If you need to build a different package within the module, set `CC_GO_PKG` to override it. + +#### gobuild + +Builds a Go package using `go install`. Set `CC_GO_PKG` to define the package path (default `main.go`). The application is moved to `$GOPATH/src/` before building. #### makefile -To build a Go project with a `Makefile`. You have to set `CC_GO_BINARY` with the path to the built binary, used to launch your application. If a `Makefile` is present with a `CC_GO_BINARY` set and no `go.mod` file at the root of your project, the `makefile` method will automatically be used. +Builds a Go project with a `Makefile`. Set `CC_GO_BINARY` with the path to the built binary, used to launch your application. The Makefile method is automatically selected when all these conditions are met: `CC_GO_BINARY` is set, a `Makefile` exists, and no `go.mod` file is present. If a `Makefile` is present without `CC_GO_BINARY`, a warning is logged and the Makefile is not used. An example of a `Makefile`, to use with `CC_GO_BINARY=bin/myApp`: @@ -137,13 +132,20 @@ build: - [A more complex project using a Go Workspace and a Makefile](https://github.com/CleverCloud/go-workspaces) -{{< callout type="warning" >}} - Using `clevercloud/go.json` to define Makefile and binary paths is a deprecated method and should no longer be used. -{{< /callout >}} +> [!WARNING] +> Using `clevercloud/go.json` to define Makefile and binary paths is a deprecated method and should no longer be used. + +### Custom run command + +By default, the built binary is executed directly. Set `CC_RUN_COMMAND` to override this behavior and run a custom command instead of the binary. When defined, the binary is still built but `CC_RUN_COMMAND` is executed at start. + +### Troubleshooting builds + +Set `CC_TROUBLESHOOT=true` to enable verbose build output (`-x` flag) and the race detector (`-race` flag) during compilation. This applies to `gomod` and `gobuild` methods. - {{% content "env-injection" %}} +{{% content "env-injection" %}} -To access environment variables from your code, just get them from the environment with `PATH`: `os.Getenv("MY_VARIABLE")`. +To access environment variables from your code, use `os.Getenv("MY_VARIABLE")`. {{% content "deploy-git" %}} @@ -155,4 +157,4 @@ To access environment variables from your code, just get them from the environme ## See also -* [Deploy EchoIP guide](/guides/go-echoip/) +- [Deploy EchoIP guide](/guides/go-echoip/) From 4faeef643630e7ac30ba7bd065770755b45fa265 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 28 Jan 2026 18:03:47 +0100 Subject: [PATCH 011/180] aplications(nodejs): add details --- content/doc/applications/nodejs.md | 46 ++++++++++++++++++------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/content/doc/applications/nodejs.md b/content/doc/applications/nodejs.md index bb602498d..181474557 100644 --- a/content/doc/applications/nodejs.md +++ b/content/doc/applications/nodejs.md @@ -60,6 +60,10 @@ Be sure that: * The folder `/node_modules` is mentioned in your `.gitignore` file * You enable production mode by setting the [environment variable](#setting-up-environment-variables-on-clever-cloud) `NODE_ENV=production` +### Memory management + +`NODE_OPTIONS` is automatically configured with `--max-old-space-size` set to 3/4 of the instance's available memory. If you already set `NODE_OPTIONS` with a `max-old-space-size` value, it is not overridden. If `NODE_OPTIONS` is set without `max-old-space-size`, the flag is appended to your existing value. + ### Build phase During the build phase, Clever Cloud will install your application dependencies with the selected package manager. @@ -153,11 +157,11 @@ Then it will be installed during deployment. You can replace `latest` with a spe ### Automatic detection -If a lock file exists in your application's main folder, the corresponding package manager is set: +If a lock file exists in your application's main folder, the corresponding package manager is set. Detection priority is: -- If a `bun.lock` file exists, `bun` is used for build/run -- If a `pnpm-lock.yaml` file exists, `pnpm` is used for build/run -- If a `yarn.lock` file exists, and a 3.x/4.x version is declared in `package.json`, `yarn-berry` is used for build/run +1. `yarn.lock` → if `packageManager` in `package.json` starts with `yarn@3` or `yarn@4`, `yarn-berry` is used; otherwise falls back to deprecated `yarn` (1.x) +2. `pnpm-lock.yaml` → `pnpm` is used for build/run +3. `bun.lock` → `bun` is used for build/run To overwrite this behavior, either delete the lock file or set the `CC_NODE_BUILD_TOOL` environment variable. @@ -165,18 +169,16 @@ To overwrite this behavior, either delete the lock file or set the `CC_NODE_BUIL ### Set Node.js version -If you need a specific version or branch of Node.js, set `CC_NODE_VERSION`. You can use major, minor, patch version, such as `24`, `23.11` or `22.15.1` for example. If this environment variable isn't set, the latest LTS version available on Clever Cloud is used. +If you need a specific version or branch of Node.js, set `CC_NODE_VERSION`. You can use major, minor, patch version, such as `24`, `23.11` or `22.15.1` for example. If this environment variable isn't set and `engines.node` is not defined in `package.json`, the latest LTS version available on Clever Cloud is used. {{< runtimes_versions node >}} > [!NOTE] -> For legacy reasons, the system prioritizes to the `engines.node` value in `package.json` over the `CC_NODE_VERSION` environment variable when both are set. +> For legacy reasons, the system prioritizes the `engines.node` value in `package.json` over the `CC_NODE_VERSION` environment variable when both are set. Any `.nvmrc` file is ignored and deleted during deployment. ### Bun version -If you use Bun, your application is deployed with the latest available version on Clever Cloud: - -{{< runtimes_versions bun >}} +If you use Bun, your application is deployed with the latest available version on Clever Cloud (`{{< runtime_version bun >}}`). To customise the Bun cache directory, set `CC_BUN_INSTALL_CACHE_DIR`. ### pnpm and Yarn versions @@ -198,16 +200,24 @@ This is the default way to manage version for pnpm and Yarn when a new project i ## Development Dependencies -Development dependencies aren't automatically installed during the deployment. You can control their installation setting `CC_NODE_DEV_DEPENDENCIES` environment variable to `install` or `ignore`. This variable overrides the default behavior of `NODE_ENV`. +Development dependencies aren't automatically installed during the deployment. You can control their installation by setting the `CC_NODE_DEV_DEPENDENCIES` environment variable to `install` or `ignore`. + +When set to `install`, an explicit flag forces development dependencies to be included regardless of `NODE_ENV`: + +- npm: `--production=false` +- npm-ci: `--include=dev` +- pnpm: `--prod false` +- yarn: `--production=false` +- bun: no flag (Bun includes all dependencies by default) + +When not set or set to `ignore`, default package manager behavior applies: + +- For Bun: development dependencies are excluded (`--omit dev` is added by default) +- For npm/yarn/pnpm: depends on `NODE_ENV`. If `NODE_ENV=production`, development dependencies are excluded. Otherwise they are included. -Here are various scenarios: +## Custom run command -* `CC_NODE_DEV_DEPENDENCIES=install`: Development dependencies are installed. -* `CC_NODE_DEV_DEPENDENCIES=ignore`: Development dependencies aren't installed. -* `NODE_ENV=production` and `CC_NODE_DEV_DEPENDENCIES=install`: Development dependencies are installed. -* `NODE_ENV=production` and `CC_NODE_DEV_DEPENDENCIES=ignore`: Development dependencies aren't installed. -* `NODE_ENV=production`: Package manager (npm/yarn) default behavior. Development dependencies aren't installed. -* Neither `NODE_ENV` nor `CC_NODE_DEV_DEPENDENCIES` are defined: Package manager (npm/yarn) default behavior. Development dependencies are installed. +To override the default start behavior (scripts.start or main field from package.json), set the `CC_RUN_COMMAND` environment variable. When defined, it takes priority over all other start methods. ## Use private repositories @@ -227,7 +237,7 @@ Then, the `.npmrc` file is created automatically for your application, with the ### With CC_NPM_BASIC_AUTH -Or you can set `CC_NPM_BASIC_AUTH` to use basic authentication +As an alternative to `NPM_TOKEN`, you can set `CC_NPM_BASIC_AUTH` to use basic authentication. The value is Base64-encoded automatically by the platform. You cannot use both `NPM_TOKEN` and `CC_NPM_BASIC_AUTH` at the same time. ```bash CC_NPM_BASIC_AUTH="user:password" From eff407d6303dfb62922867f4d664cd58f86611f8 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 28 Jan 2026 18:41:46 +0100 Subject: [PATCH 012/180] layout(shortcodes): add runtime_version.html --- layouts/shortcodes/runtime_version.html | 1 + 1 file changed, 1 insertion(+) create mode 100644 layouts/shortcodes/runtime_version.html diff --git a/layouts/shortcodes/runtime_version.html b/layouts/shortcodes/runtime_version.html new file mode 100644 index 000000000..084945473 --- /dev/null +++ b/layouts/shortcodes/runtime_version.html @@ -0,0 +1 @@ +{{ $software := .Get 0 }}{{ $key := or (.Get 1) "default" }}{{ with index .Site.Data.runtime_versions $software }}{{ with index . $key }}{{ index . 0 }}{{ end }}{{ end }} \ No newline at end of file From bad2fa57aa03d084634b0a8d6c1282b75e079c65 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 28 Jan 2026 16:16:38 +0100 Subject: [PATCH 013/180] changelog: Terraform 1.8/1.9 --- .../changelog/2025/12-19-terraform-1.8.0.md | 20 +++++++++++++++++++ .../changelog/2026/01-23-terraform-1.9.0.md | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 content/changelog/2025/12-19-terraform-1.8.0.md create mode 100644 content/changelog/2026/01-23-terraform-1.9.0.md diff --git a/content/changelog/2025/12-19-terraform-1.8.0.md b/content/changelog/2025/12-19-terraform-1.8.0.md new file mode 100644 index 000000000..a77805f65 --- /dev/null +++ b/content/changelog/2025/12-19-terraform-1.8.0.md @@ -0,0 +1,20 @@ +--- +title: Terraform provider 1.8.0 +description: Exposed environment variables for applications, upload action for FS Buckets and more Keycloak features in the Clever Cloud Terraform provider +date: 2025-12-19 +tags: + - addons + - terraform +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 + - name: Rémi Collignon-Ducret + link: https://github.com/miton18 + image: https://github.com/miton18.png?size=40 +excludeSearch: true +--- + +The [1.8.0 release](https://github.com/CleverCloud/terraform-provider-clevercloud/releases/tag/v1.8.0) of the Clever Cloud Terraform provider is available. It brings bug fixes, `clevercloud_database_query` action, upload action for FS Buckets, exposed environment variables for applications and more Clever Cloud's Keycloak features. + +* Learn more about [Clever Cloud Terraform provider](https://registry.terraform.io/providers/CleverCloud/clevercloud/latest/docs) diff --git a/content/changelog/2026/01-23-terraform-1.9.0.md b/content/changelog/2026/01-23-terraform-1.9.0.md new file mode 100644 index 000000000..84d820929 --- /dev/null +++ b/content/changelog/2026/01-23-terraform-1.9.0.md @@ -0,0 +1,20 @@ +--- +title: Terraform provider 1.9.0 +description: Java JAR support, app-to-app dependencies, more integrations and backup datasource for PostgreSQL in the Clever Cloud Terraform provider +date: 2026-01-23 +tags: + - addons + - terraform +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 + - name: Rémi Collignon-Ducret + link: https://github.com/miton18 + image: https://github.com/miton18.png?size=40 +excludeSearch: true +--- + +The [1.9.0 release](https://github.com/CleverCloud/terraform-provider-clevercloud/releases/tag/v1.9.0) of the Clever Cloud Terraform provider is available. It brings bug fixes, Java JAR support, app-to-app dependencies, more integrations and backup datasource for PostgreSQL. + +* Learn more about [Clever Cloud Terraform provider](https://registry.terraform.io/providers/CleverCloud/clevercloud/latest/docs) From cd8e6c484880aed65bf6779ff4c361e150f886dd Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 2 Feb 2026 11:54:09 +0100 Subject: [PATCH 014/180] changelog: Otoroshi 17.12 --- .../changelog/2026/01-30-otoroshi-17.12.md | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 content/changelog/2026/01-30-otoroshi-17.12.md diff --git a/content/changelog/2026/01-30-otoroshi-17.12.md b/content/changelog/2026/01-30-otoroshi-17.12.md new file mode 100644 index 000000000..46e49004a --- /dev/null +++ b/content/changelog/2026/01-30-otoroshi-17.12.md @@ -0,0 +1,36 @@ +--- +title: Otoroshi 17.12 is available with JWT Verification, new WAF engine and plugin improvements +description: JWT verification via OIDC with session extraction, JVM-native WAF engine with OWASP CRS, plugin development enhancements and LLM extensions updates +date: 2026-01-30 +tags: + - addons + - otoroshi +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Otoroshi v17.12](https://github.com/MAIF/otoroshi/releases/tag/v17.12.0) is available with multiple improvements. It brings JWT verification support based on the settings of an OIDC authentication module with optional user session extraction through OIDCJwtVerifier. The release also allows Fail2Ban to be triggered by other plugins that can't use the `requestError` phase. + +This version also integrates a new WAF engine providing JVM-native implementation of ModSecurity SecLang with the OWASP Core Rule Set included. This eliminates binary dependencies and simplifies deployment in containerized environments, with flexible modes for comprehensive WAF inspection or lightweight request validation. + +For plugin developers, this version introduces various internal improvements: Monaco editor support in classic forms for enhanced code editing experience, provider helpers to create customizable errors in plugins, and the ability to always display plugins even if missing from the JS plugins list. + +This release includes LLM extensions [0.0.68](https://github.com/cloud-apim/otoroshi-llm-extension/releases/tag/0.0.68) and [0.0.69](https://github.com/cloud-apim/otoroshi-llm-extension/releases/tag/0.0.69), bringing OpenResponses-compatible endpoints for standardized LLM response handling through the [OpenResponses framework](https://www.openresponses.org/). These versions embed rate limit and budget consumption data in `GatewayEvents` and `LLMAuditEvents` for enhanced tracking, and support exposing any model with an Anthropic API compatible format. + +You can update through add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_OTOROSHI_VERSION` of the underlying Java application to `v17.12.0_1769783775` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever otoroshi version check yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId v17.12.0_1769783775 +``` + +- [Learn more about Otoroshi with LLM on Clever Cloud](/doc/addons/otoroshi/) From 215cd1c2d321fab3c632325ff9e3465ffc0dd0c2 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 3 Feb 2026 18:54:37 +0100 Subject: [PATCH 015/180] changelog: add mention of environment variable checking --- content/changelog/2025/12-17-images-update.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/content/changelog/2025/12-17-images-update.md b/content/changelog/2025/12-17-images-update.md index ac37acf9c..199fc4ce3 100644 --- a/content/changelog/2025/12-17-images-update.md +++ b/content/changelog/2025/12-17-images-update.md @@ -61,3 +61,9 @@ Starting with this release, we don't support end-of-life Elixir versions from 1. Static Web Server 2.40.1 includes a new feature we contributed to, allowing to serve Markdown version of a web page when it exists and the request contains the `Accept: text/markdown` header. This is useful to serve documentation to LLMs following the [llms.txt proposal](https://llmstxt.org/). It can be combined with Hugo [transform.HTMLToMarkdown](https://gohugo.io/functions/transform/htmltomarkdown/) function for example, as we do on this documentation. To enable this feature, just set `SERVER_ACCEPT_MARKDOWN` environment variable to `true` in your Static Web Server application. + +## Environment variable checking + +Starting with this release, if an invalid environment variable name or value is set in your application, deployment will fail with an error message. You can set `CC_TROUBLESHOOT=true` to get more details. + +- [Learn more about Environment variable sanity checking](https://www.clever.cloud/blog/engineering/2025/12/22/deployment-variables-got-more-consistent/) From 5fd4150516f35d3acb184629d391ef4b24e7c3dd Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 3 Feb 2026 18:53:50 +0100 Subject: [PATCH 016/180] changelog: images updates, 2026W6 --- content/changelog/2026/02-03-images-update.md | 40 +++++++++++++++++++ content/doc/applications/php.md | 2 +- data/runtime_versions.yml | 2 +- 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 content/changelog/2026/02-03-images-update.md diff --git a/content/changelog/2026/02-03-images-update.md b/content/changelog/2026/02-03-images-update.md new file mode 100644 index 000000000..aff53a078 --- /dev/null +++ b/content/changelog/2026/02-03-images-update.md @@ -0,0 +1,40 @@ +--- +title: "Images update: Bun 1.3.8, OAuth2 Proxy 7.14, more PHP 8.5 extensions" +description: "Many tiny updates, and some surprises we'll detail soon" +date: 2026-02-03 +tags: + - images + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated all our images. Deployment is in progress for all our users. + +* **Common:** + * OAuth2 Proxy 7.14.2 + * Otoroshictl 0.0.15 + * Tailscale 1.94.1 +* **.Net:** + * Update to 6.0.136 +* **Docker:** + * Docker Buildx 0.31.1 +* **Java:** + * Update to 11.0.30_p7 + * Update to 17.0.18_p8 + * Update to 21.0.10_p7 + * Update to 25.0.2_p10 + * Gradle 9.3.1 +* **Node.js & Bun:** + * Bun 1.3.8 +* **PHP:** + * Composer 2.9.5 + * mcrypt extension 1.0.9 + * PDFlib extension 11.0.0 + * solr extension 2.9.1 + * xdebug extension 3.5.0 +* **Python:** + * uv 0.9.28 diff --git a/content/doc/applications/php.md b/content/doc/applications/php.md index 8c85aa3fa..03d612ad4 100644 --- a/content/doc/applications/php.md +++ b/content/doc/applications/php.md @@ -374,7 +374,7 @@ You can also enable the following extensions on demand: `apcu`, `blackfire`, `elastic_apm_agent`, `event`, `excimer`, `geos`, `gnupg`, `grpc`, `ioncube`, `imap`, `mailparse`, `maxminddb`, `mongo`, `newrelic`, `oauth`, `opentelemetry`, `pcs`, `PDFlib`, `pdo_sqlsrv`, `protobuf`, `pspell`, `rdkafka`, `scoutapm`, `sqlsrv`, `sqreen`, `tideways`, `uopz`, `uploadprogress`, `xdebug`, `xmlrpc`, `yaml` >[!NOTE] ->Only some extensions support PHP 8.5 for now: `amqp`, `apcu`, `blackfire`, `event`, `excimer`, `gnupg`, `grpc`, `imagick`, `imap`, `mailparse`, `maxminddb`, `memcached`, `oauth`, `opentelemetry`, `pdo_sqlsrv`, `protobuf`, `pspell`, `rdkafka`, `redis`, `sqlsrv`, `ssh2`, `tideways`, `uploadprogress`, `yaml`, `zip`. We'll add support for more extensions as they are released. +>Only some extensions support PHP 8.5 for now: `amqp`, `apcu`, `blackfire`, `event`, `excimer`, `gnupg`, `grpc`, `imagick`, `imap`, `mailparse`, `maxminddb`, `mcrypt`, `memcached`, `oauth`, `opentelemetry`, `PDFlib`, `pdo_sqlsrv`, `protobuf`, `pspell`, `rdkafka`, `redis`, `solr`, `sqlsrv`, `ssh2`, `tideways`, `uploadprogress`, `xdebug`, `yaml`, `zip`. We'll add support for more extensions as they are released. You can check extensions and versions by viewing our `phpinfo()` for: diff --git a/data/runtime_versions.yml b/data/runtime_versions.yml index e7be6456b..c95642316 100644 --- a/data/runtime_versions.yml +++ b/data/runtime_versions.yml @@ -1,7 +1,7 @@ bun: eol_source: https://github.com/oven-sh/bun/releases default: - - 1.3.7 + - 1.3.8 caddy: eol_source: https://github.com/caddyserver/caddy/releases From 504d16856b4f8ec332c18573f3fdcd7c72d3281b Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 3 Feb 2026 19:16:56 +0100 Subject: [PATCH 017/180] docker: invert Docker Buildx default value --- content/changelog/2026/02-03-images-update.md | 4 ++++ content/doc/applications/docker.md | 2 +- content/doc/reference/reference-environment-variables.md | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/content/changelog/2026/02-03-images-update.md b/content/changelog/2026/02-03-images-update.md index aff53a078..1095f3596 100644 --- a/content/changelog/2026/02-03-images-update.md +++ b/content/changelog/2026/02-03-images-update.md @@ -38,3 +38,7 @@ We updated all our images. Deployment is in progress for all our users. * xdebug extension 3.5.0 * **Python:** * uv 0.9.28 + +## Docker Buildx + +As previously announced, Docker Buildx is now the default build system for Docker applications. You can switch back to the legacy build system by setting the `CC_DOCKER_BUILDX` environment variable to `false`. diff --git a/content/doc/applications/docker.md b/content/doc/applications/docker.md index 0cd0b2a84..22466c28b 100644 --- a/content/doc/applications/docker.md +++ b/content/doc/applications/docker.md @@ -76,7 +76,7 @@ CMD ### Docker Buildx -The default build uses `docker build` with BuildKit disabled. To use Docker Buildx instead, set `CC_DOCKER_BUILDX` to `true`. Buildx uses the `--load` flag to make the image available locally. +The default build uses `docker buildx`. To use Docker legacy build instead, set `CC_DOCKER_BUILDX` to `false`. ### Memory management diff --git a/content/doc/reference/reference-environment-variables.md b/content/doc/reference/reference-environment-variables.md index 571d003ce..87996a656 100644 --- a/content/doc/reference/reference-environment-variables.md +++ b/content/doc/reference/reference-environment-variables.md @@ -150,7 +150,7 @@ If `TAILSCALE_LOGIN_SERVER` is provided, the agent will be configured to reach a | Name | Description | Default value | |-----------------------|------------------------------|--------------------------------| |`CC_DOCKERFILE` | The name of the Dockerfile to build. | Dockerfile | -|`CC_DOCKER_BUILDX` | Set to `true` to use`buildx` for building your image | false | +|`CC_DOCKER_BUILDX` | Set to `false` to use Docker legacy build | true | |`CC_DOCKER_EXPOSED_HTTP_PORT` | Set to custom HTTP port if your Docker container runs on custom port. | 8080 | |`CC_DOCKER_EXPOSED_TCP_PORT` | Set to custom TCP port if your Docker container runs on custom port. | 4040 | |`CC_DOCKER_FIXED_CIDR_V6` | Activate the support of IPv6 with an IPv6 subnet int the docker daemon. | | From 20fa4738ea62aea7a0df27a1d3ec2adc17a58203 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 3 Feb 2026 19:20:14 +0100 Subject: [PATCH 018/180] changelog: add Docker Buildx link --- content/changelog/2026/02-03-images-update.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/changelog/2026/02-03-images-update.md b/content/changelog/2026/02-03-images-update.md index 1095f3596..6f2ec00cd 100644 --- a/content/changelog/2026/02-03-images-update.md +++ b/content/changelog/2026/02-03-images-update.md @@ -41,4 +41,4 @@ We updated all our images. Deployment is in progress for all our users. ## Docker Buildx -As previously announced, Docker Buildx is now the default build system for Docker applications. You can switch back to the legacy build system by setting the `CC_DOCKER_BUILDX` environment variable to `false`. +[As previously announced](/changelog/2025/11-04-docker-buildx-default/), Docker Buildx is now the default build system for Docker applications. You can switch back to the legacy build system by setting the `CC_DOCKER_BUILDX` environment variable to `false`. From 71b7dcad250268051d1f342149f2e47a7c548ead Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 4 Feb 2026 16:47:12 +0100 Subject: [PATCH 019/180] changelog: Matomo 5.7 --- content/changelog/2026/02-04-matomo-5.7.md | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 content/changelog/2026/02-04-matomo-5.7.md diff --git a/content/changelog/2026/02-04-matomo-5.7.md b/content/changelog/2026/02-04-matomo-5.7.md new file mode 100644 index 000000000..60f1e6b2b --- /dev/null +++ b/content/changelog/2026/02-04-matomo-5.7.md @@ -0,0 +1,23 @@ +--- +title: Matomo 5.7 is available +description: No specific new features but many improvements in stability, performance, usability and security +date: 2026-02-04 +tags: + - addons + - matomo +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +The [Matomo](https://matomo.org/) add-on on Clever Cloud has been updated to version `5.7.1`, which is now used by default. The `5.7` branch focuses on stability, performance, usability and security. + +You can deploy this release from the [Clever Cloud Console](https://console.clever-cloud.com) or [Clever Tools](/doc/cli/). Existing customers' add-ons are already up-to-date. + +- [Learn more about Matomo 5.7](https://matomo.org/changelog/matomo-5-7-0/) +- [Learn more about Matomo on Clever Cloud](/doc/addons/matomo/) From 4fe5d90173740d0839e63a499fed6e2e5f2e7326 Mon Sep 17 00:00:00 2001 From: Raspy Date: Mon, 15 Dec 2025 15:10:47 +0100 Subject: [PATCH 020/180] addons(kv): add SET supported commands --- .../changelog/2026/02-05-materia-kv-set.md | 19 ++++++++++++++++ content/doc/addons/materia-kv.md | 22 +++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 content/changelog/2026/02-05-materia-kv-set.md diff --git a/content/changelog/2026/02-05-materia-kv-set.md b/content/changelog/2026/02-05-materia-kv-set.md new file mode 100644 index 000000000..33794afdc --- /dev/null +++ b/content/changelog/2026/02-05-materia-kv-set.md @@ -0,0 +1,19 @@ +--- +title: "Materia KV supports Set value type" +description: Manage sets in Materia KV with new commands such as SADD, SREM, SMEMBERS, SISMEMBER or SCARD. +date: 2026-02-05 +tags: + - addons + - materia + - kv +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Materia KV](/doc/addons/materia-kv/) now supports multiple commands to create and manage sets. You don't have anything to change in your configuration to benefit from this new feature, just use the new supported commands in your applications. + +- [Learn more about Materia KV](/doc/addons/materia-kv/) +- [Learn more about Materia KV supported commands](/doc/addons/materia-kv/#supported-types-and-commands) diff --git a/content/doc/addons/materia-kv.md b/content/doc/addons/materia-kv.md index 9ecebfa0a..cdcb7163e 100644 --- a/content/doc/addons/materia-kv.md +++ b/content/doc/addons/materia-kv.md @@ -133,6 +133,7 @@ We've prepared a few examples to help you get started with Materia KV: Supported value types are: - Hash +- Set - String Find below the list of currently supported commands: @@ -180,13 +181,30 @@ Find below the list of currently supported commands: | `PERSIST` | Remove the existing time to live associated with the `key`. | | `PEXPIRE` | Set a `key` time to live in milliseconds. After the timeout has expired, the `key` will be automatically deleted. The time to live can be updated using the `PEXPIRE` command or cleared using the `PERSIST` command. | | `PING` | Returns `PONG` if no argument is provided, otherwise return a copy of the argument as a bulk. | -| `PTTL` | RReturns the remaining time to live of a `key`, in milliseconds. | +| `PTTL` | Returns the remaining time to live of a `key`, in milliseconds. | +| `SADD` | Add the specified members to the set stored at `key`. Specified members that are already a member of this set are ignored. If `key` doesn't exist, a new set is created before adding the specified members. | | `SCAN` | Incrementally iterate over a collection of elements. It is a cursor based iterator, this means that at every call of the command, the server returns an updated cursor that the user needs to use as the cursor argument in the next call. An iteration starts when the cursor is set to `0`, and terminates when the cursor returned by the server is `0`. | +| `SCARD` | Returns the set cardinality (number of elements) of the set stored at `key`. | +| `SDIFF` | Returns the members of the set resulting from the difference between the first set and all the successive sets. | +| `SDIFFSTORE` | This command is equal to `SDIFF`, but instead of returning the resulting set, it is stored in `destination`. If `destination` already exists, it is overwritten. | | `SET` | Set `key` to hold the string `value`. If key already holds a value, it is overwritten, regardless of its type. | | `SETBIT` | Sets or clears the bit at offset in the string value stored at `key`. | +| `SINTER` | Returns the members of the set resulting from the intersection of all the given sets. | +| `SINTERCARD` | Returns the number of elements that would result from the intersection of all given sets. | +| `SINTERSTORE` | This command is equal to `SINTER`, but instead of returning the resulting set, it is stored in `destination`. If `destination` already exists, it is overwritten. | +| `SISMEMBER` | Returns if `member` is a member of the set stored at `key`. | +| `SMEMBERS` | Returns all the members of the set value stored at `key`. | +| `SMISMEMBER` | Returns whether each member is a member of the set stored at `key`. For every member, `1` is returned if the value is a member of the set, or `0` if the element is not a member of the set or if `key` doesn't exist. | +| `SMOVE` | Move `member` from the set at `source` to the set at `destination`. This operation is atomic. In every given moment the element will appear to be a member of `source` or `destination` for other clients. | +| `SPOP` | Removes and returns one or more random members from the set value stored at `key`. | +| `SRANDMEMBER` | When called with just the `key` argument, return a random element from the set value stored at `key`. | +| `SREM` | Remove the specified members from the set stored at `key`. Specified members that are not a member of this set are ignored. If `key` doesn't exist, it is treated as an empty set and this command returns `0`. | +| `SSCAN` | Incrementally iterate over set elements. It is a cursor based iterator, this means that at every call of the command, the server returns an updated cursor that the user needs to use as the cursor argument in the next call. An iteration starts when the cursor is set to `0`, and terminates when the cursor returned by the server is `0`. | +| `SUNION` | Returns the members of the set resulting from the union of all the given sets. | +| `SUNIONSTORE` | This command is equal to `SUNION`, but instead of returning the resulting set, it is stored in `destination`. If `destination` already exists, it is overwritten. | | `STRLEN` | Returns the length of the string value stored at `key`. An error is returned when key holds a non-string value. | | `TTL` | Returns the remaining time to live of a `key`, in seconds. | -| `TYPE` | Returns the string representation of the type of the value stored at `key`. Can be: `hash`, `list` or `string`. | +| `TYPE` | Returns the string representation of the type of the value stored at `key`. Can be: `hash`, `list`, `set` or `string`. | ### JSON commands From 663cf10c0404365aa350d1944097c21af996c1fd Mon Sep 17 00:00:00 2001 From: David Legrand Date: Thu, 12 Feb 2026 12:09:12 +0100 Subject: [PATCH 021/180] changelog: images updates, 2026W7 --- content/changelog/2026/02-12-images-update.md | 55 +++++++++++++++++++ data/runtime_versions.yml | 6 +- 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 content/changelog/2026/02-12-images-update.md diff --git a/content/changelog/2026/02-12-images-update.md b/content/changelog/2026/02-12-images-update.md new file mode 100644 index 000000000..4663f1b1d --- /dev/null +++ b/content/changelog/2026/02-12-images-update.md @@ -0,0 +1,55 @@ +--- +title: "Images update: .Net 10, Go 1.26, Mise 2026.2, Python 3.14, uv 0.10" +description: "Many tiny updates, and some surprises we'll detail soon" +date: 2026-02-12 +tags: + - images + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated all our images. Deployment is in progress for all our users. + +* **Common:** + * git 2.53.0 + * Mise 2026.2.8 + * nginx 1.28.2 + * pgpool2 4.7 +* **.Net:** + * Update to 10.0.102 +* **Docker:** + * Docker 29.2.1 +* **Go:** + * Update to 1.26.0 +* **Node.js & Bun:** + * Bun 1.3.9 + * nvm 0.40.4 +* **Python:** + * Update to 3.13.12 + * Update to 3.14.3 + * pip 26.0.1 + * uv 0.10.2 + +## .Net 10 support + +You can now set `CC_DOTNET_VERSION=10.0`, default version is still `8.0`. We'll move to `10.0` in the coming weeks. + +## Python 3.14 support + +You can now set `CC_PYTHON_VERSION=3.14`, default version is still `3.13`. We'll move to `3.14` in the coming weeks. + +## Mise 2026.2 + +Latest branch of Mise includes many changes such as Node.js version detection from `package.json`, hooks overhaul or Shell-style variable expansion in env values. You can benefit from them with this release. + +- [Learn more about latest versions of Mise](https://github.com/jdx/mise/releases) + +## Fixes + +This release includes: +- A fix for [Mise Tasks management in Linux Runtime](/doc/applications/linux/#build-and-run-commands) +- A fix for `CC_NODE_BUILD_TOOL=yarn-berry` diff --git a/data/runtime_versions.yml b/data/runtime_versions.yml index c95642316..0ade4d4ae 100644 --- a/data/runtime_versions.yml +++ b/data/runtime_versions.yml @@ -1,7 +1,7 @@ bun: eol_source: https://github.com/oven-sh/bun/releases default: - - 1.3.8 + - 1.3.9 caddy: eol_source: https://github.com/caddyserver/caddy/releases @@ -15,14 +15,13 @@ dotnet: accepted: - 6.0 (EOL) - 9.0 + - 10.0 (LTS) elixir: eol_source: https://hexdocs.pm/elixir/compatibility-and-deprecations.html default: - 1.19 accepted: - - 1.12 (EOL) - - 1.13 (EOL) - 1.14 (EOL) - 1.15 - 1.16 @@ -100,3 +99,4 @@ python: - 3.11 - 3.12 - 3.13 + - 3.14 From fac542eb1b71fe298a2250656a345585996ea801 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 17 Feb 2026 10:40:07 +0100 Subject: [PATCH 022/180] applications: update Request Flow instructions/support --- content/doc/applications/dotnet.md | 1 + content/doc/applications/elixir.md | 3 +++ content/doc/applications/haskell.md | 3 +++ content/doc/applications/rust.md | 1 + content/doc/develop/request-flow.md | 40 ++++++++++++++++++++++++++--- shared/request-flow.md | 3 ++- 6 files changed, 46 insertions(+), 5 deletions(-) diff --git a/content/doc/applications/dotnet.md b/content/doc/applications/dotnet.md index b89e0ab78..dc7874c7c 100644 --- a/content/doc/applications/dotnet.md +++ b/content/doc/applications/dotnet.md @@ -162,3 +162,4 @@ To access environment variables from your code, you can use `System.Environment. {{% content "more-config" %}} {{% content "url_healthcheck" %}} +{{% content "request-flow" %}} diff --git a/content/doc/applications/elixir.md b/content/doc/applications/elixir.md index 29fa44166..d04070486 100644 --- a/content/doc/applications/elixir.md +++ b/content/doc/applications/elixir.md @@ -77,3 +77,6 @@ Note: If you need to specify the timezone of your application, you can do it wit {{% content "link-addon" %}} {{% content "more-config" %}} + +{{% content "url_healthcheck" %}} +{{% content "request-flow" %}} diff --git a/content/doc/applications/haskell.md b/content/doc/applications/haskell.md index 3dbf79d80..3af098c04 100644 --- a/content/doc/applications/haskell.md +++ b/content/doc/applications/haskell.md @@ -135,3 +135,6 @@ CC_HASKELL_STACK_TARGET="mypackage" {{% content "link-addon" %}} {{% content "more-config" %}} + +{{% content "url_healthcheck" %}} +{{% content "request-flow" %}} diff --git a/content/doc/applications/rust.md b/content/doc/applications/rust.md index e838647f8..a2d663f87 100644 --- a/content/doc/applications/rust.md +++ b/content/doc/applications/rust.md @@ -176,4 +176,5 @@ This loads the environment variable in your `main` function and use `.expect` to {{% content "more-config" %}} {{% content "url_healthcheck" %}} +{{% content "request-flow" %}} diff --git a/content/doc/develop/request-flow.md b/content/doc/develop/request-flow.md index 57bccd695..652556f7a 100644 --- a/content/doc/develop/request-flow.md +++ b/content/doc/develop/request-flow.md @@ -2,12 +2,14 @@ type: docs linkTitle: Request Flow title: Request Flow -description: Automatically chain reverse proxies and middleware (Varnish, Redirection.io, custom) in front of your application with Request Flow on Clever Cloud +description: Automatically chain reverse proxies and middleware (Varnish, Redirection.io, OAuth2 Proxy, custom) in front of your application with Request Flow on Clever Cloud keywords: - request flow - reverse proxy - varnish - redirection.io +- oauth2-proxy +- otoroshi - middleware - port configuration aliases: @@ -20,9 +22,13 @@ Request Flow is Clever Cloud's automatic middleware chaining mechanism. It confi Request Flow is available in the following runtimes: +- [.NET](/doc/applications/dotnet/) +- [Elixir](/doc/applications/elixir/) - [FrankenPHP](/doc/applications/frankenphp/) +- [Haskell](/doc/applications/haskell/) - [Linux](/doc/applications/linux/) - [Python with uv](/doc/applications/python/uv/) +- [Rust](/doc/applications/rust/) - [Static](/doc/applications/static/) - [V (Vlang)](/doc/applications/v/) @@ -30,18 +36,22 @@ Request Flow is available in the following runtimes: | Service | Activation | Description | |---------|-----------|-------------| -| `varnish` | `clevercloud/varnish.vcl` file or `CC_VARNISH_FILE` | HTTP cache accelerator | -| `redirectionio` | `CC_REDIRECTIONIO_PROJECT_KEY` | HTTP redirects, rewrites, SEO | +| `block` | `CC_REQUEST_FLOW="block"` | Blocks public access with a `200 OK` response. Other ports remain accessible through [Network Groups](/doc/develop/network-groups/) | | `custom` | `CC_REQUEST_FLOW_CUSTOM` | Any custom reverse proxy | +| `oauth2-proxy` | `CC_REQUEST_FLOW="oauth2-proxy"` | Authentication proxy using [OAuth2 Proxy](https://oauth2-proxy.github.io/oauth2-proxy/) | +| `otoroshi-challenge` | `OTOROSHI_CHALLENGE_SECRET` | [Otoroshi](/doc/addons/otoroshi/) challenge verification proxy | +| `redirectionio` | `CC_REDIRECTIONIO_PROJECT_KEY` | HTTP redirects, rewrites, SEO | +| `varnish` | `clevercloud/varnish.vcl` file or `CC_VARNISH_FILE` | HTTP cache accelerator | ## Automatic detection When no `CC_REQUEST_FLOW` is set, Clever Cloud detects and activates services automatically: +- If `OTOROSHI_CHALLENGE_SECRET` is set, Otoroshi Challenge is activated - If a `clevercloud/varnish.vcl` file exists (or `CC_VARNISH_FILE` is set), Varnish is activated - If `CC_REDIRECTIONIO_PROJECT_KEY` is set, Redirection.io is activated -Both can be active simultaneously. Default order: Varnish first, then Redirection.io. +All three can be active simultaneously. Default order: Otoroshi Challenge first, then Varnish, then Redirection.io. ## Port management @@ -74,6 +84,25 @@ To disable Request Flow entirely and have your application listen directly on po CC_REQUEST_FLOW="disable" ``` +## Block public access + +Setting `CC_REQUEST_FLOW=block` replaces the public endpoint (port `8080`) with a service that responds `200 OK` to every request. Your application still runs normally, but no external HTTP traffic reaches it through the default route. This is useful for applications that should only communicate through [Network Groups](/doc/develop/network-groups/) or internal services, while keeping the public health check endpoint alive. + +When `block` is set, all other Request Flow services are ignored. + +```bash +CC_REQUEST_FLOW="block" +``` + +### Health check with block mode + +By default, `block` responds `200 OK` regardless of your application's actual state. If [`CC_HEALTH_CHECK_PATH` or `CC_HEALTH_CHECK_PATH_0` to `CC_HEALTH_CHECK_PATH_5`](/doc/develop/healthcheck/) are configured, the blocking service also checks these paths on your application and responds accordingly: + +- `200 OK` if all configured paths return a `2xx` status +- `503 Service Unavailable` if the application is down or any path returns a non-`2xx` status + +This way, the platform's health check still reflects the actual state of your application even when public traffic is blocked. + ## Custom middleware To insert a custom reverse proxy in the chain, add `custom` to `CC_REQUEST_FLOW` and define the command with `CC_REQUEST_FLOW_CUSTOM`. The deployment process replaces `@@LISTEN_PORT@@` and `@@FORWARD_PORT@@` placeholders with the actual allocated ports: @@ -96,7 +125,10 @@ In this example: | `CC_REQUEST_FLOW_CUSTOM` | Command to start a custom middleware. Must contain `@@LISTEN_PORT@@` and `@@FORWARD_PORT@@` placeholders | | `CC_REDIRECTIONIO_PROJECT_KEY` | Redirection.io project key. Activates Redirection.io in the request flow | | `CC_VARNISH_FILE` | Path to a custom Varnish VCL file (default: `clevercloud/varnish.vcl`) | +| `OTOROSHI_CHALLENGE_SECRET` | Otoroshi challenge secret. Activates Otoroshi Challenge verification in the request flow | - [Learn more about Varnish on Clever Cloud](/doc/administrate/cache/) - [Learn more about Redirection.io](https://redirection.io/) +- [Learn more about OAuth2 Proxy](https://oauth2-proxy.github.io/oauth2-proxy/) +- [Learn more about Otoroshi on Clever Cloud](/doc/addons/otoroshi/) - [Learn more about Network Groups](/doc/develop/network-groups/) diff --git a/shared/request-flow.md b/shared/request-flow.md index 145239f86..3fa0ef7c7 100644 --- a/shared/request-flow.md +++ b/shared/request-flow.md @@ -2,10 +2,11 @@ Request Flow automatically chains reverse proxies between port `8080` (public) and your application, managing port allocation with no manual configuration. Supported services are activated by their presence in your project: +- **Otoroshi Challenge**: set `OTOROSHI_CHALLENGE_SECRET` - **Varnish**: add a `clevercloud/varnish.vcl` file or set `CC_VARNISH_FILE` - **Redirection.io**: set `CC_REDIRECTIONIO_PROJECT_KEY` -Both can be active simultaneously. To control the order, set `CC_REQUEST_FLOW` (e.g. `redirectionio,varnish`). To add a custom middleware, include `custom` in the chain and define `CC_REQUEST_FLOW_CUSTOM` with `@@LISTEN_PORT@@` and `@@FORWARD_PORT@@` placeholders. To block public access, set `CC_REQUEST_FLOW=block`. +All three can be active simultaneously. To control the order, set `CC_REQUEST_FLOW` (e.g. `redirectionio,varnish`). To add a custom middleware, include `custom` in the chain and define `CC_REQUEST_FLOW_CUSTOM` with `@@LISTEN_PORT@@` and `@@FORWARD_PORT@@` placeholders. To block public access, set `CC_REQUEST_FLOW=block`. When at least one middleware is active, your application must listen on port `9000` instead of `8080`. From ce0a7e0ba03de5b184a079d8210460dc0f130cc3 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 28 Jan 2026 18:58:12 +0100 Subject: [PATCH 023/180] applications(php): split in multiple files --- content/doc/applications/php.md | 627 ------------------ content/doc/applications/php/_index.md | 218 ++++++ content/doc/applications/php/apache.md | 137 ++++ content/doc/applications/php/composer.md | 91 +++ content/doc/applications/php/extensions.md | 167 +++++ .../doc/applications/php/sessions-emails.md | 64 ++ .../reference-environment-variables.md | 8 +- 7 files changed, 681 insertions(+), 631 deletions(-) delete mode 100644 content/doc/applications/php.md create mode 100644 content/doc/applications/php/_index.md create mode 100644 content/doc/applications/php/apache.md create mode 100644 content/doc/applications/php/composer.md create mode 100644 content/doc/applications/php/extensions.md create mode 100644 content/doc/applications/php/sessions-emails.md diff --git a/content/doc/applications/php.md b/content/doc/applications/php.md deleted file mode 100644 index 03d612ad4..000000000 --- a/content/doc/applications/php.md +++ /dev/null @@ -1,627 +0,0 @@ ---- -type: docs -linkTitle: PHP with Apache -title: PHP with Apache -description: Deploy PHP applications on Clever Cloud with support for multiple frameworks, Composer dependencies, and database integration -keywords: -- php hosting -- apache -- composer -- laravel -- symfony -- wordpress -aliases: -- /applications/php -- /deploy/application/php/php-apps -- /doc/deploy/application/php -- /doc/deploy/application/php/php-apps -- /doc/doc/php -- /doc/php -- /doc/php/php-apps -- /doc/doc/php/php-apps -- /doc/getting-started/by-language/php -- /doc/partials/language-specific-deploy/php -- /getting-started/by-language/php -- /php -- /php/php-apps ---- - -## Overview - -PHP is a widely-used general-purpose scripting language that is especially suited for Web development and can be embedded -into HTML. - -The HTTP server is [Apache 2](https://httpd.apache.org/), and the PHP code is executed by [PHP-FPM](https://php-fpm.org/). - -{{% content "create-application" %}} - -{{% content "set-env-vars" %}} - -## Configure your PHP application - -### Choose your PHP version - -Set the `CC_PHP_VERSION` environment variable to one of the following versions. - -{{< runtimes_versions php >}} - -All new PHP applications are created with a default `CC_PHP_VERSION`. You can of course change it whenever you want then redeploy your application to use the version you want. We only support values based on the first two digits (`X` or `X.Y`, not `X.Y.Z`). - -### Change the webroot - -Since one of the best practices of PHP development is to take the libraries and core files outside the webroot, you may -want to set another webroot than the default one (*the root of your application*). - -#### Using an environment variable - -Add a new environment variable called `CC_WEBROOT` and set `/public` as its value. - -```shell -clever env set CC_WEBROOT /public -``` - -### Change PHP settings - -#### PHP settings - -Most PHP settings can be changed using a `.user.ini` file. - -If you want the settings to be applied to the whole application, you should put this file in your `webroot`. If you did not change it (see above), then your `webroot` is the root of the repository. - -If you put the `.user.ini` file in a subdirectory; settings will be applied recursively starting from this subdirectory. - -#### Same configuration between PHP-CLI and PHP-FPM. - -`.user.ini` files aren't loaded by the PHP CLI by default. - -However, some PHP applications may want to check for the PHP-FPM configuration pre-requisites, `post_max_size` or `upload_max_filesize` values for example. - -To load the PHP-FPM `.user.ini` file during a PHP-CLI process, in a [hook](/doc/develop/build-hooks/), use the `PHP_INI_SCAN_DIR` environment variable to load the additional file. - -Assuming the script runs at the root-folder of the application: - -```bash -#!/usr/bin/env bash - -export PHP_INI_SCAN_DIR=":." -php myscript.php -``` - -This appends the current directory while still loading the default configuration. - -**Note**: The `:` at the beginning of the string is mandatory. It indicates defaults files must still load. - -A specific `.ini` file can be loaded with: - -``` -#!/usr/bin/env bash - -export PHP_INI_SCAN_DIR=":.php-configuration/" -php myscript.php -``` - -This loads every `.ini` files in the `php-configuration/` directory. - -##### Timezone configuration - -All instances on Clever Cloud run on the UTC timezone. We recommend to handle all your dates in UTC internally, and only handle timezones when reading or displaying dates. - -Additionally, you can set PHP's time zone setting with `.user.ini`. For instance, to use the french time zone, edit `.user.ini` to add this line: - -```ini -date.timezone=Europe/Paris -``` - -##### Header injection - -###### With .htaccess - -To inject headers on HTTP responses, add this configuration to `.htaccess` file: - -```sh -Header Set Access-Control-Allow-Origin "https://www.example.com" -Header Set Access-Control-Allow-Headers "Authorization" -``` - -{{< callout type="info" >}} -You can use a `.htaccess` file to create or update headers, but you can't delete them. -{{< /callout >}} - -###### With PHP - -You can also do it from PHP: - -```php -header("Access-Control-Allow-Origin: https://www.example.com"); -header("Access-Control-Allow-Headers: Authorization"); -``` - -If you want to keep this separate from your application, you can configure the application to execute some code on every request. - -In `.user.ini`, add the following line (you need to create `inject_headers.php` first): - -```ini -auto_prepend_file=./inject_headers.php -``` - -Please refer to the [official documentation](https://www.php.net/manual/en/configuration.file.per-user.php) for more information. - -You can review the [available directives](https://www.php.net/manual/en/ini.list.php); all the `PHP_INI_USER`, `PHP_INI_PERDIR`, and `PHP_INI_ALL` directives can be set from within `.user.ini`. - -##### Memory Limit - -When php-fpm spawns a worker it allocates a smaller part of the application's memory to the worker, here is the allocated memory for each flavor: - - | Flavor | Memory Limit | - |----------|--------------| - |Pico | 64M | - |Nano | 64M | - |XS | 128M | - |S | 256M | - |M | 384M | - |L | 512M | - |XL | 768M | - |2XL | 1024M | - |3XL | 1536M | - |4XL+ | 2048M | - -To change this limit you can define `MEMORY_LIMIT` [environment variable](/doc/reference/reference-environment-variables#php). - -If you define a limit exceeding the application memory it will use the default one. - -## Configure Apache - -We use Apache 2 as HTTP Server. In order to configure it, you can create a `.htaccess` file and set directives inside this file. - -### htaccess - -The `.htaccess` file can be created everywhere in you app, depending of the part of the application covered by directives. - -However, directives who applies to the entire application must be declared in a `.htaccess` file to the application root. - -### htpasswd - -You can configure basic authentication using [environment variables](/doc/reference/reference-environment-variables#php). You will need to set `CC_HTTP_BASIC_AUTH` variable to your own `login:password` pair. If you need to allow access to multiple users, you can create additional environment `CC_HTTP_BASIC_AUTH_n` (where `n` is a number) variables. - -### Define a custom HTTP timeout - -You can define the timeout of an HTTP request in Apache using the `HTTP_TIMEOUT` [environment variable](/doc/develop/env-variables). - -**By default, the HTTP timeout is set to 3 minutes (180 seconds)**. - -### Header size - -Default Apache header size is `8k`. If you need to increase it, you can set `CC_APACHE_HEADERS_SIZE` environment variable, between `8` and `256`. Effective value depends on deployment region. [Ask for a dedicated load balancer](https://console.clever-cloud.com/ticket-center-choice) for a specific value. - -### Force HTTPS traffic - -Load balancers handle HTTPS traffic ahead of your application. You can use the `X-Forwarded-Proto` header to know the original protocol (`http` or `https`). - -Place the following snippet in a `.htaccess` file to ensure that your visitors only access your application through HTTPS. - -```conf -RewriteEngine On -RewriteCond %{HTTPS} off -RewriteCond %{HTTP:X-Forwarded-Proto} !https -RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] -``` - -### Prevent Apache to redirect HTTPS calls to HTTP when adding a trailing slash - -`DirectorySlash` is enabled by default on the PHP scalers, therefore Apache will add a trailing slash to a resource when it detects that it is a directory. - -E.g. if foobar is a directory, Apache will automatically redirect `http://example.com/foobar` to `http://example.com/foobar/`. - -Unfortunately the module is unable to detect if the request comes from a secure connection or not. As a result it will force an HTTPS call to be redirected to HTTP. - -In order to prevent this behavior, you can add the following statements in a `.htaccess` file: - -```conf -DirectorySlash Off -RewriteEngine On -RewriteCond %{REQUEST_FILENAME} -d -RewriteRule ^(.+[^/])$ %{HTTP:X-Forwarded-Proto}://%{HTTP_HOST}/$1/ [R=301,L,QSA] -``` - -These statements will keep the former protocol of the request when issuing the redirect. Assuming that the header `X-Forwarded-Proto` is always filled (which is the case on our platform). - -If you want to force all redirects to HTTPS, you can replace `%{HTTP:X-Forwarded-Proto}` with `https`. - -### Change the FastCGI module - -You can choose between two FastCGI modules, `fastcgi` and `proxy_fcgi`, using the `CC_CGI_IMPLEMENTATION` environment variable. If you don't set it `proxy_fcgi` is used as default value. We recommend it, as `fastcgi` implementation is not maintained anymore. - -If you have issues with downloading content, it could be related to the `fastcgi` module not working correctly in combination with the `deflate` module, as the `Content-Length` header is not updated to the new size of the encoded content. To resolve this issue, use `proxy_fcgi`. - -### Environment injection - -As mentioned above, Clever Cloud can inject environment variables that are defined in the -dashboard and by add-ons linked to your application. - -To access the variables, use the `getenv` function. So, for example, if -your application has a postgresql add-on linked: - -```php -$host = getenv("POSTGRESQL_ADDON_HOST"); -$database = getenv("POSTGRESQL_ADDON_DB"); -$username = getenv("POSTGRESQL_ADDON_USER"); -$password = getenv("POSTGRESQL_ADDON_PASSWORD"); - -$pg = new PDO("postgresql:host={$host};dbname={$database}, $username, $password); -``` - -{{< callout type="warning" >}} -Environment variables are displayed in the default output of `phpinfo()`. If you want to use `phpinfo()` without exposing environment variables, you have to call it this way: `phpinfo(INFO_GENERAL | INFO_CREDITS | INFO_CONFIGURATION | INFO_MODULES | INFO_LICENSE)` -{{< /callout >}} - -## Composer - -We support Composer build out of the box. You just need to provide a `composer.json` file in the root of your repository and we will run `composer.phar install --no-ansi --no-progress --no-interaction --no-dev` for you. - -You can also set the `CC_COMPOSER_VERSION` to `1` or `2` to select the composer version to use. - -{{< callout type="info" >}} -If you encounter any issues, add your own `composer.phar` file in the root of your repository which will override the version we use. -{{< /callout >}} - -You can perform your own `composer.phar install` by using the [Post Build hook](/doc/develop/build-hooks#post-build-cc_post_build_hook). - -Example of a `composer.json` file: - -```json{linenos=table} -{ - "require": { - "laravel/framework": "4.1.*", - "ruflin/Elastica": "dev-master", - "shift31/laravel-elasticsearch": "dev-master", - "natxet/CssMin": "dev-master" - }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/timothylhuillier/laravel-elasticsearch.git" - } - ], - "autoload": { - "classmap": [ - "app/controllers", - "app/models", - "app/database/migrations", - "app/database/seeds" - ], - "psr-0": { - "SomeApp": "app" - } - }, - "config": { - "preferred-install": "dist" - }, - "minimum-stability": "dev" -} -``` - -Example of a minimalist PHP application using composer and custom scripts: [php-composer-demo](https://github.com/CleverCloud/php-composer-demo) - -## Development Dependencies - -Development dependencies will not be automatically installed during the deployment. You can control their installation by using the `CC_PHP_DEV_DEPENDENCIES` environment variable which takes `install` value. - -Any other value than `install` will prevent development dependencies from being installed. - -### GitHub rate limit - -Sometimes, you can encounter the following error when downloading dependencies: - -```txt -Failed to download symfony/symfony from dist: Could not authenticate against GitHub.com -``` - -To prevent this download dependencies's fails that is often caused by rate limit of GitHub API while deploying your apps, -we recommend you to add `oauth` token in your composer configuration file or in separate file named as described in -[composer FAQ (API rate limit and OAuth tokens)](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens). - -You can find more documentation about composer configuration at [getcomposer.com](https://getcomposer.org/doc/04-schema.md). - -#### Example - -You use Artisan to manage your project and you want to execute *artisan migrate* before running your app. - -To do this, we use a post build hook, you have to set a new environment variable on your Clever application as following: - -```bash -CC_POST_BUILD_HOOK=php artisan migrate --force -``` - -**Note:** You must add the *execute* permission to your file (`chmod u+x yourfile`) before pushing it. - -## Frameworks and CMS - -The following is the list of tested CMS by our team. - -It's quite not exhaustive, so it does not mean that other CMS can't work on the Clever Cloud platform. - -{{< cards >}} - {{< card link="/developers/guides/tutorial-drupal" title="Drupal" subtitle= "Deploy a Drupal-based website on Clever Cloud" icon="drupal" >}} - {{< card link="/developers/guides/tutorial-laravel" title="Laravel" subtitle= "Deploy a Laravel app on Clever Cloud" icon="laravel" >}} - {{< card link="/developers/guides/tutorial-symfony" title="Symfony" subtitle= "Deploy a Symfony application on Clever Cloud" icon="symfony" >}} - {{< card link="/developers/guides/tutorial-wordpress" title="WordPress" subtitle= "Deploy WordPress on Clever Cloud" icon="wordpress" >}} - {{< card link="/developers/guides/moodle" title="Moodle" subtitle="Full Moodle installation and configuration guide" icon="moodle" >}} - -{{< /cards >}} - -Others PHP frameworks tested on Clever Cloud: - -- Prestashop -- Dokuwiki -- Joomla -- SugarCRM -- Drupal -- Magento -- Status.net -- Symfony -- Thelia -- Laravel -- Sylius - -## Available extensions and modules - -Clever Cloud PHP with Apache applications enable the following extensions by default: - -`amqp`, `bcmath`, `bz2`, `calendar`, `ctype`, `curl`, `dba`, `exif`, `fileinfo`, `filter`, `ftp`, `gd`, `gettext`, `gmp`, `iconv`, `imagick`, `imap`, `intl`, `json`, `ldap`, `libsodium`, `mbstring`, `mcrypt`, `memcached`, `memcache`, `mongodb`, `mysql`, `mysqli`, `opcache`, `pcntl`, `pcre`, `pdo-mysql`, `pdo-odbc`, `pdo-pgsql`, `pdo-sqlite`, `pgsql`, `phar`, `posix`, `pspell`, `readline`, `redis`, `session`, `shmop`, `sockets`, `sodium`, `solr`, `ssh2`, `ssl`, `tidy`, `tokenizer`, `unixodbc`, `xml`, `xmlrpc`, `xsl`, `zip`, `zlib`. - -You can also enable the following extensions on demand: - -`apcu`, `blackfire`, `elastic_apm_agent`, `event`, `excimer`, `geos`, `gnupg`, `grpc`, `ioncube`, `imap`, `mailparse`, `maxminddb`, `mongo`, `newrelic`, `oauth`, `opentelemetry`, `pcs`, `PDFlib`, `pdo_sqlsrv`, `protobuf`, `pspell`, `rdkafka`, `scoutapm`, `sqlsrv`, `sqreen`, `tideways`, `uopz`, `uploadprogress`, `xdebug`, `xmlrpc`, `yaml` - ->[!NOTE] ->Only some extensions support PHP 8.5 for now: `amqp`, `apcu`, `blackfire`, `event`, `excimer`, `gnupg`, `grpc`, `imagick`, `imap`, `mailparse`, `maxminddb`, `mcrypt`, `memcached`, `oauth`, `opentelemetry`, `PDFlib`, `pdo_sqlsrv`, `protobuf`, `pspell`, `rdkafka`, `redis`, `solr`, `sqlsrv`, `ssh2`, `tideways`, `uploadprogress`, `xdebug`, `yaml`, `zip`. We'll add support for more extensions as they are released. - -You can check extensions and versions by viewing our `phpinfo()` for: - -- [PHP 5.6](https://php56info.cleverapps.io) -- [PHP 7.1](https://php71info.cleverapps.io) -- [PHP 7.2](https://php72info.cleverapps.io) -- [PHP 7.3](https://php73info.cleverapps.io) -- [PHP 7.4](https://php74info.cleverapps.io) -- [PHP 8.0](https://php80info.cleverapps.io) -- [PHP 8.1](https://php81info.cleverapps.io) -- [PHP 8.2](https://php82info.cleverapps.io) -- [PHP 8.3](https://php83info.cleverapps.io) -- [PHP 8.4](https://php84info.cleverapps.io) -- [PHP 8.5](https://php85info.cleverapps.io) - -If you have a request about extensions, contact [Clever Cloud Support](https://console.clever-cloud.com/ticket-center-choice). - -### Enable specific extensions - -Some extensions need to be enabled explicitly. To do so, set the corresponding [environment variable](#setting-up-environment-variables-on-clever-cloud): - -- APCu: set `ENABLE_APCU` to `true`. - - APCu is an in-memory key-value store for PHP. Keys are of type string and values can be any PHP variables. - -- Elastic APM Agent: set `ENABLE_ELASTIC_APM_AGENT` to `true` (default if `ELASTIC_APM_SERVER_URL` is defined). - - Elastic APM agent is Elastic's APM agent extension for PHP. The PHP agent enables you to trace the execution of operations - in your application, sending performance metrics and errors to the Elastic APM server. - **Warning**: This extension is available starting PHP 7.2. - -- Event: set `ENABLE_EVENT` to `true`. - - Event is an extension to schedule I/O, time and signal based events. - -- Excimer: set `ENABLE_EXCIMER` to `true`. - - Excimer is an extension that provides a low-overhead interrupting timer and sampling profiler. - -- GEOS: set `ENABLE_GEOS` to `true`. - - GEOS (Geometry Engine - Open Source) is a C++ port of the Java Topology Suite (JTS). - -- GnuPG: set `ENABLE_GNUPG` to `true`. - - GnuPG is an extension that provides methods to interact with GNU Privacy Guard (OpenPGP implementation). - -- gRPC: set `ENABLE_GRPC` to `true`. - - gRPC is an extension for the high performance, open source, general RPC framework layered over HTTP/2. - -- IonCube: set `ENABLE_IONCUBE` to `true`. - - IonCube is a tool to obfuscate PHP code. It's often used by paying Prestashop and WordPress plugins. - -- IMAP (only for PHP 8.4+): set `ENABLE_IMAP` to `true`. - - IMAP is an extension to operate with the IMAP protocol, as well as the NNTP, POP3, and local mailbox access methods. - -- Mailparse: set `ENABLE_MAILPARSE` to `true`. - - Mailparse is an extension for parsing and working with email messages. It can deal with RFC 822 and RFC 2045 (MIME) compliant messages. - -- MaxMind DB: set `ENABLE_MAXMINDDB` to `true`. - - Extension for reading MaxMind DB files. MaxMind DB is a binary file format that stores data indexed by IP address subnets (IPv4 or IPv6). - -- Mongo: set `ENABLE_MONGO` to `true`. - - MongoDB is a NoSQL Database. This extension allows to use it from PHP. - **Warning**: this extension is now superseded by the `mongodb` extension. We provide it for backward compatibility. - -- NewRelic: set `ENABLE_NEWRELIC` to `true`. - - Newrelic Agent for PHP. Newrelic is a software analytics tool. - -- OAuth: set `ENABLE_OAUTH` to `true`. - - OAuth consumer extension. OAuth is an authorization protocol built on top of HTTP. - -- OpenTelemetry: set `ENABLE_OPENTELEMETRY` to `true`. - - OpenTelemetry is an extension to facilitate the generation, export, collection of telemetry data such as traces, metrics, and logs. - -- PCS: set `ENABLE_PCS` to `true`. - - PCS provides a fast and easy way to mix C and PHP code in your PHP extension. - -- PDFlib: set `ENABLE_PDFlib` to `true`. - - PDFlib is a commercial library for generating PDF files. It provides a PHP extension to create and manipulate PDF documents. - -- Protobuf: set `ENABLE_PROTOBUF` to `true`. - - Protobuf is an extension for the language-neutral, platform-neutral extensible mechanism for serializing structured data. - -- Pspell: set `ENABLE_PSPELL` to `true`. - - Pspell is an extension to check the spelling of words and offer suggestions. - -- Rdkafka: set `ENABLE_RDKAFKA` to `true`. - - PHP-rdkafka is a thin librdkafka binding providing a working PHP 5 / PHP 7 Kafka client. - -- Scout APM: set `ENABLE_SCOUTAPM` to `true`. - - The Scout APM extension to provide additional capabilities to application monitoring over just using the base PHP userland library. - -- SQL Server: set `ENABLE_SQLSRV` or `ENABLE_PDO_SQLSRV` to `true`. - - These extensions enable drivers that rely on the Microsoft ODBC Driver to handle the low-level communication with SQL Server. The `SQLSRV` extension provides a procedural interface while the `PDO_SQLSRV` extension implements PDO for accessing data in all editions of SQL Server 2012 and later (including Azure SQL DB). - -- Sqreen: The Sqreen agent is started automatically after adding the environment variables (`SQREEN_API_APP_NAME` and `SQREEN_API_TOKEN`). - -- Tideways: set `ENABLE_TIDEWAYS` to `true`. - - Tideways is an extension that provides profiling and monitoring capabilities for PHP applications. - -- Uopz: set `ENABLE_UOPZ` to `true`. - - The uopz extension is focused on providing utilities to aid with unit testing PHP code. - -- Uploadprogress: set `ENABLE_UPLOADPROGRESS` to `true`. - - The uploadprogress extension is used to track the progress of a file download. - -- XDebug: set `ENABLE_XDEBUG` to `true`. - - XDebug is a debugger and profiler tool for PHP. - -- XML RPC: set `ENABLE_XMLRPC` to `true`. - - XML-RPC is an extension for server and client bindings - -- YAML: set `ENABLE_YAML` to `true`. - - YAML is an extension providing a YAML-1.1 parser and emitter - -You can use `DISABLE_=true` in your [environment variables](/doc/reference/reference-environment-variables/) to disable an extension. - -## Configure the session storage - -By default, an [FS Bucket](/doc/addons/fs-bucket/) is created for each PHP applications, so that session data is available on each instance. This FS Bucket is also used to store TMP files by default. You can change this behavior by setting the `TMPDIR` environment variable. You can set it to `/tmp` for example. - -> [!NOTE] FS Buckets are not available in HDS regions -> To deploy a PHP application on an HDS region, set [`CC_PHP_DISABLE_APP_BUCKET=true`](/doc/applications/php/#speed-up-or-disable-the-session-fs-bucket). Consider using Redis to manage PHP sessions. - -### Speed up or disable the session FS Bucket - -You can set the following environment variables: - -- `CC_PHP_ASYNC_APP_BUCKET=async` to mount the session FS Bucket with the `async` option. - It speeds up the FS Bucket usage, but it can corrupt files in case of a network outage. -- `CC_PHP_DISABLE_APP_BUCKET=(true|yes|disable)` to entirely prevent the session FS Bucket - from being mounted. - Use this if you don't use the default PHP session library. - It will speed up your application but users might lose their session across instances - and deployments. - -### Use Materia KV or Redis to store PHP Sessions - -Clever Cloud allows to store PHP sessions easily in a [Materia KV](/doc/addons/materia-kv) or [Redis](/doc/addons/redis) add-on to improve performance/reliability. - -To enable this feature, you need to: - -- Set `ENABLE_REDIS=true` as [environment variable](/doc/develop/env-variables) in the PHP application -- Set `SESSION_TYPE=redis` as [environment variable](/doc/develop/env-variables) in the PHP application -- Create and link a Materia KV or Redis add-on to the PHP application - -## Sending emails - -The PHP language has the `mail` function to directly send emails. While we do not provide a SMTP server (needed to send the emails), you can configure one through environment variables. - -We provide Mailpace add-on to send emails through PHP `mail()` function. You have to turn TLS on with port 465 (environment variable `CC_MTA_SERVER_USE_TLS=true`) to make Mailpace working. - -We also recommend you to use [Mailgun](https://www.mailgun.com/) or [Mailjet](https://www.mailjet.com/) if your project supports it. These services already have everything you need to send emails from your code. - -### Configure the SMTP server - -Services like [Mailgun](https://www.mailgun.com/) or [Mailjet](https://www.mailjet.com/) provide SMTP servers. If your application has no other way but to use the `mail` function of PHP to send emails, you have to configure a SMTP server. This can be done through environment variables: - -- `CC_MTA_SERVER_HOST`: Host of the SMTP server. -- `CC_MTA_SERVER_PORT`: Port of the SMTP server. Defaults to `465` whether TLS is enabled or not. -- `CC_MTA_AUTH_USER`: User to authenticate to the SMTP server. -- `CC_MTA_AUTH_PASSWORD`: Password to authenticate to the SMTP server. -- `CC_MTA_SERVER_USE_TLS`: Enable or disable TLS. Defaults to `true`. -- `CC_MTA_SERVER_STARTTLS`: Enable or disable STARTTLS. Defaults to `false`. -- `CC_MTA_SERVER_AUTH_METHOD`: Enable or disable authentication. Defaults to `on`. - -## Configure Monolog - -A lot of frameworks (including Symfony) use Monolog to handle logging. The default configuration of Monolog doesn't allow to log errors into the console. - -Here is a basic configuration of Monolog to send your application's logs into our logging system and access them into the Console: - -```yaml -monolog: - handlers: - clever_logs: - type: error_log - level: warning -``` - -You can change the level to whatever level you desire. For Symfony, the configuration file is `app/config/config_prod.yml`. - -Laravel doesn't need Monolog to retrieve logs via Clever console or Clever CLI. Here, ensure that you have the following line in `config/app.php`: - -```php -return [ - // … - 'log' => env('APP_LOG'), - // … -]; -``` - -Then, set `APP_LOG=syslog` as Clever application environment variable. - -## Using HTTP authentication - -Using basic HTTP authentication, PHP usually handles the values of user and password in variables named `$_SERVER['PHP_AUTH_USER']` and `$_SERVER['PHP_AUTH_PW']`. - -At Clever Cloud, we have enabled an Apache option to pass directly the Authorization header, even though we are using FastCGI; still, the header is not used by PHP, and the aforementioned variables are empty. - -You can do this to fill them using the Authorization header: - -```php -list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':' , base64_decode(substr($_SERVER['Authorization'], 6))); -``` - -{{% content "new-relic" %}} - -{{% content "blackfire" %}} - -## Deploy on Clever Cloud - -Application deployment on Clever Cloud is via **Git or FTP**. - -{{% content "deploy-git" %}} - -{{% content "deploy-ftp" %}} - -## ProxySQL - -{{% content "proxysql" %}} - -You can learn more about ProxySQL on the [dedicated documentation page](/guides/proxysql) - -{{% content "more-config" %}} - -{{% content "url_healthcheck" %}} diff --git a/content/doc/applications/php/_index.md b/content/doc/applications/php/_index.md new file mode 100644 index 000000000..c7dacd53f --- /dev/null +++ b/content/doc/applications/php/_index.md @@ -0,0 +1,218 @@ +--- +type: docs +linkTitle: PHP with Apache +title: PHP with Apache application runtime +description: Deploy PHP applications on Clever Cloud with Apache, PHP-FPM, Composer dependencies, and framework support +keywords: +- php hosting +- apache +- composer +- laravel +- symfony +- wordpress +aliases: +- /applications/php +- /deploy/application/php/php-apps +- /doc/deploy/application/php +- /doc/deploy/application/php/php-apps +- /doc/doc/php +- /doc/php +- /doc/php/php-apps +- /doc/doc/php/php-apps +- /doc/getting-started/by-language/php +- /doc/partials/language-specific-deploy/php +- /getting-started/by-language/php +- /php +- /php/php-apps +--- + +## Overview + +PHP is a widely-used general-purpose scripting language that is especially suited for Web development and can be embedded +into HTML. + +The HTTP server is [Apache 2](https://httpd.apache.org/), and the PHP code is executed by [PHP-FPM](https://php-fpm.org/). + +## Create your PHP application + +To create a new PHP application, use the [Clever Cloud Console](https://console.clever-cloud.com) or [Clever Tools](https://github.com/CleverCloud/clever-tools): + +```bash +clever create --type php +``` +- [Learn more about Clever Tools](/doc/cli/) +- [Learn more about Clever Cloud application deployment](/doc/quickstart/#create-an-application-step-by-step) + +## Configure your PHP application + +### Mandatory needs + +PHP runtime requires a working web application. The HTTP server is Apache 2 with PHP-FPM. If you need to serve files from a specific directory, set the `CC_WEBROOT` environment variable (e.g. `/public`). + +```shell +clever env set CC_WEBROOT /public +``` + +- [Learn more about environment variables on Clever Cloud](/doc/reference/reference-environment-variables/) + +### Build phase + +Composer build is supported out of the box. If a `composer.json` file is present at the root of your repository, dependencies are installed automatically during the build phase. + +- [Learn more about Composer on Clever Cloud](/doc/applications/php/composer/) +- [Learn more about Deployment hooks](/doc/develop/build-hooks/) + +### PHP version + +Set the `CC_PHP_VERSION` environment variable to one of the following versions. + +{{< runtimes_versions php >}} + +All new PHP applications are created with a default `CC_PHP_VERSION`. You can change it whenever you want then redeploy your application to use the version you want. Only values based on the first two digits (`X` or `X.Y`, not `X.Y.Z`) are supported. + +### Custom PHP configuration + +Most PHP settings can be changed using a `.user.ini` file. + +If you want the settings to be applied to the whole application, you should put this file in your `webroot`. If you did not change it, then your `webroot` is the root of the repository. + +If you put the `.user.ini` file in a subdirectory, settings will be applied recursively starting from this subdirectory. + +You can review the [available directives](https://www.php.net/manual/en/ini.list.php); all the `PHP_INI_USER`, `PHP_INI_PERDIR`, and `PHP_INI_ALL` directives can be set from within `.user.ini`. + +- [Learn more about .user.ini](https://www.php.net/manual/en/configuration.file.per-user.php) + +#### Same configuration between PHP-CLI and PHP-FPM + +`.user.ini` files are not loaded by the PHP CLI by default. + +However, some PHP applications may want to check for the PHP-FPM configuration pre-requisites, `post_max_size` or `upload_max_filesize` values for example. + +To load the PHP-FPM `.user.ini` file during a PHP-CLI process, in a [hook](/doc/develop/build-hooks/), use the `PHP_INI_SCAN_DIR` environment variable to load the additional file. + +Assuming the script runs at the root-folder of the application: + +```bash +#!/usr/bin/env bash + +export PHP_INI_SCAN_DIR=":." +php myscript.php +``` + +This appends the current directory while still loading the default configuration. + +> [!NOTE] +> The `:` at the beginning of the string is mandatory. It indicates defaults files must still load. + +A specific `.ini` file can be loaded with: + +```bash +#!/usr/bin/env bash + +export PHP_INI_SCAN_DIR=":.php-configuration/" +php myscript.php +``` + +This loads every `.ini` files in the `php-configuration/` directory. + +#### Timezone configuration + +All instances on Clever Cloud run on the UTC timezone. We recommend to handle all your dates in UTC internally, and only handle timezones when reading or displaying dates. + +Additionally, you can set PHP's time zone setting with `.user.ini`. For instance, to use the french time zone, edit `.user.ini` to add this line: + +```ini +date.timezone=Europe/Paris +``` + +#### Memory Limit + +When php-fpm spawns a worker it allocates a smaller part of the application's memory to the worker, here is the allocated memory for each flavor: + + | Flavor | Memory Limit | + |----------|--------------| + |Pico | 64M | + |Nano | 64M | + |XS | 128M | + |S | 256M | + |M | 384M | + |L | 512M | + |XL | 768M | + |2XL | 1024M | + |3XL | 1536M | + |4XL+ | 2048M | + +To change this limit you can define `MEMORY_LIMIT` [environment variable](/doc/reference/reference-environment-variables#php). + +If you define a limit exceeding the application memory it will use the default one. + +## Frameworks and CMS + +The following is the list of tested CMS by our team. + +It's quite not exhaustive, so it does not mean that other CMS can't work on the Clever Cloud platform. + +{{< cards >}} + {{< card link="/developers/guides/tutorial-drupal" title="Drupal" subtitle= "Deploy a Drupal-based website on Clever Cloud" icon="drupal" >}} + {{< card link="/developers/guides/tutorial-laravel" title="Laravel" subtitle= "Deploy a Laravel app on Clever Cloud" icon="laravel" >}} + {{< card link="/developers/guides/tutorial-symfony" title="Symfony" subtitle= "Deploy a Symfony application on Clever Cloud" icon="symfony" >}} + {{< card link="/developers/guides/tutorial-wordpress" title="WordPress" subtitle= "Deploy WordPress on Clever Cloud" icon="wordpress" >}} + {{< card link="/developers/guides/moodle" title="Moodle" subtitle="Full Moodle installation and configuration guide" icon="moodle" >}} + +{{< /cards >}} + +Others PHP frameworks tested on Clever Cloud: + +- Prestashop +- Dokuwiki +- Joomla +- SugarCRM +- Drupal +- Magento +- Status.net +- Symfony +- Thelia +- Laravel +- Sylius + +## Configure Monolog + +A lot of frameworks (including Symfony) use Monolog to handle logging. The default configuration of Monolog doesn't allow to log errors into the console. + +Here is a basic configuration of Monolog to send your application's logs into our logging system and access them into the Console: + +```yaml +monolog: + handlers: + clever_logs: + type: error_log + level: warning +``` + +You can change the level to whatever level you desire. For Symfony, the configuration file is `app/config/config_prod.yml`. + +Laravel doesn't need Monolog to retrieve logs via Clever console or Clever CLI. Here, ensure that you have the following line in `config/app.php`: + +```php +return [ + // … + 'log' => env('APP_LOG'), + // … +]; +``` + +Then, set `APP_LOG=syslog` as Clever application environment variable. + +{{% content "new-relic" %}} + +{{% content "blackfire" %}} + +## ProxySQL + +{{% content "proxysql" %}} + +You can learn more about ProxySQL on the [dedicated documentation page](/guides/proxysql) + +{{% content "url_healthcheck" %}} +{{% content "redirectionio" %}} +{{% content "varnish" %}} diff --git a/content/doc/applications/php/apache.md b/content/doc/applications/php/apache.md new file mode 100644 index 000000000..26483460d --- /dev/null +++ b/content/doc/applications/php/apache.md @@ -0,0 +1,137 @@ +--- +type: docs +linkTitle: Apache +title: Apache web server configuration +description: Configure Apache 2 for PHP applications on Clever Cloud with htaccess, authentication, HTTPS redirection, and FastCGI settings +keywords: +- apache +- htaccess +- htpasswd +- fastcgi +- https redirect +--- + +## Configure Apache + +Apache 2 is used as HTTP Server for PHP applications on Clever Cloud. You can configure it with `.htaccess` files and environment variables. + +### htaccess + +The `.htaccess` file can be created anywhere in your app, depending on the part of the application that the directives cover. + +However, directives that apply to the entire application must be declared in a `.htaccess` file at the application root. + +### Basic authentication + +You can configure basic authentication using [environment variables](/doc/reference/reference-environment-variables#php). You will need to set `CC_HTTP_BASIC_AUTH` variable to your own `login:password` pair. If you need to allow access to multiple users, you can create additional environment `CC_HTTP_BASIC_AUTH_n` (where `n` is a number) variables. + +### HTTP timeout + +You can define the timeout of an HTTP request in Apache using the `HTTP_TIMEOUT` [environment variable](/doc/develop/env-variables). + +**By default, the HTTP timeout is set to 3 minutes (180 seconds)**. + +### Header size + +Default Apache header size is `8k`. If you need to increase it, you can set `CC_APACHE_HEADERS_SIZE` environment variable, between `8` and `256`. Effective value depends on deployment region. [Ask for a dedicated load balancer](https://console.clever-cloud.com/ticket-center-choice) for a specific value. + +### Force HTTPS traffic + +Load balancers handle HTTPS traffic ahead of your application. You can use the `X-Forwarded-Proto` header to know the original protocol (`http` or `https`). + +Place the following snippet in a `.htaccess` file to ensure that your visitors only access your application through HTTPS. + +```conf +RewriteEngine On +RewriteCond %{HTTPS} off +RewriteCond %{HTTP:X-Forwarded-Proto} !https +RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] +``` + +### Prevent Apache to redirect HTTPS calls to HTTP when adding a trailing slash + +`DirectorySlash` is enabled by default on the PHP scalers, therefore Apache will add a trailing slash to a resource when it detects that it is a directory. + +E.g. if foobar is a directory, Apache will automatically redirect `http://example.com/foobar` to `http://example.com/foobar/`. + +Unfortunately the module is unable to detect if the request comes from a secure connection or not. As a result it will force an HTTPS call to be redirected to HTTP. + +In order to prevent this behavior, you can add the following statements in a `.htaccess` file: + +```conf +DirectorySlash Off +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} -d +RewriteRule ^(.+[^/])$ %{HTTP:X-Forwarded-Proto}://%{HTTP_HOST}/$1/ [R=301,L,QSA] +``` + +These statements will keep the former protocol of the request when issuing the redirect. Assuming that the header `X-Forwarded-Proto` is always filled (which is the case on our platform). + +If you want to force all redirects to HTTPS, you can replace `%{HTTP:X-Forwarded-Proto}` with `https`. + +### Change the FastCGI module + +You can choose between two FastCGI modules, `fastcgi` and `proxy_fcgi`, using the `CC_CGI_IMPLEMENTATION` environment variable. If you don't set it `proxy_fcgi` is used as default value. `proxy_fcgi` is recommended, as `fastcgi` implementation is not maintained anymore. + +If you have issues with downloading content, it could be related to the `fastcgi` module not working correctly in combination with the `deflate` module, as the `Content-Length` header is not updated to the new size of the encoded content. To resolve this issue, use `proxy_fcgi`. + +## Environment injection + +Clever Cloud injects environment variables defined in the Console and by linked add-ons. To access them from PHP, use the `getenv` function. For example, if your application has a PostgreSQL add-on linked: + +```php +$host = getenv("POSTGRESQL_ADDON_HOST"); +$database = getenv("POSTGRESQL_ADDON_DB"); +$username = getenv("POSTGRESQL_ADDON_USER"); +$password = getenv("POSTGRESQL_ADDON_PASSWORD"); + +$pg = new PDO("pgsql:host={$host};dbname={$database}", $username, $password); +``` + +> [!WARNING] +> Environment variables are displayed in the default output of `phpinfo()`. To use `phpinfo()` without exposing environment variables, call it this way: `phpinfo(INFO_GENERAL | INFO_CREDITS | INFO_CONFIGURATION | INFO_MODULES | INFO_LICENSE)` + +## Header injection + +### With .htaccess + +To inject headers on HTTP responses, add this configuration to `.htaccess` file: + +```sh +Header Set Access-Control-Allow-Origin "https://www.example.com" +Header Set Access-Control-Allow-Headers "Authorization" +``` + +> [!NOTE] +> You can use a `.htaccess` file to create or update headers, but you can't delete them. + +### With PHP + +You can also do it from PHP: + +```php +header("Access-Control-Allow-Origin: https://www.example.com"); +header("Access-Control-Allow-Headers: Authorization"); +``` + +If you want to keep this separate from your application, you can configure the application to execute some code on every request. + +In `.user.ini`, add the following line (you need to create `inject_headers.php` first): + +```ini +auto_prepend_file=./inject_headers.php +``` + +- [Learn more about .user.ini directives](https://www.php.net/manual/en/configuration.file.per-user.php) + +## Using HTTP authentication + +Using basic HTTP authentication, PHP usually handles the values of user and password in variables named `$_SERVER['PHP_AUTH_USER']` and `$_SERVER['PHP_AUTH_PW']`. + +At Clever Cloud, an Apache option is enabled to pass directly the Authorization header, even though FastCGI is used; still, the header is not used by PHP, and the aforementioned variables are empty. + +You can do this to fill them using the Authorization header: + +```php +list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':' , base64_decode(substr($_SERVER['Authorization'], 6))); +``` diff --git a/content/doc/applications/php/composer.md b/content/doc/applications/php/composer.md new file mode 100644 index 000000000..f5cf73e93 --- /dev/null +++ b/content/doc/applications/php/composer.md @@ -0,0 +1,91 @@ +--- +type: docs +linkTitle: Composer +title: Composer and dependencies +description: Manage PHP dependencies with Composer on Clever Cloud, including version selection, development dependencies, and private repositories +keywords: +- composer +- php dependencies +- github rate limit +- dev dependencies +--- + +## Composer + +Composer build is supported out of the box. If a `composer.json` file is detected at the root of your project, dependencies are installed during the build phase with `--no-interaction --no-progress --no-scripts --no-dev` flags. To override the base flags (`--no-interaction --no-progress --no-scripts`), set the `CC_PHP_COMPOSER_FLAGS` environment variable. + +To install development dependencies, set `CC_PHP_DEV_DEPENDENCIES` to `install`. This removes the `--no-dev` flag independently of `CC_PHP_COMPOSER_FLAGS`. + +Set `CC_COMPOSER_VERSION` to select the Composer version: `1`, `2` (default) or `lts` (maps to `2.2`). + +> [!TIP] Use a local Composer version +> If you put a `composer.phar` file at the root of your project, it will be used to install dependencies. + +You can perform your own `composer.phar install` by using the [Post Build hook](/doc/develop/build-hooks#post-build-cc_post_build_hook). + +Example of a `composer.json` file: + +```json +{ + "require": { + "laravel/framework": "4.1.*", + "ruflin/Elastica": "dev-master", + "shift31/laravel-elasticsearch": "dev-master", + "natxet/CssMin": "dev-master" + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/timothylhuillier/laravel-elasticsearch.git" + } + ], + "autoload": { + "classmap": [ + "app/controllers", + "app/models", + "app/database/migrations", + "app/database/seeds" + ], + "psr-0": { + "SomeApp": "app" + } + }, + "config": { + "preferred-install": "dist" + }, + "minimum-stability": "dev" +} +``` + +- [Example: minimalist PHP application using Composer and custom scripts](https://github.com/CleverCloud/php-composer-demo) + +## Development Dependencies + +Development dependencies are not automatically installed during the deployment. Set `CC_PHP_DEV_DEPENDENCIES` to `install` to include them. Set it to `skip` or leave it unset to exclude them. + +## GitHub rate limit + +Sometimes, you can encounter the following error when downloading dependencies: + +```txt +Failed to download symfony/symfony from dist: Could not authenticate against GitHub.com +``` + +To prevent this download dependencies's fails that is often caused by rate limit of GitHub API while deploying your apps, +we recommend you to add `oauth` token in your composer configuration file or in separate file named as described in +[composer FAQ (API rate limit and OAuth tokens)](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens). + +You can find more documentation about composer configuration at [getcomposer.com](https://getcomposer.org/doc/04-schema.md). + +## Post-build hook example + +You use Artisan to manage your project and you want to execute *artisan migrate* before running your app. + +To do this, use a post build hook by setting the following environment variable on your Clever application: + +```bash +CC_POST_BUILD_HOOK=php artisan migrate --force +``` + +> [!NOTE] +> You must add the *execute* permission to your file (`chmod u+x yourfile`) before pushing it. diff --git a/content/doc/applications/php/extensions.md b/content/doc/applications/php/extensions.md new file mode 100644 index 000000000..29c7ab429 --- /dev/null +++ b/content/doc/applications/php/extensions.md @@ -0,0 +1,167 @@ +--- +type: docs +linkTitle: Extensions +title: PHP extensions +description: Available and on-demand PHP extensions on Clever Cloud, with activation instructions and phpinfo links +keywords: +- php extensions +- apcu +- xdebug +- opcache +- grpc +- opentelemetry +--- + +## Available extensions and modules + +Clever Cloud PHP with Apache applications enables the following extensions by default: + +`amqp`, `bcmath`, `bz2`, `calendar`, `ctype`, `curl`, `dba`, `exif`, `fileinfo`, `filter`, `ftp`, `gd`, `gettext`, `gmp`, `iconv`, `imagick`, `imap`, `intl`, `json`, `ldap`, `libsodium`, `mbstring`, `mcrypt`, `memcached`, `memcache`, `mongodb`, `mysql`, `mysqli`, `opcache`, `pcntl`, `pcre`, `pdo-mysql`, `pdo-odbc`, `pdo-pgsql`, `pdo-sqlite`, `pgsql`, `phar`, `posix`, `pspell`, `readline`, `redis`, `session`, `shmop`, `sockets`, `sodium`, `solr`, `ssh2`, `ssl`, `tidy`, `tokenizer`, `unixodbc`, `xml`, `xmlrpc`, `xsl`, `zip`, `zlib`. + +You can also enable the following extensions on demand: + +`apcu`, `blackfire`, `elastic_apm_agent`, `event`, `excimer`, `geos`, `gnupg`, `grpc`, `ioncube`, `imap`, `mailparse`, `maxminddb`, `mongo`, `newrelic`, `oauth`, `opentelemetry`, `pcs`, `PDFlib`, `pdo_sqlsrv`, `protobuf`, `pspell`, `rdkafka`, `scoutapm`, `sqlsrv`, `sqreen`, `tideways`, `uopz`, `uploadprogress`, `xdebug`, `xmlrpc`, `yaml` + +> [!NOTE] +> Only some extensions support PHP 8.5 for now: `amqp`, `apcu`, `blackfire`, `event`, `excimer`, `gnupg`, `grpc`, `imagick`, `imap`, `mailparse`, `maxminddb`, `memcached`, `oauth`, `opentelemetry`, `pdo_sqlsrv`, `protobuf`, `pspell`, `rdkafka`, `redis`, `sqlsrv`, `ssh2`, `tideways`, `uploadprogress`, `yaml`, `zip`. More extensions will be added as they are released. + +You can check extensions and versions by viewing the `phpinfo()` for: + +- [PHP 5.6](https://php56info.cleverapps.io) +- [PHP 7.1](https://php71info.cleverapps.io) +- [PHP 7.2](https://php72info.cleverapps.io) +- [PHP 7.3](https://php73info.cleverapps.io) +- [PHP 7.4](https://php74info.cleverapps.io) +- [PHP 8.0](https://php80info.cleverapps.io) +- [PHP 8.1](https://php81info.cleverapps.io) +- [PHP 8.2](https://php82info.cleverapps.io) +- [PHP 8.3](https://php83info.cleverapps.io) +- [PHP 8.4](https://php84info.cleverapps.io) +- [PHP 8.5](https://php85info.cleverapps.io) + +If you have a request about extensions, contact [Clever Cloud Support](https://console.clever-cloud.com/ticket-center-choice). + +## Enable specific extensions + +Some extensions need to be enabled explicitly. To do so, set the corresponding environment variable: + +- APCu: set `ENABLE_APCU` to `true`. + + APCu is an in-memory key-value store for PHP. Keys are of type string and values can be any PHP variables. + +- Elastic APM Agent: set `ENABLE_ELASTIC_APM_AGENT` to `true` (default if `ELASTIC_APM_SERVER_URL` is defined). + + Elastic APM agent is Elastic's APM agent extension for PHP. The PHP agent enables you to trace the execution of operations + in your application, sending performance metrics and errors to the Elastic APM server. + **Warning**: This extension is available starting PHP 7.2. + +- Event: set `ENABLE_EVENT` to `true`. + + Event is an extension to schedule I/O, time and signal based events. + +- Excimer: set `ENABLE_EXCIMER` to `true`. + + Excimer is an extension that provides a low-overhead interrupting timer and sampling profiler. + +- GEOS: set `ENABLE_GEOS` to `true`. + + GEOS (Geometry Engine - Open Source) is a C++ port of the Java Topology Suite (JTS). + +- GnuPG: set `ENABLE_GNUPG` to `true`. + + GnuPG is an extension that provides methods to interact with GNU Privacy Guard (OpenPGP implementation). + +- gRPC: set `ENABLE_GRPC` to `true`. + + gRPC is an extension for the high performance, open source, general RPC framework layered over HTTP/2. + +- IonCube: set `ENABLE_IONCUBE` to `true`. + + IonCube is a tool to obfuscate PHP code. It's often used by paying Prestashop and WordPress plugins. + +- IMAP (only for PHP 8.4+): set `ENABLE_IMAP` to `true`. + + IMAP is an extension to operate with the IMAP protocol, as well as the NNTP, POP3, and local mailbox access methods. + +- Mailparse: set `ENABLE_MAILPARSE` to `true`. + + Mailparse is an extension for parsing and working with email messages. It can deal with RFC 822 and RFC 2045 (MIME) compliant messages. + +- MaxMind DB: set `ENABLE_MAXMINDDB` to `true`. + + Extension for reading MaxMind DB files. MaxMind DB is a binary file format that stores data indexed by IP address subnets (IPv4 or IPv6). + +- Mongo: set `ENABLE_MONGO` to `true`. + + MongoDB is a NoSQL Database. This extension allows to use it from PHP. + **Warning**: this extension is now superseded by the `mongodb` extension. It is provided for backward compatibility. + +- NewRelic: set `ENABLE_NEWRELIC` to `true`. + + Newrelic Agent for PHP. Newrelic is a software analytics tool. + +- OAuth: set `ENABLE_OAUTH` to `true`. + + OAuth consumer extension. OAuth is an authorization protocol built on top of HTTP. + +- OpenTelemetry: set `ENABLE_OPENTELEMETRY` to `true`. + + OpenTelemetry is an extension to facilitate the generation, export, collection of telemetry data such as traces, metrics, and logs. + +- PCS: set `ENABLE_PCS` to `true`. + + PCS provides a fast and easy way to mix C and PHP code in your PHP extension. + +- PDFlib: set `ENABLE_PDFlib` to `true`. + + PDFlib is a commercial library for generating PDF files. It provides a PHP extension to create and manipulate PDF documents. + +- Protobuf: set `ENABLE_PROTOBUF` to `true`. + + Protobuf is an extension for the language-neutral, platform-neutral extensible mechanism for serializing structured data. + +- Pspell: set `ENABLE_PSPELL` to `true`. + + Pspell is an extension to check the spelling of words and offer suggestions. + +- Rdkafka: set `ENABLE_RDKAFKA` to `true`. + + PHP-rdkafka is a thin librdkafka binding providing a working PHP 5 / PHP 7 Kafka client. + +- Scout APM: set `ENABLE_SCOUTAPM` to `true`. + + The Scout APM extension to provide additional capabilities to application monitoring over just using the base PHP userland library. + +- SQL Server: set `ENABLE_SQLSRV` or `ENABLE_PDO_SQLSRV` to `true`. + + These extensions enable drivers that rely on the Microsoft ODBC Driver to handle the low-level communication with SQL Server. The `SQLSRV` extension provides a procedural interface while the `PDO_SQLSRV` extension implements PDO for accessing data in all editions of SQL Server 2012 and later (including Azure SQL DB). + +- Sqreen: The Sqreen agent is started automatically after adding the environment variables (`SQREEN_API_APP_NAME` and `SQREEN_API_TOKEN`). + +- Tideways: set `ENABLE_TIDEWAYS` to `true`. + + Tideways is an extension that provides profiling and monitoring capabilities for PHP applications. + +- Uopz: set `ENABLE_UOPZ` to `true`. + + The uopz extension is focused on providing utilities to aid with unit testing PHP code. + +- Uploadprogress: set `ENABLE_UPLOADPROGRESS` to `true`. + + The uploadprogress extension is used to track the progress of a file download. + +- XDebug: set `ENABLE_XDEBUG` to `true`. + + XDebug is a debugger and profiler tool for PHP. + +- XML RPC: set `ENABLE_XMLRPC` to `true`. + + XML-RPC is an extension for server and client bindings + +- YAML: set `ENABLE_YAML` to `true`. + + YAML is an extension providing a YAML-1.1 parser and emitter + +## Disable extensions + +You can use `DISABLE_=true` in your [environment variables](/doc/reference/reference-environment-variables/) to disable an extension. diff --git a/content/doc/applications/php/sessions-emails.md b/content/doc/applications/php/sessions-emails.md new file mode 100644 index 000000000..c8c2ccf4b --- /dev/null +++ b/content/doc/applications/php/sessions-emails.md @@ -0,0 +1,64 @@ +--- +type: docs +linkTitle: Sessions and Emails +title: PHP sessions and email configuration +description: Configure PHP session storage with FS Buckets or Redis, and SMTP email sending on Clever Cloud +keywords: +- php sessions +- fs bucket +- redis sessions +- materia kv +- smtp +- email sending +--- + +## Configure the session storage + +By default, an [FS Bucket](/doc/addons/fs-bucket/) is created for each PHP application, so that session data is available on each instance. This FS Bucket is also used to store TMP files by default. You can change this behavior by setting the `TMPDIR` environment variable. You can set it to `/tmp` for example. + +> [!NOTE] FS Buckets are not available in HDS regions +> To deploy a PHP application on an HDS region, set [`CC_PHP_DISABLE_APP_BUCKET=true`](#speed-up-or-disable-the-session-fs-bucket). Consider using Redis to manage PHP sessions. + +### Speed up or disable the session FS Bucket + +You can set the following environment variables: + +- `CC_PHP_ASYNC_APP_BUCKET=async` to mount the session FS Bucket with the `async` option. + It speeds up the FS Bucket usage, but it can corrupt files in case of a network outage. +- `CC_PHP_DISABLE_APP_BUCKET=(true|yes|disable)` to entirely prevent the session FS Bucket + from being mounted. + Use this if you don't use the default PHP session library. + It will speed up your application but users might lose their session across instances + and deployments. + +### Use Materia KV or Redis to store PHP Sessions + +Clever Cloud allows you to store PHP sessions easily in a [Materia KV](/doc/addons/materia-kv) or [Redis](/doc/addons/redis) add-on to improve performance/reliability. + +To enable this feature, you need to: + +- Set `ENABLE_REDIS=true` as [environment variable](/doc/develop/env-variables) in the PHP application +- Set `SESSION_TYPE=redis` as [environment variable](/doc/develop/env-variables) in the PHP application +- Create and link a Materia KV or Redis add-on to the PHP application + +## Sending emails + +The PHP language has the `mail` function to directly send emails. While no SMTP server is provided (needed to send the emails), you can configure one through environment variables. + +Mailpace add-on can send emails through PHP `mail()` function. You have to turn TLS on with port 465 (environment variable `CC_MTA_SERVER_USE_TLS=true`) to make Mailpace working. + +[Mailgun](https://www.mailgun.com/) or [Mailjet](https://www.mailjet.com/) are also recommended if your project supports it. These services already have everything you need to send emails from your code. + +### Configure the SMTP server + +Services like [Mailgun](https://www.mailgun.com/) or [Mailjet](https://www.mailjet.com/) provide SMTP servers. If your application has no other way but to use the `mail` function of PHP to send emails, you have to configure a SMTP server. This can be done through environment variables: + +| Name | Description | Default | +|------|-------------|---------| +| `CC_MTA_SERVER_HOST` | Host of the SMTP server | | +| `CC_MTA_SERVER_PORT` | Port of the SMTP server | `465` | +| `CC_MTA_AUTH_USER` | User to authenticate to the SMTP server | | +| `CC_MTA_AUTH_PASSWORD` | Password to authenticate to the SMTP server | | +| `CC_MTA_SERVER_USE_TLS` | Enable or disable TLS | `true` | +| `CC_MTA_SERVER_STARTTLS` | Enable or disable STARTTLS | `false` | +| `CC_MTA_SERVER_AUTH_METHOD` | Enable or disable authentication | `on` | diff --git a/content/doc/reference/reference-environment-variables.md b/content/doc/reference/reference-environment-variables.md index 87996a656..8b2cabd7f 100644 --- a/content/doc/reference/reference-environment-variables.md +++ b/content/doc/reference/reference-environment-variables.md @@ -289,10 +289,10 @@ Use Linux runtime with [Mise package manager](#install-tools-with-mise-package-m |`CC_OPCACHE_MAX_ACCELERATED_FILES` | Maximum number of files handled by opcache. | Default depends on the scaler size | |`CC_OPCACHE_MEMORY` | Set the shared opcache memory size | Default is about 1/8 of the RAM | |`CC_OPCACHE_PRELOAD` | The path of the PHP preload file (PHP version 7.4 or higher). | | -|[`CC_PHP_ASYNC_APP_BUCKET`](/doc/applications/php/#speed-up-or-disable-the-session-fs-bucket "Speed up or disable the session on FS Bucket") | Mount the default app FS bucket asynchronously. If set, should have value `async` | | -|[`CC_PHP_DEV_DEPENDENCIES`](/doc/applications/php/#development-dependencies "Development dependencies") | Control if development dependencies are installed or not. Values are either `install` or `ignore` | | -|[`CC_PHP_DISABLE_APP_BUCKET`](/doc/applications/php/#speed-up-or-disable-the-session-fs-bucket "Speed up or disable the session on FS Bucket") | Disable entirely the app FS Bucket. Values are either `true`, `yes` or `disable` | | -|`CC_PHP_VERSION` | Choose your PHP version among [those supported](/doc/applications/php/#choose-your-php-version) | 8.3 | +|[`CC_PHP_ASYNC_APP_BUCKET`](/doc/applications/php/sessions-emails/#speed-up-or-disable-the-session-fs-bucket "Speed up or disable the session on FS Bucket") | Mount the default app FS bucket asynchronously. If set, should have value `async` | | +|[`CC_PHP_DEV_DEPENDENCIES`](/doc/applications/php/composer/#development-dependencies "Development dependencies") | Set to `install` to include dev dependencies; unset or set to `skip` to exclude them | | +|[`CC_PHP_DISABLE_APP_BUCKET`](/doc/applications/php/sessions-emails/#speed-up-or-disable-the-session-fs-bucket "Speed up or disable the session on FS Bucket") | Disable entirely the app FS Bucket. Values are either `true`, `yes` or `disable` | | +|`CC_PHP_VERSION` | Choose your PHP version among [those supported](/doc/applications/php/#php-version) | 8.3 | |`CC_REALPATH_CACHE_TTL` | The size of the realpath cache to be used by PHP | 120 | |`CC_WEBROOT` | Define the `DocumentRoot` of your project | `.` | |`ENABLE_ELASTIC_APM_AGENT` | Elastic APM Agent for PHP | `true` if `ELASTIC_APM_SERVER_URL` is defined, `false` otherwise | From 4d35ec55618af5887d3e92ffdc1007c72041d63e Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 28 Jan 2026 18:58:56 +0100 Subject: [PATCH 024/180] applications(python): split in multiple files --- content/doc/applications/php/composer.md | 8 +- content/doc/applications/python.md | 240 --------------------- content/doc/applications/python/_index.md | 138 ++++++++++++ content/doc/applications/python/servers.md | 113 ++++++++++ content/doc/applications/python/uv.md | 66 ++++++ 5 files changed, 321 insertions(+), 244 deletions(-) delete mode 100644 content/doc/applications/python.md create mode 100644 content/doc/applications/python/_index.md create mode 100644 content/doc/applications/python/servers.md create mode 100644 content/doc/applications/python/uv.md diff --git a/content/doc/applications/php/composer.md b/content/doc/applications/php/composer.md index f5cf73e93..635ef8940 100644 --- a/content/doc/applications/php/composer.md +++ b/content/doc/applications/php/composer.md @@ -71,11 +71,11 @@ Sometimes, you can encounter the following error when downloading dependencies: Failed to download symfony/symfony from dist: Could not authenticate against GitHub.com ``` -To prevent this download dependencies's fails that is often caused by rate limit of GitHub API while deploying your apps, -we recommend you to add `oauth` token in your composer configuration file or in separate file named as described in -[composer FAQ (API rate limit and OAuth tokens)](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens). +To avoid failed dependency downloads caused by GitHub API rate limits during deployment, +configure an OAuth token in your Composer configuration file or in a separate file as described in the +[Composer FAQ (API rate limit and OAuth tokens)](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens). -You can find more documentation about composer configuration at [getcomposer.com](https://getcomposer.org/doc/04-schema.md). +You can find more documentation about Composer configuration in the [Composer schema reference](https://getcomposer.org/doc/04-schema.md). ## Post-build hook example diff --git a/content/doc/applications/python.md b/content/doc/applications/python.md deleted file mode 100644 index 90aa3989f..000000000 --- a/content/doc/applications/python.md +++ /dev/null @@ -1,240 +0,0 @@ ---- -type: docs -linkTitle: Python -title: Python -description: Deploy Python 2 and 3 applications with uv support, Django, Flask, various frameworks, and configurable runtime settings -keywords: -- django -- fastapi -- flask -- pip -- python app hosting -- python cloud -- uv deployment -- uv native support -aliases: -- /applications/python -- /deploy/application/python/python_apps -- /doc/applications/python -- /doc/deploy/application/python -- /doc/deploy/application/python/python_apps -- /doc/en/python-hosting -- /doc/getting-started/by-language/python -- /doc/partials/language-specific-deploy/python -- /doc/python -- /doc/python/python_apps -- /doc/python-hosting -- /doc/reference/python -- /python -- /python/python_apps ---- - -## Overview - -Python is a programming language that lets you work more quickly and integrate your systems more efficiently. - -### Supported Versions - -The default version of Python on Clever Cloud is the latest we support from branch `3.x`. If you want to use Python `2.x`, create an [environment variable](#setting-up-environment-variables-on-clever-cloud) `CC_PYTHON_VERSION` set to `2`, it will default to Python 2.7. Other supported values are : - -{{< runtimes_versions python >}} - -{{% content "create-application" %}} - -{{% content "set-env-vars" %}} - -## Configure your Python application - -### General configuration - -Python apps can be launched in a variety of ways. You can specify how to start your application (for instance which module to run) by setting [environment variables](#setting-up-environment-variables-on-clever-cloud). - -To select which module you want to start, use the `CC_PYTHON_MODULE` environment variable. - -```bash -CC_PYTHON_MODULE="mymodule:app" -``` - -The module (without .py) must be importable, i.e. be in `PYTHONPATH`. Basically, you should just point to a WSGI capable object. - -For example with *Flask*, it's gonna be the name of your main server file, followed by your Flask object: `server:app` for instance if you have a `server.py` file at the root of your project with a Flask `app` object inside. - -You can also use `CC_RUN_COMMAND` to launch Python application your way. In such case, it must listen on port `9000`. - -### Use uv as a package manager - -Built in Rust, `uv` is a modern package and project manager for Python. It's fast to install dependencies, can be used as a drop-in replacement for `pip` and to sideload unsupported versions of Python. For example to use it with a `app.py` file, you just need to set `CC_RUN_COMMAND="uv run app.py"`. If your application listens on port `9000` with `0.0.0.0` as host, it will work fine on Clever Cloud. - -* [Learn more about uv](https://github.com/astral-sh/uv) - -{{< callout type="info" >}} - `uv` is part of our Enthusiast tools initiative, it's included and can be used, but there is no active support for it yet. -{{< /callout >}} - -### Select the python backend - -Currently, we support `daphne`, `gunicorn`, `uvicorn` and `uwsgi` for Python backends. If not specified, the default backend is `uwsgi`. - -To select one, set the `CC_PYTHON_BACKEND` [environment variable](#setting-up-environment-variables-on-clever-cloud) with either `daphne`, `gunicorn`, `uvicorn` or `uwsgi`. - -Please contact the support if you need another backend. - -### Dependencies - -If you do not have a `requirements.txt` file to commit you can obtain it via the command `pip freeze > requirements.txt` (or `pip3 freeze > requirements.txt` if you use Python 3.x) at the root of your project folder in your terminal. - -For example to install *PostgreSQL* and don't want to use the `pip freeze` command above you have to create a file `requirements.txt` at the root of your application folder: - -```txt -psycopg2>=2.7 --no-binary psycopg2 -``` - -**Note**: We recommend using `psycopg2>=2.7 --no-binary psycopg2` to avoid wsgi issues. - -You can define a custom `requirements.txt` file with the environnement variable `CC_PIP_REQUIREMENTS_FILE` for example: `CC_PIP_REQUIREMENTS_FILE=config/production.txt`. - -{{% content "cached-dependencies" %}} - -### Use setup.py - -We support execution of a single `setup.py` goal. Usually, this would be to execute custom tasks after the installation of dependencies. - -The goal will be launched after the dependencies from `requirements.txt` have been installed. - -To execute a goal, you can define the [environment variable](#setting-up-environment-variables-on-clever-cloud) `PYTHON_SETUP_PY_GOAL=""`. - - {{% content "env-injection" %}} - -To access [environment variables](#setting-up-environment-variables-on-clever-cloud) from your code, just get them from the environment with: - -```python -import os -os.getenv("MY_VARIABLE") -``` - -### Manage your static files - -To enable Nginx to serve your static resources, you have to set two [environment variables](#setting-up-environment-variables-on-clever-cloud). - -`STATIC_FILES_PATH`: should point to a directory where your static files are stored. - -`STATIC_URL_PREFIX`: the URL path under which you want to serve static files (e.g. `/public`). - -Also, you are able to use a Filesystem Bucket to store your static files. Please refer to the [File System Buckets](/doc/addons/fs-bucket) section. - -**Note**: the path of your folder must be absolute regarding the root of your application. - -**Note**: setting the `STATIC_URL_PREFIX` to `/` will cause the deployment failure. - -#### Static files example - -Here is how to serve static files, the `test.png` being the static file you want to serve: - -```txt -├── -│   ├── flask-app.py -│   ├── static -│   │   └── test.png -│   └── requirements.txt -``` - -Using the environment variables `STATIC_FILES_PATH=static/` and `STATIC_URL_PREFIX=/public` the `test.png` file will be accessed under: `https:///public/test.png`. - -### uWSGI, Gunicorn and Nginx configuration - -uWSGI, gunicorn and nginx settings can be configured by setting [environment variables](#setting-up-environment-variables-on-clever-cloud): - -#### uWSGI - -- `HARAKIRI`: timeout (in seconds) after which an unresponding process is killed. (Default: 180) -- `WSGI_BUFFER_SIZE`: maximal size (in bytes) for the headers of a request. (Default: 4096) -- `WSGI_POST_BUFFERING`: buffer size (in bytes) for uploads. (Default: 4096) -- `WSGI_WORKERS`: number of workers. (Default: depends on the scaler) -- `WSGI_THREADS`: number of threads per worker. (Default: depends on the scaler) - -##### uWSGI asynchronous/non-blocking modes - -To enable [uWSGI asynchronous](https://uwsgi-docs.readthedocs.io/en/latest/Async.html) mode, you can use these two environment variables: - -- `UWSGI_ASYNC`: [number of cores](https://uwsgi-docs.readthedocs.io/en/latest/Async.html#async-switches) to use for uWSGI asynchronous/non-blocking modes. -- `UWSGI_ASYNC_ENGINE`: select the [asynchronous engine for uWSGI](https://uwsgi-docs.readthedocs.io/en/latest/Async.html#suspend-resume-engines) (optional). - -#### Gunicorn - -- `GUNICORN_WORKER_CLASS`: type of worker to use. Default to `sync`. [Available workers](https://docs.gunicorn.org/en/stable/settings.html#worker-class) -- `CC_GUNICORN_TIMEOUT`: gunicorn timeout. Defaults to `30` - -#### Nginx - -- `NGINX_READ_TIMEOUT`: a bit like `HARAKIRI`, the response timeout in seconds. (Default: 300) -- `ENABLE_GZIP_COMPRESSION`: "on|yes|true" gzip-compress the output. -- `GZIP_TYPES`: the mime types to gzip. Defaults to `text/plain text/css text/xml text/javascript application/json application/xml application/javascript image/svg+xml`. - -##### Basic authentication - -If you need basic authentication, you can enable it using [environment variables](/doc/reference/reference-environment-variables#python). You will need to set `CC_HTTP_BASIC_AUTH` variable to your own `login:password` pair. If you need to allow access to multiple users, you can create additional environment `CC_HTTP_BASIC_AUTH_n` (where `n` is a number) variables. - -#### Nginx optional configuration with `clevercloud/http.json` - -Nginx settings can be configured further in `clevercloud/http.json`. All its fields are optional. - -- `languages`: configure a default language and redirections -- `error_pages`: configure custom files for error pages -- `force_https`: automatically redirect HTTP traffic to HTTPS -- `aliases`: set up redirections -- `charset`: force a specific charset - -```json -{ - "languages": { - "default": {"rewrite": "en"}, - "fr": {"rewrite": "en"} - }, - "error_pages": { - "404": "path/to/page" - }, - "force_https": true, - "aliases": { - "/path": "redirection" - }, - "charset": "latin-1" -} -``` - -### Using the Gevent loop engine - -Whether you use uwsgi or gunicorn, you can enable the Gevent loop engine. - -To do so, add the `CC_PYTHON_USE_GEVENT` [environment variable](#setting-up-environment-variables-on-clever-cloud) to your application, with the `true` value. - - {{% content "new-relic" %}} - -## Celery apps - -**Note**: Please note that Celery support is not available yet for `gunicorn`. - -We also support celery apps out of the box. To deploy a celery app, use the `CC_PYTHON_CELERY_MODULE` [environment variable](#setting-up-environment-variables-on-clever-cloud): - -```bash -CC_PYTHON_CELERY_MODULE="mymodule" -``` - -{{< callout type="warning" >}} -Celery needs to be defined as a dependency in your requirements.txt. Otherwise the deployment will be aborted if Celery support is enabled. -{{< /callout >}} - -You can also activate beat with `CC_PYTHON_CELERY_USE_BEAT=true` and provide a given log dir for celery with `CC_PYTHON_CELERY_LOGFILE="/path/to/logdir"`. - -The `CC_PYTHON_CELERY_LOGFILE` path is relative to the application's path. - -{{< callout type="warning" >}} -There is a bug in versions <4.2 of Celery. You need to add the `CELERY_TIMEZONE = 'UTC'` environment variable. The bug is documented here: [https://github.com/celery/celery/issues/4184](https://github.com/celery/celery/issues/4184). -{{< /callout >}} - -{{% content "deploy-git" %}} - -{{% content "link-addon" %}} - -{{% content "more-config" %}} - -{{% content "url_healthcheck" %}} diff --git a/content/doc/applications/python/_index.md b/content/doc/applications/python/_index.md new file mode 100644 index 000000000..508cd7123 --- /dev/null +++ b/content/doc/applications/python/_index.md @@ -0,0 +1,138 @@ +--- +type: docs +linkTitle: Python +title: Python application runtime +description: Deploy Python 2 and 3 applications with uv support, Django, Flask, various frameworks, and configurable runtime settings +keywords: +- django +- fastapi +- flask +- pip +- python app hosting +- python cloud +- uv deployment +- uv native support +aliases: +- /applications/python +- /deploy/application/python/python_apps +- /doc/applications/python +- /doc/deploy/application/python +- /doc/deploy/application/python/python_apps +- /doc/en/python-hosting +- /doc/getting-started/by-language/python +- /doc/partials/language-specific-deploy/python +- /doc/python +- /doc/python/python_apps +- /doc/python-hosting +- /doc/reference/python +- /python +- /python/python_apps +--- + +## Overview + +Python is a programming language that lets you work more quickly and integrate your systems more efficiently. + +## Create your Python application + +To create a new Python application, use the [Clever Cloud Console](https://console.clever-cloud.com) or [Clever Tools](https://github.com/CleverCloud/clever-tools): + +```bash +clever create --type python +``` +- [Learn more about Clever Tools](/doc/cli/) +- [Learn more about Clever Cloud application deployment](/doc/quickstart/#create-an-application-step-by-step) + +## Configure your Python application + +### Mandatory needs + +Python apps can be launched in a variety of ways. To select which module you want to start, use the `CC_PYTHON_MODULE` environment variable. + +```bash +CC_PYTHON_MODULE="mymodule:app" +``` + +The module (without .py) must be importable, that is, be in `PYTHONPATH`. You should point to a WSGI-capable object. + +For example, with *Flask*, this is the name of your main server file, followed by your Flask object: `server:app`, if you have a `server.py` file at the root of your project with a Flask `app` object inside. + +You can also use `CC_RUN_COMMAND` to launch a Python application with a custom command. In such case, it must listen on port `9000`. + +- [Learn more about environment variables on Clever Cloud](/doc/reference/reference-environment-variables/) + +### Python version + +The default version of Python on Clever Cloud is the latest supported from branch `3.x`. If you want to use Python `2.x`, set `CC_PYTHON_VERSION` to `2`, it will default to Python 2.7. Other supported values are: + +{{< runtimes_versions python >}} + +### Build phase + +#### Dependencies + +If you do not have a `requirements.txt` file to commit you can obtain it via the command `pip freeze > requirements.txt` (or `pip3 freeze > requirements.txt` if you use Python 3.x) at the root of your project folder in your terminal. + +For example, if you want to install *PostgreSQL* without using the `pip freeze` command above, create a `requirements.txt` file at the root of your application folder: + +```txt +psycopg2>=2.7 --no-binary psycopg2 +``` + +> [!NOTE] +> Using `psycopg2>=2.7 --no-binary psycopg2` is recommended to avoid WSGI issues. + +You can define a custom `requirements.txt` file with the environment variable `CC_PIP_REQUIREMENTS_FILE` for example: `CC_PIP_REQUIREMENTS_FILE=config/production.txt`. + +{{% content "cached-dependencies" %}} + +#### Use setup.py + +Execution of a single `setup.py` goal is supported. Usually, this would be to execute custom tasks after the installation of dependencies. + +The goal will be launched after the dependencies from `requirements.txt` have been installed. + +To execute a goal, define the environment variable `PYTHON_SETUP_PY_GOAL=""`. + +- [Learn more about Deployment hooks](/doc/develop/build-hooks/) + +### Select the Python backend + +Currently, `daphne`, `gunicorn`, `uvicorn` and `uwsgi` are supported for Python backends. If not specified, the default backend is `uwsgi`. + +To select one, set the `CC_PYTHON_BACKEND` environment variable with either `daphne`, `gunicorn`, `uvicorn` or `uwsgi`. + +Contact the support if you need another backend. + +> [!NOTE] +> Backend selection only applies to the legacy deployment mode (without `uv.lock`). [Native uv deployments](/doc/applications/python/uv/) manage their own HTTP server. + +### Using the Gevent loop engine + +Whether you use uwsgi or gunicorn, you can enable the Gevent loop engine. + +To do so, add the `CC_PYTHON_USE_GEVENT` environment variable to your application, with the `true` value. + +## Celery apps + +Celery apps are supported out of the box. To deploy a celery app, use the `CC_PYTHON_CELERY_MODULE` environment variable: + +```bash +CC_PYTHON_CELERY_MODULE="mymodule" +``` + +> [!WARNING] +> Celery needs to be defined as a dependency in your requirements.txt. Otherwise the deployment will be aborted if Celery support is enabled. + +You can also activate beat with `CC_PYTHON_CELERY_USE_BEAT=true` and provide a given log dir for celery with `CC_PYTHON_CELERY_LOGFILE="/path/to/logdir"`. + +The `CC_PYTHON_CELERY_LOGFILE` path is relative to the application's path. + +> [!WARNING] +> There is a bug in versions <4.2 of Celery. You need to add the `CELERY_TIMEZONE = 'UTC'` environment variable. The bug is documented here: [https://github.com/celery/celery/issues/4184](https://github.com/celery/celery/issues/4184). + +{{% content "new-relic" %}} + +{{% content "url_healthcheck" %}} +{{% content "redirectionio" %}} +{{% content "varnish" %}} diff --git a/content/doc/applications/python/servers.md b/content/doc/applications/python/servers.md new file mode 100644 index 000000000..947453d9f --- /dev/null +++ b/content/doc/applications/python/servers.md @@ -0,0 +1,113 @@ +--- +type: docs +linkTitle: Servers +title: Python servers configuration +description: Configure uWSGI, Gunicorn, and Nginx settings for Python applications on Clever Cloud including static files, HTTPS, and basic authentication +keywords: +- uwsgi +- gunicorn +- nginx +- python server +- static files +--- + +## uWSGI, Gunicorn and Nginx configuration + +> [!NOTE] +> This page applies to the legacy Python deployment mode (without `uv.lock`). [Native uv deployments](/doc/applications/python/uv/) manage their own HTTP server and do not use uWSGI, Gunicorn, or Nginx. + +uWSGI, Gunicorn and Nginx settings can be configured by setting environment variables. + +### uWSGI + +| Name | Description | Default | +|------|-------------|---------| +| `HARAKIRI` | Timeout (in seconds) after which an unresponsive process is killed | `180` | +| `WSGI_BUFFER_SIZE` | Maximal size (in bytes) for the headers of a request | `4096` | +| `WSGI_POST_BUFFERING` | Buffer size (in bytes) for uploads | `4096` | +| `WSGI_WORKERS` | Number of workers | depends on the scaler | +| `WSGI_THREADS` | Number of threads per worker | depends on the scaler | + +You can inject additional uWSGI configuration directives with `CC_UWSGI_EXTRA_CONFIG`. To disable the file wrapper, set `CC_UWSGI_DISABLE_FILE_WRAPPER` to `true`. + +#### uWSGI asynchronous/non-blocking modes + +To enable [uWSGI asynchronous](https://uwsgi-docs.readthedocs.io/en/latest/Async.html) mode, you can use these two environment variables: + +- `UWSGI_ASYNC`: [number of cores](https://uwsgi-docs.readthedocs.io/en/latest/Async.html#async-switches) to use for uWSGI asynchronous/non-blocking modes. +- `UWSGI_ASYNC_ENGINE`: select the [asynchronous engine for uWSGI](https://uwsgi-docs.readthedocs.io/en/latest/Async.html#suspend-resume-engines) (optional). + +### Gunicorn + +| Name | Description | Default | +|------|-------------|---------| +| `CC_GUNICORN_WORKER_CLASS` | Type of worker to use. [Available workers](https://docs.gunicorn.org/en/stable/settings.html#worker-class) | `sync` | +| `CC_GUNICORN_TIMEOUT` | Gunicorn timeout (in seconds) | `30` | +| `CC_GUNICORN_LOGLEVEL` | Gunicorn log level | `info` | + +### Nginx + +| Name | Description | Default | +|------|-------------|---------| +| `NGINX_READ_TIMEOUT` | Response timeout in seconds (similar to `HARAKIRI`) | `300` | +| `ENABLE_GZIP_COMPRESSION` | Enable gzip compression (`on`, `yes`, or `true`) | | +| `GZIP_TYPES` | The mime types to gzip | `text/plain text/css text/xml text/javascript application/json application/xml application/javascript image/svg+xml` | + +#### Basic authentication + +If you need basic authentication, you can enable it using [environment variables](/doc/reference/reference-environment-variables#python). Set `CC_HTTP_BASIC_AUTH` variable to your own `login:password` pair. If you need to allow access to multiple users, you can create additional environment `CC_HTTP_BASIC_AUTH_n` (where `n` is a number) variables. + +#### Nginx optional configuration with `clevercloud/http.json` + +Nginx settings can be configured further in `clevercloud/http.json`. All its fields are optional. + +- `languages`: configure a default language and redirections +- `error_pages`: configure custom files for error pages +- `force_https`: automatically redirect HTTP traffic to HTTPS +- `aliases`: set up redirections +- `charset`: force a specific charset + +```json +{ + "languages": { + "default": {"rewrite": "en"}, + "fr": {"rewrite": "en"} + }, + "error_pages": { + "404": "path/to/page" + }, + "force_https": true, + "aliases": { + "/path": "redirection" + }, + "charset": "latin-1" +} +``` + +## Manage static files + +To enable Nginx to serve your static resources, you have to set two environment variables. + +| Name | Description | +|------|-------------| +| `STATIC_FILES_PATH` | Directory where your static files are stored (absolute path relative to the application root) | +| `STATIC_URL_PREFIX` | URL path under which you want to serve static files (e.g. `/public`) | + +You can also use a [Filesystem Bucket](/doc/addons/fs-bucket) to store your static files. + +> [!WARNING] +> Setting `STATIC_URL_PREFIX` to `/` makes static files override the default application location. The static configuration replaces the main `location /` block in Nginx, and your application becomes accessible only through the `@app` fallback. + +### Static files example + +Here is how to serve static files, the `test.png` being the static file you want to serve: + +```txt +├── +│ ├── flask-app.py +│ ├── static +│ │ └── test.png +│ └── requirements.txt +``` + +Using the environment variables `STATIC_FILES_PATH=static/` and `STATIC_URL_PREFIX=/public` the `test.png` file will be accessed under: `https:///public/test.png`. diff --git a/content/doc/applications/python/uv.md b/content/doc/applications/python/uv.md new file mode 100644 index 000000000..51fe77282 --- /dev/null +++ b/content/doc/applications/python/uv.md @@ -0,0 +1,66 @@ +--- +type: docs +linkTitle: Deploy with uv +title: Deploy Python with uv +description: Deploy Python applications with native uv support on Clever Cloud, using uv.lock for dependency management and direct HTTP server binding +keywords: +- uv +- python uv +- uv deployment +- uv sync +- astral +--- + +## Overview + +[uv](https://docs.astral.sh/uv/) is a modern package and project manager for Python, built in Rust. Clever Cloud provides native uv deployment support as an alternative to the legacy WSGI-based deployment (uWSGI/Gunicorn + Nginx). With uv, your application manages its own HTTP server. + +## Activation + +Native uv deployment activates when both conditions are met: + +- A `uv.lock` file exists at the root of your project +- The `CC_PYTHON_UV_RUN_COMMAND` environment variable is set to a valid command + +If either condition is missing, the application deploys with the [legacy Python backend](/doc/applications/python/#select-the-python-backend). + +## Build phase + +During the build phase, dependencies are installed with: + +```bash +uv sync --locked --no-progress --no-dev +``` + +To install development dependencies, set `ENVIRONMENT=development`. The `--no-dev` flag is then removed. + +The uv cache (`~/.cache/uv`) is included in the build cache to speed up subsequent deployments. + +- [Learn more about Deployment hooks](/doc/develop/build-hooks/) + +## Run phase + +The application starts with the command defined in `CC_PYTHON_UV_RUN_COMMAND`. It must start an HTTP server listening on `0.0.0.0:8080`. + +`CC_RUN_COMMAND` takes precedence over `CC_PYTHON_UV_RUN_COMMAND` if both are set. + +| Name | Description | Required | Default | +|------|-------------|----------|---------| +| `CC_PYTHON_UV_RUN_COMMAND` | Command to start the application (e.g. `uv run python app.py`) | Yes | - | +| `CC_RUN_COMMAND` | Overrides `CC_PYTHON_UV_RUN_COMMAND` if set | No | - | +| `ENVIRONMENT` | Set to `development` to include dev dependencies during build | No | `production` | + +## Differences with legacy Python deployment + +With native uv deployment: + +- No Nginx, uWSGI, or Gunicorn is involved +- Your application listens on port `8080` (not `9000`) +- `CC_PYTHON_MODULE` and `CC_PYTHON_BACKEND` are ignored +- [Redirection.io, Varnish and custom proxies](/doc/develop/request-flow/) are configured through Request Flow, not Nginx +- [Server configuration](/doc/applications/python/servers/) settings do not apply + +- [Learn more about uv](https://docs.astral.sh/uv/) + +{{% content "url_healthcheck" %}} +{{% content "request-flow" %}} From 5ab13f21d77dcc9d7904e651b9708fbce6770d25 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 17 Feb 2026 23:24:03 +0100 Subject: [PATCH 025/180] fix: update php/python links after split --- content/changelog/2023/12-18-php-8-default.md | 2 +- content/changelog/2024/05-30-php-8.3-java-22.md | 2 +- content/changelog/2025/10-15-materia-kv-v2.md | 2 +- content/changelog/2026/01-15-images-update.md | 2 +- content/doc/addons/fs-bucket.md | 4 ++-- content/doc/develop/env-variables.md | 4 ++-- content/doc/find-help/faq.md | 2 +- content/doc/reference/reference-environment-variables.md | 6 +++--- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/content/changelog/2023/12-18-php-8-default.md b/content/changelog/2023/12-18-php-8-default.md index c348cf0fd..3ec9b559b 100644 --- a/content/changelog/2023/12-18-php-8-default.md +++ b/content/changelog/2023/12-18-php-8-default.md @@ -19,4 +19,4 @@ excludeSearch: true PHP 8.x is available for years on Clever Cloud, but is now deployed as the default version for new applications. Of course, you can set the one of your choice between those available through `CC_PHP_VERSION` environment variable. -- Read our documentation [about PHP version](/doc/applications/php/#choose-your-php-version) +- Read our documentation [about PHP version](/doc/applications/php/#php-version) diff --git a/content/changelog/2024/05-30-php-8.3-java-22.md b/content/changelog/2024/05-30-php-8.3-java-22.md index fa77b4da1..582470604 100644 --- a/content/changelog/2024/05-30-php-8.3-java-22.md +++ b/content/changelog/2024/05-30-php-8.3-java-22.md @@ -28,5 +28,5 @@ We've updated some of our images and deployed them without any impact for our us * Support for NewRelic with Java 22 * **PHP:** * Composer 2.7.6 - * Support for PHP 8.3 with [all supported extensions](/doc/applications/php/#available-extensions-and-modules) + * Support for PHP 8.3 with [all supported extensions](/doc/applications/php/extensions/#available-extensions-and-modules) * PHP info apps are available for PHP [8.0](https://php80info.cleverapps.io), [8.1](https://php81info.cleverapps.io), [8.2](https://php82info.cleverapps.io) and [8.3](https://php83info.cleverapps.io) diff --git a/content/changelog/2025/10-15-materia-kv-v2.md b/content/changelog/2025/10-15-materia-kv-v2.md index f8b8eb2fb..d654bc4d1 100644 --- a/content/changelog/2025/10-15-materia-kv-v2.md +++ b/content/changelog/2025/10-15-materia-kv-v2.md @@ -17,7 +17,7 @@ excludeSearch: true Since then, we were focused on a major overhaul of the Redis® compatibility layer, to bring more data structures support, better performance and reliability. It's now available, and Materia KV in Beta stage. -It's still free to use, but now with more commands and `Hash` support. `Set` support is next to come. You can also [use Materia for your PHP sessions](/doc/applications/php/#use-materia-kv-or-redis-to-store-php-sessions) more easily, as TLS transport is now supported during application deployment. There nothing more to do than to link a Materia KV to your application. +It's still free to use, but now with more commands and `Hash` support. `Set` support is next to come. You can also [use Materia for your PHP sessions](/doc/applications/php/sessions-emails/#use-materia-kv-or-redis-to-store-php-sessions) more easily, as TLS transport is now supported during application deployment. There nothing more to do than to link a Materia KV to your application. - [Learn more about Materia KV](/doc/addons/materia-kv/) - [Learn more about Materia KV supported commands](/doc/addons/materia-kv/#supported-types-and-commands) diff --git a/content/changelog/2026/01-15-images-update.md b/content/changelog/2026/01-15-images-update.md index 35fbb1a54..24b1131b5 100644 --- a/content/changelog/2026/01-15-images-update.md +++ b/content/changelog/2026/01-15-images-update.md @@ -59,7 +59,7 @@ We updated all our images. Deployment is in progress for all our users. PHP 8.5 is now available. To use it, set `CC_PHP_VERSION=8.5` as PHP 8.4 release is still the default version. We'll move to PHP 8.5 as the default version in April 2026. PHP 8.1 is now [considered as end-of-life](https://www.php.net/supported-versions.php). -[Supported extensions](/doc/applications/php/#available-extensions-and-modules) for PHP 8.5 are: `amqp`, `apcu`, `blackfire`, `event`, `excimer`, `gnupg`, `grpc`, `imagick`, `imap`, `mailparse`, `maxminddb`, `memcached`, `oauth`, `opentelemetry`, `pdo_sqlsrv`, `protobuf`, `pspell`, `rdkafka`, `redis`, `sqlsrv`, `ssh2`, `tideways`, `uploadprogress`, `yaml`, `zip`. +[Supported extensions](/doc/applications/php/extensions/#available-extensions-and-modules) for PHP 8.5 are: `amqp`, `apcu`, `blackfire`, `event`, `excimer`, `gnupg`, `grpc`, `imagick`, `imap`, `mailparse`, `maxminddb`, `memcached`, `oauth`, `opentelemetry`, `pdo_sqlsrv`, `protobuf`, `pspell`, `rdkafka`, `redis`, `sqlsrv`, `ssh2`, `tideways`, `uploadprogress`, `yaml`, `zip`. Composer 2.9 introduces a new default behavior: it [automatically blocks updates to packages with known security advisories](https://blog.packagist.com/composer-2-9/). As mentioned by developers, "*it prevents you from accidentally updating to vulnerable package versions. You can configure this behavior via the new audit.block-insecure config settings if needed.*" diff --git a/content/doc/addons/fs-bucket.md b/content/doc/addons/fs-bucket.md index ff5934b07..e394ed13c 100644 --- a/content/doc/addons/fs-bucket.md +++ b/content/doc/addons/fs-bucket.md @@ -35,8 +35,8 @@ FS Buckets are provided for application needing file-system backward compatibili - FS Buckets are note available in Health Data Hosting (HDS) Zone - Clever Cloud provides automated backups every 24 hours, with only 72 hours of retention for FS Buckets (7 days for databases) -> [!NOTE] PHP applications includes a default FS Bucket for session storage -> To deploy a PHP application on an HDS region, set [`CC_PHP_DISABLE_APP_BUCKET=true`](/doc/applications/php/#speed-up-or-disable-the-session-fs-bucket). Consider using Redis to manage PHP sessions. +> [!NOTE] PHP applications include a default FS Bucket for session storage +> To deploy a PHP application on an HDS region, set [`CC_PHP_DISABLE_APP_BUCKET=true`](/doc/applications/php/sessions-emails/#speed-up-or-disable-the-session-fs-bucket). Consider using Redis to manage PHP sessions. ## Configuring your application diff --git a/content/doc/develop/env-variables.md b/content/doc/develop/env-variables.md index c5f96c0b1..e799ee5f5 100644 --- a/content/doc/develop/env-variables.md +++ b/content/doc/develop/env-variables.md @@ -233,8 +233,8 @@ Here is a non-exhaustive summary: {{< card link="/developers/doc/applications/scala/play-framework-2/#environment-injection" title="Play-2" icon="play" >}} {{< card link="/developers/doc/applications/nodejs#environment-injection" title="Node.js" icon="node" >}} {{< card link="/developers/guides/ruby-rack-app-tutorial/#environment-injection" title="Ruby" icon="ruby" >}} - {{< card link="/developers/doc/applications/php/#environment-injection" title="PHP" icon="php" >}} - {{< card link="/developers/doc/applications/python/#environment-injection" title="Python" icon="python" >}} + {{< card link="/developers/doc/applications/php/apache/#environment-injection" title="PHP" icon="php" >}} + {{< card link="/developers/doc/applications/python/#configure-your-python-application" title="Python" icon="python" >}} {{< card link="/developers/doc/applications/rust/#environment-injection" title="Rust" icon="rust" >}} {{< card link="/developers/doc/applications/scala/#environment-injection" title="Scala" icon="scala" >}} {{< card link="/developers/doc/applications/elixir/#setting-up-environment-variables-on-clever-cloud" title="Elixir" icon="elixir" >}} diff --git a/content/doc/find-help/faq.md b/content/doc/find-help/faq.md index 176617ace..940d03489 100644 --- a/content/doc/find-help/faq.md +++ b/content/doc/find-help/faq.md @@ -102,7 +102,7 @@ In order to use `request.secure` instead of accessing the header, you must add ` ## PHP: `$_SERVER` auth variables are always empty, how do I make this work? -- [Lean more about the $_SERVER variable on Clever Cloud](/doc/applications/php/#using-http-authentication) +- [Learn more about the $_SERVER variable on Clever Cloud](/doc/applications/php/apache/#using-http-authentication) ## How to get the user's IP address? diff --git a/content/doc/reference/reference-environment-variables.md b/content/doc/reference/reference-environment-variables.md index 8b2cabd7f..a2834bb1a 100644 --- a/content/doc/reference/reference-environment-variables.md +++ b/content/doc/reference/reference-environment-variables.md @@ -275,7 +275,7 @@ Use Linux runtime with [Mise package manager](#install-tools-with-mise-package-m |-----------------------|------------------------------|--------------------------------| |`ALWAYS_POPULATE_RAW_POST_DATA` | | | |`CC_COMPOSER_VERSION` | Choose your composer version between `2` or `lts` | 2 | -|[`CC_CGI_IMPLEMENTATION`](/doc/applications/php/#change-the-fastcgi-module) | Choose the Apache FastCGI module between `fastcgi` and `proxy_fcgi` | proxy_fcgi | +|[`CC_CGI_IMPLEMENTATION`](/doc/applications/php/apache/#change-the-fastcgi-module) | Choose the Apache FastCGI module between `fastcgi` and `proxy_fcgi` | proxy_fcgi | |`CC_HTTP_BASIC_AUTH` | Restrict HTTP access to your application. Example: `login:password`. You can define multiple credentials using additional `CC_HTTP_BASIC_AUTH_n` (where `n` is a number) environment variables. | | | `CC_APACHE_HEADERS_SIZE` | Set the maximum size of the headers in Apache, between `8` and `256`. Effective value depends on deployment region. [Ask for a dedicated load balancer](https://console.clever-cloud.com/ticket-center-choice) for a specific value | 8 | |`CC_LDAP_CA_CERT` | | | @@ -303,7 +303,7 @@ Use Linux runtime with [Mise package manager](#install-tools-with-mise-package-m |`LDAPTLS_CACERT` | | | |`MAX_INPUT_VARS` | | | |`MEMORY_LIMIT` | Change the default memory limit | | -|[`SESSION_TYPE`](/doc/applications/php/#use-redis-to-store-php-sessions "Use Redis to store PHP sessions") | Choose `redis` to use it as session store | | +|[`SESSION_TYPE`](/doc/applications/php/sessions-emails/#use-materia-kv-or-redis-to-store-php-sessions "Use Materia KV or Redis to store PHP sessions") | Choose `redis` to use it as session store | | |`SOCKSIFY_EVERYTHING` | | | |`SQREEN_API_APP_NAME` | The name of your sqreen application. | | |`SQREEN_API_TOKEN` | organisation token. | | @@ -325,7 +325,7 @@ Use Linux runtime with [Mise package manager](#install-tools-with-mise-package-m |[`CC_PYTHON_MANAGE_TASKS`](/guides/python-django-sample/#manage-py-tasks) | Comma-separated list of Django manage tasks | | |`CC_PYTHON_MODULE` | Select which module you want to start with the path to the folder containing the app object. For example, a module called **server.py** in a folder called **/app** would be used here as **app.server:app** | | |`CC_PYTHON_USE_GEVENT` | Set to true to enable Gevent | | -|`CC_PYTHON_VERSION` | Choose the Python version among [those supported](/doc/applications/python/#supported-versions) | 3 | +|`CC_PYTHON_VERSION` | Choose the Python version among [those supported](/doc/applications/python/#python-version) | 3 | |`ENABLE_GZIP_COMPRESSION` | Set to `true` to gzip-compress through Nginx | | |`GZIP_TYPES` | Set the mime types to compress. | text/plain text/css text/xml text/javascript application/json application/xml application/javascript image/svg+xml | |`HARAKIRI` | Timeout (in seconds) after which an unresponding process is killed | 180 | From cbe5aa3d0ffc35ddf267b9a02b1eb42c599ddd25 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 18 Feb 2026 11:30:34 +0100 Subject: [PATCH 026/180] changelog: images updates, 2026W8 --- content/changelog/2026/02-18-images-update.md | 43 +++++++++++++++++++ data/runtime_versions.yml | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 content/changelog/2026/02-18-images-update.md diff --git a/content/changelog/2026/02-18-images-update.md b/content/changelog/2026/02-18-images-update.md new file mode 100644 index 000000000..45442cbea --- /dev/null +++ b/content/changelog/2026/02-18-images-update.md @@ -0,0 +1,43 @@ +--- +title: "Images update: Kernel 6.19, Request Flow in Go, Java, Node.js, PHP and Static with Apache" +description: "Use Request Flow almost everywhere, and benefit from many updates in our images" +date: 2026-02-18 +tags: + - images + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated all our images. Deployment is in progress for all our users. + +* **Common:** + * Linux Kernel 6.19.2 + * ClamAV 1.5.1 + * Mise 2026.2.13 + * Otoroshictl 0.0.16 +* **.Net:** + * Update to 8.0.123 + * Update to 9.0.113 +* **Elixir:** + * Erlang 27.3.4.7 +* **Node.js & Bun:** + * Update to 24.13.1 (npm 11.8.0) +* **PHP:** + * Update to 8.4.18 + * Update to 8.5.3 +* **Rust:** + * Update to 1.93.1 + +## Apache Basic Auth + +`X-Robots-Tag: noindex, nofollow` header is now added to responses [with Basic Authentication](/doc/applications/php/apache/#basic-authentication) through Apache + +## Request Flow extension + +Request Flow is now available in Go, Java/Scala, Meteor, Node.js & Bun, PHP and Static with Apache runtimes. Python (without uv) and Ruby are coming soon. If your application currently uses Varnish in Go or Node.js, you must ask support to switch to this new release. Your application will have to move from port `8081` to `9000`. + +- [Learn more about Request Flow](/doc/develop/request-flow/) diff --git a/data/runtime_versions.yml b/data/runtime_versions.yml index 0ade4d4ae..f91941812 100644 --- a/data/runtime_versions.yml +++ b/data/runtime_versions.yml @@ -56,7 +56,7 @@ java: node: eol_source: https://nodejs.org/en/about/releases/ default: - - 24.13.0 (npm 11.6.2) + - 24.13.1 (npm 11.8.0) php: eol_source: https://www.php.net/supported-versions.php From 79c294c5bc0e54e8989f37dd59f4e76f4595f80e Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 18 Feb 2026 11:50:57 +0100 Subject: [PATCH 027/180] develop(request-flow): add new supported runtimes --- content/doc/applications/golang.md | 1 + content/doc/applications/java/java-gradle.md | 1 + content/doc/applications/java/java-jar.md | 1 + content/doc/applications/java/java-maven.md | 1 + content/doc/applications/java/java-war.md | 1 + content/doc/applications/nodejs.md | 1 + content/doc/applications/php/_index.md | 3 +-- content/doc/develop/request-flow.md | 9 ++++++++- 8 files changed, 15 insertions(+), 3 deletions(-) diff --git a/content/doc/applications/golang.md b/content/doc/applications/golang.md index d9a7d224a..7560d24a5 100644 --- a/content/doc/applications/golang.md +++ b/content/doc/applications/golang.md @@ -154,6 +154,7 @@ To access environment variables from your code, use `os.Getenv("MY_VARIABLE")`. {{% content "more-config" %}} {{% content "url_healthcheck" %}} +{{% content "request-flow" %}} ## See also diff --git a/content/doc/applications/java/java-gradle.md b/content/doc/applications/java/java-gradle.md index 6665a278f..df428f9af 100644 --- a/content/doc/applications/java/java-gradle.md +++ b/content/doc/applications/java/java-gradle.md @@ -129,3 +129,4 @@ Just create and commit the `gradlew` file and the wrapper `jar` and `properties` {{% content "more-config" %}} {{% content "url_healthcheck" %}} +{{% content "request-flow" %}} diff --git a/content/doc/applications/java/java-jar.md b/content/doc/applications/java/java-jar.md index f64c304a0..c672c6add 100644 --- a/content/doc/applications/java/java-jar.md +++ b/content/doc/applications/java/java-jar.md @@ -142,3 +142,4 @@ For Groovy applications, just use the `System.getProperty("MY_VARIABLE")`. {{% content "more-config" %}} {{% content "url_healthcheck" %}} +{{% content "request-flow" %}} diff --git a/content/doc/applications/java/java-maven.md b/content/doc/applications/java/java-maven.md index afd650e87..dc2ef5706 100644 --- a/content/doc/applications/java/java-maven.md +++ b/content/doc/applications/java/java-maven.md @@ -135,3 +135,4 @@ CC_RUN_COMMAND="java -jar somefile.jar " {{% content "more-config" %}} {{% content "url_healthcheck" %}} +{{% content "request-flow" %}} diff --git a/content/doc/applications/java/java-war.md b/content/doc/applications/java/java-war.md index 9cb09d5ad..a6b49ddd2 100644 --- a/content/doc/applications/java/java-war.md +++ b/content/doc/applications/java/java-war.md @@ -161,6 +161,7 @@ Here's the list of the configuration values for the "container" field in `war.js | WILDFLY23 | Use Wildfly servlet container 23.x (see ) | | {{% content "url_healthcheck" %}} +{{% content "request-flow" %}} ## Custom run command diff --git a/content/doc/applications/nodejs.md b/content/doc/applications/nodejs.md index 181474557..2d269c60e 100644 --- a/content/doc/applications/nodejs.md +++ b/content/doc/applications/nodejs.md @@ -290,3 +290,4 @@ To access environment variables from your code, you can use `process.env.MY_VARI {{% content "more-config" %}} {{% content "url_healthcheck" %}} +{{% content "request-flow" %}} diff --git a/content/doc/applications/php/_index.md b/content/doc/applications/php/_index.md index c7dacd53f..6b701439d 100644 --- a/content/doc/applications/php/_index.md +++ b/content/doc/applications/php/_index.md @@ -214,5 +214,4 @@ Then, set `APP_LOG=syslog` as Clever application environment variable. You can learn more about ProxySQL on the [dedicated documentation page](/guides/proxysql) {{% content "url_healthcheck" %}} -{{% content "redirectionio" %}} -{{% content "varnish" %}} +{{% content "request-flow" %}} diff --git a/content/doc/develop/request-flow.md b/content/doc/develop/request-flow.md index 652556f7a..623b92a59 100644 --- a/content/doc/develop/request-flow.md +++ b/content/doc/develop/request-flow.md @@ -25,11 +25,18 @@ Request Flow is available in the following runtimes: - [.NET](/doc/applications/dotnet/) - [Elixir](/doc/applications/elixir/) - [FrankenPHP](/doc/applications/frankenphp/) +- [Go](/doc/applications/golang/) - [Haskell](/doc/applications/haskell/) +- [Java](/doc/applications/java/) (Gradle, Jar, Maven, War/Ear) - [Linux](/doc/applications/linux/) +- [Meteor](/doc/applications/meteor/) +- [Node.js & Bun](/doc/applications/nodejs/) +- [PHP with Apache](/doc/applications/php/) - [Python with uv](/doc/applications/python/uv/) - [Rust](/doc/applications/rust/) +- [Scala](/doc/applications/scala/) - [Static](/doc/applications/static/) +- [Static with Apache](/doc/applications/static-apache/) - [V (Vlang)](/doc/applications/v/) ## Supported services @@ -64,7 +71,7 @@ Request Flow allocates ports in a chain from port `8080` (public) down to the ap Your application must listen on port `8080` when no middleware is active, or on port `9000` when at least one middleware is configured. > [!NOTE] -> In runtimes where Clever Cloud manages the port configuration (FrankenPHP, Static), port allocation is handled transparently with no additional configuration. +> In runtimes where Clever Cloud manages the port configuration (FrankenPHP, Java, PHP, Static), port allocation is handled transparently with no additional configuration. ## Explicit configuration with CC_REQUEST_FLOW From defacf4d66246bcfe1e000f7a2eae1a3d32a1a94 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 18 Feb 2026 11:35:43 +0100 Subject: [PATCH 028/180] develop(varnish): move and update page content --- content/changelog/2025/06-17-images-update.md | 2 +- content/changelog/2025/07-17-request-flow.md | 2 +- content/doc/administrate/_index.md | 1 - content/doc/administrate/cache.md | 71 ------------------- content/doc/applications/static.md | 2 +- content/doc/develop/request-flow.md | 2 +- content/doc/develop/varnish.md | 67 +++++++++++++++++ .../reference-environment-variables.md | 4 +- content/guides/tutorial-wordpress.md | 4 +- data/runtime_versions.yml | 10 +++ shared/request-flow.md | 2 +- shared/varnish.md | 2 - 12 files changed, 86 insertions(+), 83 deletions(-) delete mode 100644 content/doc/administrate/cache.md create mode 100644 content/doc/develop/varnish.md diff --git a/content/changelog/2025/06-17-images-update.md b/content/changelog/2025/06-17-images-update.md index 30636134c..7d487c97d 100644 --- a/content/changelog/2025/06-17-images-update.md +++ b/content/changelog/2025/06-17-images-update.md @@ -37,7 +37,7 @@ We updated all our images. Deployment is in progress for all our users. ## Other changes - Multiple fixes for logs -- [Varnish support](/doc/administrate/cache/) for FrankenPHP, [upcoming Linux, Static and V runtimes](https://github.com/CleverCloud/Community/discussions/66) +- [Varnish support](/doc/develop/varnish/) for FrankenPHP, [upcoming Linux, Static and V runtimes](https://github.com/CleverCloud/Community/discussions/66) - Astro, Docusaurus, MkDocs autobuild support for [upcoming static runtime](https://github.com/CleverCloud/Community/discussions/66) - `-x -race` flags are added to `go install` if `CC_TROUBLESHOOT` is set to `true` in Go runtime - `proxy_fcgi` is now default in PHP with Apache if `CC_CGI_IMPLEMENTATION` environment variable is not set diff --git a/content/changelog/2025/07-17-request-flow.md b/content/changelog/2025/07-17-request-flow.md index 978fc61a7..557959c85 100644 --- a/content/changelog/2025/07-17-request-flow.md +++ b/content/changelog/2025/07-17-request-flow.md @@ -12,7 +12,7 @@ description: Ease your reverse proxy configuration more and more excludeSearch: true --- -Clever Cloud exists to ease developers' life. For many years, you can use [Varnish in front of your application](/doc/administrate/cache/) just by adding a `varnish.vcl` file in your repository. For some months, you can also use [Redirection.io](/doc/reference/reference-environment-variables/#use-redirectionio-as-a-proxy) as a reverse proxy to handle redirects, rewrites, and more. In our latest release, we've gone a step further with Request Flow, available in new runtimes first : +Clever Cloud exists to ease developers' life. For many years, you can use [Varnish in front of your application](/doc/develop/varnish/) just by adding a `varnish.vcl` file in your repository. For some months, you can also use [Redirection.io](/doc/reference/reference-environment-variables/#use-redirectionio-as-a-proxy) as a reverse proxy to handle redirects, rewrites, and more. In our latest release, we've gone a step further with Request Flow, available in new runtimes first : - `frankenphp` - `linux` diff --git a/content/doc/administrate/_index.md b/content/doc/administrate/_index.md index 5a3edc7cd..239e4c090 100644 --- a/content/doc/administrate/_index.md +++ b/content/doc/administrate/_index.md @@ -30,7 +30,6 @@ aliases: {{< card link="/developers/doc/administrate/service-dependencies" title="Service dependencies" icon="plug" >}} {{< card link="/developers/doc/administrate/ssh-clever-tools" title="SSH access to running instances" icon="command-line" >}} {{< card link="/developers/doc/administrate/tcp-redirections" title="TCP redirections with Clever Tools" icon="tcp-ip-service" >}} - {{< card link="/developers/doc/administrate/cache" title="Varnish as HTTP cache" icon="bubbles" >}} {{< card link="/developers/doc/administrate/zone-migration" title="Zone migration" icon="map-pin" >}} {{< /cards >}} diff --git a/content/doc/administrate/cache.md b/content/doc/administrate/cache.md deleted file mode 100644 index 68322069b..000000000 --- a/content/doc/administrate/cache.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -type: docs -linkTitle: Varnish as HTTP Cache -title: Varnish as HTTP Cache -description: Configure Varnish HTTP accelerator on Clever Cloud for performance optimization, content delivery, and traffic management -keywords: -- varnish cache -- http accelerator -- performance optimization -- content delivery -- reverse proxy -- caching strategy -aliases: -- /administrate/cache -- /doc/tools/varnish ---- - -## Overview - -[Varnish](https://www.varnish-cache.org/) is a HTTP proxy-cache, which works as a reverse proxy between your application -and the client. Following rules defined by the user, Varnish will cache the data of an application to reduce the load on its server. We use **Varnish 8.0 and varnish-modules 0.27**. - - -> [!NOTE] Supported runtimes -> Varnish is available on **FrankenPHP**, **Go**, **Linux**, **Node.js**, **PHP with Apache**, **Static**, and **V (Vlang)** applications - -## Enable Varnish for your application - -To enable it, create a `varnish.vcl` file in the `/clevercloud` folder. You can also define `CC_VARNISH_FILE=/path/to/varnish.vcl` environment variable relative to your application root. This file describes how Varnish caches your applications and how it decides to return a cached resource or not. To know how to write your `varnish.vcl` file, have a look at the [Varnish documentation](https://varnish-cache.org/docs/8.0/index.html). - -The `vcl 4.1;` and backend section of the `varnish.vcl` configuration file are not necessary as they are already handled by Clever Cloud. -If you have a PHP FTP application or if your `varnish.vcl` file is on an FS Bucket, make sure you redeploy the application for the changes to take effect. - -## Listen on the right port - -Once varnish is enabled, your application should no longer listen on port **8080**, but on port **8081**. Because it's Varnish that will listen on port **8080**, and it will have in its configuration your application as backend. - -## Configure the cache size - -Change the storage size specified in the varnish.params file with the `CC_VARNISH_STORAGE_SIZE` environment variable (the default value is `1G`). - -```bash -CC_VARNISH_STORAGE_SIZE=2G -``` - -## Varnish migration - -If you have a configuration for an older version of varnish, read: -- [Upgrading to Varnish 7.0](https://varnish-cache.org/docs/7.0/whats-new/upgrading-7.0.html) guide -- [Upgrading to Varnish 8.0](https://varnish-cache.org/docs/8.0/whats-new/upgrading-8.0.html) guide - -## Example files - -We provide some [examples of Varnish configuration files](https://github.com/CleverCloud/varnish-examples) that you can -use for your application. Create a `/clevercloud` folder at the root of your application if it does not exist, -rename the file to `varnish.vcl` and move it in the `/clevercloud` folder. - -## Varnish with a monorepo - -If you use a monorepo, you may want to use Varnish for only some of its applications. Use a dedicated `CC_VARNISH_FILE` for that. - -If you have a `/clevercloud/varnish.vcl` file at the root of your monorepo, all of your applications automatically start using it with Varnish. To resolve this create a symlink during the deployments: - -1. Put your `varnish.vcl` file anywhere but at the root of your monorepo. -2. Create a symlink inside a `CC_PRE_BUILD_HOOK` to the app that needs to use Varnish, such as: - -```bash -CC_PRE_BUILD_HOOK="mkdir $APP_HOME/clevercloud; ln -s $APP_HOME/path/to/your/file/varnish.vcl $APP_HOME/clevercloud/varnish.vcl" -``` - -If you don't add this variable, the application won't use Varnish. diff --git a/content/doc/applications/static.md b/content/doc/applications/static.md index 35b7ef2cd..6de99f7fc 100644 --- a/content/doc/applications/static.md +++ b/content/doc/applications/static.md @@ -29,7 +29,7 @@ aliases: ## Overview -Static is a flexible, light and simple runtime dedicated to static sites generators (SSG), designed for minimum configuration effort with Auto-build feature. Pico instances are available, it allows users to put services in front of it, such as [Redirection.io](/doc/reference/reference-environment-variables/#use-redirectionio-as-a-proxy) or [Varnish](/doc/administrate/cache/). +Static is a flexible, light and simple runtime dedicated to static sites generators (SSG), designed for minimum configuration effort with Auto-build feature. Pico instances are available, it allows users to put services in front of it, such as [Redirection.io](/doc/reference/reference-environment-variables/#use-redirectionio-as-a-proxy) or [Varnish](/doc/develop/varnish/). > [!NOTE] Static is a new runtime > Help us to improve it by reporting any issue or suggestion on the [Clever Cloud Community](https://github.com/CleverCloud/Community/discussions/categories/paas-runtimes) diff --git a/content/doc/develop/request-flow.md b/content/doc/develop/request-flow.md index 623b92a59..e96df6e81 100644 --- a/content/doc/develop/request-flow.md +++ b/content/doc/develop/request-flow.md @@ -134,7 +134,7 @@ In this example: | `CC_VARNISH_FILE` | Path to a custom Varnish VCL file (default: `clevercloud/varnish.vcl`) | | `OTOROSHI_CHALLENGE_SECRET` | Otoroshi challenge secret. Activates Otoroshi Challenge verification in the request flow | -- [Learn more about Varnish on Clever Cloud](/doc/administrate/cache/) +- [Learn more about Varnish on Clever Cloud](/doc/develop/varnish/) - [Learn more about Redirection.io](https://redirection.io/) - [Learn more about OAuth2 Proxy](https://oauth2-proxy.github.io/oauth2-proxy/) - [Learn more about Otoroshi on Clever Cloud](/doc/addons/otoroshi/) diff --git a/content/doc/develop/varnish.md b/content/doc/develop/varnish.md new file mode 100644 index 000000000..4a32b6bcf --- /dev/null +++ b/content/doc/develop/varnish.md @@ -0,0 +1,67 @@ +--- +type: docs +linkTitle: Varnish as HTTP Cache +title: Varnish as HTTP Cache +description: Configure Varnish HTTP accelerator on Clever Cloud for performance optimization, content delivery, and traffic management +keywords: +- varnish cache +- http accelerator +- performance optimization +- content delivery +- reverse proxy +- caching strategy +aliases: +- /administrate/cache +- /doc/administrate/cache +- /doc/tools/varnish +--- + +## Overview + +[Varnish](https://www.varnish-cache.org/) is an HTTP proxy-cache that sits as a reverse proxy between your application and the client. It caches responses according to rules you define, reducing load on your application. Clever Cloud provides **Varnish {{< runtime_version varnish >}} and varnish-modules {{< runtime_version varnish-modules >}}**. + +> [!NOTE] Supported runtimes +> Varnish is available on all runtimes that support [Request Flow](/doc/develop/request-flow/): **.NET**, **Elixir**, **FrankenPHP**, **Go**, **Haskell**, **Java**, **Linux**, **Node.js & Bun**, **PHP with Apache**, **Rust**, **Static**, and **V (Vlang)** + +## Enable Varnish for your application + +Create a `varnish.vcl` file in the `clevercloud/` folder at the root of your application. You can also set the `CC_VARNISH_FILE` environment variable to a custom path within your application root, written as an absolute path starting at `/` (for example `CC_VARNISH_FILE=/config/varnish.vcl`). If the file does not exist, deployment fails. + +This file describes how Varnish caches your application's responses and when it returns a cached resource. To learn how to write your `varnish.vcl` file, refer to the [Varnish documentation](https://varnish-cache.org/docs/8.0/index.html). + +The `vcl 4.1;` declaration and backend section are not necessary as they are already handled by Clever Cloud. If your `varnish.vcl` file is stored on an FS Bucket, redeploy the application for changes to take effect. + +## Listen on the right port + +Varnish is managed through [Request Flow](/doc/develop/request-flow/). Once Varnish is enabled, your application must listen on port **9000** instead of **8080**. Request Flow places Varnish (and any other configured middleware) between the public port (`8080`) and your application. In runtimes where Clever Cloud manages the port configuration (FrankenPHP, Java, PHP, Static), this is handled transparently. + +## Configure the cache size + +Set the `CC_VARNISH_STORAGE_SIZE` environment variable to configure the Varnish cache size (default: `1G`). + +```bash +CC_VARNISH_STORAGE_SIZE=2G +``` + +## Varnish migration + +If you have a configuration for an older version of Varnish, read: + +- [Upgrading to Varnish 7.0](https://varnish-cache.org/docs/7.0/whats-new/upgrading-7.0.html) guide +- [Upgrading to Varnish 8.0](https://varnish-cache.org/docs/8.0/whats-new/upgrading-8.0.html) guide + +## Example files + +Clever Cloud provides [example Varnish configuration files](https://github.com/CleverCloud/varnish-examples). Download the one that fits your needs, rename it to `varnish.vcl` and place it in the `clevercloud/` folder at the root of your application. + +## Varnish with a monorepo + +If you use a monorepo, you may want to use Varnish for only some of its applications. Use `CC_VARNISH_FILE` to point to a specific configuration file. + +A `clevercloud/varnish.vcl` file at the root of your monorepo activates Varnish for all applications. To limit Varnish to specific applications, place the file elsewhere and create a symlink during deployment only for the applications that need it: + +```bash +CC_PRE_BUILD_HOOK="mkdir $APP_HOME/clevercloud; ln -s $APP_HOME/path/to/your/file/varnish.vcl $APP_HOME/clevercloud/varnish.vcl" +``` + +Applications without this hook or without `CC_VARNISH_FILE` set will not use Varnish. diff --git a/content/doc/reference/reference-environment-variables.md b/content/doc/reference/reference-environment-variables.md index a2834bb1a..3c9acd1e4 100644 --- a/content/doc/reference/reference-environment-variables.md +++ b/content/doc/reference/reference-environment-variables.md @@ -110,8 +110,8 @@ Use these to define [commands to run](/doc/develop/build-hooks) between various |[`CC_METRICS_PROMETHEUS_PORT`](/doc/metrics/#publish-your-own-metrics "Publish your own metrics") | Define the port on which the Prometheus endpoint is available | 9100 | |[`CC_METRICS_PROMETHEUS_RESPONSE_TIMEOUT`](/doc/metrics/#publish-your-own-metrics "Publish your own metrics") | Define the timeout in seconds to collect the application metrics. This value **must** be below 60 seconds as data are collected every minutes | 3 | |[`CC_METRICS_PROMETHEUS_USER`](/doc/metrics/#publish-your-own-metrics "Publish your own metrics") | Define the user for the basic auth of the Prometheus endpoint | | -|[`CC_VARNISH_FILE`](/doc/administrate/cache "Cache") | The path to the Varnish configuration file, relative to your application root | `/clevercloud/varnish.vcl` | -|[`CC_VARNISH_STORAGE_SIZE`](/doc/administrate/cache "Cache") | Configure the size of the Varnish cache. | 1G | +|[`CC_VARNISH_FILE`](/doc/develop/varnish "Cache") | The path to the Varnish configuration file, relative to your application root | `/clevercloud/varnish.vcl` | +|[`CC_VARNISH_STORAGE_SIZE`](/doc/develop/varnish "Cache") | Configure the size of the Varnish cache. | 1G | |[`CC_WORKER_COMMAND`](/doc/develop/workers "Workers") | Command to run in background as a worker process. You can run multiple workers. | | {{% content "mise" %}} diff --git a/content/guides/tutorial-wordpress.md b/content/guides/tutorial-wordpress.md index b2f4aabce..f0f7b6568 100644 --- a/content/guides/tutorial-wordpress.md +++ b/content/guides/tutorial-wordpress.md @@ -148,7 +148,7 @@ To uninstall the plugin, the procedure is the same as before except that you hav ## Optimise and speed-up your WordPress There are multiple ways to optimise your WordPress and speed-up its response time. -We provide different tools and software to help you in this task as [Varnish](/doc/administrate/cache) for the HTTP cache, and [Redis](/doc/addons/redis) for the object caching. +We provide different tools and software to help you in this task as [Varnish](/doc/develop/varnish) for the HTTP cache, and [Redis](/doc/addons/redis) for the object caching. ### Performance plugins @@ -159,7 +159,7 @@ We noticed performances problems when performance plugins are enabled and we rec ### HTTP Cache with Varnish -Enabling [Varnish](/doc/administrate/cache) for your application is very simple. All instances of PHP provide [Varnish](/doc/administrate/cache), you just have to configure your application to use it. +Enabling [Varnish](/doc/develop/varnish) for your application is very simple. All instances of PHP provide [Varnish](/doc/develop/varnish), you just have to configure your application to use it. 1. To use Varnish in your application, you have to create a `varnish.vcl` file in the `clevercloud` folder of your application. If this folder doesn't exist, create it in the **root** of your project. diff --git a/data/runtime_versions.yml b/data/runtime_versions.yml index f91941812..4f301c592 100644 --- a/data/runtime_versions.yml +++ b/data/runtime_versions.yml @@ -82,6 +82,16 @@ sws: default: - "2.40.1" +varnish: + eol_source: https://varnish-cache.org/releases/ + default: + - "8.0" + +varnish-modules: + eol_source: https://github.com/varnish/varnish-modules/releases + default: + - "0.27" + v: eol_source: https://github.com/vlang/v/releases default: diff --git a/shared/request-flow.md b/shared/request-flow.md index 3fa0ef7c7..1fe40c7f3 100644 --- a/shared/request-flow.md +++ b/shared/request-flow.md @@ -11,5 +11,5 @@ All three can be active simultaneously. To control the order, set `CC_REQUEST_FL When at least one middleware is active, your application must listen on port `9000` instead of `8080`. - [Learn more about Request Flow](/doc/develop/request-flow/) -- [Learn more about Varnish on Clever Cloud](/doc/administrate/cache/) +- [Learn more about Varnish on Clever Cloud](/doc/develop/varnish/) - [Learn more about Redirection.io](https://redirection.io/) diff --git a/shared/varnish.md b/shared/varnish.md index df8f59279..98462e666 100644 --- a/shared/varnish.md +++ b/shared/varnish.md @@ -1,5 +1,3 @@ ## Use Varnish as cache Varnish is a powerful HTTP accelerator that can be used to cache your web application's responses, improving performance and reducing load. To use it, create a Varnish configuration file in `clevercloud/varnish.vcl` and configure your application to listen on port `8081`. - -- [Learn more about Varnish on Clever Cloud](/doc/administrate/cache/) From 4a92a3bc69b79c96f8b2ecb1b803b06076b23392 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 18 Feb 2026 12:05:45 +0100 Subject: [PATCH 029/180] fix: update .NET --- content/changelog/2026/01-28-images-update.md | 2 +- content/changelog/2026/02-03-images-update.md | 2 +- content/changelog/2026/02-12-images-update.md | 6 +++--- content/changelog/2026/02-18-images-update.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/content/changelog/2026/01-28-images-update.md b/content/changelog/2026/01-28-images-update.md index a4871c409..41ae82cc3 100644 --- a/content/changelog/2026/01-28-images-update.md +++ b/content/changelog/2026/01-28-images-update.md @@ -45,7 +45,7 @@ We updated all our images. Deployment is in progress for all our users. ## Request Flow expansion -Request Flow is now available in .Net, Elixir, Haskell and Rust applications. We plan its expansion to more runtimes in the coming weeks in three releases: +Request Flow is now available in .NET, Elixir, Haskell and Rust applications. We plan its expansion to more runtimes in the coming weeks in three releases: - Go and Node.js/Bun - Java and PHP - Python and Ruby diff --git a/content/changelog/2026/02-03-images-update.md b/content/changelog/2026/02-03-images-update.md index 6f2ec00cd..c20166cad 100644 --- a/content/changelog/2026/02-03-images-update.md +++ b/content/changelog/2026/02-03-images-update.md @@ -18,7 +18,7 @@ We updated all our images. Deployment is in progress for all our users. * OAuth2 Proxy 7.14.2 * Otoroshictl 0.0.15 * Tailscale 1.94.1 -* **.Net:** +* **.NET:** * Update to 6.0.136 * **Docker:** * Docker Buildx 0.31.1 diff --git a/content/changelog/2026/02-12-images-update.md b/content/changelog/2026/02-12-images-update.md index 4663f1b1d..cfd658ae4 100644 --- a/content/changelog/2026/02-12-images-update.md +++ b/content/changelog/2026/02-12-images-update.md @@ -1,5 +1,5 @@ --- -title: "Images update: .Net 10, Go 1.26, Mise 2026.2, Python 3.14, uv 0.10" +title: "Images update: .NET 10, Go 1.26, Mise 2026.2, Python 3.14, uv 0.10" description: "Many tiny updates, and some surprises we'll detail soon" date: 2026-02-12 tags: @@ -19,7 +19,7 @@ We updated all our images. Deployment is in progress for all our users. * Mise 2026.2.8 * nginx 1.28.2 * pgpool2 4.7 -* **.Net:** +* **.NET:** * Update to 10.0.102 * **Docker:** * Docker 29.2.1 @@ -34,7 +34,7 @@ We updated all our images. Deployment is in progress for all our users. * pip 26.0.1 * uv 0.10.2 -## .Net 10 support +## .NET 10 support You can now set `CC_DOTNET_VERSION=10.0`, default version is still `8.0`. We'll move to `10.0` in the coming weeks. diff --git a/content/changelog/2026/02-18-images-update.md b/content/changelog/2026/02-18-images-update.md index 45442cbea..e1802795d 100644 --- a/content/changelog/2026/02-18-images-update.md +++ b/content/changelog/2026/02-18-images-update.md @@ -19,7 +19,7 @@ We updated all our images. Deployment is in progress for all our users. * ClamAV 1.5.1 * Mise 2026.2.13 * Otoroshictl 0.0.16 -* **.Net:** +* **.NET:** * Update to 8.0.123 * Update to 9.0.113 * **Elixir:** From 6d58b68115f5f714cd3a7cfa01c622aec222020c Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 18 Feb 2026 12:23:03 +0100 Subject: [PATCH 030/180] fix: add support link --- content/changelog/2026/02-18-images-update.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/changelog/2026/02-18-images-update.md b/content/changelog/2026/02-18-images-update.md index e1802795d..e8c0926dc 100644 --- a/content/changelog/2026/02-18-images-update.md +++ b/content/changelog/2026/02-18-images-update.md @@ -38,6 +38,6 @@ We updated all our images. Deployment is in progress for all our users. ## Request Flow extension -Request Flow is now available in Go, Java/Scala, Meteor, Node.js & Bun, PHP and Static with Apache runtimes. Python (without uv) and Ruby are coming soon. If your application currently uses Varnish in Go or Node.js, you must ask support to switch to this new release. Your application will have to move from port `8081` to `9000`. +Request Flow is now available in Go, Java/Scala, Meteor, Node.js & Bun, PHP and Static with Apache runtimes. Python (without uv) and Ruby are coming soon. If your application currently uses Varnish in Go or Node.js, you must [ask support](https://console.clever-cloud.com/ticket-center-choice) to switch to this new release. Your application will have to move from port `8081` to `9000`. - [Learn more about Request Flow](/doc/develop/request-flow/) From ae2bfd6a8158ada2979971b933533bb1ef0d9759 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 18 Feb 2026 12:28:40 +0100 Subject: [PATCH 031/180] fix: typos/links --- content/doc/develop/request-flow.md | 2 +- content/doc/develop/varnish.md | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/content/doc/develop/request-flow.md b/content/doc/develop/request-flow.md index e96df6e81..dd3765693 100644 --- a/content/doc/develop/request-flow.md +++ b/content/doc/develop/request-flow.md @@ -27,7 +27,7 @@ Request Flow is available in the following runtimes: - [FrankenPHP](/doc/applications/frankenphp/) - [Go](/doc/applications/golang/) - [Haskell](/doc/applications/haskell/) -- [Java](/doc/applications/java/) (Gradle, Jar, Maven, War/Ear) +- [Java](/doc/applications/java/) - [Linux](/doc/applications/linux/) - [Meteor](/doc/applications/meteor/) - [Node.js & Bun](/doc/applications/nodejs/) diff --git a/content/doc/develop/varnish.md b/content/doc/develop/varnish.md index 4a32b6bcf..54583b333 100644 --- a/content/doc/develop/varnish.md +++ b/content/doc/develop/varnish.md @@ -21,7 +21,7 @@ aliases: [Varnish](https://www.varnish-cache.org/) is an HTTP proxy-cache that sits as a reverse proxy between your application and the client. It caches responses according to rules you define, reducing load on your application. Clever Cloud provides **Varnish {{< runtime_version varnish >}} and varnish-modules {{< runtime_version varnish-modules >}}**. > [!NOTE] Supported runtimes -> Varnish is available on all runtimes that support [Request Flow](/doc/develop/request-flow/): **.NET**, **Elixir**, **FrankenPHP**, **Go**, **Haskell**, **Java**, **Linux**, **Node.js & Bun**, **PHP with Apache**, **Rust**, **Static**, and **V (Vlang)** +> Varnish is available on all runtimes that support [Request Flow](/doc/develop/request-flow/). ## Enable Varnish for your application @@ -56,9 +56,7 @@ Clever Cloud provides [example Varnish configuration files](https://github.com/C ## Varnish with a monorepo -If you use a monorepo, you may want to use Varnish for only some of its applications. Use `CC_VARNISH_FILE` to point to a specific configuration file. - -A `clevercloud/varnish.vcl` file at the root of your monorepo activates Varnish for all applications. To limit Varnish to specific applications, place the file elsewhere and create a symlink during deployment only for the applications that need it: +If you use a monorepo, you may want to use Varnish for only some of its applications. Use `CC_VARNISH_FILE` to point to a specific configuration file. A `clevercloud/varnish.vcl` file at the root of your monorepo activates Varnish for all applications. To limit Varnish to specific applications, place the file elsewhere and create a symlink during deployment only for the applications that need it: ```bash CC_PRE_BUILD_HOOK="mkdir $APP_HOME/clevercloud; ln -s $APP_HOME/path/to/your/file/varnish.vcl $APP_HOME/clevercloud/varnish.vcl" From 5c50d59ca4c75f2baaed3950e48043e124c07b31 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 18 Feb 2026 16:01:40 +0100 Subject: [PATCH 032/180] changelog: Clever Tools 4.6 --- .../changelog/2026/02-18-clever-tools-4.6.md | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 content/changelog/2026/02-18-clever-tools-4.6.md diff --git a/content/changelog/2026/02-18-clever-tools-4.6.md b/content/changelog/2026/02-18-clever-tools-4.6.md new file mode 100644 index 000000000..fb335674f --- /dev/null +++ b/content/changelog/2026/02-18-clever-tools-4.6.md @@ -0,0 +1,105 @@ +--- +title: "Clever Tools 4.6: multi-profile, config providers, system Git and AI skill" +date: 2026-02-18 +description: Clever Tools 4.6 adds multi-profile account management, config provider CLI commands, faster deploys with system Git and an AI coding skill +tags: + - clever-tools + - cli +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 + - name: Hubert Sablonnière + link: https://github.com/hsablonniere + image: https://github.com/hsablonniere.png?size=40 +excludeSearch: true +--- + +[Clever Tools 4.6.0](https://github.com/CleverCloud/clever-tools/releases/tag/4.6.0) is available. This release brings multi-profile support, config provider commands, add-on logs migration to the v4 API, optional system Git support and an AI assistant skill. + +## Multi-profile support + +You can now login to multiple Clever Cloud accounts, each with its own credentials and alias. New commands allow you to list and switch between profiles, making it easier to work across different accounts or environments from a single machine. Each profile can also have per-profile endpoint and OAuth overrides configured during login for custom Clever Cloud deployments. + +```bash +# Create named profiles +clever login --alias personal +clever login --alias work + +# Create a profile with custom API endpoint +clever login --alias staging --api-host https://clever-cloud-api.example.com + +# List all profiles +clever profile list + +# Switch to another profile +clever profile switch --alias work + +# With exactly 2 profiles, switch to the other one +clever profile switch + +# Display the current profile +clever profile +``` + +## Config providers + +New `config-provider` commands are now available, giving you direct access to manage [config providers](/doc/addons/config-provider/) from the CLI. You can list, get, set, remove and import environment variables. + +```bash +# List all config providers +clever config-provider list + +# Get variables from a config provider (by name or ID) +clever config-provider get my-config-provider + +# Export variables in shell format +clever config-provider get my-config-provider --format shell + +# Set a variable +clever config-provider set my-config-provider MY_VAR "my-value" + +# Remove a variable +clever config-provider rm my-config-provider MY_VAR + +# Import variables from a .env file +cat my-vars.env | clever config-provider import my-config-provider + +# Import variables from JSON +echo '[{"name":"FOO","value":"bar"}]' | clever config-provider import my-config-provider -F json +``` + +## AI assistant skill + +Clever Tools is now available as a skill for AI coding assistants such as Claude Code, Codex, Cursor or GitHub Copilot. Once installed, the assistant gets knowledge of CLI commands, Clever Cloud concepts, available runtimes, add-on providers and common workflows. + +```bash +# Install the skill for your AI coding assistant +npx skills add CleverCloud/clever-tools +``` + +## System Git support (beta) + +By default, Clever Tools uses an embedded JavaScript Git implementation for deploy operations. While it works without requiring Git to be installed, it can be slow on large repositories or branches with rewritten history (rebases, squashes), and does not support SSH-based protocols. + +You can now opt in to use your system's native `git` command instead, for faster and more reliable deployments. This feature is currently in beta and requires `git` to be available in your `PATH`. + +```bash +# Enable system Git +clever features enable system-git + +# Deploy as usual, now using your system's git +clever deploy + +# Disable if not needed anymore +clever features disable system-git +``` + +## How to upgrade + +To upgrade Clever Tools, [use your favorite package manager](/doc/cli/install/). For example with `npm`: + +``` +npm update -g clever-tools +clever version +``` From a8cdd670d51c1880c97b9c6d41abbf6cfc0bb8fb Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 18 Feb 2026 17:01:53 +0100 Subject: [PATCH 033/180] cli: update from last changes --- content/doc/cli/_index.md | 15 ++- content/doc/cli/applications/_index.md | 5 +- content/doc/cli/applications/configuration.md | 2 +- .../cli/applications/deployment-lifecycle.md | 55 +++++++++ content/doc/cli/kubernetes.md | 2 +- content/doc/cli/logs-drains.md | 108 ++++-------------- content/doc/cli/network-groups.md | 6 +- content/doc/cli/operators.md | 20 +++- content/doc/cli/profiles.md | 74 ++++++++++++ 9 files changed, 194 insertions(+), 93 deletions(-) create mode 100644 content/doc/cli/profiles.md diff --git a/content/doc/cli/_index.md b/content/doc/cli/_index.md index 89ec97a77..44fd9168b 100644 --- a/content/doc/cli/_index.md +++ b/content/doc/cli/_index.md @@ -36,6 +36,15 @@ You can contribute to it through [issue](https://github.com/CleverCloud/clever-t - [How to install Clever Tools](install) - [Create a Clever Cloud account](https://console.clever-cloud.com) +Use Clever Tools through `npx` or `npm exec` for one-off usage or in CI/CD pipelines for example: + +```bash +# Set/Export CLEVER_TOKEN and CLEVER_SECRET to login with a given account +# --yes is used to skip the interactive prompts +npx --yes clever-tools@latest version +npm exec -- clever-tools@3.14 profile --format json +``` + You'll find below the first commands to know to connect Clever Tools to your account, get its information and manage some options. Others are developed in dedicated pages: {{< cards >}} @@ -47,7 +56,9 @@ You'll find below the first commands to know to connect Clever Tools to your acc {{< card link="/developers/doc/cli/network-groups" title="Network Groups" icon="tcp-ip-service" >}} {{< card link="/developers/doc/cli/notifications-webhooks" title="Notifications, Web hooks" icon="bell" >}} {{< card link="/developers/doc/cli/operators" title="Operators (Managed services)" icon="document-check" >}} + {{< card link="/developers/doc/cli/profiles" title="Profiles and overrides" icon="user" >}} {{< card link="/developers/doc/cli/services-depedencies" title="Services dependencies" icon="endpoints" >}} + {{< card link="/developers/doc/cli/kubernetes" title="Kubernetes" icon="server-stack" >}} {{< /cards >}} ## basic commands @@ -116,7 +127,7 @@ To connect to your Clever Cloud account, use: clever login ``` -It will open your default browser and start an Open Authorization ([OAuth](https://en.wikipedia.org/wiki/OAuth)) process to get a `token` and `secret` pair added in your account if it succeeds. You can manage it from the [Console](https://console.clever-cloud.com/users/me/oauth-tokens). Clever Tools will automatically store these `token` and `secret` values in a hidden `clever-tools.json` config file in the current local user home folder. +It will open your default browser and start an Open Authorization ([OAuth](https://en.wikipedia.org/wiki/OAuth)) process to get a `token` and `secret` pair added in your account if it succeeds. You can manage it from the [Console](https://console.clever-cloud.com/users/me/tokens). Clever Tools will automatically store these `token` and `secret` values in a hidden `clever-tools.json` config file in the current local user home folder. If you already know them, you can use: @@ -143,6 +154,8 @@ clever profile open clever profile -F json ``` +To manage multiple profiles or configure per-profile overrides, see: [Profiles and overrides](/doc/cli/profiles/) + ## emails To list primary email and secondary emails associated with your Clever Cloud account, you can use: diff --git a/content/doc/cli/applications/_index.md b/content/doc/cli/applications/_index.md index fef09d021..f7d310adf 100644 --- a/content/doc/cli/applications/_index.md +++ b/content/doc/cli/applications/_index.md @@ -78,8 +78,9 @@ Default region is our Paris datacenters (`par`), but it can be: - `syd` (Sydney, OVHcloud) - `wsw` (Warsaw, OVHcloud) -> [!NOTE] To benefit from certified hosting for health data, you need to deploy in an HDS zone and to sign up to a specific contract -> This begins by having [an initial discussion with Clever Cloud team](https://www.clever.cloud/fr/hebergement-donnees-de-sante/contact-hds/) +> [!NOTE] +> To benefit from certified hosting for health data, you need to deploy in an HDS zone and to sign up to a specific contract. \ +> This begins with [an initial discussion with our team](https://www.clever.cloud/fr/hebergement-donnees-de-sante/contact-hds/). After the application creation, you can ask for a `json` formatted report instead of an `human` sentence: diff --git a/content/doc/cli/applications/configuration.md b/content/doc/cli/applications/configuration.md index 10d82a176..821cfecde 100644 --- a/content/doc/cli/applications/configuration.md +++ b/content/doc/cli/applications/configuration.md @@ -33,7 +33,7 @@ clever config set parameter value To update multiple configuration parameters at a time, use: ``` -clever config update --option1 value1 --option2 value2 --option3 value3 +clever config update FLAGS ``` Available parameters are : diff --git a/content/doc/cli/applications/deployment-lifecycle.md b/content/doc/cli/applications/deployment-lifecycle.md index df19e73bf..6aa23fcf2 100644 --- a/content/doc/cli/applications/deployment-lifecycle.md +++ b/content/doc/cli/applications/deployment-lifecycle.md @@ -106,6 +106,61 @@ To ssh a specific application, use: clever ssh --app APP_ID_OR_NAME ``` +## logs + +When you deploy an application on Clever Cloud, we collect its logs, hosted in our internal Pulsar stack, all included. To listen to the stream, use: + +``` +clever logs +``` + +You can also get logs from a specific timeline, deployment or add-on through options: + +``` +[--before, --until] BEFORE Fetch logs before this date/time (ISO8601 date, positive number in seconds or duration, e.g.: 1h) +[--after, --since] AFTER Fetch logs after this date/time (ISO8601 date, positive number in seconds or duration, e.g.: 1h) +[--search] SEARCH Fetch logs matching this pattern +[--deployment-id] DEPLOYMENT_ID Fetch logs for a given deployment +[--addon] ADDON_ID Add-on ID +[--format, -F] FORMAT Output format (human, json, json-stream) (default: human) +``` + +## access logs + +When you deploy an application on Clever Cloud, we collect its access logs, hosted in our internal Pulsar stack, all included. To listen to the stream, use: + +``` +clever accesslogs +``` + +> [!TIP] +> This now uses our v4 API, it's available as Alpha feature for now. + +You can also get access logs from a specific timeline or add-on through options, in multiple formats: + +``` +[--before, --until] BEFORE Fetch logs before this date/time (ISO8601 date, positive number in seconds or duration, e.g.: 1h) +[--after, --since] AFTER Fetch logs after this date/time (ISO8601 date, positive number in seconds or duration, e.g.: 1h) +[--format, -F] FORMAT Output format (human, json, json-stream) (default: human) +``` + +You can for example get access logs in JSON stream format for the last hour with: + +``` +clever accesslogs --format json-stream --since 1h +clever accesslogs -F json-stream | jq '.source.ip' +``` + +or JSON if you add a date/time end limit: + +``` +clever accesslogs --app APP_NAME --since 2025-04-21T13:37:42 --until 1d -F json | jq '[.[] | {date, countryCode: .source.countryCode, ip: .source.ip, port: .source.port}]' +clever accesslogs --app APP_NAME --since 2025-04-21T13:37:42 --until 1d -F json | jq '.[] | [.date, .source.countryCode, .source.ip, .source.port] | @sh' +``` + +> [!TIP] +> `jq` offers multiple table formatting options, like `@csv`, `@tsv`, `@json`, `@html`, `@uri`, `@base64`, etc. + ## activity To get deployment activity, use: diff --git a/content/doc/cli/kubernetes.md b/content/doc/cli/kubernetes.md index fb77ce1d7..ece2d44ba 100644 --- a/content/doc/cli/kubernetes.md +++ b/content/doc/cli/kubernetes.md @@ -76,7 +76,7 @@ Classic response is a table: ├─────────┼─────────────────────────────────────────┤ │ Name │ 'myKubeCluster' │ │ ID │ 'kubernetes_id' │ -│ Version │ 1.34 │ +│ Version │ 1.34.1 │ │ Status │ 'ACTIVE' │ └─────────┴─────────────────────────────────────────┘ ``` diff --git a/content/doc/cli/logs-drains.md b/content/doc/cli/logs-drains.md index c8695bf10..f1d0a36ad 100644 --- a/content/doc/cli/logs-drains.md +++ b/content/doc/cli/logs-drains.md @@ -1,8 +1,8 @@ --- type: docs -linkTitle: Logs, Drains -title: Logs, Drains -description: Access application logs and manage log drains using Clever Tools CLI for centralized logging, monitoring, and troubleshooting capabilities +linkTitle: Logs Drains +title: Logs Drains +description: Manage log drains using Clever Tools CLI for centralized logging, monitoring, and troubleshooting capabilities keywords: - logs - drains @@ -15,73 +15,14 @@ aliases: - /doc/logs-drains --- -## logs - -When you deploy an application on Clever Cloud, we collect its logs, hosted in our internal Pulsar stack, all included. - -To listen the stream, use: - -``` -clever logs -``` - -You can also get logs from a specific timeline, deployment or add-on through options: - -``` -[--before, --until] BEFORE Fetch logs before this date/time (ISO8601 date, positive number in seconds or duration, e.g.: 1h) -[--after, --since] AFTER Fetch logs after this date/time (ISO8601 date, positive number in seconds or duration, e.g.: 1h) -[--search] SEARCH Fetch logs matching this pattern -[--deployment-id] DEPLOYMENT_ID Fetch logs for a given deployment -[--addon] ADDON_ID Add-on ID -[--format, -F] FORMAT Output format (human, json, json-stream) (default: human) -``` - -## access logs - -When you deploy an application on Clever Cloud, we collect its access logs, hosted in our internal Pulsar stack, all included. - -To listen the stream, use: - -``` -clever accesslogs -``` - -> [!TIP] -> This now uses our v4 API, it's available as Alpha feature for now. - -You can also get access logs from a specific timeline or add-on through options, in multiple formats: - -``` -[--before, --until] BEFORE Fetch logs before this date/time (ISO8601 date, positive number in seconds or duration, e.g.: 1h) -[--after, --since] AFTER Fetch logs after this date/time (ISO8601 date, positive number in seconds or duration, e.g.: 1h) -[--format, -F] FORMAT Output format (human, json, json-stream) (default: human) -``` - -You can for example get access logs in JSON stream format for the last hour with: - -``` -clever accesslogs --format json-stream --since 1h -clever accesslogs -F json-stream | jq '.source.ip' -``` - -or JSON if you add a date/time end limit: - -``` -clever accesslogs --app APP_NAME --since 2025-04-21T13:37:42 --until 1d -F json | jq '[.[] | {date, countryCode: .source.countryCode, ip: .source.ip, port: .source.port}]' -clever accesslogs --app APP_NAME --since 2025-04-21T13:37:42 --until 1d -F json | jq '.[] | [.date, .source.countryCode, .source.ip, .source.port] | @sh' -``` - -> [!TIP] -> `jq` offers multiple table formatting options, like `@csv`, `@tsv`, `@json`, `@html`, `@uri`, `@base64`, etc. - -## drain - -You can use Clever Tools to control logs drains, through following commands. Each can target a specific add-on with `--addon ADDON_ID ` or application, adding `--app APP_ID_OR_NAME` or a local alias (`--alias`, `-a`): +You can use Clever Tools to control logs drains, through following commands. Each can target a specific application, adding `--app APP_ID_OR_NAME` or a local alias (`--alias`, `-a`): ``` clever drain -clever drain --format json +clever drain -F json clever drain create +clever drain get +clever drain get --format json clever drain remove clever drain enable clever drain disable @@ -89,44 +30,45 @@ clever drain disable Where `DRAIN-TYPE` is one of: -- `DatadogHTTP`: for Datadog endpoint (note that this endpoint needs your Datadog API Key) -- `ElasticSearch`: for ElasticSearch endpoint (note that this endpoint requires username/password parameters as HTTP Basic Authentication) -- `HTTP`: for TCP syslog endpoint (note that this endpoint has optional username/password parameters as HTTP Basic Authentication) -- `NewRelicHTTP`: for NewRelic endpoint (note that this endpoint needs your NewRelic API Key) -- `TCPSyslog`: for TCP syslog endpoint -- `UDPSyslog`: for UDP syslog endpoint +- `datadog`: for Datadog endpoint (note that this endpoint needs your Datadog API Key) +- `elasticsearch`: for ElasticSearch endpoint (note that this endpoint requires username/password parameters as HTTP Basic Authentication) +- `newrelic`: for NewRelic endpoint (note that this endpoint needs your NewRelic API Key) +- `ovh-tcp`: for OVH TCP syslog endpoint (note that this endpoint has an optional sd-params parameter) +- `raw-http`: for HTTP endpoint (note that this endpoint has optional username/password parameters as HTTP Basic Authentication) +- `syslog-tcp`: for TCP syslog endpoint +- `syslog-udp`: for UDP syslog endpoint Drain creation supports the following options: ``` -[--username, -u] USERNAME (HTTP drains) basic auth username -[--password, -p] PASSWORD (HTTP drains) basic auth password -[--api-key, -k] API_KEY (NewRelic drains) API key -[--index-prefix, -i] INDEX_PREFIX (ElasticSearch drains) Index prefix (default: logstash-) -[--sd-params, -s] SD_PARAMS (TCP and UDP drains) sd-params string (e.g.: `X-OVH-TOKEN=\"REDACTED\"`) +[--username, -u] USERNAME Basic auth username (for elasticsearch or raw-http) +[--password, -p] PASSWORD Basic auth password (for elasticsearch or raw-http) +[--api-key, -k] API_KEY API key (for newrelic) +[--index-prefix, -i] INDEX_PREFIX Optional index prefix (for elasticsearch), `logstash` value is used if not set +[--sd-params, -s] SD_PARAMS RFC5424 structured data parameters (for ovh-tcp), e.g.: `X-OVH-TOKEN=\"REDACTED\"` ``` -### ElasticSearch logs drains +## ElasticSearch logs drains ElasticSearch drains use the Elastic bulk API. To match this endpoint, specify `/_bulk` at the end of your ElasticSearch endpoint. -### Datadog logs drains +## Datadog logs drains Datadog has two zones, EU and COM. An account on one zone is not available on the other, make sure to target the good EU or COM intake endpoint. To create a [Datadog](https://docs.datadoghq.com/api/?lang=python#send-logs-over-http) drain, you just need to use one of the following command depending on your zone: ``` # EU -clever drain create DatadogHTTP "https://http-intake.logs.datadoghq.eu/v1/input/?ddsource=clevercloud&service=&host=" +clever drain create datadog "https://http-intake.logs.datadoghq.eu/v1/input/?ddsource=clevercloud&service=&host=" # US -clever drain create DatadogHTTP "https://http-intake.logs.datadoghq.com/v1/input/?ddsource=clevercloud&service=&host=" +clever drain create datadog "https://http-intake.logs.datadoghq.com/v1/input/?ddsource=clevercloud&service=&host=" ``` The `host` query parameter is not mandatory: in the Datadog pipeline configuration, you can map `@source_host` which is the host provided by Clever Cloud in logs as `host` property. -### NewRelic logs drains +## NewRelic logs drains NewRelic has two zones, EU and US. An account on one zone is not available on the other, make sure to target the good EU or US intake endpoint. To create a [NewRelic](https://docs.newrelic.com/docs/logs/log-api/introduction-log-api/) drain, you just need to use: ``` -clever drain create NewRelicHTTP "https://log-api.eu.newrelic.com/log/v1" --api-key +clever drain create newrelic "https://log-api.eu.newrelic.com/log/v1" --api-key ``` diff --git a/content/doc/cli/network-groups.md b/content/doc/cli/network-groups.md index f3dbcbe1c..3ba769922 100644 --- a/content/doc/cli/network-groups.md +++ b/content/doc/cli/network-groups.md @@ -31,7 +31,7 @@ Tell us what you think of Network Groups and what features you need from it in [ ## How it works -When you create a Network Group, a Wireguard configuration is generated with a corresponding [CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing). Then, you can, for example, add a Clever Cloud application and an associated add-on to the same Network Group. These are members, defined by an `id`, a `label`, a `kind` and a `domain name`. +When you create a Network Group, a WireGuard configuration is generated with a corresponding [CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing). Then, you can, for example, add a Clever Cloud application and an associated add-on to the same Network Group. These are members, defined by an `id`, a `label`, a `kind` and a `domain name`. When an application connects to a Network Group, you can reach it on any port inside a NG through its domain name. Any instance of this application is a peer, you can reach independently through an IP (from the attributed CIDR). It works the same way for add-ons and external resources. @@ -133,9 +133,9 @@ clever ng search text_to_search -F json > The search command is case-insensitive and will return all resources containing the search string > The get command look for an exact match and will return an error if multiple resources are found -## Get the Wireguard configuration of a Peer +## Get the WireGuard configuration of a Peer -To get the Wireguard configuration of a peer (a `json` formatted output is available): +To get the WireGuard configuration of a peer (a `json` formatted output is available): ``` clever ng get-config peerIdOrLabel myNG diff --git a/content/doc/cli/operators.md b/content/doc/cli/operators.md index bf60c69f5..22c7c807d 100644 --- a/content/doc/cli/operators.md +++ b/content/doc/cli/operators.md @@ -89,5 +89,21 @@ clever keycloak enable-ng myKeycloak clever otoroshi disable-ng otoroshi_id ``` -> [!NOTE] Keycloak Secured Multi Instances -> When you enable the Network Group on a Clever Cloud Keycloak, it activates [Secured Multi Instances](/doc/addons/keycloak/#secured-multi-instances): a cluster is configured with 2 instances of the Java application. When you disable the Network Group, the application is scaled down to 1 instance and the cluster is removed. +> [!NOTE] +> On Clever Cloud Keycloak uses Network Groups for its secure cluster feature. When you enable it, the Keycloak application is automatically scaled to 2 instances and the cluster automatically configured. When you disable the Network Group feature, the application is scaled down to 1 instance and the cluster is removed. + +## Otoroshictl + +Otoroshi instances can be managed using the `otoroshictl` command line tool. Clever Tools provides an easy way to use it, by providing Otoroshi instances configuration in a compliant YAML format: + +```bash +# Install otoroshictl with Rust's Cargo and enable operators/otoroshi command in Clever Tools: +cargo install otoroshictl +clever features enable operators + +clever otoroshi get-config | otoroshictl config import --current --stdin +otoroshictl resources get routes +``` + +> [!TIP] +> You can add as many Otoroshi instances as you want to your `otoroshictl` configuration by repeating this command with different instance IDs or names. Just add the `--current` flag to the one you want to use by default. diff --git a/content/doc/cli/profiles.md b/content/doc/cli/profiles.md new file mode 100644 index 000000000..a0a89e7d6 --- /dev/null +++ b/content/doc/cli/profiles.md @@ -0,0 +1,74 @@ +--- +type: docs +linkTitle: Profiles +title: Profiles and Overrides +description: Manage multiple Clever Tools profiles and configure per-profile overrides for custom Clever Cloud deployments +keywords: +- profiles +- cli +- configuration +- overrides +- multi-account +- authentication +--- + +You can use multiple profiles with Clever Tools, all stored in the configuration file. The active profile is the first one in the list and is used for all commands. Each profile contains your authentication data and an optional set of overrides for custom Clever Cloud deployments (API host, Console URL, etc.). + +> [!TIP] +> The configuration file lives in your OS config directory: +> - Windows: `%APPDATA%\clever-cloud\clever-tools.json` +> - Other systems: XDG config directory (typically `~/.config/clever-cloud/clever-tools.json`) + +## Create and use multiple profiles + +By default, `clever login` stores a profile under the `default` alias. To manage multiple accounts, log in with explicit aliases: + +```bash +clever login --alias personal +clever login --alias work +``` + +List and inspect profiles (the active one is marked): + +```bash +clever profile list +``` + +Switch to another profile: + +```bash +clever profile switch --alias work +``` + +Log out from a specific profile: + +```bash +clever logout --alias personal +``` + +## Overrides + +Overrides are stored per profile and are applied only when that profile is active. You can set them at login time: + +```bash +clever login --alias staging \ + --api-host https://api.clever-cloud.com \ + --console-url https://console.clever-cloud.com \ + --auth-bridge-host https://api-bridge.clever-cloud.com \ + --ssh-gateway ssh@sshgateway-clevercloud-customers.services.clever-cloud.com +``` + +Resolution order for configuration values: + +1. Defaults provided by Clever Tools +2. Active profile overrides +3. Environment variables + +To change overrides later, log in again with the same alias. The profile will be replaced and becomes active. + +## Special cases + +- If `CLEVER_TOKEN` and `CLEVER_SECRET` are set, Clever Tools injects a virtual `$env` profile that becomes active. You cannot switch or logout while `$env` is active; unset those environment variables first. +- The alias `$env` is reserved and cannot be used with `clever login --alias`. +- If there is only one stored profile, `clever profile switch` fails. With exactly two profiles and no `--alias`, it switches to the other one; with more than two, it prompts you to pick a profile. +- Logging in with an existing alias replaces the stored profile (including any previous overrides) and makes it active. From ecd6afd6c78e0a1072dd4066ce94c000eb9dc455 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 18 Feb 2026 17:15:32 +0100 Subject: [PATCH 034/180] reference(cli): update script source, Clever Tools 4.6 --- content/doc/reference/cli.md | 1561 ++++++++++++++++++++++++++++++---- update-cli-reference.sh | 2 +- 2 files changed, 1394 insertions(+), 169 deletions(-) diff --git a/content/doc/reference/cli.md b/content/doc/reference/cli.md index da6873642..3fbc1a5c2 100644 --- a/content/doc/reference/cli.md +++ b/content/doc/reference/cli.md @@ -19,7 +19,7 @@ aliases: - /reference/clever-tools/getting_started --- -This document is automatically generated from Clever Tools `4.5.1` and Clever Cloud API. It covers all Clever Tools commands and options. Use it to better understand this CLI and its capabilities or to train/use LLMs, AI-assisted IDEs. +This document is automatically generated from Clever Tools and Clever Cloud API. It covers all Clever Tools commands and options. Use it to better understand this CLI and its capabilities or to train/use LLMs, AI-assisted IDEs. To use Clever Tools, you need: - A Clever Cloud account, create one at https://console.clever-cloud.com/ @@ -256,63 +256,88 @@ Applications deployment zones (region): `par`, `parhds`, `fr-north-hds`, `grahds - zones: `par`, `parhds`, `grahds`, `ldn`, `mtl`, `rbx`, `rbxhds`, `scw`, `sgp`, `syd`, `wsw` - `redis-addon`: - - plans: `s_mono`, `m_mono`, `l_mono`, `xl_mono`, `xxl_mono`, `xxxl_mono`, `xxxxl_mono` + - plans: `s_mono`, `m_mono`, `l_mono`, `xl_mono`, `xxl_mono`, `xxxl_mono`, `xxxxl_mono`, `5xl_mono`, `6xl_mono`, `7xl_mono` - zones: `par`, `parhds`, `grahds`, `ldn`, `mtl`, `rbx`, `rbxhds`, `scw`, `sgp`, `syd`, `wsw` Default deployment zone is `par`, default plan is the lowest available. ## accesslogs -Description:** Fetch access logs +**Description:** Fetch access logs + +**Since:** 2.1.0 **Usage** +``` clever accesslogs [options] +``` **Options** - --addon Add-on ID +``` + --addon Add-on ID or real ID --after, --since Fetch logs after this date/time (ISO8601 date, positive number in seconds or duration, e.g.: 1h) -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) --before, --until Fetch logs before this date/time (ISO8601 date, positive number in seconds or duration, e.g.: 1h) -F, --format Output format (human, json, json-stream) (default: human) +``` ## activity -Description:** Show last deployments of an application +**Description:** Show last deployments of an application + +**Since:** 0.2.3 **Usage** +``` clever activity [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) -f, --follow Track new deployments in activity list -F, --format Output format (human, json, json-stream) (default: human) --show-all Show all activity +``` ## addon -Description:** Manage add-ons +**Description:** Manage add-ons + +**Since:** 0.2.3 **Usage** +``` clever addon [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ### addon create -Description:** Create an add-on +**Description:** Create an add-on + +**Since:** 0.2.3 **Usage** +``` clever addon create [options] +``` **Arguments** +``` addon-provider Add-on provider addon-name Add-on name +``` **Options** +``` --addon-version The version to use for the add-on -F, --format Output format (human, json) (default: human) -l, --link Link the created add-on to the app with the specified alias @@ -321,164 +346,254 @@ addon-name Add-on name -p, --plan Add-on plan, depends on the provider -r, --region Region to provision the add-on in, depends on the provider (default: par) -y, --yes Skip confirmation even if the add-on is not free +``` ### addon delete -Description:** Delete an add-on +**Description:** Delete an add-on + +**Since:** 0.2.3 **Usage** +``` clever addon delete [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) -y, --yes Skip confirmation and delete the add-on directly +``` ### addon env -Description:** List environment variables for an add-on +**Description:** List environment variables for an add-on + +**Since:** 2.11.0 **Usage** +``` clever addon env [options] +``` **Arguments** +``` addon-id Add-on ID or real ID +``` **Options** +``` -F, --format Output format (human, json, shell) (default: human) -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ### addon list -Description:** List available add-ons +**Description:** List available add-ons + +**Since:** 0.2.3 **Usage** +``` clever addon list [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ### addon providers -Description:** List available add-on providers +**Description:** List available add-on providers + +**Since:** 0.2.3 **Usage** +``` clever addon providers [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` #### addon providers show -Description:** Show information about an add-on provider +**Description:** Show information about an add-on provider + +**Since:** 0.2.3 **Usage** +``` clever addon providers show [options] +``` **Arguments** +``` addon-provider Add-on provider +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ### addon rename -Description:** Rename an add-on +**Description:** Rename an add-on + +**Since:** 0.3.0 **Usage** +``` clever addon rename [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) addon-name Add-on name +``` **Options** +``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ## applications -Description:** List linked applications +**Description:** List linked applications + +**Since:** 0.3.0 **Usage** +``` clever applications [options] +``` **Options** +``` -j, --json Show result in JSON format --only-aliases List only application aliases +``` ### applications list -Description:** List all applications +**Description:** List all applications + +**Since:** 3.8.0 **Usage** +``` clever applications list [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ## cancel-deploy -Description:** Cancel an ongoing deployment +**Description:** Cancel an ongoing deployment + +**Since:** 0.2.0 **Usage** +``` clever cancel-deploy [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ## config -Description:** Display or edit the configuration of your application +**Description:** Display or edit the configuration of your application + +**Since:** 2.5.0 **Usage** +``` clever config [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ### config get -Description:** Display the current configuration +**Description:** Display the current configuration + +**Since:** 2.5.0 **Usage** +``` clever config get [options] +``` **Arguments** +``` configuration-name Configuration to manage: name, description, zero-downtime, sticky-sessions, cancel-on-push, force-https, or task +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ### config set -Description:** Edit one configuration setting +**Description:** Edit one configuration setting + +**Since:** 2.5.0 **Usage** +``` clever config set [options] +``` **Arguments** +``` configuration-name Configuration to manage: name, description, zero-downtime, sticky-sessions, cancel-on-push, force-https, or task configuration-value The new value of the configuration +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ### config update -Description:** Edit multiple configuration settings at once +**Description:** Edit multiple configuration settings at once + +**Since:** 2.5.0 **Usage** +``` clever config update [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) --description Set application description @@ -493,29 +608,164 @@ clever config update [options] --enable-task Enable application as Clever Task --enable-zero-downtime Enable zero-downtime deployment --name Set application name +``` + +## config-provider + +**Description:** Manage configuration providers + +**Since:** 4.6.0 + +**Usage** +``` +clever config-provider +``` + +### config-provider get + +**Description:** List environment variables of a configuration provider + +**Since:** 4.6.0 + +**Usage** +``` +clever config-provider get [options] +``` + +**Arguments** +``` +addon-id|config-provider-id|addon-name Add-on ID, real ID (config_xxx) or name (if unambiguous) +``` + +**Options** +``` +-F, --format Output format (human, json, shell) (default: human) +``` + +### config-provider import + +**Description:** Load environment variables from STDIN +(WARNING: this deletes all current variables and replaces them with the new list loaded from STDIN) + +**Since:** 4.6.0 + +**Usage** +``` +clever config-provider import [options] +``` + +**Arguments** +``` +addon-id|config-provider-id|addon-name Add-on ID, real ID (config_xxx) or name (if unambiguous) +``` + +**Options** +``` +-F, --format Input format (name-equals-value, json) (default: name-equals-value) +``` + +### config-provider list + +**Description:** List configuration providers + +**Since:** 4.6.0 + +**Usage** +``` +clever config-provider list [options] +``` + +**Options** +``` +-F, --format Output format (human, json) (default: human) +``` + +### config-provider open + +**Description:** Open the configuration provider in Clever Cloud Console + +**Since:** 4.6.0 + +**Usage** +``` +clever config-provider open +``` + +**Arguments** +``` +addon-id|config-provider-id|addon-name Add-on ID, real ID (config_xxx) or name (if unambiguous) +``` + +### config-provider rm + +**Description:** Remove an environment variable from a configuration provider + +**Since:** 4.6.0 + +**Usage** +``` +clever config-provider rm +``` + +**Arguments** +``` +addon-id|config-provider-id|addon-name Add-on ID, real ID (config_xxx) or name (if unambiguous) +variable-name Name of the environment variable +``` + +### config-provider set + +**Description:** Add or update an environment variable named with the value + +**Since:** 4.6.0 + +**Usage** +``` +clever config-provider set +``` + +**Arguments** +``` +addon-id|config-provider-id|addon-name Add-on ID, real ID (config_xxx) or name (if unambiguous) +variable-name Name of the environment variable +variable-value Value of the environment variable +``` ## console -Description:** Open an application in the Console +**Description:** Open an application in the Console + +**Since:** 1.0.0 **Usage** +``` clever console [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ## create -Description:** Create an application +**Description:** Create an application + +**Since:** 0.2.0 **Usage** +``` clever create --type [] [options] +``` **Arguments** +``` app-name Application name (current directory name is used if not specified) (optional) +``` **Options** +``` -t, --type Instance type (required) -a, --alias Short name for the application -F, --format Output format (human, json) (default: human) @@ -523,70 +773,106 @@ app-name Application name (current directory name -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) -r, --region Region, can be 'par', 'parhds', 'grahds', 'rbx', 'rbxhds', 'scw', 'ldn', 'mtl', 'sgp', 'syd', 'wsw' (default: par) -T, --task The application launch as a task executing the given command, then stopped +``` ## curl -Description:** Query Clever Cloud's API using Clever Tools credentials +**Description:** Query Clever Cloud's API using Clever Tools credentials + +**Since:** 2.10.0 **Usage** +``` clever curl +``` ## database -Description:** Manage databases and backups +**Description:** Manage databases and backups + +**Since:** 2.10.0 **Usage** +``` clever database +``` ### database backups -Description:** List available database backups +**Description:** List available database backups + +**Since:** 2.10.0 **Usage** +``` clever database backups [options] +``` **Arguments** +``` database-id|addon-id Any database ID (format: addon_UUID, postgresql_UUID, mysql_UUID, ...) +``` **Options** +``` -F, --format Output format (human, json) (default: human) -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` #### database backups download -Description:** Download a database backup +**Description:** Download a database backup + +**Since:** 2.10.0 **Usage** +``` clever database backups download [options] +``` **Arguments** +``` database-id|addon-id Any database ID (format: addon_UUID, postgresql_UUID, mysql_UUID, ...) backup-id A Database backup ID (format: UUID) +``` **Options** +``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) --output, --out Redirect the output of the command in a file +``` ## delete -Description:** Delete an application +**Description:** Delete an application + +**Since:** 0.7.0 **Usage** +``` clever delete [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) -y, --yes Skip confirmation and delete the application directly +``` ## deploy -Description:** Deploy an application +**Description:** Deploy an application + +**Since:** 0.2.0 **Usage** +``` clever deploy [options] +``` **Options** +``` -a, --alias Short name for the application -b, --branch Branch to push (current branch by default) -e, --exit-on Step at which the logs streaming is ended, steps are: deploy-start, deploy-end, never (default: deploy-end) @@ -595,142 +881,216 @@ clever deploy [options] -q, --quiet Don't show logs during deployment -p, --same-commit-policy What to do when local and remote commit are identical (error, ignore, restart, rebuild) (default: error) -t, --tag Tag to push (none by default) +``` ## diag -Description:** Diagnose the current installation (prints various informations for support) +**Description:** Diagnose the current installation (prints various informations for support) + +**Since:** 1.6.0 **Usage** +``` clever diag [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ## domain -Description:** Manage domain names for an application +**Description:** Manage domain names for an application + +**Since:** 0.2.0 **Usage** +``` clever domain [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) -F, --format Output format (human, json) (default: human) +``` ### domain add -Description:** Add a domain name to an application +**Description:** Add a domain name to an application + +**Since:** 0.2.0 **Usage** +``` clever domain add [options] +``` **Arguments** +``` fqdn Domain name of the application +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ### domain diag -Description:** Check if domains associated to a specific app are properly configured +**Description:** Check if domains associated to a specific app are properly configured + +**Since:** 3.9.0 **Usage** +``` clever domain diag [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) --filter Check only domains containing the provided text -F, --format Output format (human, json) (default: human) +``` ### domain favourite -Description:** Manage the favourite domain name for an application +**Description:** Manage the favourite domain name for an application + +**Since:** 2.7.0 **Usage** +``` clever domain favourite [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) -F, --format Output format (human, json) (default: human) +``` #### domain favourite set -Description:** Set the favourite domain for an application +**Description:** Set the favourite domain for an application + +**Since:** 2.7.0 **Usage** +``` clever domain favourite set [options] +``` **Arguments** +``` fqdn Domain name of the application +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` #### domain favourite unset -Description:** Unset the favourite domain for an application +**Description:** Unset the favourite domain for an application + +**Since:** 2.7.0 **Usage** +``` clever domain favourite unset [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ### domain overview -Description:** Get an overview of all your domains (all orgas, all apps) +**Description:** Get an overview of all your domains (all orgas, all apps) + +**Since:** 3.9.0 **Usage** +``` clever domain overview [options] +``` **Options** +``` --filter Get only domains containing the provided text -F, --format Output format (human, json) (default: human) +``` ### domain rm -Description:** Remove a domain name from an application +**Description:** Remove a domain name from an application + +**Since:** 0.2.0 **Usage** +``` clever domain rm [options] +``` **Arguments** +``` fqdn Domain name of the application +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ## drain -Description:** Manage drains +**Description:** Manage drains + +**Since:** 0.9.0 **Usage** +``` clever drain [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) -F, --format Output format (human, json) (default: human) +``` ### drain create -Description:** Create a drain +**Description:** Create a drain + +**Since:** 0.9.0 **Usage** +``` clever drain create [options] +``` **Arguments** +``` drain-type No description available drain-url Drain URL +``` **Options** +``` -a, --alias Short name for the application -k, --api-key API key (for newrelic) --app Application to manage by its ID (or name, if unambiguous) @@ -738,521 +1098,831 @@ drain-url Drain URL -p, --password Basic auth password (for elasticsearch or raw-http) -s, --sd-params RFC5424 structured data parameters (for ovh-tcp), e.g.: `X-OVH-TOKEN=\"REDACTED\"` -u, --username Basic auth username (for elasticsearch or raw-http) +``` ### drain disable -Description:** Disable a drain +**Description:** Disable a drain + +**Since:** 0.9.0 **Usage** +``` clever drain disable [options] +``` **Arguments** +``` drain-id Drain ID +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ### drain enable -Description:** Enable a drain +**Description:** Enable a drain + +**Since:** 0.9.0 **Usage** +``` clever drain enable [options] +``` **Arguments** +``` drain-id Drain ID +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ### drain get -Description:** Get drain info +**Description:** Get drain info + +**Since:** 0.9.0 **Usage** +``` clever drain get [options] +``` **Arguments** +``` drain-id Drain ID +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) -F, --format Output format (human, json) (default: human) +``` ### drain remove -Description:** Remove a drain +**Description:** Remove a drain + +**Since:** 0.9.0 **Usage** +``` clever drain remove [options] +``` **Arguments** +``` drain-id Drain ID +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ## emails -Description:** Manage email addresses of the current user +**Description:** Manage email addresses of the current user + +**Since:** 3.13.0 **Usage** +``` clever emails [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ### emails add -Description:** Add a new secondary email address to the current user +**Description:** Add a new secondary email address to the current user + +**Since:** 3.13.0 **Usage** +``` clever emails add +``` **Arguments** +``` email Email address +``` ### emails open -Description:** Open the email addresses management page in the Console +**Description:** Open the email addresses management page in the Console + +**Since:** 3.13.0 **Usage** +``` clever emails open +``` ### emails primary -Description:** Set the primary email address of the current user +**Description:** Set the primary email address of the current user + +**Since:** 3.13.0 **Usage** +``` clever emails primary +``` **Arguments** +``` email Email address +``` ### emails remove -Description:** Remove a secondary email address from the current user +**Description:** Remove a secondary email address from the current user + +**Since:** 3.13.0 **Usage** +``` clever emails remove +``` **Arguments** +``` email Email address +``` ### emails remove-all -Description:** Remove all secondary email addresses from the current user +**Description:** Remove all secondary email addresses from the current user + +**Since:** 3.13.0 **Usage** +``` clever emails remove-all [options] +``` **Options** +``` -y, --yes Skip confirmation +``` ## env -Description:** Manage environment variables of an application +**Description:** Manage environment variables of an application + +**Since:** 0.2.0 **Usage** +``` clever env [options] +``` **Options** +``` --add-export Display sourceable env variables setting (deprecated, use `--format shell` instead) -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) -F, --format Output format (human, json, shell) (default: human) +``` ### env import -Description:** Load environment variables from STDIN +**Description:** Load environment variables from STDIN (WARNING: this deletes all current variables and replace them with the new list loaded from STDIN) +**Since:** 0.3.0 + **Usage** +``` clever env import [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) --json Import variables as JSON (an array of { "name": "THE_NAME", "value": "THE_VALUE" } objects) +``` ### env import-vars -Description:** Add or update environment variables named (comma-separated), taking their values from the current environment +**Description:** Add or update environment variables named (comma-separated), taking their values from the current environment + +**Since:** 2.0.0 **Usage** +``` clever env import-vars [options] +``` **Arguments** +``` variable-names Comma separated list of names of the environment variables +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ### env rm -Description:** Remove an environment variable from an application +**Description:** Remove an environment variable from an application + +**Since:** 0.3.0 **Usage** +``` clever env rm [options] +``` **Arguments** +``` variable-name Name of the environment variable +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ### env set -Description:** Add or update an environment variable named with the value +**Description:** Add or update an environment variable named with the value + +**Since:** 0.3.0 **Usage** +``` clever env set [options] +``` **Arguments** +``` variable-name Name of the environment variable variable-value Value of the environment variable +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ## features -Description:** Manage Clever Tools experimental features +**Description:** Manage Clever Tools experimental features + +**Since:** 3.11.0 **Usage** +``` clever features [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ### features disable -Description:** Disable experimental features +**Description:** Disable experimental features + +**Since:** 3.11.0 **Usage** +``` clever features disable +``` **Arguments** +``` features Comma-separated list of experimental features to manage +``` ### features enable -Description:** Enable experimental features +**Description:** Enable experimental features + +**Since:** 3.11.0 **Usage** +``` clever features enable +``` **Arguments** +``` features Comma-separated list of experimental features to manage +``` ### features info -Description:** Display info about an experimental feature +**Description:** Display info about an experimental feature + +**Since:** 3.11.0 **Usage** +``` clever features info +``` **Arguments** +``` feature Experimental feature to manage +``` ### features list -Description:** List available experimental features +**Description:** List available experimental features + +**Since:** 3.11.0 **Usage** +``` clever features list [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ## help -Description:** Display help about the Clever Cloud CLI +**Description:** Display help about the Clever Cloud CLI + +**Since:** 0.1.0 **Usage** +``` clever help +``` ## k8s -Description:** Manage Kubernetes clusters +**Description:** Manage Kubernetes clusters + +**Since:** 4.3.0 **Usage** +``` clever k8s +``` ### k8s add-persistent-storage -Description:** Activate persistent storage to a deployed Kubernetes cluster +**Description:** Activate persistent storage to a deployed Kubernetes cluster + +**Since:** 4.3.0 **Usage** +``` clever k8s add-persistent-storage [options] +``` **Arguments** +``` cluster-id|cluster-name Kubernetes cluster ID or name +``` **Options** +``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ### k8s create -Description:** Create a Kubernetes cluster +**Description:** Create a Kubernetes cluster + +**Since:** 4.3.0 **Usage** +``` clever k8s create [options] +``` **Arguments** +``` cluster-name Kubernetes cluster name +``` **Options** +``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) -w, --watch Watch the deployment until the cluster is deployed +``` ### k8s delete -Description:** Delete a Kubernetes cluster +**Description:** Delete a Kubernetes cluster + +**Since:** 4.3.0 **Usage** +``` clever k8s delete [options] +``` **Arguments** +``` cluster-id|cluster-name Kubernetes cluster ID or name +``` **Options** +``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) -y, --yes Skip confirmation and delete the add-on directly +``` ### k8s get -Description:** Get information about a Kubernetes cluster +**Description:** Get information about a Kubernetes cluster + +**Since:** 4.3.0 **Usage** +``` clever k8s get [options] +``` **Arguments** +``` cluster-id|cluster-name Kubernetes cluster ID or name +``` **Options** +``` -F, --format Output format (human, json) (default: human) -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ### k8s get-kubeconfig -Description:** Get configuration of a Kubernetes cluster +**Description:** Get configuration of a Kubernetes cluster + +**Since:** 4.3.0 **Usage** +``` clever k8s get-kubeconfig [options] +``` **Arguments** +``` cluster-id|cluster-name Kubernetes cluster ID or name +``` **Options** +``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ### k8s list -Description:** List Kubernetes clusters +**Description:** List Kubernetes clusters + +**Since:** 4.3.0 **Usage** +``` clever k8s list [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ## keycloak -Description:** Manage Clever Cloud Keycloak services +**Description:** Manage Clever Cloud Keycloak services + +**Since:** 3.13.0 **Usage** +``` clever keycloak [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ### keycloak disable-ng -Description:** Unlink Keycloak from its Network Group +**Description:** Unlink Keycloak from its Network Group + +**Since:** 3.13.0 **Usage** +``` clever keycloak disable-ng +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### keycloak enable-ng -Description:** Link Keycloak to a Network Group, used for multi-instances secure communication +**Description:** Link Keycloak to a Network Group, used for multi-instances secure communication + +**Since:** 3.13.0 **Usage** +``` clever keycloak enable-ng +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### keycloak get -Description:** Get information about a deployed Keycloak +**Description:** Get information about a deployed Keycloak + +**Since:** 3.13.0 **Usage** +``` clever keycloak get [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ### keycloak open -Description:** Open the Keycloak dashboard in Clever Cloud Console +**Description:** Open the Keycloak dashboard in Clever Cloud Console + +**Since:** 3.13.0 **Usage** +``` clever keycloak open +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` #### keycloak open logs -Description:** Open the Keycloak application logs in Clever Cloud Console +**Description:** Open the Keycloak application logs in Clever Cloud Console + +**Since:** 3.13.0 **Usage** +``` clever keycloak open logs +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` #### keycloak open webui -Description:** Open the Keycloak admin console in your browser +**Description:** Open the Keycloak admin console in your browser + +**Since:** 3.13.0 **Usage** +``` clever keycloak open webui +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### keycloak rebuild -Description:** Rebuild Keycloak +**Description:** Rebuild Keycloak + +**Since:** 3.13.0 **Usage** +``` clever keycloak rebuild +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### keycloak restart -Description:** Restart Keycloak +**Description:** Restart Keycloak + +**Since:** 3.13.0 **Usage** +``` clever keycloak restart +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### keycloak version -Description:** Check Keycloak deployed version +**Description:** Check Keycloak deployed version + +**Since:** 3.13.0 **Usage** +``` clever keycloak version [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` #### keycloak version check -Description:** Check Keycloak deployed version +**Description:** Check Keycloak deployed version + +**Since:** 3.13.0 **Usage** +``` clever keycloak version check [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` #### keycloak version update -Description:** Update Keycloak deployed version +**Description:** Update Keycloak deployed version + +**Since:** 3.13.0 **Usage** +``` clever keycloak version update [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` --target Target version to upgrade to (e.g.: 24, 2.4, 2.4.1) +``` ## kv -Description:** Send a raw command to a Materia KV or Redis® add-on +**Description:** Send a raw command to a Materia KV or Redis® add-on + +**Since:** 3.11.0 **Usage** +``` clever kv [options] +``` **Arguments** +``` kv-id|addon-id|addon-name Add-on/Real ID (or name, if unambiguous) of a Materia KV or Redis® add-on command The raw command to send to the Materia KV or Redis® add-on +``` **Options** +``` -F, --format Output format (human, json) (default: human) -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ## link -Description:** Link this repo to an existing application +**Description:** Link this repo to an existing application + +**Since:** 0.2.0 **Usage** +``` clever link [options] +``` **Arguments** +``` app-id|app-name Application ID (or name, if unambiguous) +``` **Options** +``` -a, --alias Short name for the application -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ## login -Description:** Login to Clever Cloud +**Description:** Login to Clever Cloud + +**Since:** 0.2.0 **Usage** +``` clever login [options] +``` **Options** - --secret Directly give an existing secret - --token Directly give an existing token +``` +-a, --alias Profile alias (default: default) + --api-host API host URL override + --auth-bridge-host Auth bridge URL override + --console-url Console URL override + --oauth-consumer-key OAuth consumer key override + --oauth-consumer-secret OAuth consumer secret override + --secret Provide an existing secret + --ssh-gateway
SSH gateway override + --token Provide an existing token +``` ## logout -Description:** Logout from Clever Cloud +**Description:** Logout from Clever Cloud + +**Since:** 1.0.0 **Usage** -clever logout +``` +clever logout [options] +``` + +**Options** +``` +-a, --alias Alias of the profile to log out +``` ## logs -Description:** Fetch application logs, continuously +**Description:** Fetch application logs, continuously + +**Since:** 0.2.0 **Usage** +``` clever logs [options] +``` **Options** - --addon Add-on ID +``` + --addon Add-on ID or real ID --after, --since Fetch logs after this date/time (ISO8601 date, positive number in seconds or duration, e.g.: 1h) -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) @@ -1260,617 +1930,1003 @@ clever logs [options] --deployment-id Fetch logs for a given deployment -F, --format Output format (human, json, json-stream) (default: human) --search Fetch logs matching this pattern +``` ## make-default -Description:** Make a linked application the default one +**Description:** Make a linked application the default one + +**Since:** 0.5.0 **Usage** +``` clever make-default +``` **Arguments** +``` app-alias Application alias +``` ## matomo -Description:** Manage Clever Cloud Matomo services +**Description:** Manage Clever Cloud Matomo services + +**Since:** 3.13.0 **Usage** +``` clever matomo [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ### matomo get -Description:** Get information about a deployed Matomo +**Description:** Get information about a deployed Matomo + +**Since:** 3.13.0 **Usage** +``` clever matomo get [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ### matomo open -Description:** Open the Matomo dashboard in Clever Cloud Console +**Description:** Open the Matomo dashboard in Clever Cloud Console + +**Since:** 3.13.0 **Usage** +``` clever matomo open +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` #### matomo open logs -Description:** Open the Matomo application logs in Clever Cloud Console +**Description:** Open the Matomo application logs in Clever Cloud Console + +**Since:** 3.13.0 **Usage** +``` clever matomo open logs +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` #### matomo open webui -Description:** Open the Matomo admin console in your browser +**Description:** Open the Matomo admin console in your browser + +**Since:** 3.13.0 **Usage** +``` clever matomo open webui +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### matomo rebuild -Description:** Rebuild Matomo +**Description:** Rebuild Matomo + +**Since:** 3.13.0 **Usage** +``` clever matomo rebuild +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### matomo restart -Description:** Restart Matomo +**Description:** Restart Matomo + +**Since:** 3.13.0 **Usage** +``` clever matomo restart +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ## metabase -Description:** Manage Clever Cloud Metabase services +**Description:** Manage Clever Cloud Metabase services + +**Since:** 3.13.0 **Usage** +``` clever metabase [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ### metabase get -Description:** Get information about a deployed Metabase +**Description:** Get information about a deployed Metabase + +**Since:** 3.13.0 **Usage** +``` clever metabase get [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ### metabase open -Description:** Open the Metabase dashboard in Clever Cloud Console +**Description:** Open the Metabase dashboard in Clever Cloud Console + +**Since:** 3.13.0 **Usage** +``` clever metabase open +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` #### metabase open logs -Description:** Open the Metabase application logs in Clever Cloud Console +**Description:** Open the Metabase application logs in Clever Cloud Console + +**Since:** 3.13.0 **Usage** +``` clever metabase open logs +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` #### metabase open webui -Description:** Open the Metabase admin console in your browser +**Description:** Open the Metabase admin console in your browser + +**Since:** 3.13.0 **Usage** +``` clever metabase open webui +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### metabase rebuild -Description:** Rebuild Metabase +**Description:** Rebuild Metabase + +**Since:** 3.13.0 **Usage** +``` clever metabase rebuild +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### metabase restart -Description:** Restart Metabase +**Description:** Restart Metabase + +**Since:** 3.13.0 **Usage** +``` clever metabase restart +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### metabase version -Description:** Manage Metabase deployed version +**Description:** Manage Metabase deployed version + +**Since:** 3.13.0 **Usage** +``` clever metabase version [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` #### metabase version check -Description:** Check Metabase deployed version +**Description:** Check Metabase deployed version + +**Since:** 3.13.0 **Usage** +``` clever metabase version check [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` #### metabase version update -Description:** Update Metabase deployed version +**Description:** Update Metabase deployed version + +**Since:** 3.13.0 **Usage** +``` clever metabase version update [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` --target Target version to upgrade to (e.g.: 24, 2.4, 2.4.1) +``` ## ng -Description:** List Network Groups +**Description:** List Network Groups + +**Since:** 3.12.0 **Usage** +``` clever ng [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ### ng create -Description:** Create a Network Group +**Description:** Create a Network Group + +**Since:** 3.12.0 **Usage** +``` clever ng create [options] +``` **Arguments** +``` ng-label Network Group label +``` **Options** +``` --description Network Group description --link Comma separated list of members IDs to link to a Network Group (app_xxx, external_xxx, mysql_xxx, postgresql_xxx, redis_xxx, etc.) -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) --tags List of tags, separated by a comma +``` #### ng create external -Description:** Create an external peer in a Network Group +**Description:** Create an external peer in a Network Group + +**Since:** 3.12.0 **Usage** +``` clever ng create external [options] +``` **Arguments** +``` external-peer-label External peer label ng-id|ng-label Network Group ID or label public-key WireGuard public key of the external peer to link to a Network Group +``` **Options** +``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ### ng delete -Description:** Delete a Network Group +**Description:** Delete a Network Group + +**Since:** 3.12.0 **Usage** +``` clever ng delete [options] +``` **Arguments** +``` ng-id|ng-label Network Group ID or label +``` **Options** +``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` #### ng delete external -Description:** Delete an external peer from a Network Group +**Description:** Delete an external peer from a Network Group + +**Since:** 3.12.0 **Usage** +``` clever ng delete external [options] +``` **Arguments** +``` peer-id|peer-label External peer ID or label ng-id|ng-label Network Group ID or label +``` **Options** +``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ### ng get -Description:** Get details about a Network Group, a member or a peer +**Description:** Get details about a Network Group, a member or a peer + +**Since:** 3.12.0 **Usage** +``` clever ng get [options] +``` **Arguments** +``` id|label ID or Label of a Network Group, a member or an (external) peer +``` **Options** +``` -F, --format Output format (human, json) (default: human) -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) --type Type of resource to look for (NetworkGroup, Member, CleverPeer, ExternalPeer) +``` ### ng get-config -Description:** Get the WireGuard configuration of a peer in a Network Group +**Description:** Get the WireGuard configuration of a peer in a Network Group + +**Since:** 3.12.0 **Usage** +``` clever ng get-config [options] +``` **Arguments** +``` peer-id|peer-label External peer ID or label ng-id|ng-label Network Group ID or label +``` **Options** +``` -F, --format Output format (human, json) (default: human) -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ### ng link -Description:** Link a resource by its ID (app_xxx, external_xxx, mysql_xxx, postgresql_xxx, redis_xxx, etc.) to a Network Group +**Description:** Link a resource by its ID (app_xxx, external_xxx, mysql_xxx, postgresql_xxx, redis_xxx, etc.) to a Network Group + +**Since:** 3.12.0 **Usage** +``` clever ng link [options] +``` **Arguments** +``` id ID of a resource to (un)link to a Network Group ng-id|ng-label Network Group ID or label +``` **Options** +``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ### ng search -Description:** Search Network Groups, members or peers and get their details +**Description:** Search Network Groups, members or peers and get their details + +**Since:** 3.12.0 **Usage** +``` clever ng search [options] +``` **Arguments** +``` id|label ID or Label of a Network Group, a member or an (external) peer +``` **Options** +``` -F, --format Output format (human, json) (default: human) -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) --type Type of resource to look for (NetworkGroup, Member, CleverPeer, ExternalPeer) +``` ### ng unlink -Description:** Unlink a resource by its ID (app_xxx, external_xxx, mysql_xxx, postgresql_xxx, redis_xxx, etc.) from a Network Group +**Description:** Unlink a resource by its ID (app_xxx, external_xxx, mysql_xxx, postgresql_xxx, redis_xxx, etc.) from a Network Group + +**Since:** 3.12.0 **Usage** +``` clever ng unlink [options] +``` **Arguments** +``` id ID of a resource to (un)link to a Network Group ng-id|ng-label Network Group ID or label +``` **Options** +``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ## notify-email -Description:** Manage email notifications +**Description:** Manage email notifications + +**Since:** 0.6.1 **Usage** +``` clever notify-email [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) --list-all List all notifications for your user or for an organisation with the '--org' option -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ### notify-email add -Description:** Add a new email notification +**Description:** Add a new email notification + +**Since:** 0.6.1 **Usage** +``` clever notify-email add --notify [options] +``` **Arguments** +``` name Notification name +``` **Options** +``` --notify Notify a user, a specific email address or the whole organisation (multiple values allowed, comma separated) (required) --event Restrict notifications to specific event types -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) - --service Restrict notifications to specific applications and add-ons + --service Restrict notifications to specific applications and add-ons (requires --org) +``` ### notify-email remove -Description:** Remove an existing email notification +**Description:** Remove an existing email notification + +**Since:** 0.6.1 **Usage** +``` clever notify-email remove [options] +``` **Arguments** +``` notification-id Notification ID +``` **Options** +``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ## open -Description:** Open an application in the Console +**Description:** Open an application in the Console + +**Since:** 0.5.0 **Usage** +``` clever open [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ## otoroshi -Description:** Manage Clever Cloud Otoroshi services +**Description:** Manage Clever Cloud Otoroshi services + +**Since:** 3.13.0 **Usage** +``` clever otoroshi [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ### otoroshi disable-ng -Description:** Unlink Otoroshi from its Network Group +**Description:** Unlink Otoroshi from its Network Group + +**Since:** 3.13.0 **Usage** +``` clever otoroshi disable-ng +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### otoroshi enable-ng -Description:** Link Otoroshi to a Network Group +**Description:** Link Otoroshi to a Network Group + +**Since:** 3.13.0 **Usage** +``` clever otoroshi enable-ng +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### otoroshi get -Description:** Get information about a deployed Otoroshi +**Description:** Get information about a deployed Otoroshi + +**Since:** 3.13.0 **Usage** +``` clever otoroshi get [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ### otoroshi get-config -Description:** Get configuration of a deployed Otoroshi in otoroshictl format +**Description:** Get configuration of a deployed Otoroshi in otoroshictl format + +**Since:** 4.4.0 **Usage** +``` clever otoroshi get-config +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### otoroshi open -Description:** Open the Otoroshi dashboard in Clever Cloud Console +**Description:** Open the Otoroshi dashboard in Clever Cloud Console + +**Since:** 3.13.0 **Usage** +``` clever otoroshi open +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` #### otoroshi open logs -Description:** Open the Otoroshi application logs in Clever Cloud Console +**Description:** Open the Otoroshi application logs in Clever Cloud Console + +**Since:** 3.13.0 **Usage** +``` clever otoroshi open logs +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` #### otoroshi open webui -Description:** Open the Otoroshi admin console in your browser +**Description:** Open the Otoroshi admin console in your browser + +**Since:** 3.13.0 **Usage** +``` clever otoroshi open webui +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### otoroshi rebuild -Description:** Rebuild Otoroshi +**Description:** Rebuild Otoroshi + +**Since:** 3.13.0 **Usage** +``` clever otoroshi rebuild +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### otoroshi restart -Description:** Restart Otoroshi +**Description:** Restart Otoroshi + +**Since:** 3.13.0 **Usage** +``` clever otoroshi restart +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` ### otoroshi version -Description:** Manage Otoroshi deployed version +**Description:** Manage Otoroshi deployed version + +**Since:** 3.13.0 **Usage** +``` clever otoroshi version [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` #### otoroshi version check -Description:** Check Otoroshi deployed version +**Description:** Check Otoroshi deployed version + +**Since:** 3.13.0 **Usage** +``` clever otoroshi version check [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` #### otoroshi version update -Description:** Update Otoroshi deployed version +**Description:** Update Otoroshi deployed version + +**Since:** 3.13.0 **Usage** +``` clever otoroshi version update [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` --target Target version to upgrade to (e.g.: 24, 2.4, 2.4.1) +``` ## profile -Description:** Display the profile of the current user +**Description:** Display the profile of the current user + +**Since:** 0.10.1 **Usage** +``` clever profile [options] +``` + +**Options** +``` +-F, --format Output format (human, json) (default: human) +``` + +### profile list + +**Description:** List all configured profiles + +**Since:** 4.6.0 + +**Usage** +``` +clever profile list [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ### profile open -Description:** Open your profile in the Console +**Description:** Open your profile in the Console + +**Since:** 3.11.0 **Usage** +``` clever profile open +``` + +### profile switch + +**Description:** Switch to a different profile + +**Since:** 4.6.0 + +**Usage** +``` +clever profile switch [options] +``` + +**Options** +``` +-a, --alias Alias of the profile to switch to +``` ## published-config -Description:** Manage the configuration made available to other applications by this application +**Description:** Manage the configuration made available to other applications by this application + +**Since:** 0.5.0 **Usage** +``` clever published-config [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) -F, --format Output format (human, json, shell) (default: human) +``` ### published-config import -Description:** Load published configuration from STDIN +**Description:** Load published configuration from STDIN (WARNING: this deletes all current variables and replace them with the new list loaded from STDIN) +**Since:** 0.5.0 + **Usage** +``` clever published-config import [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) --json Import variables as JSON (an array of { "name": "THE_NAME", "value": "THE_VALUE" } objects) +``` ### published-config rm -Description:** Remove a published configuration variable from an application +**Description:** Remove a published configuration variable from an application + +**Since:** 0.5.0 **Usage** +``` clever published-config rm [options] +``` **Arguments** +``` variable-name Name of the environment variable +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ### published-config set -Description:** Add or update a published configuration item named with the value +**Description:** Add or update a published configuration item named with the value + +**Since:** 0.5.0 **Usage** +``` clever published-config set [options] +``` **Arguments** +``` variable-name Name of the environment variable variable-value Value of the environment variable +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ## restart -Description:** Start or restart an application +**Description:** Start or restart an application + +**Since:** 0.4.0 **Usage** +``` clever restart [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) --commit Restart the application with a specific commit ID @@ -1878,15 +2934,21 @@ clever restart [options] --follow Continue to follow logs after deployment has ended (deprecated, use `--exit-on never` instead) -q, --quiet Don't show logs during deployment --without-cache Restart the application without using cache +``` ## scale -Description:** Change scalability of an application +**Description:** Change scalability of an application + +**Since:** 0.4.0 **Usage** +``` clever scale [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) --build-flavor The size of the build instance, or 'disabled' if you want to disable dedicated build instances @@ -1896,304 +2958,467 @@ clever scale [options] --max-instances The maximum number of parallel instances --min-flavor The minimum scale size of your application --min-instances The minimum number of parallel instances +``` ## service -Description:** Manage service dependencies +**Description:** Manage service dependencies + +**Since:** 0.5.0 **Usage** +``` clever service [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) -F, --format Output format (human, json) (default: human) --only-addons Only show add-on dependencies --only-apps Only show app dependencies --show-all Show all available add-ons and applications +``` ### service link-addon -Description:** Link an existing add-on to this application +**Description:** Link an existing add-on to this application + +**Since:** 0.5.0 **Usage** +``` clever service link-addon [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ### service link-app -Description:** Add an existing app as a dependency +**Description:** Add an existing app as a dependency + +**Since:** 0.5.0 **Usage** +``` clever service link-app [options] +``` **Arguments** +``` app-id|app-name Application ID (or name, if unambiguous) +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ### service unlink-addon -Description:** Unlink an add-on from this application +**Description:** Unlink an add-on from this application + +**Since:** 0.5.0 **Usage** +``` clever service unlink-addon [options] +``` **Arguments** +``` addon-id|addon-name Add-on ID (or name, if unambiguous) +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ### service unlink-app -Description:** Remove an app from the dependencies +**Description:** Remove an app from the dependencies + +**Since:** 0.5.0 **Usage** +``` clever service unlink-app [options] +``` **Arguments** +``` app-id|app-name Application ID (or name, if unambiguous) +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ## ssh -Description:** Connect to running instances through SSH +**Description:** Connect to running instances through SSH + +**Since:** 0.7.0 **Usage** +``` clever ssh [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) -i, --identity-file SSH identity file +``` ## ssh-keys -Description:** Manage SSH keys of the current user +**Description:** Manage SSH keys of the current user + +**Since:** 3.13.0 **Usage** +``` clever ssh-keys [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ### ssh-keys add -Description:** Add a new SSH key to the current user +**Description:** Add a new SSH key to the current user + +**Since:** 3.13.0 **Usage** +``` clever ssh-keys add +``` **Arguments** +``` ssh-key-name SSH key name ssh-key-path SSH public key path (.pub) +``` ### ssh-keys open -Description:** Open the SSH keys management page in the Console +**Description:** Open the SSH keys management page in the Console + +**Since:** 3.13.0 **Usage** +``` clever ssh-keys open +``` ### ssh-keys remove -Description:** Remove a SSH key from the current user +**Description:** Remove a SSH key from the current user + +**Since:** 3.13.0 **Usage** +``` clever ssh-keys remove +``` **Arguments** +``` ssh-key-name SSH key name +``` ### ssh-keys remove-all -Description:** Remove all SSH keys from the current user +**Description:** Remove all SSH keys from the current user + +**Since:** 3.13.0 **Usage** +``` clever ssh-keys remove-all [options] +``` **Options** +``` -y, --yes Skip confirmation and remove all SSH keys directly +``` ## status -Description:** See the status of an application +**Description:** See the status of an application + +**Since:** 0.2.0 **Usage** +``` clever status [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) -F, --format Output format (human, json) (default: human) +``` ## stop -Description:** Stop a running application +**Description:** Stop a running application + +**Since:** 0.2.0 **Usage** +``` clever stop [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ## tcp-redirs -Description:** Control the TCP redirections from reverse proxies to your application +**Description:** Control the TCP redirections from reverse proxies to your application + +**Since:** 2.3.0 **Usage** +``` clever tcp-redirs [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) -F, --format Output format (human, json) (default: human) +``` ### tcp-redirs add -Description:** Add a new TCP redirection to the application +**Description:** Add a new TCP redirection to the application + +**Since:** 2.3.0 **Usage** +``` clever tcp-redirs add --namespace [options] +``` **Options** +``` --namespace Namespace in which the TCP redirection should be (required) -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ### tcp-redirs list-namespaces -Description:** List the namespaces in which you can create new TCP redirections +**Description:** List the namespaces in which you can create new TCP redirections + +**Since:** 2.3.0 **Usage** +``` clever tcp-redirs list-namespaces [options] +``` **Options** +``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) -F, --format Output format (human, json) (default: human) +``` ### tcp-redirs remove -Description:** Remove a TCP redirection from the application +**Description:** Remove a TCP redirection from the application + +**Since:** 2.3.0 **Usage** +``` clever tcp-redirs remove --namespace [options] +``` **Arguments** +``` port port identifying the TCP redirection +``` **Options** +``` --namespace Namespace in which the TCP redirection should be (required) -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +``` ## tokens -Description:** Manage API tokens to query Clever Cloud API from https://api-bridge.clever-cloud.com +**Description:** Manage API tokens to query Clever Cloud API from https://api-bridge.clever-cloud.com + +**Since:** 3.12.0 **Usage** +``` clever tokens [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) +``` ### tokens create -Description:** Create an API token +**Description:** Create an API token + +**Since:** 3.12.0 **Usage** +``` clever tokens create [options] +``` **Arguments** +``` api-token-name API token name +``` **Options** +``` -e, --expiration Duration until API token expiration (e.g.: 1h, 4d, 2w, 6M) (default: 1y) -F, --format Output format (human, json) (default: human) +``` ### tokens revoke -Description:** Revoke an API token +**Description:** Revoke an API token + +**Since:** 3.12.0 **Usage** +``` clever tokens revoke +``` **Arguments** +``` api-token-id API token ID +``` ## unlink -Description:** Unlink this repo from an existing application +**Description:** Unlink this repo from an existing application + +**Since:** 0.2.0 **Usage** +``` clever unlink +``` **Arguments** +``` app-alias Application alias +``` ## version -Description:** Display the clever-tools version +**Description:** Display the clever-tools version + +**Since:** 1.0.0 **Usage** +``` clever version +``` ## webhooks -Description:** Manage webhooks +**Description:** Manage webhooks + +**Since:** 0.6.0 **Usage** +``` clever webhooks [options] +``` **Options** +``` -F, --format Output format (human, json) (default: human) --list-all List all notifications for your user or for an organisation with the '--org' option -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ### webhooks add -Description:** Register webhook to be called when events happen +**Description:** Register webhook to be called when events happen + +**Since:** 0.6.0 **Usage** +``` clever webhooks add [options] +``` **Arguments** +``` name Notification name url Webhook URL +``` **Options** +``` --event Restrict notifications to specific event types --format Format of the body sent to the webhook ('raw', 'slack', 'gitter', or 'flowdock') (default: raw) -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) - --service Restrict notifications to specific applications and add-ons + --service Restrict notifications to specific applications and add-ons (requires --org) +``` ### webhooks remove -Description:** Remove an existing webhook +**Description:** Remove an existing webhook + +**Since:** 0.6.0 **Usage** +``` clever webhooks remove [options] +``` **Arguments** +``` notification-id Notification ID +``` **Options** +``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` ## Clever Cloud complete documentation diff --git a/update-cli-reference.sh b/update-cli-reference.sh index 6e3a89f7e..1f7d1446b 100755 --- a/update-cli-reference.sh +++ b/update-cli-reference.sh @@ -1,7 +1,7 @@ #!/bin/bash FILE="content/doc/reference/cli.md" -URL="https://raw.githubusercontent.com/CleverCloud/clever-tools/refs/heads/master/docs/llms-documentation.md" +URL="https://raw.githubusercontent.com/CleverCloud/clever-tools/refs/heads/master/skills/clever-tools/references/full-documentation.md" front_matter="""--- type: docs From a707ba60ad1d47e32498658f1fd753b6e50368ee Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 18 Feb 2026 17:28:49 +0100 Subject: [PATCH 035/180] cli: fixes from reference update --- content/doc/cli/_index.md | 3 ++ content/doc/cli/addons.md | 44 +++++++++++++++++++ content/doc/cli/applications/_index.md | 2 +- content/doc/cli/applications/configuration.md | 17 +++++-- .../cli/applications/deployment-lifecycle.md | 2 - content/doc/cli/kubernetes.md | 2 +- content/doc/cli/network-groups.md | 27 ++++++++++-- content/doc/cli/operators.md | 4 +- content/doc/cli/profiles.md | 4 +- content/doc/cli/services-depedencies.md | 2 +- 10 files changed, 92 insertions(+), 15 deletions(-) diff --git a/content/doc/cli/_index.md b/content/doc/cli/_index.md index 44fd9168b..42c8deb91 100644 --- a/content/doc/cli/_index.md +++ b/content/doc/cli/_index.md @@ -142,6 +142,7 @@ To log out, delete this file or use: ``` clever logout +clever logout --alias ALIAS ``` ## profile @@ -162,6 +163,7 @@ To list primary email and secondary emails associated with your Clever Cloud acc ``` clever emails +clever emails -F json ``` To open the email management page in your browser, use: @@ -196,6 +198,7 @@ To list public SSH keys associated with your Clever Cloud account, you can use: ``` clever ssh-keys +clever ssh-keys -F json ``` To open the public SSH keys management page in your browser, use: diff --git a/content/doc/cli/addons.md b/content/doc/cli/addons.md index 648a30ed4..374b3807d 100644 --- a/content/doc/cli/addons.md +++ b/content/doc/cli/addons.md @@ -19,6 +19,16 @@ Add-ons on Clever Cloud are databases, storage services, tools or third party se [--org, -o, --owner] Organisation ID (or name, if unambiguous) ``` +## list + +To list provisioned add-ons, use: + +``` +clever addon +clever addon list +clever addon list --format json +``` + ## providers To use add-ons, you need to identify the corresponding provider. To get information about them (plans, regions, versions), use: @@ -90,6 +100,40 @@ redis-cli -h $KV_HOST -p $KV_PORT --tls > clever addon env ADDON_ID --format shell | source > ``` +## config-provider + +Configuration Providers are add-ons that store environment variables you can share across multiple applications through [service dependencies](/doc/cli/services-depedencies/). They have their own dedicated commands: + +``` +clever config-provider list +clever config-provider list --format json +``` + +To get, set or remove environment variables of a Configuration Provider, use: + +``` +clever config-provider get CONFIG_PROVIDER_ID_OR_NAME +clever config-provider get CONFIG_PROVIDER_ID_OR_NAME --format shell +clever config-provider set CONFIG_PROVIDER_ID_OR_NAME VARIABLE_NAME VARIABLE_VALUE +clever config-provider rm CONFIG_PROVIDER_ID_OR_NAME VARIABLE_NAME +``` + +You can import environment variables from `stdin` (this replaces all existing variables): + +``` +clever config-provider import CONFIG_PROVIDER_ID_OR_NAME < .env +clever config-provider import CONFIG_PROVIDER_ID_OR_NAME --format json < config.json +``` + +To open the Configuration Provider in Clever Cloud Console, use: + +``` +clever config-provider open CONFIG_PROVIDER_ID_OR_NAME +``` + +> [!TIP] +> You can target a Configuration Provider by its add-on ID, real ID (`config_xxx`) or name. + ## database backups Databases are backup every day, with last 7 days of backups available to download. You can list them, available formats are: `human` (default) or `json`: diff --git a/content/doc/cli/applications/_index.md b/content/doc/cli/applications/_index.md index f7d310adf..6edd4b044 100644 --- a/content/doc/cli/applications/_index.md +++ b/content/doc/cli/applications/_index.md @@ -20,7 +20,7 @@ aliases: ## create -You can create a new application on Clever Cloud, linked to your local folder. Only its `type` is required, it should be one of: `docker`, `elixir`, `frankenphp`, `go`, `gradle`, `haskell`, `jar`, `linux`, `maven`, `meteor`, `node`, `php`, `play1`, `play2`, `python`, `ruby`, `rust`, `sbt`, `static`, `static-apache`, `v` or `war`. Result can be printed in `human` or `json` format. +You can create a new application on Clever Cloud, linked to your local folder. Only its `type` is required, it should be one of: `docker`, `dotnet`, `elixir`, `frankenphp`, `go`, `gradle`, `haskell`, `jar`, `linux`, `maven`, `meteor`, `node`, `php`, `play1`, `play2`, `python`, `ruby`, `rust`, `sbt`, `static`, `static-apache`, `v` or `war`. Result can be printed in `human` or `json` format. ``` clever create -t TYPE APP_NAME diff --git a/content/doc/cli/applications/configuration.md b/content/doc/cli/applications/configuration.md index 821cfecde..5a77adbd5 100644 --- a/content/doc/cli/applications/configuration.md +++ b/content/doc/cli/applications/configuration.md @@ -77,12 +77,15 @@ clever env clever env > .env ``` -You can also export environment variable in a sourceable format (`export ENV_NAME="VALUE";`): +You can also export environment variables in a sourceable format (`export ENV_NAME="VALUE";`): ``` -clever env --add-export +clever env --format shell ``` +> [!NOTE] +> The `--add-export` option is deprecated. Use `--format shell` instead. + ## domain By default, a Clever Cloud application gets `app_id.cleverapps.io` as fully qualified domain name ([FQDN](https://fr.wikipedia.org/wiki/Fully_qualified_domain_name)). To see it, use: @@ -116,7 +119,7 @@ To (un)set [the favourite domain](/doc/administrate/domain-names/#primary-favour ``` clever domain favourite set FQDN -clever domain favourite unset FQDN +clever domain favourite unset ``` To check if the domains of an application are properly configured, use: @@ -165,11 +168,17 @@ clever tcp-redirs add --namespace NAMESPACE clever tcp-redirs remove --namespace NAMESPACE PORT ``` -To list enabled TCP redirection, use: +To list enabled TCP redirections, use: ``` clever tcp-redirs clever tcp-redirs --format json ``` +To list available namespaces, use: + +``` +clever tcp-redirs list-namespaces +``` + - [Learn more about TCP redirections](/doc/administrate/tcp-redirections/) diff --git a/content/doc/cli/applications/deployment-lifecycle.md b/content/doc/cli/applications/deployment-lifecycle.md index 6aa23fcf2..998a5c2c7 100644 --- a/content/doc/cli/applications/deployment-lifecycle.md +++ b/content/doc/cli/applications/deployment-lifecycle.md @@ -39,7 +39,6 @@ It will `git push` your code on the remote repository of your application on Cle [--tag, -t] TAG Tag to push (none by default) (default: ) [--quiet, -q] Don't show logs during deployment (default: false) [--force, -f] Force deploy even if it's not fast-forwardable (default: false) -[--follow] Continue to follow logs after deployment has ended (default: false) [--same-commit-policy, -p] POLICY What to do when local and remote commit are identical (error, ignore, restart, rebuild) (default: error) [--exit-on, -e] STEP Step at which the logs streaming is ended, steps are: deploy-start, deploy-end, never (default: deploy-end) ``` @@ -79,7 +78,6 @@ By default, it will use its build cache when available. But you can override it [--commit] COMMIT ID Restart the application with a specific commit ID [--without-cache] Restart the application without using cache (default: false) [--quiet, -q] Don't show logs during deployment (default: false) -[--follow] Continue to follow logs after deployment has ended (default: false) [--exit-on, -e] STEP Step at which the logs streaming is ended, steps are: deploy-start, deploy-end, never (default: deploy-end) ``` diff --git a/content/doc/cli/kubernetes.md b/content/doc/cli/kubernetes.md index ece2d44ba..3d5cb6175 100644 --- a/content/doc/cli/kubernetes.md +++ b/content/doc/cli/kubernetes.md @@ -41,7 +41,7 @@ In all the following examples, you can target a specific organisation with the ` To create a Kubernetes cluster, you just need a name and you can wait for it to be in `ACTIVE` state: ``` clever k8s create myKubeCluster -clever k8s delete myKubeCluster --watch +clever k8s create myKubeCluster --watch ``` To delete a cluster, use: diff --git a/content/doc/cli/network-groups.md b/content/doc/cli/network-groups.md index 3ba769922..ce8a43de2 100644 --- a/content/doc/cli/network-groups.md +++ b/content/doc/cli/network-groups.md @@ -111,9 +111,23 @@ clever ng unlink redis_xxx ngIdorLabel After an unlink, you may need to restart the application to apply the changes. > [!TIP] -> To link add-ons to a Network Group, use real IDs (`mysql_xxx`, `postgresql_xxx`, `redis_`, etc.). \ +> To link add-ons to a Network Group, use real IDs (`mysql_xxx`, `postgresql_xxx`, `redis_xxx`, etc.). \ > Only add-ons deployed as of 2024 support Network Groups. If you can't access your add-on, migrate or restart it. +## Manage external peers + +To create an external peer in a Network Group, you need to provide a label, Network Group and WireGuard public key: + +``` +clever ng create external myExternalPeer myNG wg_public_key +``` + +To delete an external peer from a Network Group: + +``` +clever ng delete external peerIdOrLabel myNG +``` + ## Get information of a Network Group, a member or a peer To get information about a Network Group or a resource (a `json` formatted output is available): @@ -123,15 +137,22 @@ clever ng get ngIdOrLabel -F json clever ng get resourceIdOrName ``` +You can specify the type of resource to look for with the `--type` option (`NetworkGroup`, `Member`, `CleverPeer`, `ExternalPeer`): + +``` +clever ng get resourceIdOrName --type ExternalPeer +``` + You can also search for Network Groups, members or peers: ``` clever ng search text_to_search -F json +clever ng search text_to_search --type Member ``` > [!NOTE] -> The search command is case-insensitive and will return all resources containing the search string -> The get command look for an exact match and will return an error if multiple resources are found +> The search command is case-insensitive and will return all resources containing the search string. +> The get command looks for an exact match and will return an error if multiple resources are found. ## Get the WireGuard configuration of a Peer diff --git a/content/doc/cli/operators.md b/content/doc/cli/operators.md index 22c7c807d..7a0f739f6 100644 --- a/content/doc/cli/operators.md +++ b/content/doc/cli/operators.md @@ -71,10 +71,10 @@ clever metabase version check myMetabase --format json To update to a specific version, use: ``` -clever keycloak version update myKeycloak 24.0.1 +clever keycloak version update myKeycloak --target 24.0.1 ``` -To see a list of available versions, don't provide a version number: +To see a list of available versions, don't provide a target version: ``` clever otoroshi version update otoroshi_id diff --git a/content/doc/cli/profiles.md b/content/doc/cli/profiles.md index a0a89e7d6..549b22f64 100644 --- a/content/doc/cli/profiles.md +++ b/content/doc/cli/profiles.md @@ -55,7 +55,9 @@ clever login --alias staging \ --api-host https://api.clever-cloud.com \ --console-url https://console.clever-cloud.com \ --auth-bridge-host https://api-bridge.clever-cloud.com \ - --ssh-gateway ssh@sshgateway-clevercloud-customers.services.clever-cloud.com + --ssh-gateway ssh@sshgateway-clevercloud-customers.services.clever-cloud.com \ + --oauth-consumer-key MY_KEY \ + --oauth-consumer-secret MY_SECRET ``` Resolution order for configuration values: diff --git a/content/doc/cli/services-depedencies.md b/content/doc/cli/services-depedencies.md index 2a1c231d4..5cc783455 100644 --- a/content/doc/cli/services-depedencies.md +++ b/content/doc/cli/services-depedencies.md @@ -22,7 +22,7 @@ To list exposed configuration, use: ``` clever published-config -clever published-config --F json +clever published-config -F json clever published-config --format shell ``` From 738fd971688e757d2a3a915088662900a2112359 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Thu, 19 Feb 2026 12:17:44 +0100 Subject: [PATCH 036/180] addons(matomo): add custom domain instructions --- .../changelog/2025/12-12-matomo-set-domain.md | 27 +++++++++++++++++++ content/doc/addons/matomo.md | 8 ++++++ 2 files changed, 35 insertions(+) create mode 100644 content/changelog/2025/12-12-matomo-set-domain.md diff --git a/content/changelog/2025/12-12-matomo-set-domain.md b/content/changelog/2025/12-12-matomo-set-domain.md new file mode 100644 index 000000000..16cce962b --- /dev/null +++ b/content/changelog/2025/12-12-matomo-set-domain.md @@ -0,0 +1,27 @@ +--- +title: Set Matomo domain at creation +description: You can now set a custom domain for Matomo web analytics interface when creating the add-on on Clever Cloud +date: 2025-12-12 +tags: + - addons + - matomo +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +When you deploy a Matomo add-on on Clever Cloud, you can access its web interface through a `-matomo.services.clever-cloud.com` domain. You can now set a custom domain at creation through the `access-domain` option in Clever Tools: + +```bash +clever addon create addon-matomo yourMatomoNameOrId --option access-domain=matomo.example.com +``` + +This domain DNS configuration needs to point to Clever Cloud's servers. For example, if the Matomo add-on is deployed in the `par` (Paris) region, you need to create a CNAME record pointing to `domain.par.clever-cloud.com.`. + +- [Learn more about Matomo on Clever Cloud](/doc/addons/matomo/) +- [Learn more about DNS and custom domains on Clever Cloud](/doc/administrate/domain-names/) diff --git a/content/doc/addons/matomo.md b/content/doc/addons/matomo.md index 27fc6bb7b..32b7a6449 100644 --- a/content/doc/addons/matomo.md +++ b/content/doc/addons/matomo.md @@ -66,6 +66,14 @@ Your Matomo is starting: - Manage it: https://console.clever-cloud.com/addon_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx ``` +By default we use Clever Cloud's domain names for the web interface, but you can set custom domains at creation through the `access-domain` option: + +```bash +clever addon create addon-matomo myMatomoName --option access-domain=matomo.example.com +``` + +These domains' DNS configuration needs to point to Clever Cloud's servers. For example, if the Matomo add-on is deployed in the `par` (Paris) region, you need to create CNAME records pointing to `domain.par.clever-cloud.com.`. + Refer to the [Clever Tools documentation](/doc/cli/addons/) for more details on add-on management. ## Accessing the Matomo interface From 6be84cc55c04ca940e22f2f35e437c0dc8f81cee Mon Sep 17 00:00:00 2001 From: David Legrand Date: Sat, 21 Feb 2026 21:38:54 +0100 Subject: [PATCH 037/180] chore: Hextra v0.12 --- go.mod | 4 ++-- go.sum | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 36623a22c..8f15f0d78 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/CleverCloud/documentation -go 1.25.0 +go 1.26 -require github.com/imfing/hextra v0.11.1 // indirect +require github.com/imfing/hextra v0.12.0 // indirect diff --git a/go.sum b/go.sum index efb638f70..478369065 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ github.com/imfing/hextra v0.11.1 h1:8pTc4ReYbzGTHAnyiebmlT3ijFfIXiGu1r7tM/UGjFI= github.com/imfing/hextra v0.11.1/go.mod h1:cEfel3lU/bSx7lTE/+uuR4GJaphyOyiwNR3PTqFTXpI= +github.com/imfing/hextra v0.12.0 h1:f6y35hW/WDJEcx9S0dOmbICOBxYE0PmP6IJFsTUgVyY= +github.com/imfing/hextra v0.12.0/go.mod h1:YAv8XRNSmcqjieFwI7fVQK1AoY2Do+45DO9HGqxSGu4= From 21e39936fd014524f7153b236e265074177f741f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:53:16 +0000 Subject: [PATCH 038/180] docs: add request-flow shared content to meteor.md and static-apache.md Co-authored-by: davlgd <1110600+davlgd@users.noreply.github.com> --- content/doc/applications/meteor.md | 2 ++ content/doc/applications/static-apache.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/content/doc/applications/meteor.md b/content/doc/applications/meteor.md index 97033aa24..ac056429e 100644 --- a/content/doc/applications/meteor.md +++ b/content/doc/applications/meteor.md @@ -105,3 +105,5 @@ If you want to migrate from your classic node.js app to a meteor application, co {{% content "more-config" %}} {{% content "url_healthcheck" %}} + +{{% content "request-flow" %}} diff --git a/content/doc/applications/static-apache.md b/content/doc/applications/static-apache.md index 86875f92f..f327d8ede 100644 --- a/content/doc/applications/static-apache.md +++ b/content/doc/applications/static-apache.md @@ -101,3 +101,5 @@ If you don't set the [`CC_WEBROOT`](/doc/reference/reference-environment-variabl ## 🎓 Static Site Generators (SSG) guides {{% content-raw "static-guides" %}} + +{{% content "request-flow" %}} From 0cbe64958bd01e6f8b1087ec41c8ef84661d2d49 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 25 Feb 2026 15:23:55 +0100 Subject: [PATCH 039/180] changelog: images updates, 2026W9 Co-Authored-By: Copilot <175728472+Copilot@users.noreply.github.com> --- content/changelog/2026/02-25-images-update.md | 45 +++++++++++++++++++ content/doc/applications/frankenphp.md | 7 ++- content/doc/applications/python/_index.md | 3 +- content/doc/applications/ruby.md | 2 + content/doc/develop/request-flow.md | 19 -------- shared/redirectionio.md | 6 +-- shared/varnish.md | 3 -- 7 files changed, 56 insertions(+), 29 deletions(-) create mode 100644 content/changelog/2026/02-25-images-update.md delete mode 100644 shared/varnish.md diff --git a/content/changelog/2026/02-25-images-update.md b/content/changelog/2026/02-25-images-update.md new file mode 100644 index 000000000..5659d6cad --- /dev/null +++ b/content/changelog/2026/02-25-images-update.md @@ -0,0 +1,45 @@ +--- +title: "Images update: FrankenPHP 1.11.3 (PHP 8.5), Request Flow in all runtimes" +description: Request Flow expansion is now done. You can use PHP 8.4 or 8.5 in FrankenPHP +date: 2026-02-25 +tags: + - images + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated all our images. Deployment is in progress for all our users. + +* **Common:** + * Clever Tools 4.6.1 + * md4c 0.5.2 + * Redis 8.6.1 + * Mise 2026.2.19 + * Otoroshictl 0.0.17 +* **FrankenPHP:** + * Update to 1.11.3 (with `CC_PHP_VERSION=8.5`) +* **Python:** + * uv 0.10.4 +* **Static:** + * Caddy 2.11.1 + * Static Web Server 2.41.0 + +## PHP Version in FrankenPHP + +Customers asked for PHP version choice in our FrankenPHP runtime. PHP 8.4 is still the default, but you can now select PHP 8.5 by setting `CC_PHP_VERSION=8.5` in your environment variables. It will use the latest FrankenPHP binary release (currently `1.11.3`). PHP 8.5 will be the default in the coming months. + +We'll only support non end-of-life (EOL) versions of PHP in FrankenPHP, so regularly check [PHP's supported versions](https://www.php.net/supported-versions.php). + +## Request Flow extension + +Request Flow is now available in all our runtimes, including Python without uv and Ruby. + +- [Learn more about Request Flow](/doc/develop/request-flow/) + +## Fixes + +This release fixes issues with logs in some Java applications. diff --git a/content/doc/applications/frankenphp.md b/content/doc/applications/frankenphp.md index ed3f704d6..fa5087f01 100644 --- a/content/doc/applications/frankenphp.md +++ b/content/doc/applications/frankenphp.md @@ -49,7 +49,10 @@ FrankenPHP runtime only requires a working web application, with an `index.php` FrankenPHP currently deployed version on Clever Cloud is `{{< runtime_version frankenphp >}}` based on PHP `{{< runtime_version frankenphp php >}}` and Caddy server `{{< runtime_version frankenphp caddy >}}`. Virtual machine image includes multiple tools from the PHP ecosystem such as Composer or Symfony CLI. The `php` command available in hooks and scripts uses `frankenphp php-cli` under the hood. -- [FrankenPHP PHP info](https://frankenphpinfo.cleverapps.io/) +You can use FrankenPHP 1.11.3 with PHP 8.5 and Caddy 2.11.1, by setting the `CC_PHP_VERSION` environment variable to `8.5`. + +- [FrankenPHP PHP 8.4 info](https://frankenphpinfo-8.4.cleverapps.io/) +- [FrankenPHP PHP 8.5 info](https://frankenphpinfo-8.5.cleverapps.io/) ### Composer native support @@ -114,5 +117,7 @@ clever deploy # or clever restart if there is no code change FrankenPHP on Clever Cloud comes with a set included PHP extensions: `amqp`,`apcu`,`ast`,`bcmath`,`brotli`,`bz2`,`calendar`,`ctype`,`curl`,`dba`,`dom`,`exif`,`fileinfo`,`filter`,`ftp`,`gd`,`gmp`,`gettext`,`iconv`,`igbinary`,`imagick`,`intl`,`ldap`,`lz4`,`mbregex`,`mbstring`,`memcache`,`memcached`,`mysqli`,`mysqlnd`,`opcache`,`openssl`,`password-argon2`,`parallel`,`pcntl`,`pdo`,`pdo_mysql`,`pdo_pgsql`,`pdo_sqlite`,`pdo_sqlsrv`,`pgsql`,`phar`,`posix`,`protobuf`,`readline`,`redis`,`session`,`shmop`,`simplexml`,`soap`,`sockets`,`sodium`,`sqlite3`,`ssh2`,`sysvmsg`,`sysvsem`,`sysvshm`,`tidy`,`tokenizer`,`xlswriter`,`xml`,`xmlreader`,`xmlwriter`,`xz`,`zip`,`zlib`,`yaml`,`zstd` +> [!NOTE] `memcache` and `pdo_sqlsrv` are not available when `CC_PHP_VERSION=8.5` is set + {{% content "url_healthcheck" %}} {{% content "request-flow" %}} diff --git a/content/doc/applications/python/_index.md b/content/doc/applications/python/_index.md index 508cd7123..6bdf7a131 100644 --- a/content/doc/applications/python/_index.md +++ b/content/doc/applications/python/_index.md @@ -134,5 +134,4 @@ The `CC_PYTHON_CELERY_LOGFILE` path is relative to the application's path. {{% content "new-relic" %}} {{% content "url_healthcheck" %}} -{{% content "redirectionio" %}} -{{% content "varnish" %}} +{{% content "request-flow" %}} diff --git a/content/doc/applications/ruby.md b/content/doc/applications/ruby.md index ca7b9fd76..c760a4c4a 100644 --- a/content/doc/applications/ruby.md +++ b/content/doc/applications/ruby.md @@ -71,3 +71,5 @@ It means you need to add `RAILS_LOG_TO_STDOUT=true` in your environment variable {{% content "more-config" %}} {{% content "url_healthcheck" %}} + +{{% content "request-flow" %}} diff --git a/content/doc/develop/request-flow.md b/content/doc/develop/request-flow.md index dd3765693..fe63d3464 100644 --- a/content/doc/develop/request-flow.md +++ b/content/doc/develop/request-flow.md @@ -20,25 +20,6 @@ aliases: Request Flow is Clever Cloud's automatic middleware chaining mechanism. It configures reverse proxies and services between the public port (`8080`) and your application, managing port allocation automatically. There is no need to manually configure listening ports for each service. -Request Flow is available in the following runtimes: - -- [.NET](/doc/applications/dotnet/) -- [Elixir](/doc/applications/elixir/) -- [FrankenPHP](/doc/applications/frankenphp/) -- [Go](/doc/applications/golang/) -- [Haskell](/doc/applications/haskell/) -- [Java](/doc/applications/java/) -- [Linux](/doc/applications/linux/) -- [Meteor](/doc/applications/meteor/) -- [Node.js & Bun](/doc/applications/nodejs/) -- [PHP with Apache](/doc/applications/php/) -- [Python with uv](/doc/applications/python/uv/) -- [Rust](/doc/applications/rust/) -- [Scala](/doc/applications/scala/) -- [Static](/doc/applications/static/) -- [Static with Apache](/doc/applications/static-apache/) -- [V (Vlang)](/doc/applications/v/) - ## Supported services | Service | Activation | Description | diff --git a/shared/redirectionio.md b/shared/redirectionio.md index b362b7d7d..04dbcaa2d 100644 --- a/shared/redirectionio.md +++ b/shared/redirectionio.md @@ -1,13 +1,11 @@ ## Use Redirection.io as a proxy -[Redirection.io](https://redirection.io) can help reduce HTTP traffic issues on your website. It gives a complete control on how HTTP requests are handled, which helps make it SEO-friendly. It can perform redirections and comes with lots of features. You can link any application to a Redirection.io project easily, setting up the proxy mode with following environment variables: +[Redirection.io](https://redirection.io) can help reduce HTTP traffic issues on your website. It gives complete control over how HTTP requests are handled, which helps make it SEO-friendly. You can link any application to a Redirection.io project by setting the following environment variables. Port allocation is managed automatically by [Request Flow](/doc/develop/request-flow/). | Name | Description | Default value | |-----------------------|------------------------------|--------------------------------| | `CC_REDIRECTIONIO_PROJECT_KEY` | The Redirection.io project key | | -| `CC_REDIRECTIONIO_FORWARD_PORT` | The listening port of your application (optional) | | +| `CC_REDIRECTIONIO_FORWARD_PORT` | Override the port Redirection.io forwards traffic to (optional) | | | `CC_REDIRECTIONIO_INSTANCE_NAME` | The name of your application (optional) | | -The Redirection.io agent will start as a service, listen to `8080` port and forward the traffic to your application port. - - [Learn more about Redirection.io](https://redirection.io/) diff --git a/shared/varnish.md b/shared/varnish.md deleted file mode 100644 index 98462e666..000000000 --- a/shared/varnish.md +++ /dev/null @@ -1,3 +0,0 @@ -## Use Varnish as cache - -Varnish is a powerful HTTP accelerator that can be used to cache your web application's responses, improving performance and reducing load. To use it, create a Varnish configuration file in `clevercloud/varnish.vcl` and configure your application to listen on port `8081`. From 7c474f5989844e2e0a341625897a69d4727652de Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 25 Feb 2026 17:42:32 +0100 Subject: [PATCH 040/180] changelog: Redis 8.6.1 --- content/changelog/2026/02-25-redis-8.6.1.md | 22 +++++++++++++++++++++ content/doc/addons/redis.md | 4 ++++ 2 files changed, 26 insertions(+) create mode 100644 content/changelog/2026/02-25-redis-8.6.1.md diff --git a/content/changelog/2026/02-25-redis-8.6.1.md b/content/changelog/2026/02-25-redis-8.6.1.md new file mode 100644 index 000000000..6fd351cd3 --- /dev/null +++ b/content/changelog/2026/02-25-redis-8.6.1.md @@ -0,0 +1,22 @@ +--- +title: Redis 8.6.1 is available (Security update) +description: Fewer bugs, better performance and a security patch are coming with Redis 8.6.1 +date: 2026-02-25 +tags: + - addons + - redis +authors: + - name: Aurélien Hébert + link: https://github.com/aurrelhebert + image: https://github.com/aurrelhebert.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated Redis™ to [release 8.6.1](https://github.com/redis/redis/releases/tag/8.6.1). It brings bug fixes, performance optimizations from [8.6.0](https://github.com/redis/redis/releases/tag/8.6.0) and a security patch. Redis™ 8.6.1 is available for new add-ons. Those already deployed can upgrade through migration. + +Starting with this release, Transparent Huge Pages (THP) are disabled at startup to prevent latency spikes and memory fragmentation under heavy workloads, in line with [official Redis documentation](https://redis.io/docs/latest/operate/oss_and_stack/management/optimization/latency/). + +- [Learn more about Redis™ on Clever Cloud](/doc/addons/redis/) diff --git a/content/doc/addons/redis.md b/content/doc/addons/redis.md index ad1114975..f90fa7f0a 100644 --- a/content/doc/addons/redis.md +++ b/content/doc/addons/redis.md @@ -48,6 +48,10 @@ This is the correct syntax for `redis-cli` URI : *redis ://password@host:port[/d {{% content "kv-explorer" %}} +## Performance optimisations + +Clever Cloud automatically applies kernel-level performance settings recommended by Redis. Transparent Huge Pages (THP) are disabled at startup to prevent latency spikes and memory fragmentation under heavy workloads, in line with [official Redis documentation](https://redis.io/docs/latest/operate/oss_and_stack/management/optimization/latency/). + ## Default retention policy By default, the eviction policy is `noeviction`. If you plan to use Redis as a LRU cache, From e8f8d3af3f56a7884b0df2a9881470bbbc2521cc Mon Sep 17 00:00:00 2001 From: Corentin BARAULT <74433435+Kirbeerus@users.noreply.github.com> Date: Mon, 2 Mar 2026 11:11:53 +0100 Subject: [PATCH 041/180] addons(cellar): add read-only policy --- content/doc/addons/cellar.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/doc/addons/cellar.md b/content/doc/addons/cellar.md index e446ed8af..5b5b5eb53 100644 --- a/content/doc/addons/cellar.md +++ b/content/doc/addons/cellar.md @@ -520,7 +520,7 @@ This policy example grants read-only access to a bucket for another user, using "arn:aws:s3:::", "arn:aws:s3:::/*" ], - "Principal": {"AWS": "arn:aws:iam::cellar_xxx"} + "Principal": {"AWS": "arn:aws:iam:::user/"} } ] @@ -528,7 +528,7 @@ This policy example grants read-only access to a bucket for another user, using ``` -Replace the `` with your bucket name in the policy file. +Replace the `` with your bucket name and `` with the ID of the cellar to which you want to grant read-only access. Set the policy to your bucket using s3cmd: From ba2aee71bdacf5021a20ca3532f8be6494bda345 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Thu, 5 Mar 2026 09:45:20 +0100 Subject: [PATCH 042/180] changelog: Keycloak 26.5.3/4, Metabase 59, Otoroshi 17.13 --- .../changelog/2026/02-10-keycloak-26.5.3.md | 30 ++++++++++++++++ .../2026/02-20-metabase-security-patches.md | 31 ++++++++++++++++ .../changelog/2026/02-23-keycloak-26.5.4.md | 30 ++++++++++++++++ content/changelog/2026/03-03-metabase-59.md | 33 +++++++++++++++++ .../changelog/2026/03-04-otoroshi-17.13.md | 36 +++++++++++++++++++ 5 files changed, 160 insertions(+) create mode 100644 content/changelog/2026/02-10-keycloak-26.5.3.md create mode 100644 content/changelog/2026/02-20-metabase-security-patches.md create mode 100644 content/changelog/2026/02-23-keycloak-26.5.4.md create mode 100644 content/changelog/2026/03-03-metabase-59.md create mode 100644 content/changelog/2026/03-04-otoroshi-17.13.md diff --git a/content/changelog/2026/02-10-keycloak-26.5.3.md b/content/changelog/2026/02-10-keycloak-26.5.3.md new file mode 100644 index 000000000..db6623d43 --- /dev/null +++ b/content/changelog/2026/02-10-keycloak-26.5.3.md @@ -0,0 +1,30 @@ +--- +title: Keycloak 26.5.3 (security update) +description: Keycloak 26.5.3 fixes four CVEs including disabled user token grants, forged invitation tokens, and UMA policy endpoint vulnerabilities +date: 2026-02-10 +tags: + - addons + - keycloak +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[The release 26.5.3](https://github.com/keycloak/keycloak/releases/tag/26.5.3) of Keycloak is available on Clever Cloud. It fixes bugs, reduces memory consumption during startup and addresses four security vulnerabilities: [CVE-2026-1609](https://github.com/keycloak/keycloak/issues/46144), [CVE-2026-1529](https://nvd.nist.gov/vuln/detail/CVE-2026-1529), [CVE-2026-1486](https://nvd.nist.gov/vuln/detail/CVE-2026-1486) and [CVE-2025-14778](https://nvd.nist.gov/vuln/detail/CVE-2025-14778). + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_KEYCLOAK_VERSION` of the underlying Java application to `26.5.3` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever keycloak version check yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId 26.5.3 +``` + +- [Learn more about Keycloak on Clever Cloud](/doc/addons/keycloak) diff --git a/content/changelog/2026/02-20-metabase-security-patches.md b/content/changelog/2026/02-20-metabase-security-patches.md new file mode 100644 index 000000000..39ff45dbb --- /dev/null +++ b/content/changelog/2026/02-20-metabase-security-patches.md @@ -0,0 +1,31 @@ +--- +title: "Metabase security patches for versions 54 to 58" +description: Metabase v0.54.20, v0.55.20, v0.56.20, v0.57.13 and v0.58.7 are available on Clever Cloud, fixing CVEs +date: 2026-02-20 +tags: + - addons + - metabase +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +Metabase versions `0.54.20`, `0.55.20`, `0.56.20`, `0.57.13` and `0.58.7` are now available on Clever Cloud. These releases fix security vulnerabilities (CVEs) and should be applied as soon as possible. + +If you use `community-latest` as your `CC_METABASE_VERSION`, you have nothing to do or simply need to restart your instance to get the latest patched version. If you use a specific version, update `CC_METABASE_VERSION` of the underlying Java application to the latest patch for your branch and rebuild it. + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com), or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever metabase version check yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId +``` + +- [Learn more about Metabase on Clever Cloud](/doc/addons/metabase/) diff --git a/content/changelog/2026/02-23-keycloak-26.5.4.md b/content/changelog/2026/02-23-keycloak-26.5.4.md new file mode 100644 index 000000000..f325cbccd --- /dev/null +++ b/content/changelog/2026/02-23-keycloak-26.5.4.md @@ -0,0 +1,30 @@ +--- +title: Keycloak 26.5.4 (security update) +description: Keycloak 26.5.4 fixes five CVEs including SAML vulnerabilities, authorization header bypass, and environment information disclosure +date: 2026-02-23 +tags: + - addons + - keycloak +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[The release 26.5.4](https://github.com/keycloak/keycloak/releases/tag/26.5.4) of Keycloak is available on Clever Cloud. It fixes bugs, improves performance and addresses five security vulnerabilities: [CVE-2026-1190](https://nvd.nist.gov/vuln/detail/CVE-2026-1190), [CVE-2026-0707](https://nvd.nist.gov/vuln/detail/CVE-2026-0707), [CVE-2025-5416](https://nvd.nist.gov/vuln/detail/CVE-2025-5416), [CVE-2026-2575](https://github.com/keycloak/keycloak/issues/46372) and [CVE-2026-2733](https://nvd.nist.gov/vuln/detail/CVE-2026-2733). + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_KEYCLOAK_VERSION` of the underlying Java application to `26.5.4` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever keycloak version check yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId 26.5.4 +``` + +- [Learn more about Keycloak on Clever Cloud](/doc/addons/keycloak) diff --git a/content/changelog/2026/03-03-metabase-59.md b/content/changelog/2026/03-03-metabase-59.md new file mode 100644 index 000000000..cf440a9a2 --- /dev/null +++ b/content/changelog/2026/03-03-metabase-59.md @@ -0,0 +1,33 @@ +--- +title: "Metabase 59 is available, with Data Studio, AI and box-and-whisker plots" +description: Data Studio, box-and-whisker plots, conditional colors for big numbers, AI text-to-SQL for open source, Agent API, and more +date: 2026-03-03 +tags: + - addons + - metabase +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +The `x.59` branch of Metabase is now available on Clever Cloud. It introduces Data Studio, a comprehensive toolkit for data governance including a semantic layer library, dependency graphs, diagnostic tools and data transforms. It also brings box-and-whisker plots, conditional colors for big number displays, AI-powered text-to-SQL for open source users via Anthropic API, a new Agent API, multiple enhancements and bug fixes. + +You can update through the add-on’s dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_METABASE_VERSION` of the underlying Java application to `0.59` or `1.59` for the enterprise edition (EE) and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever metabase version check yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId 0.59 +``` + +This new branch is not yet the default if you use `community-latest`, we'll move to it in the next few weeks. + +- [Learn more about Metabase 59](https://www.metabase.com/changelog/59) +- [Learn more about Metabase on Clever Cloud](/doc/addons/metabase/) diff --git a/content/changelog/2026/03-04-otoroshi-17.13.md b/content/changelog/2026/03-04-otoroshi-17.13.md new file mode 100644 index 000000000..a53724a3a --- /dev/null +++ b/content/changelog/2026/03-04-otoroshi-17.13.md @@ -0,0 +1,36 @@ +--- +title: Otoroshi 17.13 brings Kubernetes Gateway API support, remote catalogs and audio STT extensions +description: Experimental Kubernetes Gateway API, remote catalogs, enhanced security headers, router fixes and LLM extension with audio STT support +date: 2026-03-04 +tags: + - addons + - otoroshi +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Otoroshi v17.13](https://github.com/MAIF/otoroshi/releases/tag/v17.13.0) is available with experimental support for the [Kubernetes Gateway API](https://maif.github.io/otoroshi/manual/topics/kubernetes-gateway-api.html), enabling standardised Kubernetes-native traffic management. This release also introduces [remote catalogs](https://maif.github.io/otoroshi/manual/topics/remote-catalogs.html), allowing to fetch and manage plugin or configuration catalogs from external sources. + +A webhook validator plugin is also included, providing HMAC signature verification for incoming webhook payloads. It supports multiple algorithms (SHA256, SHA512, SHA384, SHA1) and is provider-agnostic with configurable signature headers and signing templates, compatible with services such as GitHub, Stripe, Slack or YouSign. + +Security headers plugin now supports `Referrer-Policy` and `Permissions-Policy` headers, and new configuration options allow exposing public keys with algorithms in JWKS endpoints. Several router fixes improve path matching with wildcard domains, query/header/cookie matching prioritisation, and trailing slash handling. The strict mode of the JWT user extractor plugin has also been fixed. + +This release includes LLM extension [0.0.73](https://github.com/cloud-apim/otoroshi-llm-extension/releases/tag/0.0.73), bringing audio speech-to-text support with [Mistral Voxtral model](https://github.com/cloud-apim/otoroshi-llm-extension/releases/tag/0.0.70) and Azure OpenAI Audio API. It also includes various provider payload cleanups for Anthropic and xAI formats. + +You can update through add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_OTOROSHI_VERSION` of the underlying Java application to `v17.13.0_1772616661` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever otoroshi version check yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId v17.13.0_1772616661 +``` + +- [Learn more about Otoroshi with LLM on Clever Cloud](/doc/addons/otoroshi/) From 54054323f56bec5e9f06ca22b6852161857c4029 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Fri, 6 Mar 2026 18:22:27 +0100 Subject: [PATCH 043/180] changelog: Keycloak 26.5.5 --- .../changelog/2026/03-06-keycloak-26.5.5.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 content/changelog/2026/03-06-keycloak-26.5.5.md diff --git a/content/changelog/2026/03-06-keycloak-26.5.5.md b/content/changelog/2026/03-06-keycloak-26.5.5.md new file mode 100644 index 000000000..5cb5f7eb4 --- /dev/null +++ b/content/changelog/2026/03-06-keycloak-26.5.5.md @@ -0,0 +1,30 @@ +--- +title: Keycloak 26.5.5 (security update) +description: Keycloak 26.5.5 fixes four CVEs related to SAML broker authentication bypass and encrypted assertion injection +date: 2026-03-06 +tags: + - addons + - keycloak +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[The release 26.5.5](https://github.com/keycloak/keycloak/releases/tag/26.5.5) of Keycloak is available on Clever Cloud. It addresses four security vulnerabilities: [CVE-2026-3047](https://nvd.nist.gov/vuln/detail/CVE-2026-3047), [CVE-2026-3009](https://nvd.nist.gov/vuln/detail/CVE-2026-3009), [CVE-2026-2603](https://github.com/keycloak/keycloak/issues/46911) and [CVE-2026-2092](https://github.com/keycloak/keycloak/issues/46912). + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_KEYCLOAK_VERSION` of the underlying Java application to `26.5.5` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever keycloak version check yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId 26.5.5 +``` + +- [Learn more about Keycloak on Clever Cloud](/doc/addons/keycloak) From 96bc4910ccf8c7907827b08cb2533966f55c3f24 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 9 Mar 2026 12:49:35 +0100 Subject: [PATCH 044/180] chore: Hextra v0.12.1 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 8f15f0d78..f9f6dd026 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/CleverCloud/documentation go 1.26 -require github.com/imfing/hextra v0.12.0 // indirect +require github.com/imfing/hextra v0.12.1 // indirect diff --git a/go.sum b/go.sum index 478369065..34448dbd0 100644 --- a/go.sum +++ b/go.sum @@ -2,3 +2,5 @@ github.com/imfing/hextra v0.11.1 h1:8pTc4ReYbzGTHAnyiebmlT3ijFfIXiGu1r7tM/UGjFI= github.com/imfing/hextra v0.11.1/go.mod h1:cEfel3lU/bSx7lTE/+uuR4GJaphyOyiwNR3PTqFTXpI= github.com/imfing/hextra v0.12.0 h1:f6y35hW/WDJEcx9S0dOmbICOBxYE0PmP6IJFsTUgVyY= github.com/imfing/hextra v0.12.0/go.mod h1:YAv8XRNSmcqjieFwI7fVQK1AoY2Do+45DO9HGqxSGu4= +github.com/imfing/hextra v0.12.1 h1:3t1n0bmJbDzSTVfht93UDcfF1BXMRjeFojA071ri2l8= +github.com/imfing/hextra v0.12.1/go.mod h1:vi+yhpq8YPp/aghvJlNKVnJKcPJ/VyAEcfC1BSV9ARo= From 23822607eb05e41b4840c71898bc31b64bca461c Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 11 Mar 2026 15:17:08 +0100 Subject: [PATCH 045/180] changelog: Clever Tools 4.7 --- .../changelog/2026/03-11-clever-tools-4.7.md | 64 +++++++++++++++++++ content/doc/administrate/ssh-clever-tools.md | 28 ++++++-- content/doc/cli/logs-drains.md | 2 + content/doc/reference/cli.md | 1 + 4 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 content/changelog/2026/03-11-clever-tools-4.7.md diff --git a/content/changelog/2026/03-11-clever-tools-4.7.md b/content/changelog/2026/03-11-clever-tools-4.7.md new file mode 100644 index 000000000..4294dcee2 --- /dev/null +++ b/content/changelog/2026/03-11-clever-tools-4.7.md @@ -0,0 +1,64 @@ +--- +title: "Clever Tools 4.7: SSH remote commands, improved drains and backup downloads" +date: 2026-03-11 +description: Clever Tools 4.7 adds remote command execution over SSH, interactive instance selection, improved drain monitoring and more reliable database backup downloads +tags: + - clever-tools + - cli +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 + - name: Hubert Sablonniere + link: https://github.com/hsablonniere + image: https://github.com/hsablonniere.png?size=40 +excludeSearch: true +--- + +[Clever Tools 4.7.0](https://github.com/CleverCloud/clever-tools/releases/tag/4.7.0) is available. This release enhances SSH with remote command execution and interactive instance selection, improves drain monitoring and fixes database backup downloads. + +## SSH remote command execution + +You can now execute a single command on a running instance with the new `--command` (or `-c`) option. The command runs in a login shell and exits immediately, making it convenient for quick diagnostics, scripting or automation. + +```bash +# List application files +clever ssh -c 'ls -lah $APP_HOME' + +# Inspect application environment variables +clever ssh -c "env | sort" + +# Read application logs +clever ssh -c "journalctl -u bas-deploy.service --no-pager -n 50" +``` + +This also enables AI coding assistants (Claude Code, Codex, Cursor, OpenCode...) to inspect the state of a running instance, analyse configuration files, check logs or troubleshoot issues on your behalf. + +## Interactive instance selection + +When multiple instances are running, `clever ssh` now displays an interactive selection prompt instead of a numbered list. This provides a more intuitive experience when choosing which instance to connect to. + +```bash +clever ssh +> ? Select an instance: +> ❯ Sleepy Ponita - Instance 0 - UP (11281f38-...) +> Tense Caterpie - Instance 1 - UP (b10d19d9-...) +``` + +## Improved drain monitoring + +The `clever drain` and `clever drain get` commands now display message rates and throughput with dynamic units (messages/hour, messages/minute, messages/second, KiB/second, MiB/second) that adapt to the actual values. Retry information is also shown, including attempt count, last and next attempt timestamps, helping you troubleshoot delivery issues more effectively. + +## Bug fixes + +- **Database backup downloads** now use Node.js stream pipelines, fixing backpressure issues that could cause incomplete downloads on large backups. +- **Detached HEAD deployments** are now handled correctly when using the system Git feature, fixing failures that occurred when deploying from a detached HEAD state. + +## How to upgrade + +To upgrade Clever Tools, [use your favourite package manager](/doc/cli/install/). For example with `npm`: + +``` +npm update -g clever-tools +clever version +``` diff --git a/content/doc/administrate/ssh-clever-tools.md b/content/doc/administrate/ssh-clever-tools.md index 848455905..438a5dfd4 100644 --- a/content/doc/administrate/ssh-clever-tools.md +++ b/content/doc/administrate/ssh-clever-tools.md @@ -55,19 +55,37 @@ clever ssh > bas@67fbf787-3518-47bb-abd9-2c2575844edd ~ $ ``` -If multiple instances are running, you will be asked which one to use: +If multiple instances are running, you are prompted to select one interactively: ```shell clever ssh -> 1) Sleepy Ponita - Instance 0 - UP (11281f38-31ff-43a7-8595-a2d82630c32b) -> 2) Tense Caterpie - Instance 1 - UP (b10d19d9-5238-408b-b038-3e32c7a301c2) -> Your choice: 1 +> ? Select an instance: +> ❯ Sleepy Ponita - Instance 0 - UP (11281f38-31ff-43a7-8595-a2d82630c32b) +> Tense Caterpie - Instance 1 - UP (b10d19d9-5238-408b-b038-3e32c7a301c2) > Opening an ssh shell > bas@11281f38-31ff-43a7-8595-a2d82630c32b ~ $ +``` + +### Running a remote command + +You can execute a single command on a running instance with the `--command` (or `-c`) option. The command runs in a login shell on the remote instance and exits immediately after completion: + +```shell +# List application files +clever ssh -c 'ls -lah $APP_HOME' + +# List running processes +clever ssh -c "ps aux" -You are now connected to the machine. +# Inspect application environment variables +clever ssh -c "env | sort" + +# Read application logs +clever ssh -c "journalctl -u bas-deploy.service --no-pager -n 50" ``` +This is useful for quick diagnostics, scripting or automation. AI coding assistants such as Claude Code, Codex, Cursor or OpenCode can use this option to inspect the state of a running instance, analyse configuration files, check logs or troubleshoot issues on your behalf. + ### Note for Windows users `$ clever ssh` command will fail on PowerShell or cmd.exe if there is no `ssh.exe` in your path. The most straightforward solution is to start `$ clever ssh` from `git-bash` but you can also add `ssh.exe` in your path.. diff --git a/content/doc/cli/logs-drains.md b/content/doc/cli/logs-drains.md index f1d0a36ad..424effd31 100644 --- a/content/doc/cli/logs-drains.md +++ b/content/doc/cli/logs-drains.md @@ -28,6 +28,8 @@ clever drain enable clever drain disable ``` +The `clever drain` command lists all drains for the target application and shows key metrics for each one. The `clever drain get` command displays detailed metrics for a single drain, including message output rate, throughput (with dynamic units), backlog size, retry attempts, and last error. These metrics help you monitor drain health and troubleshoot delivery issues. + Where `DRAIN-TYPE` is one of: - `datadog`: for Datadog endpoint (note that this endpoint needs your Datadog API Key) diff --git a/content/doc/reference/cli.md b/content/doc/reference/cli.md index 3fbc1a5c2..a52b055ca 100644 --- a/content/doc/reference/cli.md +++ b/content/doc/reference/cli.md @@ -3084,6 +3084,7 @@ clever ssh [options] ``` -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) +-c, --command Execute a command on the remote instance and exit -i, --identity-file SSH identity file ``` From 42a25f0980643c85c6c5efb8efd5f3a7706d2c74 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 10 Mar 2026 17:12:35 +0100 Subject: [PATCH 046/180] changelog: PostgreSQL 18.3, 17.9, 16.13, 15.17, 14.22 --- .../changelog/2026/03-09-pg-update-18.3.md | 24 +++++++++++++++++++ content/doc/addons/postgresql.md | 4 ++-- data/software_versions_shared_dedicated.yml | 13 +++++----- 3 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 content/changelog/2026/03-09-pg-update-18.3.md diff --git a/content/changelog/2026/03-09-pg-update-18.3.md b/content/changelog/2026/03-09-pg-update-18.3.md new file mode 100644 index 000000000..358790e41 --- /dev/null +++ b/content/changelog/2026/03-09-pg-update-18.3.md @@ -0,0 +1,24 @@ +--- +title: PostgreSQL 18.3, 17.9, 16.13, 15.17, 14.22 are available +description: Bug fixes, improvements, and extension changes for PostgreSQL 14 to 18 +date: 2026-03-09 +tags: + - addons + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +New PostgreSQL versions are available for new add-ons and migration: +* PostgreSQL 18.3 +* PostgreSQL 17.9 +* PostgreSQL 16.13 +* PostgreSQL 15.17 +* PostgreSQL 14.22 + +These versions include [multiple bug fixes and improvements](https://www.postgresql.org/about/news/postgresql-183-179-1613-1517-and-1422-released-3246/). PostgreSQL 14 and 15 are now shipped without `plls` and `plcoffee` extensions, which are no longer supported with up to date add-ons. + +* [Learn more about PostgreSQL on Clever Cloud](/doc/addons/postgresql/) diff --git a/content/doc/addons/postgresql.md b/content/doc/addons/postgresql.md index 36f066ac6..e24e4d387 100644 --- a/content/doc/addons/postgresql.md +++ b/content/doc/addons/postgresql.md @@ -114,8 +114,8 @@ Extension              | Description  pgrowlocks             | Show row-level locking information  pgstattuple            | Show tuple-level statistics pgvector | Vector data type and ivfflat and hnsw access methods - plcoffee               | PL/CoffeeScript (v8) trusted procedural language (not supported on PostgreSQL 16+) - plls                   | PL/LiveScript (v8) trusted procedural language (not supported on PostgreSQL 16+) + plcoffee               | PL/CoffeeScript (v8) trusted procedural language (not supported on PostgreSQL 14+) + plls                   | PL/LiveScript (v8) trusted procedural language (not supported on PostgreSQL 14+)  plpgsql                | PL/pgSQL procedural language  plv8                   | PL/JavaScript (v8) trusted procedural language (not supported on PostgreSQL 16+)  postgis                | PostGIS geometry and geography spatial types and functions diff --git a/data/software_versions_shared_dedicated.yml b/data/software_versions_shared_dedicated.yml index c63a43b75..308c19005 100644 --- a/data/software_versions_shared_dedicated.yml +++ b/data/software_versions_shared_dedicated.yml @@ -22,14 +22,13 @@ mysql: pg: dedicated: - - v13.22 - - v14.19 - - v15.14 - - v16.10 - - v17.6 - - v18.0 + - v14.22 + - v15.17 + - v16.13 + - v17.9 + - v18.3 dev: - - v15.14 + - v15.17 redis: dedicated: From e19d65a76c36866aa256bd599c2d7f684ca59087 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Thu, 5 Mar 2026 10:08:33 +0100 Subject: [PATCH 047/180] changelog: Terraform 1.10.0 --- .../changelog/2026/03-03-terraform-1.10.0.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 content/changelog/2026/03-03-terraform-1.10.0.md diff --git a/content/changelog/2026/03-03-terraform-1.10.0.md b/content/changelog/2026/03-03-terraform-1.10.0.md new file mode 100644 index 000000000..6a8000a9f --- /dev/null +++ b/content/changelog/2026/03-03-terraform-1.10.0.md @@ -0,0 +1,20 @@ +--- +title: Terraform provider 1.10.0 +description: Linux runtime support, database feature flags and bug fixes in the Clever Cloud Terraform provider +date: 2026-03-03 +tags: + - addons + - terraform +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 + - name: Rémi Collignon-Ducret + link: https://github.com/miton18 + image: https://github.com/miton18.png?size=40 +excludeSearch: true +--- + +The [1.10.0 release](https://github.com/CleverCloud/terraform-provider-clevercloud/releases/tag/v1.10.0) of the Clever Cloud Terraform provider is available. It adds Linux runtime support for applications, missing feature flags for MySQL, PostgreSQL and MongoDB add-ons, and includes multiple bug fixes (add-on API responses, app state handling, Elasticsearch versioning, OAuth signature method). + +* Learn more about [Clever Cloud Terraform provider](https://registry.terraform.io/providers/CleverCloud/clevercloud/latest/docs) From 49375c2b27b836ba85b69a167418a337391d76f8 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Thu, 19 Mar 2026 09:38:23 +0100 Subject: [PATCH 048/180] addons(cellar): mention Bun native support --- content/doc/addons/cellar.md | 56 +++++++++++++++++++++++++++--- content/doc/applications/nodejs.md | 4 +++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/content/doc/addons/cellar.md b/content/doc/addons/cellar.md index 5b5b5eb53..327b6bdaf 100644 --- a/content/doc/addons/cellar.md +++ b/content/doc/addons/cellar.md @@ -151,12 +151,60 @@ s3cmd --host-bucket=cellar-c2.services.clever-cloud.com mb s3://cdn.example.com Then, create a CNAME record on your domain pointing to `cellar-c2.services.clever-cloud.com.`. -## Using AWS SDK +## Using SDKs -To use cellar from your applications, you can use the [AWS SDK](https://aws.amazon.com/tools/#sdk). -You only need to specify a custom endpoint (eg `cellar-c2.services.clever-cloud.com`). +To use Cellar from your applications, you can use the [AWS SDK](https://aws.amazon.com/tools/#sdk) or any S3-compatible client. +You only need to specify a custom endpoint (e.g. `cellar-c2.services.clever-cloud.com`). -{{< tabs items="Node.js,Java,Python,Ruby" >}} +{{< tabs items="Bun,Node.js,Java,Python,Ruby" >}} + + {{< tab >}} + **Bun (native S3 client)** + + [Bun](https://bun.sh) includes a [native S3 client](https://bun.sh/docs/api/s3) with no external dependency. It works with any S3-compatible service, including Cellar. + + **Required environment variables:** + - `CELLAR_ADDON_HOST` - Cellar endpoint (e.g., `cellar-c2.services.clever-cloud.com`) + - `CELLAR_ADDON_KEY_ID` - Your Cellar access key ID + - `CELLAR_ADDON_KEY_SECRET` - Your Cellar secret access key + + These variables are automatically available in your application when you [link a Cellar add-on](/doc/addons/cellar/) to it on Clever Cloud, regardless of the runtime (for example in a [Node.js & Bun application](/doc/applications/nodejs/)). + + ```typescript + import { S3Client } from "bun"; + + // Create Bun S3 client with Cellar endpoint + const cellar = new S3Client({ + accessKeyId: process.env.CELLAR_ADDON_KEY_ID, + secretAccessKey: process.env.CELLAR_ADDON_KEY_SECRET, + endpoint: `https://${process.env.CELLAR_ADDON_HOST}`, + bucket: "my-bucket", + }); + + // Upload a file + await cellar.write("hello.txt", "Hello from Cellar!"); + + // Read a file + const file = cellar.file("hello.txt"); + const text = await file.text(); + + // Generate a presigned URL (synchronous, no network request) + const url = cellar.presign("hello.txt", { + expiresIn: 3600, // 1 hour + }); + + // Delete a file + await cellar.delete("hello.txt"); + ``` + + You can also use the `s3://` protocol with `fetch` and `Bun.file()`. Set `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY` and `S3_ENDPOINT` environment variables (or their `AWS_*` equivalents) to use this approach: + + ```typescript + const response = await fetch("s3://my-bucket/hello.txt"); + const content = await response.text(); + ``` + + {{< /tab >}} {{< tab >}} **Node.js** diff --git a/content/doc/applications/nodejs.md b/content/doc/applications/nodejs.md index 2d269c60e..0dc281abb 100644 --- a/content/doc/applications/nodejs.md +++ b/content/doc/applications/nodejs.md @@ -180,6 +180,10 @@ If you need a specific version or branch of Node.js, set `CC_NODE_VERSION`. You If you use Bun, your application is deployed with the latest available version on Clever Cloud (`{{< runtime_version bun >}}`). To customise the Bun cache directory, set `CC_BUN_INSTALL_CACHE_DIR`. +Bun includes a [native S3 client](https://bun.sh/docs/api/s3) that works with [Cellar](/doc/addons/cellar/#using-sdks), the Clever Cloud S3-compatible object storage service, with no external dependency required. + +* [Learn more about Bun native Clever Cloud add-ons support with examples](https://github.com/CleverCloud/bun-addons-examples) + ### pnpm and Yarn versions To load a specific version, set the `packageManager` field in your `package.json` file. For example, to use `pnpm@10.14.0`: From 8fa2af257ac2692a8b1e47fcb736e2438dc2277d Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 18 Mar 2026 16:52:00 +0100 Subject: [PATCH 049/180] changelog: legacy drain stack deprecation --- content/changelog/2026/03-19-drains-legacy.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 content/changelog/2026/03-19-drains-legacy.md diff --git a/content/changelog/2026/03-19-drains-legacy.md b/content/changelog/2026/03-19-drains-legacy.md new file mode 100644 index 000000000..31a9aa940 --- /dev/null +++ b/content/changelog/2026/03-19-drains-legacy.md @@ -0,0 +1,28 @@ +--- +title: Drains legacy stack deprecation +date: 2026-03-19 +description: Deprecation of the legacy drains stack in Clever Tools, encouraging migration to the new drain stack for improved reliability and monitoring. +tags: + - clever-tools + - cli +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +For the past few months, Clever Cloud has provided a new drain stack and a `v4` API that is more reliable and provides better monitoring. It's available through Clever Tools since [release `4.4.0`](https://github.com/CleverCloud/clever-tools/releases/tag/4.4.0). If you still use the legacy drains implementation, we encourage you to migrate to the new stack as soon as possible. + +The legacy drains will be disabled on June 1st, 2026. In the meantime, we will make some changes to prepare for the migration and help you transition smoothly: +- March 19th, 2026: we added `CC_PREVENT_LEGACY_LOGSCOLLECTION=false` environment variable on applications linked to a legacy active drain +- Starting with the next image release, if not set to `false`, we will consider this environment variable as `true` and only push logs of applications with a drain to the new drain stack +- June 1st, 2026: legacy drains will be disabled and all applications will need to use the new drain stack to receive logs + +## How to migrate + +To migrate to the new drain stack, just create a new drain with Clever Tools, then remove `CC_PREVENT_LEGACY_LOGSCOLLECTION=false` from your application environment variables and restart it. The new drain should start receiving logs immediately. + +If you have any questions or if you need help with the migration, contact [our support team](https://console.clever-cloud.com/ticket-center-choice). + +- [Learn more about Clever Tools drain command ](/doc/cli/logs-drains/) From 2dffb667aeb3b58dbf5778119a3fcd8fcfef0ee0 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Thu, 19 Mar 2026 11:27:07 +0100 Subject: [PATCH 050/180] fix: content-width Fixes #882 --- layouts/404.html | 2 +- layouts/changelog/list.html | 2 +- layouts/changelog/single.html | 4 ++-- layouts/docs/single.html | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/layouts/404.html b/layouts/404.html index 01d04f898..24e760c53 100644 --- a/layouts/404.html +++ b/layouts/404.html @@ -3,7 +3,7 @@
+ class="hx:w-full hx:min-w-0 hextra-max-content-width hx:flex hx:flex-col hx:items-center hx:justify-center">
Clever Cloud 404 Logo

{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
-
+

{{ if .Title }}

{{ .Title }}

{{ end }}
diff --git a/layouts/changelog/single.html b/layouts/changelog/single.html index a19e1e13d..bfc300bb6 100644 --- a/layouts/changelog/single.html +++ b/layouts/changelog/single.html @@ -1,9 +1,9 @@ {{ define "main" }} -
+
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" false) }} {{ partial "toc.html" . }}
-
+
{{ partial "breadcrumb.html" (dict "page" . "enable" true) }} {{ if .Title }}

{{ .Title }}

{{ end }}
diff --git a/layouts/docs/single.html b/layouts/docs/single.html index cbef660a5..4a60b0c77 100644 --- a/layouts/docs/single.html +++ b/layouts/docs/single.html @@ -3,7 +3,7 @@ {{ partial "sidebar.html" (dict "context" .) }} {{ partial "toc.html" . }}
-
+
{{ partial "breadcrumb.html" (dict "page" . "enable" true) }}
{{ if .Title }}

{{ .Title }}

{{ end }} From edcc6ea587982bb6c3fd03d2c0c1b22ef62d43ea Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 23 Mar 2026 11:18:45 +0100 Subject: [PATCH 051/180] changelog: Keycloak 26.5.6 --- .../changelog/2026/03-23-keycloak-26.5.6.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 content/changelog/2026/03-23-keycloak-26.5.6.md diff --git a/content/changelog/2026/03-23-keycloak-26.5.6.md b/content/changelog/2026/03-23-keycloak-26.5.6.md new file mode 100644 index 000000000..3a06de41d --- /dev/null +++ b/content/changelog/2026/03-23-keycloak-26.5.6.md @@ -0,0 +1,30 @@ +--- +title: Keycloak 26.5.6 (security update) +description: Keycloak 26.5.6 fixes eight CVEs including SSRF, IDOR, privilege escalation and information disclosure vulnerabilities +date: 2026-03-23 +tags: + - addons + - keycloak +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[The release 26.5.6](https://github.com/keycloak/keycloak/releases/tag/26.5.6) of Keycloak is available on Clever Cloud. It addresses eight security vulnerabilities: [CVE-2026-1180](https://nvd.nist.gov/vuln/detail/CVE-2026-1180), [CVE-2026-1035](https://nvd.nist.gov/vuln/detail/CVE-2026-1035), [CVE-2025-14777](https://nvd.nist.gov/vuln/detail/CVE-2025-14777), [CVE-2025-14082](https://nvd.nist.gov/vuln/detail/CVE-2025-14082), [CVE-2026-3121](https://nvd.nist.gov/vuln/detail/CVE-2026-3121), [CVE-2026-3190](https://nvd.nist.gov/vuln/detail/CVE-2026-3190), [CVE-2026-3911](https://nvd.nist.gov/vuln/detail/CVE-2026-3911) and [CVE-2026-2366](https://nvd.nist.gov/vuln/detail/CVE-2026-2366). + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_KEYCLOAK_VERSION` of the underlying Java application to `26.5.6` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever keycloak version check yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId 26.5.6 +``` + +- [Learn more about Keycloak on Clever Cloud](/doc/addons/keycloak) From 300b77915e73e805a1280db42b126451e4578766 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 23 Mar 2026 11:18:58 +0100 Subject: [PATCH 052/180] changelog: Metabase 59 is default --- .../2026/03-23-metabase-59-default.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 content/changelog/2026/03-23-metabase-59-default.md diff --git a/content/changelog/2026/03-23-metabase-59-default.md b/content/changelog/2026/03-23-metabase-59-default.md new file mode 100644 index 000000000..506bf1850 --- /dev/null +++ b/content/changelog/2026/03-23-metabase-59-default.md @@ -0,0 +1,33 @@ +--- +title: Metabase 59 is now used by default +description: Metabase 59 is now the default version for new and existing add-ons on Clever Cloud +date: 2026-03-23 +tags: + - addons + - metabase +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +The `x.59` branch of Metabase is available on Clever Cloud [since earlier this month](/changelog/2026/03-03-metabase-59/). It's now the default branch deployed with the [release 0.59.3](https://github.com/metabase/metabase/releases/tag/v0.59.3). It means that: +- All new add-ons will use it +- All add-ons using default configuration (`community-latest`) will use it after a rebuild + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_METABASE_VERSION` of the underlying Java application to `0.59` or `1.59` for the enterprise edition (EE) and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever metabase version check yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId 0.59 +``` + +- [Learn more about Metabase 59](https://www.metabase.com/changelog/59) +- [Learn more about Metabase on Clever Cloud](/doc/addons/metabase/) From 57b20c6c0c9e3cb4706c71dff98bb7ff8272b693 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 23 Mar 2026 16:02:00 +0100 Subject: [PATCH 053/180] changelog: fix Keycloak 26.5.6 links --- content/changelog/2026/03-23-keycloak-26.5.6.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/changelog/2026/03-23-keycloak-26.5.6.md b/content/changelog/2026/03-23-keycloak-26.5.6.md index 3a06de41d..12d95e14b 100644 --- a/content/changelog/2026/03-23-keycloak-26.5.6.md +++ b/content/changelog/2026/03-23-keycloak-26.5.6.md @@ -15,7 +15,7 @@ authors: excludeSearch: true --- -[The release 26.5.6](https://github.com/keycloak/keycloak/releases/tag/26.5.6) of Keycloak is available on Clever Cloud. It addresses eight security vulnerabilities: [CVE-2026-1180](https://nvd.nist.gov/vuln/detail/CVE-2026-1180), [CVE-2026-1035](https://nvd.nist.gov/vuln/detail/CVE-2026-1035), [CVE-2025-14777](https://nvd.nist.gov/vuln/detail/CVE-2025-14777), [CVE-2025-14082](https://nvd.nist.gov/vuln/detail/CVE-2025-14082), [CVE-2026-3121](https://nvd.nist.gov/vuln/detail/CVE-2026-3121), [CVE-2026-3190](https://nvd.nist.gov/vuln/detail/CVE-2026-3190), [CVE-2026-3911](https://nvd.nist.gov/vuln/detail/CVE-2026-3911) and [CVE-2026-2366](https://nvd.nist.gov/vuln/detail/CVE-2026-2366). +[The release 26.5.6](https://github.com/keycloak/keycloak/releases/tag/26.5.6) of Keycloak is available on Clever Cloud. It addresses eight security vulnerabilities: [CVE-2026-1180](https://github.com/advisories/GHSA-7vw6-5q2f-7w5r), [CVE-2026-1035](https://github.com/advisories/GHSA-m2w5-7xhv-w6fh), [CVE-2025-14777](https://github.com/advisories/GHSA-4cj5-g32w-86fv), [CVE-2025-14082](https://github.com/advisories/GHSA-6q37-7866-h27j), [CVE-2026-3121](https://github.com/keycloak/keycloak/issues/46719), [CVE-2026-3190](https://github.com/keycloak/keycloak/issues/46723), [CVE-2026-3911](https://github.com/advisories/GHSA-xh32-c9wx-phrp) and [CVE-2026-2366](https://github.com/advisories/GHSA-r8jr-wg88-fq5c). You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_KEYCLOAK_VERSION` of the underlying Java application to `26.5.6` and rebuild it, or use [Clever Tools](/doc/cli/operators/): From c32e3757f4b1c4449b6913992121f9c9808897b8 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 25 Mar 2026 11:11:29 +0100 Subject: [PATCH 054/180] api(howto): enhance OAuth documentation with example Co-Authored-By: Julien Durillon --- content/api/howto.md | 262 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 238 insertions(+), 24 deletions(-) diff --git a/content/api/howto.md b/content/api/howto.md index 867a028c8..747f8834b 100644 --- a/content/api/howto.md +++ b/content/api/howto.md @@ -78,40 +78,254 @@ You can request the Clever Cloud API from multiple languages through our officia ### OAuth1 -If you have an application that needs to access Clever Cloud resources on behalf of your users, you can use OAuth1. This is the recommended way to authenticate third-party applications. To manage OAuth tokens linked to your account, use the [Clever Cloud Console](https://console.clever-cloud.com/users/me/oauth-tokens). +If you have an application that needs to access Clever Cloud resources on behalf of your users, you can use OAuth1. This is the recommended way to authenticate third-party applications. + +- To manage OAuth tokens linked to your account, use the [Clever Cloud Console](https://console.clever-cloud.com/users/me/oauth-tokens) +- A complete working example (Node.js) is available at [github.com/CleverCloud/oauth1-example](https://github.com/CleverCloud/oauth1-example) #### Create an OAuth consumer -First, you'll need to create an OAuth consumer for your application. This can be done in the [Clever Cloud console](https://console.clever-cloud.com). Go to your organisation, click on **Create…**, then on **an OAuth consumer** and fill the form. You will get a consumer key and a consumer secret for your application. +First, you'll need to create an OAuth consumer for your application. This can be done in the [Clever Cloud Console](https://console.clever-cloud.com). Go to your organisation, click on **Create…**, then on **an OAuth consumer** and fill the form. You will get: -#### Integrate your application +* A **consumer key** (public identifier for your application) +* A **consumer secret** (private key, never expose it client-side) -Your application must implement the OAuth 1 dance. It mostly consists of the following steps: +> [!NOTE] +> The **base URL** you set when creating the consumer is important: the callback URL you use during the OAuth flow must match this base URL's domain. For local development, register a separate consumer with `http://localhost:` as the base URL. -* Get a "request token" - * [`POST /oauth/request_token`](/api/v2/#post-/oauth/request_token) - * You will get a temporary `oauth_token` and `oauth_token_secret` -* Redirect the user to the authorization page with the `oauth_token` - * [`GET /oauth/authorize`](/api/v2/#get-/oauth/authorize) - * Once the user is logged in, the browser will be redirected to your application with the query params `oauth_verifier` and `oauth_token` -* Make sure the `oauth_token` from the first step matches the one you get after the redirection -* Get the "access token" with the `oauth_token`, `oauth_token_secret` and `oauth_verifier` - * [`POST /oauth/access_token`](/api/v2/#post-/oauth/access_token) - * You will get the user `oauth_token` and `oauth_token_secret` +#### The OAuth1 flow -Once done, your application can make API requests on behalf of the user with an OAuth 1 compatible client and the following tokens: +Your application must implement the [OAuth 1.0a flow](https://oauth.net/core/1.0a/) (also known as the "OAuth dance"). It consists of three steps: obtaining a request token, redirecting the user for authorization, and exchanging for an access token. -* Consumer key -* Consumer secret -* User token -* User token secret +{{% steps %}} -More information about [OAuth dance](https://oauth.net/core/1.0/#anchor9). +##### Get a request token -#### About the OAuth1 signature +Request a temporary token from the API. OAuth parameters can be sent as query string parameters or as a form-encoded body: -There are 3 supported methods for the signature: `PLAINTEXT`, `HMAC-SHA1` and `HMAC-SHA512`. While `PLAINTEXT` is way easier, `HMAC-SHA512` ensures that the request is totally verified. The `Authorization` header must start with `OAuth`, with a specific format for key/values: +* `POST https://api.clever-cloud.com/v2/oauth/request_token_query` — parameters as query string +* `POST https://api.clever-cloud.com/v2/oauth/request_token` — parameters as `application/x-www-form-urlencoded` body -```bash -Authorization: OAuth key="value", key2="value2" +**Required parameters:** + +| Parameter | Description | +|---|---| +| `oauth_consumer_key` | Your consumer key | +| `oauth_signature_method` | `HMAC-SHA512`, `HMAC-SHA1`, or `PLAINTEXT` | +| `oauth_signature` | Request signature (see [Signing requests](#signing-requests)) | +| `oauth_timestamp` | Current Unix timestamp (seconds) | +| `oauth_nonce` | Unique random string for this request | +| `oauth_version` | Must be `1.0` | +| `oauth_callback` | URL to redirect the user to after authorization | + +**Example request** (see [Signing requests](#signing-requests) for how to compute the signature): + +```javascript +const url = "https://api.clever-cloud.com/v2/oauth/request_token_query"; + +const params = { + oauth_consumer_key: CONSUMER_KEY, + oauth_signature_method: "HMAC-SHA512", + oauth_timestamp: Math.floor(Date.now() / 1000).toString(), + oauth_nonce: crypto.randomUUID().replace(/-/g, ""), + oauth_version: "1.0", + oauth_callback: "http://localhost:8080/auth/callback", +}; + +// buildBaseString and sign are defined in the "Signing requests" section +const baseString = buildBaseString("POST", url, params); +params.oauth_signature = sign(baseString); + +const qs = new URLSearchParams(params).toString(); +const res = await fetch(`${url}?${qs}`, { method: "POST" }); +``` + +> [!TIP] +> For quick testing, you can use `PLAINTEXT` with curl (percent-encode your consumer secret if it contains non-alphanumeric characters): +> ```bash +> curl -X POST "https://api.clever-cloud.com/v2/oauth/request_token_query?\ +> oauth_consumer_key=&oauth_signature_method=PLAINTEXT&\ +> oauth_signature=%26&oauth_timestamp=$(date +%s)&\ +> oauth_nonce=$(uuidgen)&oauth_version=1.0&\ +> oauth_callback=http%3A%2F%2Flocalhost%3A8080%2Fauth%2Fcallback" +> ``` + +**Response** (`application/x-www-form-urlencoded`) + +```text +oauth_token=&oauth_token_secret=&oauth_callback_confirmed=true +``` + +Store the `oauth_token_secret` securely server-side — you will need it in step 3. + +##### Redirect the user to authorize + +Redirect the user's browser to the authorization page with the request token: + +```text +https://api.clever-cloud.com/v2/oauth/authorize?oauth_token= +``` + +The user logs into Clever Cloud (if not already) and is presented with a permissions form. Available permissions are: + +| Permission | Description | +|---|---| +| `access_organisations` | Access organisations | +| `access_organisations_bills` | Access organisations' bills | +| `access_organisations_consumption_statistics` | Access organisations' consumption statistics | +| `access_organisations_credit_count` | Access organisations' credit count | +| `access_personal_information` | Access personal information | +| `manage_organisations` | Manage organisations | +| `manage_organisations_applications` | Manage organisations' applications | +| `manage_organisations_members` | Manage organisations' members | +| `manage_organisations_services` | Manage organisations' add-ons | +| `manage_personal_information` | Manage personal information | +| `manage_ssh_keys` | Manage SSH keys | + +You can retrieve this list programmatically with `GET https://api.clever-cloud.com/v2/oauth/rights`. + +Once the user approves, the browser is redirected to your `oauth_callback` URL with the following **query string** parameters: + +| Parameter | Description | +|---|---| +| `oauth_token` | The request token (must match the one from step 1) | +| `oauth_verifier` | Verification code to exchange for an access token | + +##### Exchange for an access token + +Exchange the request token and verifier for an access token. As with step 1, parameters can be sent as query string or form body: + +* `POST https://api.clever-cloud.com/v2/oauth/access_token_query` — parameters as query string +* `POST https://api.clever-cloud.com/v2/oauth/access_token` — parameters as `application/x-www-form-urlencoded` body + +**Required parameters:** + +| Parameter | Description | +|---|---| +| `oauth_consumer_key` | Your consumer key | +| `oauth_signature_method` | Same method as step 1 | +| `oauth_signature` | Request signature (signed with the request token secret from step 1) | +| `oauth_timestamp` | Current Unix timestamp (seconds) | +| `oauth_nonce` | Unique random string | +| `oauth_version` | `1.0` | +| `oauth_token` | The request token from step 1 | +| `oauth_verifier` | The verifier from the callback redirect | + +**Response** (`application/x-www-form-urlencoded`) + +```text +oauth_token=&oauth_token_secret=&expiration_date= +``` + +Store both `oauth_token` and `oauth_token_secret` securely — you will need them to sign every subsequent API request. + +> [!NOTE] +> Access tokens expire after **3 months** by default. The response includes an `expiration_date` field (ISO 8601 format). + +{{% /steps %}} + +#### Making authenticated API requests + +Once you have the four credentials (consumer key, consumer secret, user token, user token secret), sign every API request using the `Authorization` header: + +```text +Authorization: OAuth oauth_consumer_key="", oauth_token="", oauth_signature_method="HMAC-SHA512", oauth_signature="", oauth_timestamp="", oauth_nonce="", oauth_version="1.0" +``` + +**Example** — Fetching the authenticated user's profile: + +```javascript +// buildBaseString, sign and authorizationHeader are defined in the "Signing requests" section +const params = { + oauth_consumer_key: CONSUMER_KEY, + oauth_signature_method: "HMAC-SHA512", + oauth_timestamp: Math.floor(Date.now() / 1000).toString(), + oauth_nonce: crypto.randomUUID().replace(/-/g, ""), + oauth_version: "1.0", + oauth_token: ACCESS_TOKEN, +}; + +const baseString = buildBaseString("GET", "https://api.clever-cloud.com/v2/self", params); +params.oauth_signature = sign(baseString, ACCESS_TOKEN_SECRET); + +const res = await fetch("https://api.clever-cloud.com/v2/self", { + headers: { Authorization: authorizationHeader(params) }, +}); +const user = await res.json(); +``` + +#### Signing requests + +Three signature methods are supported. `HMAC-SHA512` is recommended for production use. + +##### PLAINTEXT + +The simplest method. The signature is the consumer secret and token secret, percent-encoded per RFC 3986 and joined with `&`: + +```javascript +const signature = percentEncode(consumerSecret) + "&" + percentEncode(tokenSecret); +``` + +For the request token step (where no token secret exists yet), leave the second part empty: + +```javascript +const signature = percentEncode(consumerSecret) + "&"; +``` + +> [!NOTE] +> The `percentEncode` function is defined in the [HMAC section below](#hmac-sha1sha512). For secrets containing only alphanumeric characters, `encodeURIComponent` produces the same result. + +##### HMAC-SHA1/SHA512 + +These methods sign a **base string** to ensure the request has not been tampered with. The code snippets below are a JavaScript/Node.js implementation example. + +**1.** Define a `percentEncode` helper per RFC 3986: + +```javascript +// Like encodeURIComponent, but also encodes !'()* +function percentEncode(str) { + return encodeURIComponent(str) + .replace(/[!'()*]/g, c => "%" + c.charCodeAt(0).toString(16).toUpperCase()); +} +``` + +**2.** Define `buildBaseString` — it concatenates the HTTP method, URL, and sorted parameters, all percent-encoded and joined with `&`: + +```javascript +function buildBaseString(method, url, params) { + const sorted = Object.keys(params) + .filter(k => k !== "oauth_signature") + .sort() + .map(k => percentEncode(k) + "=" + percentEncode(params[k])) + .join("&"); + return method.toUpperCase() + "&" + percentEncode(url) + "&" + percentEncode(sorted); +} +``` + +The three components are: + +* The HTTP method in uppercase (`GET`, `POST`, etc.) +* The base URL (without query string), percent-encoded +* All request parameters (OAuth parameters excluding `oauth_signature`, plus any query string or form body parameters), sorted alphabetically by key — then by value in case of duplicates — formatted as `key=value` pairs joined by `&`, then percent-encoded as a single string + +**3.** Define `sign` — it computes an HMAC with the chosen algorithm and returns the base64-encoded result: + +```javascript +const { createHmac } = await import("node:crypto"); + +function sign(baseString, tokenSecret = "") { + const signingKey = percentEncode(consumerSecret) + "&" + percentEncode(tokenSecret); + return createHmac("sha512", signingKey).update(baseString).digest("base64"); +} +``` + +**4. Build the Authorization header** with all OAuth parameters (including the signature): + +```javascript +function authorizationHeader(params) { + const pairs = Object.entries(params) + .map(([k, v]) => percentEncode(k) + '="' + percentEncode(v) + '"') + .join(", "); + return "OAuth " + pairs; +} ``` From 26733341dd4f03f27e7ea6b5d6a4ffc9b0d178fb Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 25 Mar 2026 15:22:22 +0100 Subject: [PATCH 055/180] changelog: images updates, 2026W13 --- content/changelog/2026/03-25-images-update.md | 66 +++++++++++++++++++ content/doc/applications/frankenphp.md | 2 +- data/runtime_versions.yml | 6 +- 3 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 content/changelog/2026/03-25-images-update.md diff --git a/content/changelog/2026/03-25-images-update.md b/content/changelog/2026/03-25-images-update.md new file mode 100644 index 000000000..4b7d41bea --- /dev/null +++ b/content/changelog/2026/03-25-images-update.md @@ -0,0 +1,66 @@ +--- +title: "Images update: FrankenPHP 1.12, Gradle 9.4, Rust 1.94, set PHP & Composer versions in all runtimes" +description: All runtimes updated except PHP. CC_PHP_VERSION and CC_COMPOSER_VERSION are now available in all runtimes +date: 2026-03-25 +tags: + - images + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated all our images, except PHP. Deployment is in progress for all our users. + +* **Common:** + * Linux kernel 6.19.7 + * Anubis 1.25.0 + * Chromium 146.0.7680.153 + * Clever Tools 4.7.1 + * cURL 8.19.0 + * FFmpeg 8.1 + * Ghostscript 10.07 + * OAuth2Proxy 7.14.3 + * Poppler 26.03 + * SQLite 3.52.0 + * Varnish 8.0.1 + * Vim 9.2.0096 +* **Elixir:** + * Erlang 26.2.5.18 + * Erlang 27.3.4.9 + * Erlang 28.4.1 +* **FrankenPHP:** + * Update to 1.12.1 with PHP 8.5.4 (with `CC_PHP_VERSION=8.5`) +* **Go:** + * Update to 1.26.1 +* **Java:** + * Gradle 9.4.1 + * Maven 3.9.14 +* **Node.js & Bun:** + * Update to 24.14.1 (npm 11.11.0) + * Bun 1.3.11 + * Yarn 4.13.0 +* **Python:** + * Update to 3.10.20 + * Update to 3.11.15 + * Update to 3.12.13 + * uv 0.10.12 +* **Ruby:** + * Update to 3.4.9 +* **Rust:** + * Update to 1.94.0 + * Rustup 1.29.0 +* **Static:** + * Caddy 2.11.2 + +## PHP & Composer version + +You can now set `CC_PHP_VERSION` and `CC_COMPOSER_VERSION` in any runtime. We also fixed a bug that prevented to use another Composer version than the LTS with FrankenPHP. + +## Log drains collection + +We removed old log drains stack, except for application still using it. + +- [Learn more about log drains old stack deprecation](/changelog/2026/03-19-drains-legacy/) diff --git a/content/doc/applications/frankenphp.md b/content/doc/applications/frankenphp.md index fa5087f01..50e9576a5 100644 --- a/content/doc/applications/frankenphp.md +++ b/content/doc/applications/frankenphp.md @@ -49,7 +49,7 @@ FrankenPHP runtime only requires a working web application, with an `index.php` FrankenPHP currently deployed version on Clever Cloud is `{{< runtime_version frankenphp >}}` based on PHP `{{< runtime_version frankenphp php >}}` and Caddy server `{{< runtime_version frankenphp caddy >}}`. Virtual machine image includes multiple tools from the PHP ecosystem such as Composer or Symfony CLI. The `php` command available in hooks and scripts uses `frankenphp php-cli` under the hood. -You can use FrankenPHP 1.11.3 with PHP 8.5 and Caddy 2.11.1, by setting the `CC_PHP_VERSION` environment variable to `8.5`. +You can use FrankenPHP 1.12.1 with PHP 8.5 and Caddy 2.11.2, by setting the `CC_PHP_VERSION` environment variable to `8.5`. - [FrankenPHP PHP 8.4 info](https://frankenphpinfo-8.4.cleverapps.io/) - [FrankenPHP PHP 8.5 info](https://frankenphpinfo-8.5.cleverapps.io/) diff --git a/data/runtime_versions.yml b/data/runtime_versions.yml index 4f301c592..afc1a821c 100644 --- a/data/runtime_versions.yml +++ b/data/runtime_versions.yml @@ -1,12 +1,12 @@ bun: eol_source: https://github.com/oven-sh/bun/releases default: - - 1.3.9 + - 1.3.11 caddy: eol_source: https://github.com/caddyserver/caddy/releases default: - - "2.10.2" + - "2.11.2" dotnet: eol_source: https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core @@ -56,7 +56,7 @@ java: node: eol_source: https://nodejs.org/en/about/releases/ default: - - 24.13.1 (npm 11.8.0) + - 24.14.1 (npm 11.11.0) php: eol_source: https://www.php.net/supported-versions.php From 2d0cd9925c60cd5b39d75b52f0fa4072e6b5f6d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Brunat?= Date: Fri, 27 Mar 2026 17:10:58 +0100 Subject: [PATCH 056/180] postmortem: 2026-03-19 --- content/postmortem/2026-03-19.md | 50 ++++++++++++++++++++++++++++++++ content/postmortem/_index.md | 1 + 2 files changed, 51 insertions(+) create mode 100644 content/postmortem/2026-03-19.md diff --git a/content/postmortem/2026-03-19.md b/content/postmortem/2026-03-19.md new file mode 100644 index 000000000..3131f7045 --- /dev/null +++ b/content/postmortem/2026-03-19.md @@ -0,0 +1,50 @@ +--- +title: '2026-03-19' +description: Memory pressure on Paris hypervisors causing service disruptions and deployment pipeline outage +date: 2026-03-27 +excludeSearch: true +type: docs +--- + +{{< hextra/hero-subtitle >}} + +A memory pressure spike on ~15 Paris hypervisors, amplified by a placement algorithm threshold effect, caused cascading failures impacting hosted services, the deployment pipeline, and observability from 15:50 to 18:10 UTC. + +{{< /hextra/hero-subtitle >}} + +The overloaded servers triggered OS memory reclaim (OOM), which consumed CPU and destabilized HAProxy load balancers — causing connectivity loss for hosted services and paralyzing internal automation (deployments, monitoring). Full recovery at 21:41 UTC. + +Status updates were communicated through: https://www.clevercloudstatus.com/ + +### Timeline + +| Time | Description | +| ---------------- | ------------------------------------------------------------ | +| 2026-03-19 15:20 | First degradation signals on internal services (VM registration, deployment service database access). No client impact yet. | +| 2026-03-19 15:50 | Degradations and outages visible for some clients. Services hosted on the most impacted hypervisors become unreachable. Deployment pipeline stops. | +| 2026-03-19 16:03 | Incident response team mobilized. Mitigation actions begin: stabilizing hypervisors under pressure, progressive restart of internal clusters (messaging, observability), partial deployment resumption. | +| 2026-03-19 17:30 | Full restoration of the deployment pipeline and observability. Continued recovery of impacted client services. | +| 2026-03-19 18:09 | All impacted client services restored. Residual alert cleanup begins. | +| 2026-03-19 21:41 | Service fully operational. All residual alerts cleared. | + +## Analysis + +A simultaneous memory consumption spike was observed on approximately fifteen host servers (hypervisors) in the Paris region. The primary suspect identified is the TCP load balancing process (HAProxy), whose instrumentation at the time did not provide sufficient granularity to immediately confirm the cause. + +An amplifying effect from the placement algorithm was identified: servers were already running at elevated load due to a threshold effect in the orchestration and resource placement algorithm. During rapid workload growth, the algorithm did not distribute load linearly beyond a certain threshold, causing some servers to be more heavily loaded than they should have been. + +The memory saturation triggered the OS memory reclaim mechanism, which itself is highly CPU-intensive. This sudden CPU load spike disrupted the load balancers (HAProxy) on those servers — cascading into connectivity interruptions for hosted services and temporarily paralyzing internal automation services (deployments, monitoring). + +### Actions + +* Placement scoring function modified to smooth load ramp-up and eliminate the threshold effect +* Dedicated monitoring added to detect any recurrence of this behavior +* Temporary fixes deployed immediately during the incident to mitigate effects +* Partial algorithm redesign to guarantee smooth and optimal resource allocation under high load +* Enhanced observability for non-VM processes running on hypervisors (especially HAProxy instrumentation) +* Memory pressure resilience improvements prioritized and delivered within the following week +* Two new datacenters planned for the Paris region in Q2 2026 to increase capacity and reduce sensitivity to load spikes + +### Conclusion + +The incident was caused by a combination of a memory spike on HAProxy processes and a placement algorithm threshold effect that left insufficient headroom on impacted hypervisors. The response team was mobilized quickly and worked continuously until full recovery. Corrective measures have been applied to the placement algorithm, and observability improvements have been deployed to accelerate future diagnosis. Additional Paris datacenter capacity is planned for Q2 2026. diff --git a/content/postmortem/_index.md b/content/postmortem/_index.md index 44db2ad46..20c65c1af 100644 --- a/content/postmortem/_index.md +++ b/content/postmortem/_index.md @@ -9,3 +9,4 @@ type: docs - [2024-08-02](/postmortem/2024-08-02) - [2025-03-03](/postmortem/2025-03-03) - [2025-10-09](/postmortem/2025-10-09) +- [2026-03-19](/postmortem/2026-03-19) From 783bc0c62df25af17047c1cb629bc273c94f36f4 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Fri, 27 Mar 2026 14:24:58 +0100 Subject: [PATCH 057/180] reference(env vars): fix a typo --- content/doc/reference/reference-environment-variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/doc/reference/reference-environment-variables.md b/content/doc/reference/reference-environment-variables.md index 3c9acd1e4..67860ea5b 100644 --- a/content/doc/reference/reference-environment-variables.md +++ b/content/doc/reference/reference-environment-variables.md @@ -192,7 +192,7 @@ If `TAILSCALE_LOGIN_SERVER` is provided, the agent will be configured to reach a | Name | Description | Default value | |---------------|-------------| ---------------| | `CC_FRANKENPHP_PORT` | The port on which FrankenPHP listens for HTTP requests | `8080` | -| `CC_FRANKENPHP_WORKER` | Path to the worker script, relative to the root of your project (e.g. `/worker/scrip.php`) | | +| `CC_FRANKENPHP_WORKER` | Path to the worker script, relative to the root of your project (e.g. `/worker/script.php`) | | | `CC_PHP_COMPOSER_FLAGS` | Flags to pass to Composer | `-n --no-dev --no-progress --no-scripts` | | `CC_PHP_DEV_DEPENDENCIES` | Set to `install` to install PHP development dependencies during build | | | `CC_WEBROOT` | Path to the web content to serve, relative to the root of your application | `/` | From 0a92ddfafb1d2384d469690f99a317c7ac67ecef Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 30 Mar 2026 10:25:08 +0200 Subject: [PATCH 058/180] changelog: Otoroshi 17.14 --- .../changelog/2026/03-30-otoroshi-17.14.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 content/changelog/2026/03-30-otoroshi-17.14.md diff --git a/content/changelog/2026/03-30-otoroshi-17.14.md b/content/changelog/2026/03-30-otoroshi-17.14.md new file mode 100644 index 000000000..e054921cf --- /dev/null +++ b/content/changelog/2026/03-30-otoroshi-17.14.md @@ -0,0 +1,34 @@ +--- +title: Otoroshi 17.14 enhances remote catalogs, adds PostgreSQL data export and MCP OAuth2 enforcement +description: Remote catalogs with Kubernetes manifests and YAML support, PostgreSQL data exporter, mandatory flags on plugins, expression language improvements and LLM extension with 40+ new providers and MCP OAuth2 +date: 2026-03-30 +tags: + - addons + - otoroshi +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Otoroshi v17.14](https://github.com/MAIF/otoroshi/releases/tag/v17.14.0) is available with significant enhancements to [remote catalogs](https://maif.github.io/otoroshi/manual/topics/remote-catalogs.html). They now support organisation scanning, additional GitHub-like providers, pattern-based and YAML-formatted descriptor files, as well as Kubernetes-like manifests. This release also introduces PostgreSQL as a data exporter target and adds Redis Sentinel password support with the Lettuce driver. + +New mandatory flags are available on client certificate plugins and OIDC JWT verification for APIs, offering finer control over authentication requirements. The expression language has been improved with path-based read support for deep structures such as user profiles, with complex structure stringification. Several fixes address tunnel handler plugin visibility, Kafka data exporter host validation, and the "Override Location header" plugin behaviour. + +This release includes LLM extension [0.0.74](https://github.com/cloud-apim/otoroshi-llm-extension/releases/tag/0.0.74), adding over 40 new providers through an enhanced generic OpenAI client (including Minimax, Morph, Cloud Temple and many others). MCP exposition has been significantly strengthened with all missing methods now implemented (resources, templates, prompts), fine-grained per-tool scoped authorisations, and OAuth2 enforcement with a new MCP Protected Resource Metadata document plugin. This version also introduces an OpenAI Responses proxy plugin and [a new OpenAI API aggregator plugin](https://cloud-apim.github.io/otoroshi-llm-extension/docs/llm-gateway/openai-compat-api/) that unifies chat completions, responses, Anthropic /messages and context serving into a single endpoint. + +You can update through add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_OTOROSHI_VERSION` of the underlying Java application to `v17.14.0_1774627527` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever otoroshi version check yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId v17.14.0_1774627527 +``` + +- [Learn more about Otoroshi with LLM on Clever Cloud](/doc/addons/otoroshi/) From 969acbdd2d1978e2629dda21bab56c1908b9bd79 Mon Sep 17 00:00:00 2001 From: Baptiste Le Morlec Date: Wed, 11 Mar 2026 17:30:46 +0100 Subject: [PATCH 059/180] addons(kv): add GETDEL, HEXISTS, HSETNX, HINCRBY, EXPIREAT, PEXPIREAT commands --- .../styles/config/vocabularies/Doc/accept.txt | 1 + .../03-30-materia-kv-hash-ttl-commands.md | 35 +++++++++++++++++++ content/doc/addons/materia-kv.md | 12 +++++-- 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 content/changelog/2026/03-30-materia-kv-hash-ttl-commands.md diff --git a/.github/styles/config/vocabularies/Doc/accept.txt b/.github/styles/config/vocabularies/Doc/accept.txt index 200ebcc50..148795ae7 100644 --- a/.github/styles/config/vocabularies/Doc/accept.txt +++ b/.github/styles/config/vocabularies/Doc/accept.txt @@ -61,6 +61,7 @@ LLM LLMs Lume LTS +Materia Matomo maven Metabase diff --git a/content/changelog/2026/03-30-materia-kv-hash-ttl-commands.md b/content/changelog/2026/03-30-materia-kv-hash-ttl-commands.md new file mode 100644 index 000000000..ca180d26a --- /dev/null +++ b/content/changelog/2026/03-30-materia-kv-hash-ttl-commands.md @@ -0,0 +1,35 @@ +--- +title: "Materia KV: new hash, string and TTL commands" +description: Materia KV now supports GETDEL, HEXISTS, HSETNX, HINCRBY, EXPIREAT and PEXPIREAT commands, with improved scan reliability and transaction retry limits. +date: 2026-03-30 +tags: + - addons + - materia + - kv +authors: + - name: Baptiste Le Morlec + link: https://github.com/baptiste-le-m + image: https://github.com/baptiste-le-m.png?size=40 + - name: Pierre Zemb + link: https://github.com/pierrez + image: https://github.com/pierrez.png?size=40 +excludeSearch: true +--- + +[Materia KV](/doc/addons/materia-kv/) adds six new commands to its Redis-compatible layer, expanding hash, string and time to live (TTL) management capabilities: + +- `HEXISTS`: check if a field exists in a hash +- `HSETNX`: set a hash field only if it doesn't already exist +- `HINCRBY`: increment the integer value of a hash field +- `GETDEL`: atomically get a string value and delete its key +- `EXPIREAT`: set key expiration using an absolute Unix timestamp in seconds +- `PEXPIREAT`: set key expiration using an absolute Unix timestamp in milliseconds + +These commands are available for new and already deployed add-ons, with no additional configuration. + +This update also brings reliability improvements to the underlying storage layer. The `SCAN`, `HSCAN`, `SSCAN` and `KEYS` commands now produce more accurate results thanks to fixes in the scan boundary logic. + +Transactions now enforce retry limits of 5 retries with a 5-second timeout, preventing cascading retries from overwhelming the cluster under heavy contention. Previously, transactions could retry indefinitely on conflict. + +- [Learn more about Materia KV](/doc/addons/materia-kv/) +- [Learn more about Materia KV supported commands](/doc/addons/materia-kv/#supported-types-and-commands) diff --git a/content/doc/addons/materia-kv.md b/content/doc/addons/materia-kv.md index cdcb7163e..0eb806de9 100644 --- a/content/doc/addons/materia-kv.md +++ b/content/doc/addons/materia-kv.md @@ -153,20 +153,25 @@ Find below the list of currently supported commands: | `DECRBY` | Decrements the number stored at `key` by the given `decrement`. If the `key` doesn't exist, it is set to `0` before performing the operation. An error is returned if `key` contains a value of the wrong type or contains a string that can not be represented as integer. This operation is limited to 64-bit signed integers. | | `DEL` | Removes the specified `key`. A key is ignored if it doesn't exist. | | `EXISTS` | Returns if `key` exists. | -| `EXPIRE` | Set a `key` time to live in seconds. After the timeout has expired, the `key` will be automatically deleted. The time to live can be updated using the `EXPIRE` command or cleared using the `PERSIST` command. | +| `EXPIRE` | Set a `key` time to live in seconds. After the timeout has expired, the `key` will be automatically deleted. The time to live can be updated using the `EXPIRE` command or cleared using the `PERSIST` command. | +| `EXPIREAT` | Sets a `key` to expire at the specified Unix timestamp (in seconds). After that time, the `key` is automatically deleted. Returns `1` if the timeout was set, `0` if the `key` doesn't exist. | | `FLUSHALL` | Delete all the keys of all the existing databases, not just the currently selected one. This command never fails. | | `FLUSHDB` | Delete all the keys of the currently selected DB. This command never fails. | | `GET` | Get the value of `key`. If the `key` doesn't exist the special value nil is returned. An error is returned if the value stored at `key` is not a string, because `GET` only handles string values. | | `GETBIT` | Returns the bit value at offset in the string value stored at `key`. | +| `GETDEL` | Gets the value of `key` and deletes the key. If the `key` doesn't exist, returns `nil`. Returns an error if the value stored at `key` isn't a string. | | `GETRANGE` | Returns the substring of the string value stored at `key`, determined by the offsets start and end (both are inclusive). Negative offsets can be used in order to provide an offset starting from the end of the string. So `-1` means the last character, `-2` the penultimate and so forth. | | `HDEL` | Removes the specified fields from the hash stored at `key`. Specified fields that do not exist within this hash are ignored. If `key` does not exist, it is treated as an empty hash and this command returns `0`. | | `HELLO` | Switch to a different protocol, optionally authenticating and setting the connection's name, or provide a contextual client report. It always replies with a list of current server and connection properties. | +| `HEXISTS` | Returns `1` if `field` exists in the hash stored at `key`, `0` if `field` or `key` don't exist. Returns an error if the value stored at `key` isn't a hash. | | `HGET` | Returns the value associated with `field` in the hash stored at `key`. If `key` does not exist, or `field` is not present in the hash, `nil` is returned. | | `HGETALL` | Returns all fields and values of the hash stored at `key`. In the returned value, every field name is followed by its value, so the length of the reply is twice the size of the hash. | +| `HINCRBY` | Increments the number stored at `field` in the hash stored at `key` by the given `increment`. If `key` doesn't exist, creates a new key holding a hash. If `field` doesn't exist, sets the value to `0` before performing the operation. Returns an error if the field contains a value of the wrong type or the resulting value exceeds a 64-bit signed integer. | | `HLEN` | Returns the number of fields contained in the hash stored at `key`. If `key` does not exist, it is treated as an empty hash and `0` is returned. | | `HMGET` | Returns the values associated with the specified `fields` in the hash stored at `key`. For every field that does not exist in the hash, a `nil` value is returned. Because of this, the operation never fails. | | `HSCAN` | Incrementally iterate over hash fields and associated values. It is a cursor based iterator, this means that at every call of the command, the server returns an updated cursor that the user needs to use as the cursor argument in the next call. An iteration starts when the cursor is set to `0`, and terminates when the cursor returned by the server is `0`. | | `HSET` | Sets the specified fields to their respective values in the hash stored at `key`. If `key` does not exist, a new key holding a hash is created. If `key` exists but does not hold a hash, an error is returned. | +| `HSETNX` | Sets `field` in the hash stored at `key` to `value`, only if `field` doesn't yet exist. If `key` doesn't exist, creates a new key holding a hash. If `field` already exists, the operation has no effect. Returns `1` if `field` is a new field in the hash and the value was set, `0` if `field` already exists. | | `INCR` | Increments the number stored at `key` by one. If the `key` doesn't exist, it is set to `0` before performing the operation. An error is returned if `key` contains a value of the wrong type or contains a string that can not be represented as integer. This operation is limited to 64-bit signed integers. | | `INCRBY` | Increments the number stored at `key` by the given `increment`. If the `key` doesn't exist, it is set to `0` before performing the operation. An error is returned if `key` contains a value of the wrong type or contains a string that can not be represented as integer. This operation is limited to 64-bit signed integers. | | `INCRBYFLOAT` | Increment the string representing a floating point number stored at `key` by the specified `increment`. If the key does not exist, it is set to `0` before performing the operation. An error is returned if the key contains a value of the wrong type or a string that can not be represented as a floating point number. | @@ -179,7 +184,8 @@ Find below the list of currently supported commands: | `MGET` | Returns the values of all specified keys. For every key that doesn't hold a string value or doesn't exist, the special value `nil` is returned. Because of this, the operation never fails. | | `MSET` | Sets the given keys to their respective values. `MSET` replaces existing values with new values, just as regular `SET`. `MSET` is atomic, so all given keys are set at once. It is not possible for clients to see that some keys were updated while others are unchanged. | | `PERSIST` | Remove the existing time to live associated with the `key`. | -| `PEXPIRE` | Set a `key` time to live in milliseconds. After the timeout has expired, the `key` will be automatically deleted. The time to live can be updated using the `PEXPIRE` command or cleared using the `PERSIST` command. | +| `PEXPIRE` | Set a `key` time to live in milliseconds. After the timeout has expired, the `key` will be automatically deleted. The time to live can be updated using the `PEXPIRE` command or cleared using the `PERSIST` command. | +| `PEXPIREAT` | Sets a `key` to expire at the specified absolute Unix timestamp in milliseconds. After that time, the `key` is automatically deleted. Returns `1` if the timeout was set, `0` if the `key` doesn't exist. | | `PING` | Returns `PONG` if no argument is provided, otherwise return a copy of the argument as a bulk. | | `PTTL` | Returns the remaining time to live of a `key`, in milliseconds. | | `SADD` | Add the specified members to the set stored at `key`. Specified members that are already a member of this set are ignored. If `key` doesn't exist, a new set is created before adding the specified members. | @@ -200,9 +206,9 @@ Find below the list of currently supported commands: | `SRANDMEMBER` | When called with just the `key` argument, return a random element from the set value stored at `key`. | | `SREM` | Remove the specified members from the set stored at `key`. Specified members that are not a member of this set are ignored. If `key` doesn't exist, it is treated as an empty set and this command returns `0`. | | `SSCAN` | Incrementally iterate over set elements. It is a cursor based iterator, this means that at every call of the command, the server returns an updated cursor that the user needs to use as the cursor argument in the next call. An iteration starts when the cursor is set to `0`, and terminates when the cursor returned by the server is `0`. | +| `STRLEN` | Returns the length of the string value stored at `key`. An error is returned when key holds a non-string value. | | `SUNION` | Returns the members of the set resulting from the union of all the given sets. | | `SUNIONSTORE` | This command is equal to `SUNION`, but instead of returning the resulting set, it is stored in `destination`. If `destination` already exists, it is overwritten. | -| `STRLEN` | Returns the length of the string value stored at `key`. An error is returned when key holds a non-string value. | | `TTL` | Returns the remaining time to live of a `key`, in seconds. | | `TYPE` | Returns the string representation of the type of the value stored at `key`. Can be: `hash`, `list`, `set` or `string`. | From b4b7368ee5e228c4ab44a974141867a168456478 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 30 Mar 2026 11:59:51 +0200 Subject: [PATCH 060/180] changelog: invoices per sub-orgs --- .../03-30-invoice-sub-organisation-summary.md | 24 ++++++++++++++++++ .../invoice-sub-organisation-summary.png | Bin 0 -> 364773 bytes 2 files changed, 24 insertions(+) create mode 100644 content/changelog/2026/03-30-invoice-sub-organisation-summary.md create mode 100644 static/images/invoice-sub-organisation-summary.png diff --git a/content/changelog/2026/03-30-invoice-sub-organisation-summary.md b/content/changelog/2026/03-30-invoice-sub-organisation-summary.md new file mode 100644 index 000000000..9c23a95d6 --- /dev/null +++ b/content/changelog/2026/03-30-invoice-sub-organisation-summary.md @@ -0,0 +1,24 @@ +--- +title: Invoices now group services by sub-organisation +date: 2026-03-30 +description: Invoices now group billed services by sub-organisation with a summary at the beginning. +tags: + - billing +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 + - name: Théo Mémin + link: https://github.com/jenoh + image: https://github.com/jenoh.png?size=40 +excludeSearch: true +--- + +Some of our customers operate organisations on behalf of third parties, such as business providers, and manage sub-organisations for them. Starting with the next billing cycle, invoices will be improved for these use cases: + +- Billed services will be grouped by sub-organisation within the invoice +- A per sub-organisation summary will be included at the beginning of the invoice, providing a clear overview of costs + +This makes it easier to track and allocate costs across sub-organisations, without changing the existing detailed breakdown. + +![Invoice detail (left) and new sub-organisation summary (right)](/images/invoice-sub-organisation-summary.png "Current invoice detail on the left, new per sub-organisation summary on the right") diff --git a/static/images/invoice-sub-organisation-summary.png b/static/images/invoice-sub-organisation-summary.png new file mode 100644 index 0000000000000000000000000000000000000000..1645edb08b45e0540d4fc7ed1ffe0fe771d4f3f2 GIT binary patch literal 364773 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%0Ds?3wU(JPeTa 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 Date: Fri, 3 Apr 2026 08:33:01 +0200 Subject: [PATCH 061/180] changelog: mySQL 8.0.45 and 8.4.8 --- .../2026/03-31-mysql-8.0.45-8.4.8.md | 19 +++++++++++++++++++ data/software_versions_shared_dedicated.yml | 6 +++--- 2 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 content/changelog/2026/03-31-mysql-8.0.45-8.4.8.md diff --git a/content/changelog/2026/03-31-mysql-8.0.45-8.4.8.md b/content/changelog/2026/03-31-mysql-8.0.45-8.4.8.md new file mode 100644 index 000000000..625ce0561 --- /dev/null +++ b/content/changelog/2026/03-31-mysql-8.0.45-8.4.8.md @@ -0,0 +1,19 @@ +--- +title: MySQL 8.0.45 and 8.4.8 are available +description: Create MySQL 8.0.45 and 8.4.8 add-ons or migrate your existing MySQL add-ons to these versions +date: 2026-03-31 +tags: + - addons + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated MySQL images for releases 8.4 (8.4.8-8) and 8.0 (8.0.45-36). They're now available for add-ons creation and migration. + +* [Learn more about MySQL on Clever Cloud](/doc/addons/mysql/) +* [Learn more about MySQL 8.0.45](https://docs.percona.com/percona-server/8.0/release-notes/8.0.45-36.html) +* [Learn more about MySQL 8.4.8](https://docs.percona.com/percona-server/8.4/release-notes/8.4.8-8.html) diff --git a/data/software_versions_shared_dedicated.yml b/data/software_versions_shared_dedicated.yml index 308c19005..770c206f4 100644 --- a/data/software_versions_shared_dedicated.yml +++ b/data/software_versions_shared_dedicated.yml @@ -15,10 +15,10 @@ mongo: mysql: dedicated: - 5.7 (EOL) - - 8.0.44 - - 8.4.7 + - 8.0.45 + - 8.4.8 dev: - - 8.0.44 + - 8.0.45 pg: dedicated: From e81434c6733c88756eb4b9b6faac76a0f0251bb7 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Thu, 2 Apr 2026 19:07:00 +0200 Subject: [PATCH 062/180] changelog: Terraform 1.11.0 --- .../changelog/2026/04-02-terraform-1.11.0.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 content/changelog/2026/04-02-terraform-1.11.0.md diff --git a/content/changelog/2026/04-02-terraform-1.11.0.md b/content/changelog/2026/04-02-terraform-1.11.0.md new file mode 100644 index 000000000..255a45f82 --- /dev/null +++ b/content/changelog/2026/04-02-terraform-1.11.0.md @@ -0,0 +1,20 @@ +--- +title: Terraform provider 1.11.0 +description: New resources, improved validation, retry logic and bug fixes in the Clever Cloud Terraform provider +date: 2026-04-02 +tags: + - addons + - terraform +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 + - name: Rémi Collignon-Ducret + link: https://github.com/miton18 + image: https://github.com/miton18.png?size=40 +excludeSearch: true +--- + +The [1.11.0 release](https://github.com/CleverCloud/terraform-provider-clevercloud/releases/tag/v1.11.0) of the Clever Cloud Terraform provider is available. It adds new resources (add-on provider, OAuth consumer, vulnerability scanner), improved validation for environment variables, add-on options and plan slugs, exponential backoff retry for 503 errors, and multiple bug fixes. + +* Learn more about [Clever Cloud Terraform provider](https://registry.terraform.io/providers/CleverCloud/clevercloud/latest/docs) From 666eda40ba12bf2ad6d1bcd331c46c30bc2bb036 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 8 Apr 2026 11:13:19 +0200 Subject: [PATCH 063/180] changelog: images updates, 2026W15 --- content/changelog/2026/04-07-images-update.md | 35 +++++++++++++++++++ data/runtime_versions.yml | 4 +-- 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 content/changelog/2026/04-07-images-update.md diff --git a/content/changelog/2026/04-07-images-update.md b/content/changelog/2026/04-07-images-update.md new file mode 100644 index 000000000..65c7d3a6f --- /dev/null +++ b/content/changelog/2026/04-07-images-update.md @@ -0,0 +1,35 @@ +--- +title: "Images update: mdBook 0.5, Static Web Server 2.42, OAuth2Proxy 7.15.1, Tailscale 1.96.3" +description: Many tool updates: check your mdBook configuration for the 0.5 release +date: 2026-04-07 +tags: + - images + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated all our images, except PHP. Deployment is in progress for all our users. + +* **Common:** + * Linux kernel 6.19.11 + * nginx 1.28.3 + * OAuth2Proxy 7.15.1 + * Tailscale 1.96.3 +* **Python:** + * uv 0.11.2 +* **Ruby:** + * Update to 3.3.11 + * Update to 4.0.2 +* **Static:** + * mdBook 0.5.2 + * Static Web Server 2.42.0 +* **V (Vlang):** + * Update to 0.5.1 + +## mdBook 0.5 + +mdBook 0.5 introduces some breaking changes. If your configuration is not yet compatible, follow the [migration guide](https://github.com/rust-lang/mdBook/blob/master/CHANGELOG.md#05-migration-guide). diff --git a/data/runtime_versions.yml b/data/runtime_versions.yml index afc1a821c..b41e1bc64 100644 --- a/data/runtime_versions.yml +++ b/data/runtime_versions.yml @@ -80,7 +80,7 @@ php: sws: eol_source: https://github.com/static-web-server/static-web-server/releases default: - - "2.40.1" + - "2.42.0" varnish: eol_source: https://varnish-cache.org/releases/ @@ -95,7 +95,7 @@ varnish-modules: v: eol_source: https://github.com/vlang/v/releases default: - - "0.5" + - "0.5.1" python: eol_source: https://devguide.python.org/versions/#python-release-cycle From d2ffce891d2bf88d7da9a36b430617925c6e2ded Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 8 Apr 2026 11:45:21 +0200 Subject: [PATCH 064/180] shared(ruby): fix Ruby version rules --- shared/ruby.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/shared/ruby.md b/shared/ruby.md index 33a0bad78..21283d4d8 100644 --- a/shared/ruby.md +++ b/shared/ruby.md @@ -13,17 +13,13 @@ You need to provide a `gems.locked` or `Gemfile.lock` file. To do that ensure yo ### Choose ruby version -If you specify a ruby version in your `gems.rb` of `Gemfile`, we'll use it, otherwise; keep reading. +You can specify a Ruby version in your `gems.rb` or `Gemfile`, or set `CC_RUBY_VERSION=`, where `` can be, for example: -On your Clever Cloud application create an [environment variable](#setting-up-environment-variables-on-clever-cloud) `CC_RUBY_VERSION=rubyversion` where `rubyversion` represents: +* "4" will select the greatest "4.x.y" version available. +* "4.0" will select the greatest "4.0.y" version available. +* "4.0.1" will select the "4.0.1" version. -* "3" will select the greatest "3.X.Y" version available. -* "3.3" will select the greatest "3.3.Y" version available. -* "3.3.1" will select the "3.3.1" version. - -Due to current landscape in ruby applications, the default version is the greatest 3.3.Y. We also provide versions 2.3.Y, 2.4.Y, 2.5.Y, 2.6.Y and 2.7.Y. - -If given `rubyversion` does not match any available version, your deployment will fail. +If the given `` does not match any available version, your deployment will fail. If no version is specified, the latest 4.x version available on the image is used. Versions from the 4.x, 3.x and 2.x branches are available, but we recommend using an [officially supported version](https://www.ruby-lang.org/en/downloads/branches/). ### Choose your environment From a5cbae665eb9d5a22af6dcde05b9e740db4d0ad8 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 18 Mar 2026 17:50:53 +0100 Subject: [PATCH 065/180] reference(cli): update to Clever Tools 4.7.1 --- content/doc/reference/cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/doc/reference/cli.md b/content/doc/reference/cli.md index a52b055ca..4bcb9800c 100644 --- a/content/doc/reference/cli.md +++ b/content/doc/reference/cli.md @@ -1085,7 +1085,7 @@ clever drain create [options] **Arguments** ``` -drain-type No description available +drain-type Drain type (datadog, elasticsearch, newrelic, ovh-tcp, raw-http, syslog-tcp, syslog-udp) drain-url Drain URL ``` From 2496309d44d30de3a669e3c36700b4f06067d5d4 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 18 Mar 2026 17:55:22 +0100 Subject: [PATCH 066/180] administrate(log): update drain types data --- content/doc/administrate/log-management.md | 26 ++++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/content/doc/administrate/log-management.md b/content/doc/administrate/log-management.md index 72389b521..63980ef74 100644 --- a/content/doc/administrate/log-management.md +++ b/content/doc/administrate/log-management.md @@ -128,11 +128,13 @@ clever drain create [--alias ] [--username ` and push logs to it. @@ -209,7 +211,7 @@ For more information, please refer to the [official documentation](https://www.e To create a [Datadog](https://docs.datadoghq.com/fr/api/latest/logs/#send-logs) drain, you just need to use: ```bash -clever drain create DatadogHTTP "https://http-intake.logs.datadoghq.com/v1/input/?ddsource=clevercloud&service=&hostname=" +clever drain create datadog "https://http-intake.logs.datadoghq.com/v1/input/?ddsource=clevercloud&service=&hostname=" ``` {{< callout type="warning" >}} @@ -221,7 +223,7 @@ Datadog has two zones, **EU** and **COM**. An account on one zone is not availab To create a [NewRelic](https://docs.newrelic.com/docs/logs/log-api/introduction-log-api/) drain, use: ```bash -clever drain create NewRelicHTTP "https://log-api.eu.newrelic.com/log/v1" --api-key "" +clever drain create newrelic "https://log-api.eu.newrelic.com/log/v1" --api-key "" ``` {{< callout type="warning" >}} @@ -232,7 +234,7 @@ NewRelic has two zones, **EU** and **US**. An account on one zone is not availab To export logs from an application or an add-on to [OVHcloud Logs Data Platform](https://help.ovhcloud.com/csm/en-ie-logs-data-platform-quick-start?id=kb_article_view&sysparm_article=KB0055819), use the following setup: -- A **TCP** drain log with `clever drain create TCPSyslog` +- A **TCP** drain log with `clever drain create ovh-tcp` - Your Logs Data Platform **host** with **port** `514` (SSL ports aren't supported for TCP drains) - The **write token** for your stream (provided on your Logs Data Platform console) @@ -243,7 +245,7 @@ On your terminal, use the following command: {{< tab >}}**Exporting logs from an application**: ```shell - clever drain create TCPSyslog tcp://:514 -app --sd-params="X-OVH-TOKEN=\"\"" + clever drain create ovh-tcp tcp://:514 -app --sd-params="X-OVH-TOKEN=\"\"" ``` Replace the following values: @@ -257,7 +259,7 @@ On your terminal, use the following command: {{< tab >}}**Exporting logs from an add-on**: ```shell - clever drain create TCPSyslog tcp://:514 -addon --sd-params="X-OVH-TOKEN=\"\"" + clever drain create ovh-tcp tcp://:514 -addon --sd-params="X-OVH-TOKEN=\"\"" ``` Replace the following values: From 252a9541e1d852df2b797859784faf40900ab226 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Fri, 10 Apr 2026 11:07:56 +0200 Subject: [PATCH 067/180] changelog: fix a description --- content/changelog/2026/04-07-images-update.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/changelog/2026/04-07-images-update.md b/content/changelog/2026/04-07-images-update.md index 65c7d3a6f..9124590c8 100644 --- a/content/changelog/2026/04-07-images-update.md +++ b/content/changelog/2026/04-07-images-update.md @@ -1,6 +1,6 @@ --- title: "Images update: mdBook 0.5, Static Web Server 2.42, OAuth2Proxy 7.15.1, Tailscale 1.96.3" -description: Many tool updates: check your mdBook configuration for the 0.5 release +description: Many tool updates, check your mdBook configuration for the 0.5 release date: 2026-04-07 tags: - images From ad85c062ba934e8cd5c14401cdc1ef9cb0a9e877 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Fri, 10 Apr 2026 10:47:47 +0200 Subject: [PATCH 068/180] changelog: Clever Tools 4.8 --- content/api/howto.md | 12 ++ .../changelog/2026/04-09-clever-tools-4.8.md | 81 +++++++++ content/doc/cli/logs-drains.md | 11 +- content/doc/reference/cli.md | 161 +++++++++++++++++- 4 files changed, 256 insertions(+), 9 deletions(-) create mode 100644 content/changelog/2026/04-09-clever-tools-4.8.md diff --git a/content/api/howto.md b/content/api/howto.md index 747f8834b..99c6b544f 100644 --- a/content/api/howto.md +++ b/content/api/howto.md @@ -90,6 +90,18 @@ First, you'll need to create an OAuth consumer for your application. This can be * A **consumer key** (public identifier for your application) * A **consumer secret** (private key, never expose it client-side) +You can also manage OAuth consumers from the CLI with the `clever oauth-consumers` command set, which covers the full lifecycle (list, create, get, update, open and delete). Use `--with-secret` on the `get` subcommand to retrieve the consumer secret: + +```bash +clever oauth-consumers create my-app \ + --description "My application" \ + --url https://my-app.example.com \ + --base-url https://my-app.example.com/oauth/callback \ + --rights access-personal-information,access-organisations + +clever oauth-consumers get my-app --with-secret +``` + > [!NOTE] > The **base URL** you set when creating the consumer is important: the callback URL you use during the OAuth flow must match this base URL's domain. For local development, register a separate consumer with `http://localhost:` as the base URL. diff --git a/content/changelog/2026/04-09-clever-tools-4.8.md b/content/changelog/2026/04-09-clever-tools-4.8.md new file mode 100644 index 000000000..b203d8bb3 --- /dev/null +++ b/content/changelog/2026/04-09-clever-tools-4.8.md @@ -0,0 +1,81 @@ +--- +title: "Clever Tools 4.8: OAuth consumers and drains for add-ons" +date: 2026-04-09 +description: Clever Tools 4.8 introduces commands to manage OAuth consumers and extends drain management to add-ons +tags: + - clever-tools + - cli +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 + - name: Hubert Sablonnière + link: https://github.com/hsablonniere + image: https://github.com/hsablonniere.png?size=40 +excludeSearch: true +--- + +[Clever Tools 4.8.0](https://github.com/CleverCloud/clever-tools/releases/tag/4.8.0) is available. This release adds a new `oauth-consumers` command set to manage OAuth consumers from the CLI and extends `clever drain` to operate on add-ons. + +## OAuth consumers management + +You can now create and manage [OAuth consumers](/api/howto/#create-an-oauth-consumer) directly from the CLI. The new `clever oauth-consumers` command set covers the full lifecycle: list, create, get details, update, open in the Console and delete. Consumers can be referenced by name (when unambiguous) or by their consumer key, and the `get` command supports a `--with-secret` flag when you need to retrieve the consumer secret for application configuration. + +```bash +# List OAuth consumers +clever oauth-consumers list + +# Create a new consumer with rights and callback URL +clever oauth-consumers create my-app \ + --description "My application" \ + --url https://my-app.example.com \ + --base-url https://my-app.example.com/oauth/callback \ + --rights access-personal-information,access-organisations + +# Get details, including the secret +clever oauth-consumers get my-app --with-secret + +# Update an existing consumer +clever oauth-consumers update my-app --description "Updated description" + +# Open the consumer page in the Console +clever oauth-consumers open my-app + +# Delete a consumer +clever oauth-consumers delete my-app --yes +``` + +These commands target the current profile's organisation by default. The `create` command accepts the `--org` option to specify the consumer's organisation. For other commands, the consumer key is enough to identify the resource. + +- [Learn more about OAuth consumers](/api/howto/#create-an-oauth-consumer) + +## Drains on add-ons + +The `clever drain` commands now accept an `--addon` option to manage [log drains](/doc/administrate/log-management/) on add-ons, in addition to applications. All drain subcommands (`create`, `enable`, `disable`, `get`, `remove` and the top-level listing) accept this new option, which resolves either an add-on ID or its real ID. The `--addon` option is mutually exclusive with `--app` and `--alias`. + +```bash +# List drains on an add-on +clever drain --addon postgresql_xxxxxxxx + +# Create a drain on an add-on +clever drain create --addon postgresql_xxxxxxxx raw-http https://logs.example.com + +# Get a specific drain +clever drain get --addon postgresql_xxxxxxxx + +# Remove a drain +clever drain remove --addon postgresql_xxxxxxxx +``` + +## Bug fixes + +- **Network groups** commands have been adapted to the `@clevercloud/client` v12 API change for peer configuration, restoring proper peer handling. + +## How to upgrade + +To upgrade Clever Tools, [use your favourite package manager](/doc/cli/install/). For example with `npm`: + +``` +npm update -g clever-tools +clever version +``` diff --git a/content/doc/cli/logs-drains.md b/content/doc/cli/logs-drains.md index 424effd31..d2107936f 100644 --- a/content/doc/cli/logs-drains.md +++ b/content/doc/cli/logs-drains.md @@ -28,7 +28,16 @@ clever drain enable clever drain disable ``` -The `clever drain` command lists all drains for the target application and shows key metrics for each one. The `clever drain get` command displays detailed metrics for a single drain, including message output rate, throughput (with dynamic units), backlog size, retry attempts, and last error. These metrics help you monitor drain health and troubleshoot delivery issues. +All drain subcommands also accept `--addon ADDON_ID_OR_REAL_ID` to target an add-on instead of an application. The `--addon` option is mutually exclusive with `--app` and `--alias`. + +``` +clever drain --addon postgresql_xxxxxxxx +clever drain create --addon postgresql_xxxxxxxx raw-http https://logs.example.com +clever drain get --addon postgresql_xxxxxxxx +clever drain remove --addon postgresql_xxxxxxxx +``` + +The `clever drain` command lists all drains for the target application or add-on and shows key metrics for each one. The `clever drain get` command displays detailed metrics for a single drain, including message output rate, throughput (with dynamic units), backlog size, retry attempts, and last error. These metrics help you monitor drain health and troubleshoot delivery issues. Where `DRAIN-TYPE` is one of: diff --git a/content/doc/reference/cli.md b/content/doc/reference/cli.md index 4bcb9800c..c248444df 100644 --- a/content/doc/reference/cli.md +++ b/content/doc/reference/cli.md @@ -367,7 +367,7 @@ addon-id|addon-name Add-on ID (or name, if unambiguous) **Options** ``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) --y, --yes Skip confirmation and delete the add-on directly +-y, --yes Skip confirmation and proceed with deletion directly ``` ### addon env @@ -389,7 +389,7 @@ addon-id Add-on ID or real ID **Options** ``` -F, --format Output format (human, json, shell) (default: human) --o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) (deprecated, organisation is now resolved automatically) ``` ### addon list @@ -816,7 +816,7 @@ database-id|addon-id Any database ID (format: addon_UUID, pos **Options** ``` -F, --format Output format (human, json) (default: human) --o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) (deprecated, organisation is now resolved automatically) ``` #### database backups download @@ -838,7 +838,7 @@ backup-id A Database backup ID (format: UUID) **Options** ``` --o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) (deprecated, organisation is now resolved automatically) --output, --out Redirect the output of the command in a file ``` @@ -875,7 +875,7 @@ clever deploy [options] ``` -a, --alias Short name for the application -b, --branch Branch to push (current branch by default) --e, --exit-on Step at which the logs streaming is ended, steps are: deploy-start, deploy-end, never (default: deploy-end) +-e, --exit-on Step at which the logs streaming is ended (deploy-start, deploy-end, never) (default: deploy-end) --follow Continue to follow logs after deployment has ended (deprecated, use `--exit-on never` instead) -f, --force Force deploy even if it's not fast-forwardable -q, --quiet Don't show logs during deployment @@ -1067,6 +1067,7 @@ clever drain [options] **Options** ``` + --addon Add-on ID or real ID -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) -F, --format Output format (human, json) (default: human) @@ -1091,6 +1092,7 @@ drain-url Drain URL **Options** ``` + --addon Add-on ID or real ID -a, --alias Short name for the application -k, --api-key API key (for newrelic) --app Application to manage by its ID (or name, if unambiguous) @@ -1118,6 +1120,7 @@ drain-id Drain ID **Options** ``` + --addon Add-on ID or real ID -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) ``` @@ -1140,6 +1143,7 @@ drain-id Drain ID **Options** ``` + --addon Add-on ID or real ID -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) ``` @@ -1162,6 +1166,7 @@ drain-id Drain ID **Options** ``` + --addon Add-on ID or real ID -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) -F, --format Output format (human, json) (default: human) @@ -1185,6 +1190,7 @@ drain-id Drain ID **Options** ``` + --addon Add-on ID or real ID -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) ``` @@ -1549,7 +1555,7 @@ cluster-id|cluster-name Kubernetes cluster ID or name **Options** ``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) --y, --yes Skip confirmation and delete the add-on directly +-y, --yes Skip confirmation and proceed with deletion directly ``` ### k8s get @@ -2394,7 +2400,6 @@ ng-id|ng-label Network Group ID or label **Options** ``` --F, --format Output format (human, json) (default: human) -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) ``` @@ -2528,6 +2533,146 @@ notification-id Notification ID -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) ``` +## oauth-consumers + +**Description:** Manage OAuth consumers used with a Clever Cloud login + +**Since:** 4.8.0 + +**Usage** +``` +clever oauth-consumers +``` + +### oauth-consumers create + +**Description:** Create an OAuth consumer + +**Since:** 4.8.0 + +**Usage** +``` +clever oauth-consumers create [options] +``` + +**Arguments** +``` +name Consumer name +``` + +**Options** +``` + --base-url OAuth callback base URL +-d, --description Consumer description +-F, --format Output format (human, json) (default: human) +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) + --picture Application logo URL + --rights Comma-separated list of rights (access-organisations, access-organisations-bills, access-organisations-consumption-statistics, access-organisations-credit-count, access-personal-information, manage-organisations, manage-organisations-applications, manage-organisations-members, manage-organisations-services, manage-personal-information, manage-ssh-keys, all) + --url Application home URL +``` + +### oauth-consumers delete + +**Description:** Delete an OAuth consumer + +**Since:** 4.8.0 + +**Usage** +``` +clever oauth-consumers delete [options] +``` + +**Arguments** +``` +consumer-key|consumer-name OAuth consumer key (or name, if unambiguous) +``` + +**Options** +``` +-y, --yes Skip confirmation and proceed with deletion directly +``` + +### oauth-consumers get + +**Description:** Get details of an OAuth consumer + +**Since:** 4.8.0 + +**Usage** +``` +clever oauth-consumers get [options] +``` + +**Arguments** +``` +consumer-key|consumer-name OAuth consumer key (or name, if unambiguous) +``` + +**Options** +``` +-F, --format Output format (human, json) (default: human) + --with-secret Include the consumer secret in the output +``` + +### oauth-consumers list + +**Description:** List OAuth consumers + +**Since:** 4.8.0 + +**Usage** +``` +clever oauth-consumers list [options] +``` + +**Options** +``` +-F, --format Output format (human, json) (default: human) +``` + +### oauth-consumers open + +**Description:** Open an OAuth consumer in the Clever Cloud Console + +**Since:** 4.8.0 + +**Usage** +``` +clever oauth-consumers open +``` + +**Arguments** +``` +consumer-key|consumer-name OAuth consumer key (or name, if unambiguous) +``` + +### oauth-consumers update + +**Description:** Update an OAuth consumer + +**Since:** 4.8.0 + +**Usage** +``` +clever oauth-consumers update [options] +``` + +**Arguments** +``` +consumer-key|consumer-name OAuth consumer key (or name, if unambiguous) +``` + +**Options** +``` + --base-url OAuth callback base URL +-d, --description Consumer description +-F, --format Output format (human, json) (default: human) +-n, --name Consumer name + --picture Application logo URL + --rights Comma-separated list of rights (access-organisations, access-organisations-bills, access-organisations-consumption-statistics, access-organisations-credit-count, access-personal-information, manage-organisations, manage-organisations-applications, manage-organisations-members, manage-organisations-services, manage-personal-information, manage-ssh-keys, all) + --url Application home URL +``` + ## open **Description:** Open an application in the Console @@ -2930,7 +3075,7 @@ clever restart [options] -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) --commit Restart the application with a specific commit ID --e, --exit-on Step at which the logs streaming is ended, steps are: deploy-start, deploy-end, never (default: deploy-end) +-e, --exit-on Step at which the logs streaming is ended (deploy-start, deploy-end, never) (default: deploy-end) --follow Continue to follow logs after deployment has ended (deprecated, use `--exit-on never` instead) -q, --quiet Don't show logs during deployment --without-cache Restart the application without using cache From e585d2683db0928e3fbcb9d5478986fdd60022c5 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 13 Apr 2026 23:51:10 +0200 Subject: [PATCH 069/180] changelog: Keycloak 26.5.7 --- .../changelog/2026/04-13-keycloak-26.5.7.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 content/changelog/2026/04-13-keycloak-26.5.7.md diff --git a/content/changelog/2026/04-13-keycloak-26.5.7.md b/content/changelog/2026/04-13-keycloak-26.5.7.md new file mode 100644 index 000000000..98c4d2e0d --- /dev/null +++ b/content/changelog/2026/04-13-keycloak-26.5.7.md @@ -0,0 +1,30 @@ +--- +title: Keycloak 26.5.7 (security update) +description: Keycloak 26.5.7 fixes seven CVEs including access control, path traversal, privilege escalation and denial of service vulnerabilities +date: 2026-04-13 +tags: + - addons + - keycloak +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[The release 26.5.7](https://github.com/keycloak/keycloak/releases/tag/26.5.7) of Keycloak is available on Clever Cloud. It addresses seven security vulnerabilities: [CVE-2025-14083](https://nvd.nist.gov/vuln/detail/CVE-2025-14083), [CVE-2026-1002](https://nvd.nist.gov/vuln/detail/CVE-2026-1002), [CVE-2026-3429](https://nvd.nist.gov/vuln/detail/CVE-2026-3429), [CVE-2026-4634](https://nvd.nist.gov/vuln/detail/CVE-2026-4634), [CVE-2026-4636](https://nvd.nist.gov/vuln/detail/CVE-2026-4636), [CVE-2026-3872](https://nvd.nist.gov/vuln/detail/CVE-2026-3872) and [CVE-2026-4282](https://nvd.nist.gov/vuln/detail/CVE-2026-4282). + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_KEYCLOAK_VERSION` of the underlying Java application to `26.5.7` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever keycloak version check yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId 26.5.7 +``` + +- [Learn more about Keycloak on Clever Cloud](/doc/addons/keycloak) From 7a12f174294daa89bf38d6b7329165cd1b2a9efa Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 13 Apr 2026 23:45:15 +0200 Subject: [PATCH 070/180] changelog: JS Client 12.1 --- .../changelog/2026/04-13-js-client-12.1.md | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 content/changelog/2026/04-13-js-client-12.1.md diff --git a/content/changelog/2026/04-13-js-client-12.1.md b/content/changelog/2026/04-13-js-client-12.1.md new file mode 100644 index 000000000..45879c5b8 --- /dev/null +++ b/content/changelog/2026/04-13-js-client-12.1.md @@ -0,0 +1,52 @@ +--- +title: 'JS Client 12.1: new features for Keycloak, Matomo, Metabase and Otoroshi' +description: Clever Cloud JS client 12.1 adds commands to manage Keycloak, Matomo, Metabase and Otoroshi add-ons, plus dev mode support for events +date: 2026-04-13 +tags: + - client + - javascript +authors: + - name: Florian Sanders + link: https://github.com/florian-sanders-cc + image: https://github.com/florian-sanders-cc.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Clever Cloud JS client 12.1](https://github.com/CleverCloud/clever-client.js/releases/tag/12.1.0) is available. This release extends the new client structure introduced in v12 with dedicated commands for four managed add-ons: [Keycloak](/doc/addons/keycloak/), [Matomo](/doc/addons/matomo/), [Metabase](/doc/addons/metabase/) and [Otoroshi](/doc/addons/otoroshi/). + +## New add-on commands + +You can now manage the lifecycle of these services directly from the client. For Matomo, the release adds `RebootMatomoCommand` and `RebuildMatomoCommand` to reboot the add-on or rebuild it without cache, and exposes the associated Materia KV identifier through `resource.kvId` in the `GetMatomoInfoCommand` output. Keycloak, Metabase and Otoroshi each get their own dedicated command set on the same model. + +```javascript +import { CcApiClient } from "@clevercloud/client/cc-api-client.js"; +import { GetMatomoInfoCommand } from "@clevercloud/client/cc-api-commands/matomo/get-matomo-info-command.js"; + +const client = new CcApiClient({ + authMethod: { + type: 'api-token', + apiToken: process.env.CLEVER_API_TOKEN, + }, +}); + +const info = await client.send(new GetMatomoInfoCommand({ id: 'addon_xxxxxxxx' })); +console.log(info.resource.kvId); +``` + +## Bug fixes + +- **Events**: the events client now uses `ws://` when the API host is served over `http://`, restoring support for local development setups. +- **Matomo**: `GetMatomoInfoCommand` no longer sorts available versions, preserving the order returned by the API. + +## How to upgrade + +Add or update `@clevercloud/client` in your project: + +```bash +npm install @clevercloud/client@12.1.0 +``` + +Refer to the [new client documentation](https://github.com/CleverCloud/clever-client.js/blob/master/NEW_CLIENT.md) for usage details and share your feedback on the [GitHub repository](https://github.com/CleverCloud/clever-client.js/issues). From ad6edc4491485794cf59d2aea253d2208a401bb7 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 22 Apr 2026 15:29:01 +0200 Subject: [PATCH 071/180] changelog: matomo 5.9 --- content/changelog/2026/04-22-matomo-5.9.md | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 content/changelog/2026/04-22-matomo-5.9.md diff --git a/content/changelog/2026/04-22-matomo-5.9.md b/content/changelog/2026/04-22-matomo-5.9.md new file mode 100644 index 000000000..a2fc6b3b6 --- /dev/null +++ b/content/changelog/2026/04-22-matomo-5.9.md @@ -0,0 +1,25 @@ +--- +title: Matomo 5.9 is available with CNIL compliance automation and segment management +description: Enforce CNIL-compliant settings in one click, manage all your segments from a dedicated page, and access new calendar presets +date: 2026-04-22 +tags: + - addons + - matomo +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +Our [Matomo](https://matomo.org/) add-on has been updated to version `5.9.0` which is now used by default. You can enable "Enforce compliance" to automatically [apply CNIL-compliant](https://matomo.org/faq/how-to/how-do-i-configure-matomo-without-tracking-consent-for-french-visitors-cnil-exemption/) settings to your sites, without manual configuration. A dedicated page lets you create, edit and organise all your segments in one place, accessible from the Visitors menu and segment selector. The updated calendar adds new date presets for frequently used reporting periods. + +This release also brings security enhancements such as multi-session sign-out, the ability to export a dashboard to scheduled reports, and various bug fixes across segment handling, visitor mapping and database optimization. No major database upgrade is required. + +You can deploy this release from our [Console](https://console.clever-cloud.com) or [Clever Tools](/doc/cli/). Existing customers' add-ons are already up-to-date. + +- [Learn more about Matomo 5.9](https://matomo.org/changelog/matomo-5-9-0/) +- [Learn more about Matomo on Clever Cloud](/doc/addons/matomo/) From a45da3ef4127e61367009aaadbb0c65980f4797b Mon Sep 17 00:00:00 2001 From: David Legrand Date: Fri, 17 Apr 2026 10:18:50 +0200 Subject: [PATCH 072/180] changelog: Matomo 5.8 --- content/changelog/2026/04-15-matomo-5.8.md | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 content/changelog/2026/04-15-matomo-5.8.md diff --git a/content/changelog/2026/04-15-matomo-5.8.md b/content/changelog/2026/04-15-matomo-5.8.md new file mode 100644 index 000000000..d0769ccd4 --- /dev/null +++ b/content/changelog/2026/04-15-matomo-5.8.md @@ -0,0 +1,23 @@ +--- +title: Matomo 5.8 is available with AI chatbot traffic tracking +description: Track AI chatbot traffic separately from human visits, with dedicated reports and dashboard metrics +date: 2026-04-15 +tags: + - addons + - matomo +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +The [Matomo](https://matomo.org/) add-on on Clever Cloud has been updated to version `5.8.0`, which is now used by default. This release introduces a new AI Assistants menu with dedicated reports to track AI chatbot traffic separately from human visits, covering platforms such as Cloudflare, Amazon CloudFront and WordPress. The All Websites dashboard now shows a "Total AI Chatbots Requests" metric, and "Total Hits" combines human visits with AI chatbot requests. This version also improves scheduled reports formatting, CSV/TSV export options and archiving performance. + +You can deploy this release from the [Clever Cloud Console](https://console.clever-cloud.com) or [Clever Tools](/doc/cli/). Existing customers' add-ons are already up-to-date. + +- [Learn more about Matomo 5.8](https://matomo.org/changelog/matomo-5-8-0/) +- [Learn more about Matomo on Clever Cloud](/doc/addons/matomo/) From 488f9b2a2e7ed7fba32ac8a920a5491ea843c5a1 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Fri, 17 Apr 2026 06:57:19 +0200 Subject: [PATCH 073/180] applications(java-war): containers list update --- .../2024/08-26-java-containers-update.md | 2 +- content/doc/applications/java/java-war.md | 42 +++++-------------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/content/changelog/2024/08-26-java-containers-update.md b/content/changelog/2024/08-26-java-containers-update.md index 38d88f50c..89d07de53 100644 --- a/content/changelog/2024/08-26-java-containers-update.md +++ b/content/changelog/2024/08-26-java-containers-update.md @@ -17,6 +17,6 @@ aliases: excludeSearch: true --- -As part of our images' enhancement process, we've tidied up the [list of supported servlet containers](/doc/applications/java/java-war/#available-containers). Those that are no longer used by our customers are removed from the Java image. If you need a container which is not listed in [the servlet container list](/doc/applications/java/java-war/#available-containers) or a specific version, please contact [our support team](https://console.clever-cloud.com/ticket-center-choice). This release also includes a [Wildfly](https://github.com/wildfly/wildfly) upgrade. Versions 27.0.1 and 33.0.1 are now available. +As part of our images' enhancement process, we've tidied up the [list of supported servlet containers](/doc/applications/java/java-war/#available-containers-for-warjson). Those that are no longer used by our customers are removed from the Java image. If you need a container which is not listed in [the servlet container list](/doc/applications/java/java-war/#available-containers-for-warjson) or a specific version, please contact [our support team](https://console.clever-cloud.com/ticket-center-choice). This release also includes a [Wildfly](https://github.com/wildfly/wildfly) upgrade. Versions 27.0.1 and 33.0.1 are now available. - Learn more about [Java on Clever Cloud](/doc/applications/java/) diff --git a/content/doc/applications/java/java-war.md b/content/doc/applications/java/java-war.md index a6b49ddd2..c9f81e1fe 100644 --- a/content/doc/applications/java/java-war.md +++ b/content/doc/applications/java/java-war.md @@ -27,18 +27,7 @@ In {{< tooltip title="JEE">}}JEE{{< /tooltip >}}, application modules are packag * {{< tooltip title="EAR" >}}EAR{{< /tooltip >}}: `*.war` and `*.jar` files are packaged as JAR file with `.ear` extension and deployed into Application Server. EAR file contains configuration such as application security role mapping, EJB reference mapping and context root URL mapping of web modules. -Note : like other runtimes, Java application needs to listen on `0.0.0.0:8080` - -## Available containers - -Clever Cloud supports many servlet containers. -The supported containers are listed below: - -| Apache Tomcat | Jetty | Payara | Wildfly | -|-------------------------------|------------------------|-------------------------|----------------------------| -| Apache Tomcat 6.0 (TOMCAT6) | Jetty 9.0 (JETTY9) | Payara 5.2022 (PAYARA5) | WildFly 26.0.0 (WILDFLY26) | -| Apache Tomcat 8.8 (TOMCAT8) | Jetty 11.0.6 (JETTY11) | Payara 6.2023 (PAYARA6) | WildFly 27.0.1 (WILDFLY27) | -| Apache Tomcat 10.0 (TOMCAT10) | | | WildFly 33.0.1 (WILDFLY33) | +Note: like other runtimes, your Java application needs to listen on `0.0.0.0:8080`. {{% content "create-application" %}} @@ -67,7 +56,7 @@ Here's what your configuration file can look like: "goal": "package" }, "deploy": { - "container": "TOMCAT8", + "container": "TOMCAT10", "war": [ { "file": "target/my-app-1.0-SNAPSHOT.war", @@ -140,25 +129,16 @@ Here's the list of the configuration values for the "container" field in `war.js | Value | Description | EOL | |------------|----------------------------------------------------------------------------------------------|-----| -| GLASSFISH3 | Use Glassfish 3.x (see ) | | -| GLASSFISH4 | Use Glassfish 4.x (see ) | | -| JBOSS6 | Use JBoss AS 6.x (see ) | | -| JBOSS7 | Use JBoss AS 7.x (see ) | | -| RESIN3 | Use Resin AS 3.x (see ) | | -| RESIN4 | Use Resin AS 4.x (see ) | | -| JETTY6 | Use Jetty servlet container 6.x (see ) | EOL | -| JETTY7 | Use Jetty servlet container 7.x (see ) | EOL | -| JETTY8 | Use Jetty servlet container 8.x (see ) | EOL | | JETTY9 | Use Jetty servlet container 9.x (see ) | EOL | -| TOMCAT4 | Use Tomcat servlet container 4.x (see ) | | -| TOMCAT5 | Use Tomcat servlet container 5.x (see ) | | -| TOMCAT6 | Use Tomcat servlet container 6.x (see ) | | -| TOMCAT7 | Use Tomcat servlet container 7.x (see ) | | -| TOMCAT8 | Use Tomcat servlet container 8.x (see ) | | -| PAYARA4 | Use Payara servlet container 4.x (see ) | | -| WILDFLY9 | Use Wildfly servlet container 9.x (see ) | | -| WILDFLY17 | Use Wildfly servlet container 17.x (see ) | | -| WILDFLY23 | Use Wildfly servlet container 23.x (see ) | | +| JETTY11 | Use Jetty servlet container 11.x (see ) | EOL | +| PAYARA5 | Use Payara servlet container 5.x (see ) | EOL | +| PAYARA6 | Use Payara servlet container 6.x (see ) | EOL | +| TOMCAT8 | Use Tomcat servlet container 8.x (see ) | EOL | +| TOMCAT9 | Use Tomcat servlet container 9.x (see ) | | +| TOMCAT10 | Use Tomcat servlet container 10.x (see ) | | +| WILDFLY9 | Use Wildfly servlet container 9.x (see ) | EOL | +| WILDFLY27 | Use Wildfly servlet container 27.x (see ) | EOL | +| WILDFLY33 | Use Wildfly servlet container 33.x (see ) | EOL | {{% content "url_healthcheck" %}} {{% content "request-flow" %}} From e435931bc91f2b9622511ef4bb9d966cc114339a Mon Sep 17 00:00:00 2001 From: David Legrand Date: Thu, 23 Apr 2026 09:09:38 +0200 Subject: [PATCH 074/180] changelog: images updates, 2026W17 --- content/changelog/2026/04-21-images-update.md | 40 +++++++++++++++++++ data/runtime_versions.yml | 4 +- 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 content/changelog/2026/04-21-images-update.md diff --git a/content/changelog/2026/04-21-images-update.md b/content/changelog/2026/04-21-images-update.md new file mode 100644 index 000000000..972f24d7a --- /dev/null +++ b/content/changelog/2026/04-21-images-update.md @@ -0,0 +1,40 @@ +--- +title: "Images update: Node.js 24.15, Clever Tools 4.8, nginx 1.30, FrankenPHP 1.12.2" +description: All runtimes updated except PHP, with a new Clever Tools release and many tool updates +date: 2026-04-21 +tags: + - images + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated all our images, except PHP. Deployment is in progress for all our users. + +* **Common:** + * Linux kernel 6.19.13 + * Chromium 147.0.7727.101 + * Clever Tools 4.8.0 + * htop 3.5.0 + * nano 9.0 + * nginx 1.30.0 + * OpenSSH 10.3p1 + * OpenSSL 3.5.6 + * SQLite 3.53.0 +* **FrankenPHP:** + * Update to 1.12.2 (with `CC_PHP_VERSION=8.5`) + * Symfony CLI 5.17.1 +* **Go:** + * Update to 1.26.2 +* **Node.js & Bun:** + * Update to 24.15.0 (npm 11.12.1) + * Bun 1.3.12 +* **Python:** + * Update to 3.13.13 + * Update to 3.14.4 + * uv 0.11.7 +* **Ruby:** + * Update to 3.2.11 diff --git a/data/runtime_versions.yml b/data/runtime_versions.yml index b41e1bc64..a5521476d 100644 --- a/data/runtime_versions.yml +++ b/data/runtime_versions.yml @@ -1,7 +1,7 @@ bun: eol_source: https://github.com/oven-sh/bun/releases default: - - 1.3.11 + - 1.3.12 caddy: eol_source: https://github.com/caddyserver/caddy/releases @@ -56,7 +56,7 @@ java: node: eol_source: https://nodejs.org/en/about/releases/ default: - - 24.14.1 (npm 11.11.0) + - 24.15.0 (npm 11.12.1) php: eol_source: https://www.php.net/supported-versions.php From 799cbe9bd7e36ac661b81f29c3bc3a323f2a9106 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Fri, 24 Apr 2026 16:09:08 +0200 Subject: [PATCH 075/180] changelog: Kubernetes 1.36 --- .../changelog/2026/04-24-kubernetes-1.36.md | 21 +++++++++++++++++++ content/doc/kubernetes/_index.md | 8 +++---- 2 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 content/changelog/2026/04-24-kubernetes-1.36.md diff --git a/content/changelog/2026/04-24-kubernetes-1.36.md b/content/changelog/2026/04-24-kubernetes-1.36.md new file mode 100644 index 000000000..7ffe44044 --- /dev/null +++ b/content/changelog/2026/04-24-kubernetes-1.36.md @@ -0,0 +1,21 @@ +--- +title: Kubernetes 1.36 is available +description: Fine-grained kubelet authorization, faster SELinux volume labelling, workload aware scheduling and removal of the gitRepo volume plugin +date: 2026-04-24 +tags: + - kubernetes + - release +authors: + - name: Gilles Biannic + link: https://github.com/GillesBIANNIC + image: https://github.com/GillesBIANNIC.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We added support for [Kubernetes 1.36.0 "Haru"](https://github.com/kubernetes/kubernetes/releases/tag/v1.36.0), which ships multiple enhancements. Fine-grained kubelet API authorization and faster SELinux labelling of volumes reach GA, while Workload Aware Scheduling and DRA device taints and tolerations move to beta. The long-deprecated `gitRepo` volume plugin is permanently removed, and `Service.spec.externalIPs` enters deprecation ahead of its removal in v1.43. Kubernetes 1.36.0 can be selected for new deployments; v1.35 remains the default for now. Migration of existing clusters will be available soon. + +- [Learn more about Kubernetes 1.36](https://kubernetes.io/blog/2026/04/22/kubernetes-v1-36-release/) +- [Learn more about Kubernetes on Clever Cloud](/doc/kubernetes/) diff --git a/content/doc/kubernetes/_index.md b/content/doc/kubernetes/_index.md index d1f7fe861..5f0289a01 100644 --- a/content/doc/kubernetes/_index.md +++ b/content/doc/kubernetes/_index.md @@ -76,12 +76,10 @@ clever k8s list --org Clever Cloud follows [the official Kubernetes version support policy](https://kubernetes.io/releases/), which maintains support for the most recent three minor versions (n-2). At any given time, the Kubernetes project maintains release branches for the latest three minor releases. -For example, if the latest release, used as default, is v1.35: - -* v1.35 (current) +* v1.36 (supported) +* v1.35 (default) * v1.34 (supported) -* v1.33 (supported) -* v1.32 (unsupported) +* v1.33 (unsupported) Each supported Kubernetes minor version typically receives patch releases for approximately 12 months after its initial release. It's a good practice to maintain your clusters on a supported version to benefit from the latest security patches, bug fixes, and features. For clusters running unsupported versions, Clever Cloud reserves the right to initiate automatic upgrades to ensure platform security and stability. From d9ae772bb96cfb21dd1dbd8273eddcabf5eb43fa Mon Sep 17 00:00:00 2001 From: David Legrand Date: Fri, 24 Apr 2026 18:24:02 +0200 Subject: [PATCH 076/180] chore: update Hextra theme to 0.12.2 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index f9f6dd026..990a24e38 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/CleverCloud/documentation go 1.26 -require github.com/imfing/hextra v0.12.1 // indirect +require github.com/imfing/hextra v0.12.2 // indirect diff --git a/go.sum b/go.sum index 34448dbd0..72fad666f 100644 --- a/go.sum +++ b/go.sum @@ -4,3 +4,5 @@ github.com/imfing/hextra v0.12.0 h1:f6y35hW/WDJEcx9S0dOmbICOBxYE0PmP6IJFsTUgVyY= github.com/imfing/hextra v0.12.0/go.mod h1:YAv8XRNSmcqjieFwI7fVQK1AoY2Do+45DO9HGqxSGu4= github.com/imfing/hextra v0.12.1 h1:3t1n0bmJbDzSTVfht93UDcfF1BXMRjeFojA071ri2l8= github.com/imfing/hextra v0.12.1/go.mod h1:vi+yhpq8YPp/aghvJlNKVnJKcPJ/VyAEcfC1BSV9ARo= +github.com/imfing/hextra v0.12.2 h1:qa+cHQ1LC/7ys9EhRNnHrRBAHu83Tm8rhh1oWO2a7cc= +github.com/imfing/hextra v0.12.2/go.mod h1:vi+yhpq8YPp/aghvJlNKVnJKcPJ/VyAEcfC1BSV9ARo= From 3d43a13752b0bb5110db7ececcf7d646e6bc8b4c Mon Sep 17 00:00:00 2001 From: Gilles BIANNIC Date: Tue, 21 Apr 2026 10:11:47 +0200 Subject: [PATCH 077/180] chore: update CKE flavors and versions --- content/doc/kubernetes/_index.md | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/content/doc/kubernetes/_index.md b/content/doc/kubernetes/_index.md index 5f0289a01..0aa6c2f1f 100644 --- a/content/doc/kubernetes/_index.md +++ b/content/doc/kubernetes/_index.md @@ -76,8 +76,10 @@ clever k8s list --org Clever Cloud follows [the official Kubernetes version support policy](https://kubernetes.io/releases/), which maintains support for the most recent three minor versions (n-2). At any given time, the Kubernetes project maintains release branches for the latest three minor releases. +The most recent Kubernetes minor version is v1.36 and CKE default version is currently set to the v1.35: + * v1.36 (supported) -* v1.35 (default) +* v1.35 (supported) #default * v1.34 (supported) * v1.33 (unsupported) @@ -127,6 +129,21 @@ A node group is a collection of Kubernetes nodes that function as the compute re Once your cluster deployed and configured you can create a node group using `kubectl` from a YAML file that defines the `NodeGroup` resource. +### Available flavors + +Each node in a node group uses a specific flavor that determines its compute resources: + +| Flavor | vCPU | RAM | +|--------|------|--------| +| 2XS | 4 | 4 GB | +| XS | 6 | 8 GB | +| S | 8 | 12 GB | +| M | 10 | 16 GB | +| L | 12 | 24 GB | +| XL | 16 | 32 GB | + +The flavor is immutable after node group creation. To change the flavor, you must create a new node group. + For example, create a file named `example-nodegroup.yaml` with the following content: ```yaml{filename="example-nodegroup.yaml"} @@ -135,7 +152,7 @@ kind: NodeGroup metadata: name: example-nodegroup spec: - flavor: M + flavor: L nodeCount: 2 ``` @@ -152,8 +169,8 @@ You can list the node groups of your cluster using `kubectl`: ```bash kubectl get nodegroups -NAME DESIREDNODECOUNT CURRENTNODECOUNT FLAVOR AGE -default 2 2 M 2m +NAME DESIREDNODECOUNT CURRENTNODECOUNT FLAVOR STATUS AGE +example-nodegroup 2 2 L Synced 2m ``` The `DESIREDNODECOUNT` is the number of nodes that you asked for, the `CURRENTNODECOUNT` is the number of nodes currently in the node group. When creating a node group, the `CURRENTNODECOUNT` is `0` and increases until it reaches the `DESIREDNODECOUNT`. @@ -163,9 +180,9 @@ You can also list the nodes of your cluster using `kubectl`: ```bash kubectl get nodes -NAME STATUS ROLES AGE VERSION -default-node0 Ready 6d17h v1.34.1 -default-node1 Ready 3d18h v1.34.1 +NAME STATUS ROLES AGE VERSION +example-nodegroup-node0 Ready 5d22h v1.35.4 +example-nodegroup-node1 Ready 3d18h v1.35.4 ``` ## Scaling a node group From 4198cb856f3739820427c3ea90b2edab3979b875 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 27 Apr 2026 13:33:01 +0200 Subject: [PATCH 078/180] kubernetes: more details, public beta Co-Authored-By: Gilles BIANNIC --- .../changelog/2026/04-22-kubernetes-1.36.md | 21 + content/doc/kubernetes/_index.md | 448 ++++++++++++++---- data/kubernetes_versions.yml | 12 + layouts/shortcodes/kubernetes_version.html | 3 + 4 files changed, 398 insertions(+), 86 deletions(-) create mode 100644 content/changelog/2026/04-22-kubernetes-1.36.md create mode 100644 data/kubernetes_versions.yml create mode 100644 layouts/shortcodes/kubernetes_version.html diff --git a/content/changelog/2026/04-22-kubernetes-1.36.md b/content/changelog/2026/04-22-kubernetes-1.36.md new file mode 100644 index 000000000..89a5a793f --- /dev/null +++ b/content/changelog/2026/04-22-kubernetes-1.36.md @@ -0,0 +1,21 @@ +--- +title: Kubernetes 1.36 is available +description: Faster SELinux volume labeling, external ServiceAccount token signing, device taints for Dynamic Resource Allocation, and gitRepo volume driver removal +date: 2026-04-22 +tags: + - kubernetes + - release +authors: + - name: Gilles Biannic + link: https://github.com/GillesBIANNIC + image: https://github.com/GillesBIANNIC.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +Kubernetes [release 1.36.0](https://github.com/kubernetes/kubernetes/releases/tag/v1.36.0) is available on Clever Kubernetes Engine. Headline changes include faster SELinux volume labeling promoted to GA, external signing of ServiceAccount tokens through cloud KMS or HSMs, and Dynamic Resource Allocation gaining taints and tolerations for physical devices. The `gitRepo` volume driver — deprecated since v1.11 — has been permanently removed; migrate to init containers or git-sync tools. Select 1.36 explicitly at creation with `--cluster-version 1.36`; the default for new clusters remains 1.35. Migration of existing clusters will be available soon. + +- [Learn more about Kubernetes 1.36](https://kubernetes.io/blog/2026/03/30/kubernetes-v1-36-sneak-peek/) +- [Learn more about Kubernetes on Clever Cloud](/doc/kubernetes/) diff --git a/content/doc/kubernetes/_index.md b/content/doc/kubernetes/_index.md index 0aa6c2f1f..4563066e8 100644 --- a/content/doc/kubernetes/_index.md +++ b/content/doc/kubernetes/_index.md @@ -3,7 +3,7 @@ type: docs weight: 4 linkTitle: Kubernetes title: Clever Kubernetes Engine (CKE) -description: Create and manage Kubernetes clusters using Clever Cloud Kubernetes Engine with Materia etcd +description: Create and manage Kubernetes clusters on Clever Cloud, with managed control plane, Materia etcd and vanilla Kubernetes experience keywords: - kubernetes - cke @@ -11,10 +11,18 @@ keywords: - etcd - k8s - cluster +- topology +- all-in-one +- dedicated compute +- distributed +- node group +- autoscaling - csi - persistent storage - kubeconfig -- load balancers +- load balancer +- pricing +- quota --- Clever Kubernetes Engine (CKE) allows you to create and manage Kubernetes clusters with ease on Clever Cloud infrastructure. It uses Materia etcd, our implementation of etcd built on top of FoundationDB, as the backing store for your cluster's state. It ensures reliability at scale. @@ -23,179 +31,369 @@ Our approach remains the same as with our other products: our Kubernetes offer i We operate the Kubernetes control plane for you: upgrades, availability, and patching are our responsibility. You manage your own node pools — scaling them up or down manually as needed — while we ensure the control plane remains stable. Access is straightforward: we provide a kubeconfig file so you can use `kubectl` or any other Kubernetes compatible tool with the same workflow you already know. -> [!NOTE] Clever Kubernetes is in private access -> Ask for activation to your sales representative or [Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice) +> [!NOTE] Clever Kubernetes is in public beta +> Activate it from your [Console Labs](https://console.clever-cloud.com/users/me/feature-list) or via the Clever Tools feature flag (`clever features enable k8s`). To raise your default quota or request test credits, contact your sales representative or [Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice). -## Prerequisites +## Why Clever Kubernetes Engine + +### Fully managed control plane on sovereign French infrastructure + +Patching, Kubernetes upgrades, certificate rotation and control plane availability are handled by Clever Cloud, on sovereign infrastructure operated end-to-end by our team in France — no foreign hyperscaler in the loop. Every control plane component and every worker runs on a **dedicated VM** — nothing is shared between clusters or between tenants. Your responsibility stops at the workloads and the node groups that host them — the control plane is just there, healthy. Clusters come with vanilla Kubernetes: you keep the classic `kubectl` workflow and you can leave the platform without rewriting a single manifest. + +### Native auto-healing + +When a control plane VM or a worker fails its health checks, the platform provisions a replacement, waits for it to become healthy, then removes the failed VM. The mechanism works with any replication factor — including `rf = 1`. No manual intervention, no `kubectl drain` to script. + +```mermaid +sequenceDiagram + participant HC as Health checks + participant Platform as Clever Cloud + participant Old as Failed VM + participant New as Replacement VM + HC->>Platform: VM unhealthy + Platform->>New: Provision replacement + New-->>Platform: Healthy + Platform->>Old: Drain workloads + Platform->>Old: Delete VM +``` + +### On-demand autoscaling + +The cluster autoscaler is opt-in, per node group. Leave it disabled for stable workloads and predictable billing, or enable it when your workloads need elasticity (`--autoscaling --min N --max N`). The autoscaler reacts to node-level resource pressure (CPU and memory load on the worker VMs) and resizes the node group within the bounds you define, up or down. See [Manage node groups](#manage-node-groups) for commands. + +### 3 datacenters in Paris and dedicated load balancer IPs + +The platform is distributed across 3 datacenters in Paris, with multi-site replication built in. Pick a control plane replication factor of `3` or more in `DEDICATED_COMPUTE` or `DISTRIBUTED`, and the VMs are spread across the three sites — a single datacenter loss does not take your control plane down. Each `LoadBalancer` Service provisions an L4 load balancer (TCP and UDP) with its own dedicated configuration and **two public IP addresses reserved for your service**. The underlying load balancer fleet is shared across customers, but every Service gets its own logical entry point and its own IPs. + +### Cilium and eBPF networking + +The CNI is Cilium, running on top of eBPF. You get fast pod-to-pod networking, native NetworkPolicies, observability hooks and the ability to plug in extra Cilium features (Hubble, mTLS, etc.) without swapping the underlying CNI. When a cluster spans multiple VMs, the underlying inter-VM transport is a [Network Group](/doc/develop/network-groups/) — a private WireGuard-based mesh that connects the control plane and worker VMs. + +### Materia etcd, made in France on FoundationDB + +The cluster state store is Materia etcd, our **serverless** implementation of the etcd API built on top of FoundationDB. Designed and developed in France by Clever Cloud engineers, Materia etcd brings FoundationDB's track record at scale to thousands of clusters without the operational burden of running, sizing or backing up etcd yourself — no etcd VM to provision, no quorum to manage. + +### Simple deployment and version management + +Create a cluster in one command, follow its rollout with `--watch`, upgrade Kubernetes with `clever k8s version update`, audit deployment events with `clever k8s activity`. The full cluster and node group lifecycle is exposed in the API and in [Clever Tools 4.9+](/doc/cli/kubernetes/) (Console support coming soon). Persistent storage is available as an opt-in through a CSI driver backed by Ceph; pods consume it via standard `PersistentVolumeClaim` resources. + +A control plane replication factor greater than `1` smooths upgrades and incident windows: VMs are rotated one at a time, so the apiserver stays reachable while the platform replaces the others. The same applies to multi-node node groups during rolling upgrades — your workloads keep running on the remaining nodes while one is being replaced. + +### Integrated with the Clever Cloud platform + +Provision PostgreSQL, Pulsar, Materia KV or any other Clever Cloud add-on directly from inside your cluster via the [Clever Kubernetes Operator](#clever-kubernetes-operator) — managed services declared as custom resources, reconciled alongside your workloads. + +## Cluster topologies -To use Clever Kubernetes you'll need : -- An authorized access for your organisation -- Clever Tools 4.3.0 or later installed -- kubectl installed +A Kubernetes cluster on Clever Cloud is made of a control plane and one or more node groups. The control plane runs the Kubernetes components (`apiserver`, `controller-manager` and `scheduler`) and the Clever Cloud operators (`cloud-controller-manager` and `node-group-operator`). Node groups are pools of worker virtual machines where your workloads are scheduled by the Kubernetes `kubelet` component. -To check if you have access to Clever Kubernetes for your organisation, run the following command in your terminal: +You choose how the control plane is distributed at creation time. The topology decides the placement of the Kubernetes components, the available flavors, whether workloads share the same VMs as the control plane, and ultimately what gets billed. Three layouts are available: + +|Topology|Customers|VMs you pay for|Flavors|Default node group included?|Typical use case| +|---|---|---|---|---|---| +|`ALL_IN_ONE`|Developers|`replicationFactor` × bundle VM (control plane + integrated worker node). Additional node groups billed separately|`S`, `M`, `L`, `XL`|No node group, but each bundle VM is also a worker node — additional node groups optional|Development, testing, small single-team clusters| +|`DEDICATED_COMPUTE`|Business|`replicationFactor` × control plane VM **plus** every worker VM in your node groups|`XS`, `S`, `M`, `L`, `XL`|No — provision a node group at creation with `--nodegroup` or later|Production clusters where control plane and workloads stay isolated| +|`DISTRIBUTED`|Enterprise|For each of 5 components: `replicationFactor` × component VM **plus** every worker VM in your node groups|`2XS`, `XS`, `S`, `M`, `L`, `XL`|No — provision a node group at creation or later|Demanding production clusters needing fine-grained HA per control plane component| + +`ALL_IN_ONE` is the default when no topology is specified at cluster creation through Clever Tools or the Console. A replication factor (`1` to `5`) applies to the control plane VMs: the higher the factor, the more resilient the control plane and, in `DEDICATED_COMPUTE` or `DISTRIBUTED`, the more sites it is spread across (up to three Parisian datacenters). In `DISTRIBUTED`, each of the five components has its own flavor and its own replication factor, letting you scale a busy `apiserver` higher than a quieter `scheduler`. + +In `ALL_IN_ONE`, every VM in the bundle is also a worker node — the bundle covers it at no extra cost. You can still attach node groups to an `ALL_IN_ONE` cluster, and those are billed at the node group rate. In `DEDICATED_COMPUTE` and `DISTRIBUTED`, the control plane VMs are dedicated to control plane workloads and your pods land on **separate worker VMs** that you provision in node groups, billed at the node group rate. See [Pricing](#pricing) for the breakdown. + +Whenever a cluster spans multiple VMs — additional node groups on `ALL_IN_ONE`, the control plane and workers in `DEDICATED_COMPUTE`, the five components and workers in `DISTRIBUTED` — the VMs talk to each other over a [Network Group](/doc/develop/network-groups/), a private WireGuard-based mesh provisioned automatically with the cluster. Inter-component traffic stays on the private network. + +**Developers (`ALL_IN_ONE`)** — one VM hosts the five control plane components plus an integrated worker node, replicated `replicationFactor` times. Additional node groups (optional) can be attached to scale compute beyond the bundle and are billed at the [node group rate](#node-groups-workers). + +```mermaid +flowchart LR + AIO["ALL_IN_ONE bundle VM (× replicationFactor)
━━━━━━━━━━━━━━━━━━━━━━
All components on the same VM

Control plane
apiserver · controller-manager · scheduler
cloud-controller-manager · node-group-operator

Integrated worker node
kubelet ← your pods"] + AIOetcd[("Materia etcd cluster")] + AIOng1["Node group 1 (optional)
kubelet ← your pods"] + AIOng2["Node group 2 (optional)
kubelet ← your pods"] + AIOng3["Node group 3 (optional)
kubelet ← your pods"] + AIO <--> AIOetcd + AIO -.-> AIOng1 + AIO -.-> AIOng2 + AIO -.-> AIOng3 + classDef optional stroke-dasharray: 5 5 + class AIOng1,AIOng2,AIOng3 optional +``` + +**Business (`DEDICATED_COMPUTE`)** — one VM dedicated to the bundled control plane, separate worker VMs in node groups. + +```mermaid +flowchart LR + DCcp["Control plane VM (× replicationFactor)
━━━━━━━━━━━━━━━━━━━━━━
All control plane components on the same VM

apiserver · controller-manager · scheduler
cloud-controller-manager · node-group-operator"] + DCetcd[("Materia etcd cluster")] + DCng1["Node group 1
kubelet ← your pods"] + DCng2["Node group 2
kubelet ← your pods"] + DCng3["Node group 3
kubelet ← your pods"] + DCcp <--> DCetcd + DCcp --> DCng1 + DCcp --> DCng2 + DCcp --> DCng3 +``` + +**Enterprise (`DISTRIBUTED`)** — one VM per control plane component (each with its own flavor and replication factor), separate worker VMs in node groups. + +```mermaid +flowchart LR + Dcm["controller-manager VM
(× replicationFactor)"] + Dsched["scheduler VM
(× replicationFactor)"] + Dccm["cloud-controller-manager VM
(× replicationFactor)"] + Dngo["node-group-operator VM
(× replicationFactor)"] + Dapi["apiserver VM
(× replicationFactor)"] + Detcd[("Materia etcd cluster")] + Dng1["Node group 1
kubelet ← your pods"] + Dng2["Node group 2
kubelet ← your pods"] + Dng3["Node group 3
kubelet ← your pods"] + Dcm --> Dapi + Dsched --> Dapi + Dccm --> Dapi + Dngo --> Dapi + Dapi <--> Detcd + Dapi --> Dng1 + Dapi --> Dng2 + Dapi --> Dng3 +``` + +Topology and replication factor are immutable after creation. Flavor is bound to the topology — changing it means recreating the cluster. + +## Prerequisites + +Clever Kubernetes is activated per user. Enable it either from your [Console Labs](https://console.clever-cloud.com/users/me/feature-list) or with the Clever Tools feature flag: ```bash clever features enable k8s -clever k8s list --org ``` -If you get an error or if you miss anything, contact your sales representative or [Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice). +You also need: -- [kubectl installation guide](https://kubernetes.io/docs/tasks/tools/#kubectl) -- [Learn more about Clever Tools k8s command](/doc/cli/kubernetes/) +- [Clever Tools](/doc/cli/) 4.9 or later installed (recommended for the full Kubernetes command set) +- `kubectl` installed ([installation guide](https://kubernetes.io/docs/tasks/tools/#kubectl)) + +Verify your setup by listing the clusters of your organisation: + +```bash +clever k8s list --org +``` ->[!NOTE] Kubernetes clusters quotas -> During the private access phase, each organization can deploy a limited number of Kubernetes clusters.\ -> If you need more clusters, contact your sales representative or [Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice). +- [Learn more about Clever Tools k8s command](/doc/cli/kubernetes/) ## Create a Kubernetes cluster -To create a Kubernetes cluster, use the following command: +The fastest way to create a cluster is to provide only a name. The platform picks `ALL_IN_ONE` as the default topology, the minimum flavor available for it (`S`) and a replication factor of `1`: ```bash clever k8s create clusterName --org ``` -Cluster is immediately created and starts its deployment. It takes approximately 1 minute to deploy and configure all the underlying infrastructure. If you want to monitor the progress after creation, use the `--watch` option: +The cluster is created immediately and starts its deployment. It takes approximately one minute to provision the underlying infrastructure. To follow progress until the cluster reaches the `ACTIVE` state, add `--watch`: ```bash -clever k8s create clusterNameOrId --org --watch +clever k8s create clusterName --org --watch ``` -You can list all your clusters at any time using: +When you need a specific shape, combine topology, flavor and replication factor: + +```bash +clever k8s create myCluster \ + --topology DEDICATED_COMPUTE --flavor S --replication-factor 3 \ + --cluster-version 1.36 \ + --description "Production cluster" \ + --tag env:prod,team:platform \ + --autoscaling \ + --persistent-storage \ + --nodegroup M:3 +``` + +The `--nodegroup :` option provisions an initial node group named `default` at creation time, so the cluster is ready to schedule workloads as soon as it reaches `ACTIVE`. It is the typical pattern for `DEDICATED_COMPUTE` and `DISTRIBUTED`. `ALL_IN_ONE` clusters already include an integrated worker node on each bundle VM; if you still pass `--nodegroup` on an `ALL_IN_ONE` cluster, Clever Tools warns you and prompts for confirmation before adding the extra pool. + +List your clusters at any time: ```bash clever k8s list --org ``` > [!TIP] -> In [Clever Cloud Console](https://console.clever-cloud.com), you can filter Kubernetes clusters in the left menu by searching for `is:k8s`, `is:kube` or `is:kubernetes` +> In [Clever Cloud Console](https://console.clever-cloud.com), you can filter Kubernetes clusters in the left menu by searching for `is:k8s`, `is:kube` or `is:kubernetes`. ## Supported versions Clever Cloud follows [the official Kubernetes version support policy](https://kubernetes.io/releases/), which maintains support for the most recent three minor versions (n-2). At any given time, the Kubernetes project maintains release branches for the latest three minor releases. -The most recent Kubernetes minor version is v1.36 and CKE default version is currently set to the v1.35: +The current Kubernetes release on the platform is **v{{< kubernetes_version current >}}**, available with `--cluster-version {{< kubernetes_version current >}}` at creation. New clusters default to **v{{< kubernetes_version default >}}** when no version is specified. Supported versions are {{< kubernetes_version supported >}}, unsupported versions are {{< kubernetes_version unsupported >}}. -* v1.36 (supported) -* v1.35 (supported) #default -* v1.34 (supported) -* v1.33 (unsupported) +Each Kubernetes minor version receives patch releases (security fixes, bug fixes) during a support window of approximately 12 months that starts at its initial release. After that window, the version is deprecated and stops receiving patches. It's a good practice to maintain your clusters on a supported version to benefit from the latest security patches, bug fixes, and features. For clusters running unsupported versions, Clever Cloud reserves the right to initiate automatic upgrades to ensure platform security and stability. -Each supported Kubernetes minor version typically receives patch releases for approximately 12 months after its initial release. It's a good practice to maintain your clusters on a supported version to benefit from the latest security patches, bug fixes, and features. For clusters running unsupported versions, Clever Cloud reserves the right to initiate automatic upgrades to ensure platform security and stability. +## Manage node groups -## Add persistent storage (CSI) +A node group is a pool of virtual machines of the same flavor (vCPU, RAM, location) that serves as the compute resources for your cluster. Node groups simplify scaling by letting you manage similar nodes together as a unit rather than one machine at a time. A cluster can host several node groups with different flavors, which is how you mix general-purpose workloads with larger workers for dedicated jobs. + +You can manage node groups either with Clever Tools or directly from Kubernetes using the `NodeGroup` custom resource. Both paths talk to the same Clever Cloud API and produce identical results. + +### With Clever Tools -You can use Clever Cloud block storage to attach a persistent volume to a cluster through a CSI (Container Storage Interface). Once its status is `ACTIVE`, use the following command: +Create a node group, then list or inspect them: ```bash -clever k8s add-persistent-storage clusterNameOrId --org +clever k8s nodegroups create myCluster workers M:3 +clever k8s nodegroups list myCluster +clever k8s nodegroups get myCluster workers ``` -## Get the kubeconfig file +To scale, toggle autoscaling, or change metadata, use `update`: -Get the kubeconfig file to interact with your cluster: +```bash +clever k8s nodegroups update myCluster workers --count 5 +clever k8s nodegroups update myCluster workers --autoscaling --min 2 --max 10 +clever k8s nodegroups update myCluster workers --disable-autoscaling +clever k8s nodegroups update myCluster workers --description "GPU-intensive workers" +``` + +Deleting a node group drains its nodes and removes the underlying VMs: ```bash -clever k8s get-kubeconfig clusterNameOrId --org +clever k8s nodegroups delete myCluster workers ``` -You can directly save it as your local kubeconfig file: +### With kubectl + +Define a `NodeGroup` resource in a YAML file and apply it: + +```yaml{filename="example-nodegroup.yaml"} +apiVersion: api.clever-cloud.com/v1 +kind: NodeGroup +metadata: + name: example-nodegroup +spec: + flavor: M + nodeCount: 2 +``` ```bash -clever k8s get-kubeconfig clusterNameOrId --org > ~/.kube/config +kubectl create -f example-nodegroup.yaml ``` -Check everything is working by listing the nodes of your cluster (it should be empty at this point): +The node group creation process takes approximately 60 to 90 seconds to complete. Once ready, the new nodes automatically join the cluster and become available for scheduling. Inspect them with the standard Kubernetes verbs: ```bash -# With the default kubeconfig file: +kubectl get nodegroups + +NAME DESIREDNODECOUNT CURRENTNODECOUNT FLAVOR STATUS AGE +example-nodegroup 2 2 M Synced 2m + kubectl get nodes -# To target a specific kubeconfig file: -kubectl get nodes --kubeconfig=kubeconfig.yaml +NAME STATUS ROLES AGE VERSION +example-nodegroup-node0 Ready 6d17h v1.35.4 +example-nodegroup-node1 Ready 3d18h v1.35.4 ``` -## Clever Kubernetes operator +`DESIREDNODECOUNT` is the number of nodes you asked for, `CURRENTNODECOUNT` is the number of nodes currently in the node group. When creating a node group, `CURRENTNODECOUNT` starts at `0` and increases until it reaches `DESIREDNODECOUNT`. -Clever Cloud's Kubernetes clusters are designed to work seamlessly with the rest of the platform. A good example is our [open-source Kubernetes Operator](https://github.com/CleverCloud/clever-kubernetes-operator), which allows you to easily provision and use Clever Cloud resources directly from inside your cluster and combine Kubernetes workloads with the services you already trust on Clever Cloud. +To scale with `kubectl`, use the standard `scale` verb: -- [Learn more about the Clever Cloud Kubernetes Operator](/doc/kubernetes/operator/) +```bash +kubectl scale nodegroup example-nodegroup --replicas=4 +``` -## Create a node group +### Autoscaling on demand -A node group is a collection of Kubernetes nodes that function as the compute resources for your cluster. Each node group consists of virtual machines of the same flavor, meaning they have identical characteristics: vCPU, RAM and location (Paris region only for now). Node groups simplify management by letting you scale or upgrade similar nodes together as a unit rather than individually managing each node. +The cluster autoscaler is not active by default. Enable it once at cluster level (at creation with `--autoscaling`, later with `clever k8s update --autoscaling`), then set the bounds per node group: -Once your cluster deployed and configured you can create a node group using `kubectl` from a YAML file that defines the `NodeGroup` resource. +```bash +clever k8s nodegroups update myCluster workers --autoscaling --min 2 --max 10 +``` -### Available flavors +Once enabled, the autoscaler reacts to node-level resource pressure (CPU and memory load on the worker VMs) and resizes the node group within the `--min` and `--max` bounds you defined. Switch it off with `--disable-autoscaling` on the node group when you want a fixed-size pool, or at cluster level to stop the autoscaler altogether. -Each node in a node group uses a specific flavor that determines its compute resources: +## Add persistent storage (CSI) -| Flavor | vCPU | RAM | -|--------|------|--------| -| 2XS | 4 | 4 GB | -| XS | 6 | 8 GB | -| S | 8 | 12 GB | -| M | 10 | 16 GB | -| L | 12 | 24 GB | -| XL | 16 | 32 GB | +You can use Clever Cloud block storage to attach a persistent volume to a cluster through a CSI (Container Storage Interface). Enable persistent storage at creation with `--persistent-storage`, or on an existing cluster: -The flavor is immutable after node group creation. To change the flavor, you must create a new node group. +```bash +clever k8s update clusterNameOrId --persistent-storage --org +clever k8s add-persistent-storage clusterNameOrId --org +``` -For example, create a file named `example-nodegroup.yaml` with the following content: +Both commands reach the same result. Persistent storage is currently a one-way toggle: once enabled, it cannot be removed from a running cluster. Create a new cluster without `--persistent-storage` if you no longer need it. -```yaml{filename="example-nodegroup.yaml"} -apiVersion: api.clever-cloud.com/v1 -kind: NodeGroup -metadata: - name: example-nodegroup -spec: - flavor: L - nodeCount: 2 -``` +## Get the kubeconfig file -Then, apply this configuration to your cluster using `kubectl create`: +Get the kubeconfig file to interact with your cluster: ```bash -kubectl create -f example-nodegroup.yaml +clever k8s get-kubeconfig clusterNameOrId --org ``` -The node group creation process takes approximately 60 to 90 seconds to complete. Once created, the new nodes will automatically join your cluster and become available for scheduling workloads. +You can directly save it as your local kubeconfig file: + +```bash +clever k8s get-kubeconfig clusterNameOrId --org > ~/.kube/config +``` -You can list the node groups of your cluster using `kubectl`: +Check everything is working by listing the nodes of your cluster (it should be empty at this point): ```bash -kubectl get nodegroups +# With the default kubeconfig file: +kubectl get nodes -NAME DESIREDNODECOUNT CURRENTNODECOUNT FLAVOR STATUS AGE -example-nodegroup 2 2 L Synced 2m +# To target a specific kubeconfig file: +kubectl get nodes --kubeconfig=kubeconfig.yaml ``` -The `DESIREDNODECOUNT` is the number of nodes that you asked for, the `CURRENTNODECOUNT` is the number of nodes currently in the node group. When creating a node group, the `CURRENTNODECOUNT` is `0` and increases until it reaches the `DESIREDNODECOUNT`. +## Pull images from authenticated registries -You can also list the nodes of your cluster using `kubectl`: +Public registries like Docker Hub rate-limit anonymous pulls, which can fail deployments when many pods come up at the same time. Authenticate your pulls by declaring your registry credentials inside the cluster as a standard `imagePullSecrets` and referencing it from your workloads. -```bash -kubectl get nodes +Create a secret of type `kubernetes.io/dockerconfigjson` with `kubectl`: -NAME STATUS ROLES AGE VERSION -example-nodegroup-node0 Ready 5d22h v1.35.4 -example-nodegroup-node1 Ready 3d18h v1.35.4 +```bash +kubectl create secret docker-registry dockerhub-creds \ + --docker-server=https://index.docker.io/v1/ \ + --docker-username= \ + --docker-password= \ + --docker-email= ``` -## Scaling a node group +Reference the secret from your Pod spec via `imagePullSecrets`: -To scale up or down a node group, use the `kubectl scale` command. For example, to scale the `example-nodegroup` to 4 nodes, run: +```yaml{filename="deployment-private.yaml"} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-app +spec: + replicas: 2 + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + imagePullSecrets: + - name: dockerhub-creds + containers: + - name: my-app + image: myorg/my-private-image:1.0 +``` + +To avoid repeating `imagePullSecrets` in every Pod, patch the default `ServiceAccount` of your namespace so all Pods inherit the credentials: ```bash -kubectl scale nodegroup example-nodegroup --replicas=4 +kubectl patch serviceaccount default \ + -p '{"imagePullSecrets":[{"name":"dockerhub-creds"}]}' ``` +The same pattern applies to any OCI-compliant registry (GitHub Container Registry, GitLab Registry, AWS ECR, Google Artifact Registry, self-hosted Harbor, etc.) — adjust `--docker-server`, `--docker-username` and `--docker-password` accordingly. For registries that issue short-lived tokens (ECR, GAR), rotate the secret with a CronJob or an external controller. + ## Deployment with a load balancer service -Here is an example of a simple NGINX deployment with a load balancer service: +Before deploying workloads, the cluster needs worker nodes. An `ALL_IN_ONE` cluster ships with an integrated worker node on each bundle VM, so the cluster is ready to schedule pods as soon as it reaches `ACTIVE`. With `DEDICATED_COMPUTE` or `DISTRIBUTED`, provision a node group first — either at cluster creation with `--nodegroup :` (see [Create a Kubernetes cluster](#create-a-kubernetes-cluster)) or afterwards with `clever k8s nodegroups create` (see [Manage node groups](#manage-node-groups)). + +Once workers are available, here is an example of a simple NGINX deployment with a load balancer service: ```bash kubectl create deployment nginx --image=nginx:alpine --replicas=2 @@ -221,7 +419,7 @@ spec: spec: containers: - name: nginx - image: nginx:1.14.2 + image: nginx:1.30.0 ports: - containerPort: 80 --- @@ -240,3 +438,81 @@ spec: port: 80 # Port accessible from outside the cluster targetPort: 80 # Port on which the container is listening ``` + +## Quotas and limits + +Each organisation starts with **40 vCPU and 40 GB of RAM** across all its Kubernetes clusters by default. This envelope covers both the control plane VMs and the worker nodes, whatever their topology. Check your current consumption and the remaining budget at any time with `clever k8s quota`. If you need more, contact your sales representative or [Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice). + +Each organisation also comes with **two public IP addresses**, which corresponds to a single `LoadBalancer` service across all its clusters. Additional `LoadBalancer` services are accepted by the Kubernetes API but stay in a pending state until the quota is lifted — `kubectl describe svc` typically reports error 507 in that case. + +## Pricing + +> [!NOTE] Pricing during the public beta +> Only the control plane and compute nodes are billed for now. Persistent storage (CSI) and load balancers will be added to your invoice before the platform reaches general availability. Customers will be notified ahead of the change — follow the [changelog](/changelog/) to stay informed. + +Prices are **excluding taxes**. The platform charges by the hour, per resource (vCPU and RAM): the per-VM rate below is `vCPU × vCPU_rate + RAM_GB × RAM_rate`. Monthly prices are computed using the standard 720-hours-per-month convention (30 × 24). + +The model is: **one price per topology**, charged per control plane element per replication, **plus** the [node group rate](#node-groups-workers) for every worker VM you provision in additional node groups. `ALL_IN_ONE` is special: its bundle price already covers the five control plane components **and** an integrated worker node on each bundle VM. If you attach additional node groups to an `ALL_IN_ONE` cluster, those extra workers are billed at the node group rate. + +In practice: + +- **`ALL_IN_ONE`** — `replicationFactor` × the bundle VM price (covers the five control plane components and the integrated worker node). Additional node groups, if any, are billed at the [node group rate](#node-groups-workers). +- **`DEDICATED_COMPUTE`** — `replicationFactor` × the control plane VM price, **plus** the [node group rate](#node-groups-workers) for every worker VM in your node groups. +- **`DISTRIBUTED`** — for each of the five components, `replicationFactor` × the component VM price (each component picks its own flavor and replication factor), **plus** the [node group rate](#node-groups-workers) for every worker VM in your node groups. + +### Developers (`ALL_IN_ONE`) bundle + +The bundle hosts the five control plane components and an integrated worker node on the same VMs. The price below covers everything; additional node groups attached to an `ALL_IN_ONE` cluster are billed separately at the [node group rate](#node-groups-workers). + +|Flavor|Resources|Hourly price|Monthly price| +|---|---|---|---| +|S|8 vCPU / 12 GB|0.0889 €|64.00 €| +|M|10 vCPU / 16 GB|0.1167 €|84.00 €| +|L|12 vCPU / 24 GB|0.1667 €|120.00 €| +|XL|16 vCPU / 32 GB|0.2222 €|160.00 €| + +### Business (`DEDICATED_COMPUTE`) control plane + +One VM per replication factor, dedicated to the control plane. + +|Flavor|Resources|Hourly price|Monthly price| +|---|---|---|---| +|XS|6 vCPU / 8 GB|0.0917 €|66.00 €| +|S|8 vCPU / 12 GB|0.1333 €|96.00 €| +|M|10 vCPU / 16 GB|0.1750 €|126.00 €| +|L|12 vCPU / 24 GB|0.2500 €|180.00 €| +|XL|16 vCPU / 32 GB|0.3333 €|240.00 €| + +### Enterprise (`DISTRIBUTED`) control plane + +For each of the five components (`apiserver`, `controller-manager`, `scheduler`, `cloud-controller-manager`, `node-group-operator`), pay `replicationFactor` × the per-VM price below. Each component picks its own flavor and replication factor. + +|Flavor|Resources|Hourly price|Monthly price per component| +|---|---|---|---| +|2XS|4 vCPU / 4 GB|0.0500 €|36.00 €| +|XS|6 vCPU / 8 GB|0.0917 €|66.00 €| +|S|8 vCPU / 12 GB|0.1333 €|96.00 €| +|M|10 vCPU / 16 GB|0.1750 €|126.00 €| +|L|12 vCPU / 24 GB|0.2500 €|180.00 €| +|XL|16 vCPU / 32 GB|0.3333 €|240.00 €| + +The price above is **per component VM** (one of the five) at `replicationFactor = 1`. A minimal Distributed cluster with all five components in 2XS at rf=1 therefore costs `5 × 36.00 € = 180.00 €/month` for the control plane alone, plus the [node group rate](#node-groups-workers) for your workers. + +### Node groups (workers) + +Worker VMs in node groups attached to any cluster. An `ALL_IN_ONE` cluster ships with one worker node integrated in each bundle VM (no extra rate); any **additional** node group you attach to an `ALL_IN_ONE` cluster is billed at the rate below, like for `DEDICATED_COMPUTE` and `DISTRIBUTED`. + +|Flavor|Resources|Hourly price|Monthly price| +|---|---|---|---| +|2XS|4 vCPU / 4 GB|0.0333 €|24.00 €| +|XS|6 vCPU / 8 GB|0.0611 €|44.00 €| +|S|8 vCPU / 12 GB|0.0889 €|64.00 €| +|M|10 vCPU / 16 GB|0.1167 €|84.00 €| +|L|12 vCPU / 24 GB|0.1667 €|120.00 €| +|XL|16 vCPU / 32 GB|0.2222 €|160.00 €| + +## Clever Kubernetes Operator + +Clever Cloud's Kubernetes clusters are designed to work seamlessly with the rest of the platform. The [open-source Clever Kubernetes Operator](https://github.com/CleverCloud/clever-kubernetes-operator) lets you declare PostgreSQL databases, Pulsar topics, and any other Clever Cloud add-on directly inside your cluster via custom resources, and combine these managed services with your Kubernetes workloads. + +- [Learn more about the Clever Cloud Kubernetes Operator](/doc/kubernetes/operator/) diff --git a/data/kubernetes_versions.yml b/data/kubernetes_versions.yml new file mode 100644 index 000000000..b2e2d7cd6 --- /dev/null +++ b/data/kubernetes_versions.yml @@ -0,0 +1,12 @@ +# Clever Kubernetes Engine — supported Kubernetes versions. +# Support policy mirrors upstream Kubernetes (n-2 from the current release). +# Update this file when a new Kubernetes version becomes available or when the +# platform default rolls forward. +current: "1.36" +default: "1.35" +supported: + - "1.36" + - "1.35" + - "1.34" +unsupported: + - "1.33" diff --git a/layouts/shortcodes/kubernetes_version.html b/layouts/shortcodes/kubernetes_version.html new file mode 100644 index 000000000..0da98df40 --- /dev/null +++ b/layouts/shortcodes/kubernetes_version.html @@ -0,0 +1,3 @@ +{{- $key := .Get 0 -}} +{{- $value := index .Site.Data.kubernetes_versions $key -}} +{{- if reflect.IsSlice $value -}}{{ delimit $value ", " }}{{- else -}}{{ $value }}{{- end -}} From 0aa50df983b6eda5ae53ca849eb51239c8ded893 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 27 Apr 2026 17:09:25 +0200 Subject: [PATCH 079/180] kubernetes: Developers > Essential --- content/doc/kubernetes/_index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/doc/kubernetes/_index.md b/content/doc/kubernetes/_index.md index 4563066e8..b9f090056 100644 --- a/content/doc/kubernetes/_index.md +++ b/content/doc/kubernetes/_index.md @@ -91,7 +91,7 @@ You choose how the control plane is distributed at creation time. The topology d |Topology|Customers|VMs you pay for|Flavors|Default node group included?|Typical use case| |---|---|---|---|---|---| -|`ALL_IN_ONE`|Developers|`replicationFactor` × bundle VM (control plane + integrated worker node). Additional node groups billed separately|`S`, `M`, `L`, `XL`|No node group, but each bundle VM is also a worker node — additional node groups optional|Development, testing, small single-team clusters| +|`ALL_IN_ONE`|Essential|`replicationFactor` × bundle VM (control plane + integrated worker node). Additional node groups billed separately|`S`, `M`, `L`, `XL`|No node group, but each bundle VM is also a worker node — additional node groups optional|Development, testing, small single-team clusters| |`DEDICATED_COMPUTE`|Business|`replicationFactor` × control plane VM **plus** every worker VM in your node groups|`XS`, `S`, `M`, `L`, `XL`|No — provision a node group at creation with `--nodegroup` or later|Production clusters where control plane and workloads stay isolated| |`DISTRIBUTED`|Enterprise|For each of 5 components: `replicationFactor` × component VM **plus** every worker VM in your node groups|`2XS`, `XS`, `S`, `M`, `L`, `XL`|No — provision a node group at creation or later|Demanding production clusters needing fine-grained HA per control plane component| @@ -101,7 +101,7 @@ In `ALL_IN_ONE`, every VM in the bundle is also a worker node — the bundle cov Whenever a cluster spans multiple VMs — additional node groups on `ALL_IN_ONE`, the control plane and workers in `DEDICATED_COMPUTE`, the five components and workers in `DISTRIBUTED` — the VMs talk to each other over a [Network Group](/doc/develop/network-groups/), a private WireGuard-based mesh provisioned automatically with the cluster. Inter-component traffic stays on the private network. -**Developers (`ALL_IN_ONE`)** — one VM hosts the five control plane components plus an integrated worker node, replicated `replicationFactor` times. Additional node groups (optional) can be attached to scale compute beyond the bundle and are billed at the [node group rate](#node-groups-workers). +**Essential (`ALL_IN_ONE`)** — one VM hosts the five control plane components plus an integrated worker node, replicated `replicationFactor` times. Additional node groups (optional) can be attached to scale compute beyond the bundle and are billed at the [node group rate](#node-groups-workers). ```mermaid flowchart LR @@ -460,7 +460,7 @@ In practice: - **`DEDICATED_COMPUTE`** — `replicationFactor` × the control plane VM price, **plus** the [node group rate](#node-groups-workers) for every worker VM in your node groups. - **`DISTRIBUTED`** — for each of the five components, `replicationFactor` × the component VM price (each component picks its own flavor and replication factor), **plus** the [node group rate](#node-groups-workers) for every worker VM in your node groups. -### Developers (`ALL_IN_ONE`) bundle +### Essential (`ALL_IN_ONE`) bundle The bundle hosts the five control plane components and an integrated worker node on the same VMs. The price below covers everything; additional node groups attached to an `ALL_IN_ONE` cluster are billed separately at the [node group rate](#node-groups-workers). From fb9a9bee87f98e54eb78760fcd315850b44c2095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Collignon-Ducret=20R=C3=A9mi?= Date: Mon, 27 Apr 2026 18:47:41 +0200 Subject: [PATCH 080/180] docs(changelog): announce logs API v2 decommission on May 23rd 2026 (#923) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 What does this PR do? _Briefly describe your changes and why they are needed_ ## 🔗 Related Issue (if applicable) - Closes # - Related to # --- ## 🧪 Type of Change - [ ] ⚠️ Bug fix - [ ] 📅 Changelog update - [ ] 📚 Documentation update - [ ] ✨ New content/feature - [ ] 🔧 Technical/maintenance --- ## ✅ Quick Checklist - [ ] I have read the [contributing guidelines](https://github.com/CleverCloud/documentation/blob/main/CONTRIBUTING.md) - [ ] The content is accurate and links work - [ ] The site builds without errors --- ## 👥 Reviewers @CleverCloud/reviewers ---

📋 For major changes (click to expand) ### Additional testing performed _Describe any specific testing done for complex changes_ ### Screenshots _Add screenshots for visual/layout changes_ ### Breaking changes _List any breaking changes or migration notes_
--------- Co-authored-by: Steven Le Roux Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../2026/04-23-logs-v2-deprecation.md | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 content/changelog/2026/04-23-logs-v2-deprecation.md diff --git a/content/changelog/2026/04-23-logs-v2-deprecation.md b/content/changelog/2026/04-23-logs-v2-deprecation.md new file mode 100644 index 000000000..58969803e --- /dev/null +++ b/content/changelog/2026/04-23-logs-v2-deprecation.md @@ -0,0 +1,26 @@ +--- +title: Decommissioning Logs API v2 endpoints +date: 2026-04-23 +description: Decommission of the legacy Logs API v2 endpoints on May 23rd, 2026. Migrate to the v4 API to ensure uninterrupted access to your application logs. +tags: + - api +authors: + - name: miton18 + link: https://github.com/miton18 + image: https://github.com/miton18.png?size=40 +excludeSearch: true +--- + +Clever Cloud is decommissioning the legacy Logs API v2 endpoints on **May 23rd, 2026**. The v4 API has been available for some time and provides a more reliable and consistent interface to access your application logs. If you still rely on v2 endpoints to fetch logs, migrate to the v4 API as soon as possible. + +- **May 23rd, 2026**: v2 logs endpoints will be permanently disabled. Any integration still using v2 will stop receiving logs. + +## How to migrate + +The v4 Logs API exposes the same capabilities with an improved interface. Update your HTTP calls to target the v4 endpoints documented in the [Logs section of the v4 API reference](/api/v4#logs). Authentication and query parameters follow the same conventions as the rest of the v4 API. + +After migrating, your integration can continue fetching logs via v4. + +If you have any questions or need help with the migration, contact [our support team](https://console.clever-cloud.com/ticket-center-choice). + +- [Logs API v4 reference](/api/v4#logs) From cff0a0ceaac378f3515fc733c4a26a29488c28d4 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 27 Apr 2026 19:31:21 +0200 Subject: [PATCH 081/180] api(v4): update routes, payloads --- content/api/v4.md | 164 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 127 insertions(+), 37 deletions(-) diff --git a/content/api/v4.md b/content/api/v4.md index 70859e23f..93f659c60 100644 --- a/content/api/v4.md +++ b/content/api/v4.md @@ -216,85 +216,163 @@ Answers with logs in a SSE (Server-Sent Events) stream. You can use optional par ### Create a logs drain -- `/v4/drains/organisations/{ownerId}/applications/{applicationId}/drains` +- `/v4/drains/organisations/{ownerId}/resources/{resourceId}/drains` - Type: `POST` ```json { "kind": "ACCESSLOG|LOG", "recipient": { - "url": "", - "type":"" - // Optional parameters, depending on recipient_type + "type": "", + "url": "" + // Additional fields, depending on recipient_type } } ``` -| Parameter | Type | Comment | -| :--- | :--- | :--- | -| `type` | `string` | `DatadogRecipient`, `ElasticsearchRecipient`, `NewRelicRecipient`, `OVHTCPRecipient`, `RawRecipient` | -| `username` | `string` | Basic Auth username | -| `password` | `string` | Basic Auth password | -| `index` | `string` | ElasticSearch index | -| `apiKey` | `string` | NewRelic API key | -| `token` | `string` | OVHcloud token | -| `rfc5424StructuredDataParameters` | `string` | OVHcloud RFC 5424 structured data parameters | +The `recipient.type` discriminates the payload shape. Allowed values and their fields: + +| `type` | Required (besides `type` and `url`) | Optional | +| :--- | :--- | :--- | +| `DATADOG` | — | — | +| `ELASTICSEARCH` | `index` | `username`, `password`, `tlsVerification` (`DEFAULT` or `TRUSTFUL`) | +| `NEWRELIC` | `apiKey` | — | +| `OVH_TCP` | — | `token`, `rfc5424StructuredDataParameters` | +| `RAW_HTTP` | — | `username`, `password` | +| `SYSLOG_TCP` | — | `rfc5424StructuredDataParameters` | +| `SYSLOG_UDP` | — | `rfc5424StructuredDataParameters` | + +Field reference: + +| Field | Type | Comment | +| :--- | :--- | :--- | +| `username` | `string` | Basic Auth username (`ELASTICSEARCH`, `RAW_HTTP`) | +| `password` | `string` | Basic Auth password (`ELASTICSEARCH`, `RAW_HTTP`) | +| `index` | `string` | Elasticsearch index name | +| `tlsVerification` | `string` | Elasticsearch TLS mode, `DEFAULT` (verify) or `TRUSTFUL` (skip verification) | +| `apiKey` | `string` | New Relic API key | +| `token` | `string` | OVHcloud Logs Data Platform token | +| `rfc5424StructuredDataParameters` | `string` | RFC 5424 structured data, e.g. `X-OVH-TOKEN="…"` | ### List logs drains -- `/v4/drains/organisations/{ownerId}/applications/{applicationId}/drains` +- `/v4/drains/organisations/{ownerId}/resources/{resourceId}/drains` - Type: `GET` +Optional query parameters: `status`, `executionStatus`, `executionStatusNotIn`. + ### Manage a logs drain -- `/v4/drains/organisations/{ownerId}/applications/{applicationId}/drains/{drainId}` +- `/v4/drains/organisations/{ownerId}/resources/{resourceId}/drains/{drainId}` - Type: `GET`/`DELETE` +### Enable/disable a logs drain + +- `/v4/drains/organisations/{ownerId}/resources/{resourceId}/drains/{drainId}/enable` +- `/v4/drains/organisations/{ownerId}/resources/{resourceId}/drains/{drainId}/disable` +- Type: `PUT` + +### Test or reset a logs drain + +- `/v4/drains/organisations/{ownerId}/resources/{resourceId}/drains/{drainId}/test-command` +- Type: `GET` — returns a `curl` command to test connectivity to the drain endpoint. +- `/v4/drains/organisations/{ownerId}/resources/{resourceId}/drains/{drainId}/reset-cursor` +- Type: `PATCH` — restarts log forwarding from the latest position. + ## Operators A Clever Cloud Operator deploys and manage resources for a given service. Available operators are: `keycloak`, `matomo`, `metabase`, `otoroshi`. - `/v4/addon-providers/addon-{operator_name}/addons/{operator_id}` - Type: `GET` -Example response: -```bash +Example response (Otoroshi — the richest payload): +```json { - "resourceId": "real_id", - "addonId": "addon_id", + "resourceId": "otoroshi_", + "addonId": "addon_", "name": "resource_name", "ownerId": "user_or_org_id", "plan": "PLAN_NAME", "version": "x.y.z", - "javaVersion": "X", # Optional - "phpVersion": "X", # Optional + "javaVersion": "X", "accessUrl": "https://service-web-ui-id.services.clever-cloud.com", - "initialCredentials": { # Optional + "availableVersions": ["x.y.z", "…"], + "initialCredentials": { "user": "user_name", "password": "user_password" }, - "api": { # Optional + "api": { "url": "https://service-api-id.services.clever-cloud.com", - … + "user": "api_user", + "secret": "api_secret", + "openapi": "https://service-api-id.services.clever-cloud.com/api/openapi.json", + "swaggerUrl": "https://service-api-id.services.clever-cloud.com/api/openapi/ui" }, - "availableVersions": [ - "x.y.z", - … - ], "resources": { - "entrypoint": "app_id", - … + "entrypoint": "app_", + "redisId": "redis_", + "pulsarId": "pulsar_", + "elasticId": "elasticsearch_", + "cellarId": "cellar_" }, "features": { - … + "networkGroup": { "id": "ng_" } } } ``` +Field availability per operator: + +All operators always return: `resourceId`, `addonId`, `name`, `ownerId`, `plan`, `version`, `accessUrl`, `availableVersions`, `resources`. + +Other fields depend on the operator (`✓` = always returned, `—` = not returned, `?` = returned when set): + +| Field | `otoroshi` | `keycloak` | `matomo` | `metabase` | +| :--- | :---: | :---: | :---: | :---: | +| `javaVersion` | ✓ | ✓ | — | ✓ | +| `phpVersion` | — | — | ✓ | — | +| `initialCredentials` | ✓ | ✓ | — | — | +| `api` | ✓ | — | — | — | +| `features.networkGroup` | ? | ? | — | — | + +For Otoroshi, `resources.entrypoint` and `resources.redisId` are always returned; `pulsarId`, `elasticId` and `cellarId` only appear when the corresponding resource exists. When the Network Group feature is enabled, `features.networkGroup` returns `{ "id": "ng_" }`; otherwise it is `null`. + - `/v4/addon-providers/addon-{operator_name}/addons/{operator_id}/reboot` - `/v4/addon-providers/addon-{operator_name}/addons/{operator_id}/rebuild` - Type: `POST` - Response code: `204` +### Version management + +Available for `keycloak`, `metabase` and `otoroshi`. + +- `/v4/addon-providers/addon-{operator_name}/addons/{operator_id}/version/check` +- Type: `GET` + +```json +{ + "installed": "x.y.z", + "latest": "x.y.z", + "available": ["x.y.z", "…"], + "needUpdate": boolean +} +``` + +- `/v4/addon-providers/addon-{operator_name}/addons/{operator_id}/version/update` +- Type: `POST` + +```json +{ + "targetVersion": "x.y.z" +} +``` + +### Otoroshi configuration export + +- `/v4/addon-providers/addon-otoroshi/addons/{operator_id}/config.yaml` +- Type: `GET` +- Response: `application/yaml` — the full `otoroshictl`-compatible configuration of the instance. + ### Network Groups You can add the deployed service main application in a [Network Group](/doc/develop/network-groups/) to activate some enhanced features: - `keycloak`: [Secured Multi Instances feature](/doc/addons/keycloak/#secured-multi-instances) @@ -329,6 +407,13 @@ If no Network Group is found, the `networkGroup` feature value is `null`: - `/v4/products/zones/{name}` - Type: `GET` +You can use optional query parameters to filter the results: + +| Parameter | Type | Comment | +| :--- | :--- | :--- | +| `tag` | `string` | Filter zones on tags (e.g. `infra:clever-cloud`) | +| `ownerId` | `string` | Restrict to zones the given owner has access to | + Example response, for each zone: ```bash [ @@ -355,17 +440,22 @@ Example response, for each zone: Define or get offload/retention policies of a [Pulsar add-on](/doc/addons/pulsar). Retention is how long messages are kept in the topic. Offload is how long messages are kept in the hot storage (NVMe SSD) before being moved to Cellar object storage (S3 compatible, HDD). Example query/response: -```bash +```json { "retentionPolicies": { - "sizeInMB": number, - "durationInDays": number + "sizeInBytes": number, + "durationInMinutes": number }, "offloadPolicies": { - "durationInDays": number, - "sizeInMB": number + "sizeInBytes": number, + "durationInMinutes": number } } ``` -A `null` value means an infinite retention or no offload. +| Field | Type | Comment | +| :--- | :--- | :--- | +| `sizeInBytes` | `int64` | Size threshold in bytes. Optional. A negative value means no limit. | +| `durationInMinutes` | `int64` | Duration threshold in minutes. Optional. A negative value means no limit. | + +When both fields are omitted (or `null`), retention is infinite and offload is disabled. From 21be2b109086b69ef50a76579f2d31329dd73378 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 28 Apr 2026 11:33:56 +0200 Subject: [PATCH 082/180] changelog: Clever Tools 4.9 --- .../changelog/2026/04-28-clever-tools-4.9.md | 113 +++++++ content/doc/cli/kubernetes.md | 202 +++++++++--- content/doc/kubernetes/_index.md | 7 +- content/doc/reference/cli.md | 306 +++++++++++++++++- 4 files changed, 579 insertions(+), 49 deletions(-) create mode 100644 content/changelog/2026/04-28-clever-tools-4.9.md diff --git a/content/changelog/2026/04-28-clever-tools-4.9.md b/content/changelog/2026/04-28-clever-tools-4.9.md new file mode 100644 index 000000000..9f7eb294d --- /dev/null +++ b/content/changelog/2026/04-28-clever-tools-4.9.md @@ -0,0 +1,113 @@ +--- +title: "Clever Tools 4.9: full Kubernetes lifecycle from the CLI" +date: 2026-04-28 +description: Clever Tools 4.9 expands the Kubernetes command set with node groups, version updates, quotas and activity, and adds Swagger UI access for Otoroshi +tags: + - clever-tools + - cli + - kubernetes + - otoroshi +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 + - name: Hubert Sablonnière + link: https://github.com/hsablonniere + image: https://github.com/hsablonniere.png?size=40 +excludeSearch: true +--- + +[Clever Tools 4.9.0](https://github.com/CleverCloud/clever-tools/releases/tag/4.9.0) is available. This release significantly extends the `clever k8s` command set to cover the full lifecycle of [Clever Kubernetes Engine](/doc/kubernetes/), as it's now in public Beta. It also adds Swagger UI access to Otoroshi services and ships smaller quality-of-life improvements. + +## Kubernetes lifecycle management + +The `clever k8s` command set now covers cluster creation with detailed topology, ongoing operations, node group management, version upgrades and quota visibility. The `create` command accepts `--topology`, `--flavor`, `--cluster-version`, `--replication-factor`, `--autoscaling`, `--persistent-storage` and `--nodegroup` to provision a cluster matching your needs in a single command. The `get` output now reports topology, features, node groups and load balancers. + +```bash +# Enable access to the k8s command set +clever features enable k8s + +# Create a cluster with a control plane topology, version and initial node group +clever k8s create my-cluster \ + --topology dedicated_compute --flavor S --replication-factor 3 \ + --cluster-version 1.36 --autoscaling \ + --nodegroup XS:3 --persistent-storage + +# Inspect the cluster (topology, features, node groups, load balancers) +clever k8s get my-cluster + +# Update metadata or features +clever k8s update my-cluster --description "Production cluster" --tag env:prod,team:platform +``` + +Node groups have their own subcommands to list, create, get, update and delete, with autoscaling bounds and arbitrary tags: + +```bash +# Create an autoscaling node group +clever k8s nodegroups create my-cluster workers XS:3 --autoscaling --min 3 --max 10 + +# List node groups attached to a cluster +clever k8s nodegroups list my-cluster + +# Update bounds or target count +clever k8s nodegroups update my-cluster workers --count 5 + +# Delete a node group +clever k8s nodegroups delete my-cluster workers --yes +``` + +Version management is now first-class. The `version` subcommand reports the installed version and offers to upgrade to the latest available release when the cluster is outdated. The `version update` subcommand upgrades a cluster to an explicit target version when you prefer to drive the upgrade yourself: + +```bash +# Check the current version and prompt for an upgrade if available +clever k8s version my-cluster + +# Upgrade the cluster to a target version +clever k8s version update my-cluster --target 1.36 +``` + +Two more commands round out the set: `clever k8s activity` shows recent deployment events of a cluster, and `clever k8s quota` reports the Kubernetes quota, current usage and remaining capacity for an organisation during public Beta. + +```bash +clever k8s activity my-cluster --limit 100 +clever k8s quota +``` + +Once your cluster reaches the `ACTIVE` state, retrieve its `kubeconfig` and drive it with `kubectl` like any other Kubernetes cluster. With `--persistent-storage` enabled at creation time, a default Ceph RBD `StorageClass` is provisioned on the cluster, so any `PersistentVolumeClaim` you create is bound automatically: + +```bash +# Generate the kubeconfig and set it as your current context +clever k8s get-kubeconfig my-cluster > ~/.kube/config + +# Inspect cluster nodes and the persistent storage driver +kubectl get nodes +kubectl get csidrivers +kubectl get storageclasses + +# Deploy a workload and let it consume the default StorageClass +kubectl create deployment nginx --image=nginx +kubectl expose deployment nginx --port=80 --type=LoadBalancer +``` + +- [Learn more about Clever Kubernetes Engine](/doc/kubernetes/) + +## Otoroshi Swagger UI + +You can now open the Otoroshi Swagger UI directly from the CLI with a new `clever otoroshi open swaggerui` subcommand. The `clever otoroshi get` command also exposes the Swagger URL alongside the other endpoints, so you can hand it over to teammates or automation without opening the Console first. + +```bash +# Open the Swagger UI of an Otoroshi service in your browser +clever otoroshi open swaggerui my-otoroshi + +# Get service details, including the Swagger URL +clever otoroshi get my-otoroshi +``` + +## How to upgrade + +To upgrade Clever Tools, [use your favourite package manager](/doc/cli/install/). For example with `npm`: + +```bash +npm update -g clever-tools +clever version +``` diff --git a/content/doc/cli/kubernetes.md b/content/doc/cli/kubernetes.md index 3d5cb6175..ca3507c37 100644 --- a/content/doc/cli/kubernetes.md +++ b/content/doc/cli/kubernetes.md @@ -2,7 +2,7 @@ type: docs linkTitle: Kubernetes title: Clever Kubernetes Engine (CKE) -description: Create and manage Kubernetes clusters using Clever Cloud Kubernetes Engine from Clever Tools +description: Create and manage Kubernetes clusters using Clever Kubernetes Engine from Clever Tools keywords: - kubernetes - cke @@ -10,92 +10,216 @@ keywords: - etcd - k8s - cluster +- topology +- node group +- autoscaling - csi - persistent storage - kubeconfig - load balancers +- quota +- version --- -Clever Cloud allows you to create and manage Kubernetes clusters directly from Clever Tools. Once created and configured, you can use them with `kubectl` or any Kubernetes-compatible tool. +Clever Tools 4.9+ exposes the full lifecycle of [Clever Kubernetes Engine](/doc/kubernetes/): cluster creation with detailed topology, ongoing operations, node group management, version upgrades and quota visibility. Once a cluster is `ACTIVE`, you drive it with `kubectl` like any other Kubernetes cluster. - [Learn more about Kubernetes on Clever Cloud](/doc/kubernetes/) ## Prerequisites -Activate `k8s` feature flag to manage Kubernetes clusters: +Activate the `k8s` feature flag once per user account: -``` +```bash clever features enable k8s ``` -Then, check it works with the following command: +Check the command set is available: -``` +```bash clever k8s ``` -In all the following examples, you can target a specific organisation with the `--org` or `-o` option. +In all examples below, target a specific organisation with the `--org` (or `-o`) option. Output format defaults to a human-readable table; pass `--format json` (or `-F json`) on read commands when you need structured output for scripts or pipelines. -## Create/Delete a Cluster +## Create a cluster -To create a Kubernetes cluster, you just need a name and you can wait for it to be in `ACTIVE` state: -``` -clever k8s create myKubeCluster -clever k8s create myKubeCluster --watch +The fastest way to create a cluster is to provide only a name. The platform picks `ALL_IN_ONE` as the default topology, the smallest available flavor (`S`) and a replication factor of `1`: + +```bash +clever k8s create myCluster --org ``` -To delete a cluster, use: +Add `--watch` to follow the deployment until the cluster reaches `ACTIVE`: + +```bash +clever k8s create myCluster --watch --org ``` -clever k8s delete myKubeCluster -clever k8s delete myKubeCluster --yes + +When you need a specific shape, combine topology, flavor, replication factor, version and an initial node group in a single command. Topology values (`all_in_one`, `dedicated_compute`, `distributed`) are accepted in lowercase or uppercase. The `--nodegroup :` option provisions an initial node group named `default`, ready to schedule workloads as soon as the cluster reaches `ACTIVE`. Use it on `dedicated_compute` and `distributed` clusters, which otherwise come up with no worker. `all_in_one` bundles already include an integrated worker on each bundle VM, so passing `--nodegroup` adds an *extra* pool — Clever Tools warns you and asks for confirmation in that case: + +```bash +clever k8s create myCluster --org \ + --topology dedicated_compute --flavor S --replication-factor 3 \ + --cluster-version 1.36 \ + --description "Production cluster" \ + --tag env:prod,team:platform \ + --autoscaling \ + --persistent-storage \ + --nodegroup M:3 ``` -## List Clusters +The `--cluster-version` value is validated against the platform-supported versions before the API call; an unsupported value (e.g. `0.99`) is rejected upfront with the list of available versions. -If you have cluster, you can list them and know their name, ID and status with: +## List, get and inspect -``` +List the Kubernetes clusters of the active organisation: + +```bash clever k8s list clever k8s list --format json ``` -## Get Cluster information +Get full details for one cluster (topology, features, node groups, load balancers, storage usage). The first form resolves a cluster by name when unambiguous; the second targets a specific cluster by ID: + +```bash +clever k8s get myCluster +clever k8s get kubernetes_id -F json +``` -To get information about a specific cluster, use: +The human format shows topology and feature state in a single table, with extra tables for control plane components (in `DISTRIBUTED`), node groups and load balancers when present: +```text +┌────────────────────┬─────────────────────────────────────────┐ +│ (index) │ Values │ +├────────────────────┼─────────────────────────────────────────┤ +│ Name │ 'myCluster' │ +│ ID │ 'kubernetes_id' │ +│ Status │ 'ACTIVE' │ +│ Version │ '1.36' │ +│ Topology │ 'DEDICATED_COMPUTE (S, rf=3)' │ +│ Autoscaling │ 'enabled' │ +│ Persistent storage │ 'enabled' │ +│ Tags │ 'env:prod, team:platform' │ +│ Description │ 'Production cluster' │ +└────────────────────┴─────────────────────────────────────────┘ ``` -clever k8s get myKubeCluster -clever k8s get kubernetes_id -F json + +## Update cluster metadata + +Rename the cluster, change its description or tags, and toggle autoscaling without redeploying. Pass at least one of `--name`, `--description`, `--tag`, `--autoscaling` or `--disable-autoscaling`. The `--autoscaling` and `--disable-autoscaling` flags are mutually exclusive: + +```bash +clever k8s update myCluster --description "Production cluster, EU" --tag env:prod,team:platform +clever k8s update myCluster --name myCluster-eu +clever k8s update myCluster --autoscaling +clever k8s update myCluster --disable-autoscaling +``` + +Updating `--tag` replaces the full list of tags. Pass an empty value to clear them. + +## Add persistent storage + +Enable persistent storage on an existing cluster with a Ceph RBD CSI driver. The cluster gains a default `StorageClass` named `csi-rbd-sc`, ready to back any `PersistentVolumeClaim`: + +```bash +clever k8s add-persistent-storage myCluster +``` + +Persistent storage is a one-way toggle; once enabled, it cannot be removed from a running cluster. Create a new cluster without `--persistent-storage` if you no longer need it. + +## Get the kubeconfig file + +Retrieve the kubeconfig of an `ACTIVE` cluster. Wait for the cluster to reach `ACTIVE` before redirecting the output to a file — the command is a no-op on non-ready clusters: + +```bash +clever k8s get-kubeconfig myCluster +clever k8s get-kubeconfig myCluster > ~/.kube/config +``` + +Once the kubeconfig is in place, drive the cluster with `kubectl` as usual. With `--persistent-storage` enabled, the default `StorageClass` is provisioned automatically. `kubectl get nodes` lists the integrated workers immediately on `all_in_one` clusters; on `dedicated_compute` and `distributed` clusters, the list stays empty until you add a node group: + +```bash +kubectl get nodes +kubectl get csidrivers +kubectl get storageclasses + +kubectl create deployment nginx --image=nginx +kubectl expose deployment nginx --port=80 --type=LoadBalancer ``` -Classic response is a table: +## Activity and quota + +`activity` lists recent deployment events of a cluster (creation steps, control plane rollout, node group resizes). The default limit is `50`; pass `--limit N` (between `1` and `1000`) to widen the window: +```bash +clever k8s activity myCluster +clever k8s activity myCluster --limit 100 +clever k8s activity myCluster -F json ``` -┌─────────┬─────────────────────────────────────────┐ -│ (index) │ Values │ -├─────────┼─────────────────────────────────────────┤ -│ Name │ 'myKubeCluster' │ -│ ID │ 'kubernetes_id' │ -│ Version │ 1.34.1 │ -│ Status │ 'ACTIVE' │ -└─────────┴─────────────────────────────────────────┘ + +`quota` reports the Kubernetes quota, current usage and remaining capacity for the active organisation. Each organisation starts with **40 vCPU and 40 GB of RAM** during the public Beta: + +```bash +clever k8s quota +clever k8s quota -F json ``` -## Add persistent storage to a Cluster +## Cluster version -You can add persistent storage to an `ACTIVE` cluster with: +Report the installed Kubernetes version of a cluster, and offer an interactive upgrade prompt when an upgrade is available: +```bash +clever k8s version myCluster +clever k8s version check myCluster ``` -clever k8s add-persistent-storage myKubeCluster + +Drive the upgrade explicitly to a target version. The target is validated against the supported versions before the API call: + +```bash +clever k8s version update myCluster --target 1.36 ``` -Once added you can't remove it, but you can start a fresh cluster without persistent storage. +## Node groups -## Get kubeconfig file of a Cluster +A node group is a pool of worker VMs of the same flavor. `dedicated_compute` and `distributed` clusters need at least one node group to schedule workloads; `all_in_one` clusters already include an integrated worker on each bundle VM and only need a node group when you want extra capacity beyond the bundle. Create one alongside a cluster (with `--nodegroup` on `clever k8s create`) or independently with `nodegroups create`. The third positional argument follows the `:` format and accepts lowercase flavors: -To get the `kubeconfig` file of an `ACTIVE` cluster, and set it as the current context, use: +```bash +clever k8s nodegroups create myCluster workers XS:3 +clever k8s nodegroups create myCluster workers XS:3 --autoscaling --min 3 --max 10 +clever k8s nodegroups create myCluster workers XS:3 --description "GPU-intensive workers" --tag env:prod +``` + +Inspect node groups attached to a cluster: + +```bash +clever k8s nodegroups list myCluster +clever k8s nodegroups list myCluster -F json +clever k8s nodegroups get myCluster workers +clever k8s nodegroups get myCluster node_group_id +``` + +Update bounds, target count, autoscaling state or metadata. Pass at least one of `--count`, `--min`, `--max`, `--autoscaling`, `--disable-autoscaling`, `--description` or `--tag`. Resizes are queued: the API rejects a second update while a previous resize is still running: +```bash +clever k8s nodegroups update myCluster workers --count 5 +clever k8s nodegroups update myCluster workers --autoscaling --min 2 --max 10 +clever k8s nodegroups update myCluster workers --disable-autoscaling +clever k8s nodegroups update myCluster workers --description "Updated description" ``` -clever k8s get-kubeconfig myKubeCluster -clever k8s get-kubeconfig myKubeCluster > ~/.kube/config + +Delete a node group; nodes are drained and the underlying VMs are removed. Skip the confirmation prompt with `--yes`: + +```bash +clever k8s nodegroups delete myCluster workers +clever k8s nodegroups delete myCluster workers --yes +``` + +## Delete a cluster + +Delete a cluster by name (when unambiguous) or by ID. Skip the confirmation prompt with `--yes`: + +```bash +clever k8s delete myCluster +clever k8s delete myCluster --yes +clever k8s delete kubernetes_id --yes ``` diff --git a/content/doc/kubernetes/_index.md b/content/doc/kubernetes/_index.md index b9f090056..6a239be84 100644 --- a/content/doc/kubernetes/_index.md +++ b/content/doc/kubernetes/_index.md @@ -197,7 +197,7 @@ When you need a specific shape, combine topology, flavor and replication factor: ```bash clever k8s create myCluster \ - --topology DEDICATED_COMPUTE --flavor S --replication-factor 3 \ + --topology dedicated_compute --flavor S --replication-factor 3 \ --cluster-version 1.36 \ --description "Production cluster" \ --tag env:prod,team:platform \ @@ -312,11 +312,10 @@ Once enabled, the autoscaler reacts to node-level resource pressure (CPU and mem You can use Clever Cloud block storage to attach a persistent volume to a cluster through a CSI (Container Storage Interface). Enable persistent storage at creation with `--persistent-storage`, or on an existing cluster: ```bash -clever k8s update clusterNameOrId --persistent-storage --org clever k8s add-persistent-storage clusterNameOrId --org ``` -Both commands reach the same result. Persistent storage is currently a one-way toggle: once enabled, it cannot be removed from a running cluster. Create a new cluster without `--persistent-storage` if you no longer need it. +Persistent storage is currently a one-way toggle: once enabled, it cannot be removed from a running cluster. Create a new cluster without `--persistent-storage` if you no longer need it. ## Get the kubeconfig file @@ -332,7 +331,7 @@ You can directly save it as your local kubeconfig file: clever k8s get-kubeconfig clusterNameOrId --org > ~/.kube/config ``` -Check everything is working by listing the nodes of your cluster (it should be empty at this point): +Check everything is working by listing the nodes of your cluster. With `ALL_IN_ONE`, the integrated worker on each bundle VM appears immediately. With `DEDICATED_COMPUTE` or `DISTRIBUTED`, the list stays empty until you provision a node group: ```bash # With the default kubeconfig file: diff --git a/content/doc/reference/cli.md b/content/doc/reference/cli.md index c248444df..7122e2a47 100644 --- a/content/doc/reference/cli.md +++ b/content/doc/reference/cli.md @@ -367,7 +367,7 @@ addon-id|addon-name Add-on ID (or name, if unambiguous) **Options** ``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) --y, --yes Skip confirmation and proceed with deletion directly +-y, --yes Skip confirmation prompts ``` ### addon env @@ -1493,6 +1493,29 @@ clever help clever k8s ``` +### k8s activity + +**Description:** Show recent deployment events of a Kubernetes cluster + +**Since:** 4.9.0 + +**Usage** +``` +clever k8s activity [options] +``` + +**Arguments** +``` +cluster-id|cluster-name Kubernetes cluster ID or name +``` + +**Options** +``` +-F, --format Output format (human, json) (default: human) + --limit Number of events to fetch (1 to 1000) (default: 50) +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` + ### k8s add-persistent-storage **Description:** Activate persistent storage to a deployed Kubernetes cluster @@ -1527,13 +1550,23 @@ clever k8s create [options] **Arguments** ``` -cluster-name Kubernetes cluster name +cluster-name Kubernetes cluster name ``` **Options** ``` --o, --org, --owner Organisation to target by its ID (or name, if unambiguous) --w, --watch Watch the deployment until the cluster is deployed + --autoscaling Enable the cluster autoscaler + --cluster-version Kubernetes version to deploy (e.g.: 1.36) + --description Free-form cluster description + --flavor Control plane flavor + --nodegroup Initial node group (format: :, e.g.: XS:3) +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) + --persistent-storage Enable persistent storage (Ceph CSI) + --replication-factor Control plane replication factor + --tag Semantic tags (comma-separated, e.g.: env:prod,team:platform) + --topology Cluster topology (must be set with --flavor and --replication-factor) +-w, --watch Watch the deployment until the cluster is deployed +-y, --yes Skip confirmation prompts ``` ### k8s delete @@ -1555,7 +1588,7 @@ cluster-id|cluster-name Kubernetes cluster ID or name **Options** ``` -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) --y, --yes Skip confirmation and proceed with deletion directly +-y, --yes Skip confirmation prompts ``` ### k8s get @@ -1618,6 +1651,251 @@ clever k8s list [options] -o, --org, --owner Organisation to target by its ID (or name, if unambiguous) ``` +### k8s nodegroups + +**Description:** Manage Kubernetes node groups + +**Since:** 4.9.0 + +**Usage** +``` +clever k8s nodegroups +``` + +#### k8s nodegroups create + +**Description:** Create a node group on a Kubernetes cluster + +**Since:** 4.9.0 + +**Usage** +``` +clever k8s nodegroups create [options] +``` + +**Arguments** +``` +cluster-id|cluster-name Kubernetes cluster ID or name +nodegroup-name Node group name (lowercase RFC 1123, max 63 chars) +flavor:count Node group flavor and target node count (format: :, e.g.: XS:3) +``` + +**Options** +``` + --autoscaling Enable cluster autoscaler for this node group (requires --min and --max) + --description Free-form node group description + --max Maximum node count when autoscaling is enabled + --min Minimum node count when autoscaling is enabled +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) + --tag Arbitrary tag attached to the node group +``` + +#### k8s nodegroups delete + +**Description:** Delete a node group from a Kubernetes cluster + +**Since:** 4.9.0 + +**Usage** +``` +clever k8s nodegroups delete [options] +``` + +**Arguments** +``` +cluster-id|cluster-name Kubernetes cluster ID or name +nodegroup-id|nodegroup-name Kubernetes node group ID or name +``` + +**Options** +``` +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +-y, --yes Skip confirmation prompts +``` + +#### k8s nodegroups get + +**Description:** Get information about a Kubernetes node group + +**Since:** 4.9.0 + +**Usage** +``` +clever k8s nodegroups get [options] +``` + +**Arguments** +``` +cluster-id|cluster-name Kubernetes cluster ID or name +nodegroup-id|nodegroup-name Kubernetes node group ID or name +``` + +**Options** +``` +-F, --format Output format (human, json) (default: human) +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` + +#### k8s nodegroups list + +**Description:** List the node groups of a Kubernetes cluster + +**Since:** 4.9.0 + +**Usage** +``` +clever k8s nodegroups list [options] +``` + +**Arguments** +``` +cluster-id|cluster-name Kubernetes cluster ID or name +``` + +**Options** +``` +-F, --format Output format (human, json) (default: human) +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` + +#### k8s nodegroups update + +**Description:** Update a node group on a Kubernetes cluster + +**Since:** 4.9.0 + +**Usage** +``` +clever k8s nodegroups update [options] +``` + +**Arguments** +``` +cluster-id|cluster-name Kubernetes cluster ID or name +nodegroup-id|nodegroup-name Kubernetes node group ID or name +``` + +**Options** +``` + --autoscaling Enable the cluster autoscaler + --count Target node count + --description Free-form node group description + --disable-autoscaling Disable the cluster autoscaler + --max Maximum node count (autoscaling bound) + --min Minimum node count (autoscaling bound) +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) + --tag Arbitrary tag attached to the node group +``` + +### k8s quota + +**Description:** Get the Kubernetes quota, usage and remaining of an organisation + +**Since:** 4.9.0 + +**Usage** +``` +clever k8s quota [options] +``` + +**Options** +``` +-F, --format Output format (human, json) (default: human) +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` + +### k8s update + +**Description:** Update a Kubernetes cluster metadata or features + +**Since:** 4.9.0 + +**Usage** +``` +clever k8s update [options] +``` + +**Arguments** +``` +cluster-id|cluster-name Kubernetes cluster ID or name +``` + +**Options** +``` + --autoscaling Enable the cluster autoscaler + --description Free-form cluster description + --disable-autoscaling Disable the cluster autoscaler + --name Rename the cluster +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) + --tag Replace tags (comma-separated, e.g.: env:prod,team:platform) +``` + +### k8s version + +**Description:** Check a Kubernetes cluster deployed version + +**Since:** 4.9.0 + +**Usage** +``` +clever k8s version [options] +``` + +**Arguments** +``` +cluster-id|cluster-name Kubernetes cluster ID or name +``` + +**Options** +``` +-F, --format Output format (human, json) (default: human) +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` + +#### k8s version check + +**Description:** Check a Kubernetes cluster deployed version + +**Since:** 4.9.0 + +**Usage** +``` +clever k8s version check [options] +``` + +**Arguments** +``` +cluster-id|cluster-name Kubernetes cluster ID or name +``` + +**Options** +``` +-F, --format Output format (human, json) (default: human) +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) +``` + +#### k8s version update + +**Description:** Update a Kubernetes cluster to a target version + +**Since:** 4.9.0 + +**Usage** +``` +clever k8s version update [options] +``` + +**Arguments** +``` +cluster-id|cluster-name Kubernetes cluster ID or name +``` + +**Options** +``` +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) + --target Target version to upgrade to (e.g.: 24, 2.4, 2.4.1) +``` + ## keycloak **Description:** Manage Clever Cloud Keycloak services @@ -2589,7 +2867,7 @@ consumer-key|consumer-name OAuth consumer key (or name, if unambiguous) **Options** ``` --y, --yes Skip confirmation and proceed with deletion directly +-y, --yes Skip confirmation prompts ``` ### oauth-consumers get @@ -2807,6 +3085,22 @@ clever otoroshi open logs addon-id|addon-name Add-on ID (or name, if unambiguous) ``` +#### otoroshi open swaggerui + +**Description:** Open the Otoroshi Swagger UI in your browser + +**Since:** 4.9.0 + +**Usage** +``` +clever otoroshi open swaggerui +``` + +**Arguments** +``` +addon-id|addon-name Add-on ID (or name, if unambiguous) +``` + #### otoroshi open webui **Description:** Open the Otoroshi admin console in your browser From 302a78365c77c1759f09f6672178f939d516bb4e Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 28 Apr 2026 12:07:55 +0200 Subject: [PATCH 083/180] fix: dead links --- content/changelog/2024/11-08-mysql-mysql-8.4.0.md | 2 +- content/changelog/2024/12-04-otoroshi-available.md | 6 +++--- content/changelog/2025/06-04-otoroshi-17.3.md | 2 +- content/changelog/2025/07-16-otoroshi-17.4.md | 2 +- content/changelog/2025/09-10-otoroshi-17.5.md | 2 +- content/changelog/2025/11-13-otoroshi-plugins.md | 2 +- content/changelog/2026/03-04-otoroshi-17.13.md | 2 +- content/changelog/2026/03-30-otoroshi-17.14.md | 2 +- content/doc/addons/otoroshi.md | 6 +++--- content/doc/applications/python/servers.md | 2 +- content/guides/astro.md | 2 +- 11 files changed, 15 insertions(+), 15 deletions(-) diff --git a/content/changelog/2024/11-08-mysql-mysql-8.4.0.md b/content/changelog/2024/11-08-mysql-mysql-8.4.0.md index b3222e81d..22003ae2c 100644 --- a/content/changelog/2024/11-08-mysql-mysql-8.4.0.md +++ b/content/changelog/2024/11-08-mysql-mysql-8.4.0.md @@ -14,7 +14,7 @@ aliases: excludeSearch: true --- -MySQL 8.4 is available on Clever Cloud. You can deploy it as a new add-on or migrate existing ones. As this is the first release (8.4.0) of this long term support (LTS) branch for the [Percona Server](https://www.percona.com/mysql/software/percona-server-for-mysql) we use, it's tagged as `early`. That means you should consider it mostly to make some tests and discover what's new. But we recommend [waiting a bit](https://www.percona.com/blog/severe-instability-of-mysql-8-0-38-8-4-1-and-9-0-resolved-in-upcoming-releases/) before using a new branch in production. +MySQL 8.4 is available on Clever Cloud. You can deploy it as a new add-on or migrate existing ones. As this is the first release (8.4.0) of this long term support (LTS) branch for the [Percona Server](https://www.percona.com/mysql/software/percona-server-for-mysql) we use, it's tagged as `early`. That means you should consider it mostly to make some tests and discover what's new. But we recommend [waiting a bit](https://www.percona.com/blog/do-not-upgrade-to-any-version-of-mysql-after-8-0-37/) before using a new branch in production. * [Learn more about MySQL 8.4](https://www.percona.com/blog/mysql-8-4-first-peek/) * [Learn more about MySQL on Clever Cloud](/doc/addons/mysql/) diff --git a/content/changelog/2024/12-04-otoroshi-available.md b/content/changelog/2024/12-04-otoroshi-available.md index 9534b12b5..ce5ea21b9 100644 --- a/content/changelog/2024/12-04-otoroshi-available.md +++ b/content/changelog/2024/12-04-otoroshi-available.md @@ -19,14 +19,14 @@ excludeSearch: true After some weeks of testing, the Clever Cloud's Otoroshi with LLM add-on, is available in public beta. Thus, you can deploy the service, from [Console](https://console.clever-cloud.com/users/me/addons/new), [API](/api) or [Clever Tools](https://github.com/CleverCloud/clever-tools), and use it in minutes. -We developed this product with its creator and core developer: [Mathieu Ancelin](https://github.com/mathieuancelin) from [Cloud APIM](https://www.cloud-apim.com/). Otoroshi is an open source reverse proxy that allows you to create your own routes, manage authentication, authorization, and rate limiting. It helps you to expose services with enterprise needs in mind, as you can manage organisations, teams, service groups, with event management, data export, multiple secret stores support, etc. And it can be managed from a web interface or [as an API](https://maif.github.io/otoroshi/manual/api.html). +We developed this product with its creator and core developer: [Mathieu Ancelin](https://github.com/mathieuancelin) from [Cloud APIM](https://www.cloud-apim.com/). Otoroshi is an open source reverse proxy that allows you to create your own routes, manage authentication, authorization, and rate limiting. It helps you to expose services with enterprise needs in mind, as you can manage organisations, teams, service groups, with event management, data export, multiple secret stores support, etc. And it can be managed from a web interface or [as an API](https://maif.github.io/otoroshi/manual/docs/api). -On Clever Cloud, it comes batteries included, pre-configured with features such as [Coraza Web Appplication Firewall](https://maif.github.io/otoroshi/manual/how-to-s/instantiate-waf-coraza.html) or [LLM extension](https://cloud-apim.github.io/otoroshi-llm-extension/docs/overview). The latter allows you to manage AI services from many providers (Anthropic, Groq, Hugging Face, Mistral, OpenAI, OVHcloud), Ollama instances or any OpenAI API compatible endpoints. +On Clever Cloud, it comes batteries included, pre-configured with features such as [Coraza Web Appplication Firewall](https://maif.github.io/otoroshi/manual/docs/tutorials/instantiate-waf-coraza) or [LLM extension](https://cloud-apim.github.io/otoroshi-llm-extension/docs/overview). The latter allows you to manage AI services from many providers (Anthropic, Groq, Hugging Face, Mistral, OpenAI, OVHcloud), Ollama instances or any OpenAI API compatible endpoints. All this with a unique interface, adding token management, rate limit, context, validation, moderation, etc. Create your own agents and flows with an only URL, distributing tokens to your team or customers, for cURL requests or integrations to many services and tools… or your own applications hosted on Clever Cloud. It's based on a Java application and a Redis® database. Once deployed you'll get a management URL with credentials. Want to learn more? Feel free to let us know what you think and ask your questions in [our GitHub Community](https://github.com/CleverCloud/Community/discussions/categories/otoroshi). -- [Learn more about Otoroshi](https://maif.github.io/otoroshi/manual/how-to-s/index.html) +- [Learn more about Otoroshi](https://maif.github.io/otoroshi/manual/docs/index.html) - [Learn more about Otoroshi with LLM on Clever Cloud](/doc/addons/otoroshi/) - [Otoroshi LLM extension video tutorials](https://www.youtube.com/watch?v=M8PbydxPw4A&list=PLNHaf5rXAx3FWk7dn2fKGwQXxeLCPhZCh) diff --git a/content/changelog/2025/06-04-otoroshi-17.3.md b/content/changelog/2025/06-04-otoroshi-17.3.md index aac448078..8fd608e1a 100644 --- a/content/changelog/2025/06-04-otoroshi-17.3.md +++ b/content/changelog/2025/06-04-otoroshi-17.3.md @@ -29,6 +29,6 @@ clever otoroshi version update yourOtoroshiNameOrId clever otoroshi version update yourOtoroshiNameOrId v17.3.1_1749049547 ``` -- [Learn more about Otoroshi Workflows](https://maif.github.io/otoroshi/manual/topics/workflows.html) +- [Learn more about Otoroshi Workflows](https://maif.github.io/otoroshi/manual/docs/topics/workflows) - [Learn more about Otoroshi with LLM on Clever Cloud](/doc/addons/otoroshi/) diff --git a/content/changelog/2025/07-16-otoroshi-17.4.md b/content/changelog/2025/07-16-otoroshi-17.4.md index 2c8ed4016..f82ce2289 100644 --- a/content/changelog/2025/07-16-otoroshi-17.4.md +++ b/content/changelog/2025/07-16-otoroshi-17.4.md @@ -27,6 +27,6 @@ clever otoroshi version update yourOtoroshiNameOrId clever otoroshi version update yourOtoroshiNameOrId v17.4.0_1752074416 ``` -- [Learn more about Otoroshi Workflows](https://maif.github.io/otoroshi/manual/topics/workflows.html) +- [Learn more about Otoroshi Workflows](https://maif.github.io/otoroshi/manual/docs/topics/workflows) - [Learn more about Otoroshi with LLM on Clever Cloud](/doc/addons/otoroshi/) diff --git a/content/changelog/2025/09-10-otoroshi-17.5.md b/content/changelog/2025/09-10-otoroshi-17.5.md index 0deb553e8..5b6d26c37 100644 --- a/content/changelog/2025/09-10-otoroshi-17.5.md +++ b/content/changelog/2025/09-10-otoroshi-17.5.md @@ -27,6 +27,6 @@ clever otoroshi version update yourOtoroshiNameOrId clever otoroshi version update yourOtoroshiNameOrId v17.5.1_1757489873 ``` -- [Learn more about Otoroshi Workflows](https://maif.github.io/otoroshi/manual/topics/workflows.html) +- [Learn more about Otoroshi Workflows](https://maif.github.io/otoroshi/manual/docs/topics/workflows) - [Learn more about Otoroshi with LLM on Clever Cloud](/doc/addons/otoroshi/) diff --git a/content/changelog/2025/11-13-otoroshi-plugins.md b/content/changelog/2025/11-13-otoroshi-plugins.md index 3bcb25514..d39ec9193 100644 --- a/content/changelog/2025/11-13-otoroshi-plugins.md +++ b/content/changelog/2025/11-13-otoroshi-plugins.md @@ -19,5 +19,5 @@ Otoroshi on Clever Cloud comes with many included plugins to manage AI based API - Add URLs pointing to the plugins JAR files, separated by a space or a new line - Rebuild the application -This works for any Otoroshi version deployed on Clever Cloud. If you want to learn more about Otoroshi plugins and how to create your own, check the [Otoroshi documentation](https://maif.github.io/otoroshi/manual/plugins/create-plugins.html). +This works for any Otoroshi version deployed on Clever Cloud. If you want to learn more about Otoroshi plugins and how to create your own, check the [Otoroshi documentation](https://maif.github.io/otoroshi/manual/docs/plugins/create-plugins). - [Learn more about Otoroshi with LLM on Clever Cloud](/doc/addons/otoroshi/) diff --git a/content/changelog/2026/03-04-otoroshi-17.13.md b/content/changelog/2026/03-04-otoroshi-17.13.md index a53724a3a..f8951ad0d 100644 --- a/content/changelog/2026/03-04-otoroshi-17.13.md +++ b/content/changelog/2026/03-04-otoroshi-17.13.md @@ -15,7 +15,7 @@ authors: excludeSearch: true --- -[Otoroshi v17.13](https://github.com/MAIF/otoroshi/releases/tag/v17.13.0) is available with experimental support for the [Kubernetes Gateway API](https://maif.github.io/otoroshi/manual/topics/kubernetes-gateway-api.html), enabling standardised Kubernetes-native traffic management. This release also introduces [remote catalogs](https://maif.github.io/otoroshi/manual/topics/remote-catalogs.html), allowing to fetch and manage plugin or configuration catalogs from external sources. +[Otoroshi v17.13](https://github.com/MAIF/otoroshi/releases/tag/v17.13.0) is available with experimental support for the [Kubernetes Gateway API](https://maif.github.io/otoroshi/manual/docs/topics/kubernetes-gateway-api), enabling standardised Kubernetes-native traffic management. This release also introduces [remote catalogs](https://maif.github.io/otoroshi/manual/docs/topics/remote-catalogs), allowing to fetch and manage plugin or configuration catalogs from external sources. A webhook validator plugin is also included, providing HMAC signature verification for incoming webhook payloads. It supports multiple algorithms (SHA256, SHA512, SHA384, SHA1) and is provider-agnostic with configurable signature headers and signing templates, compatible with services such as GitHub, Stripe, Slack or YouSign. diff --git a/content/changelog/2026/03-30-otoroshi-17.14.md b/content/changelog/2026/03-30-otoroshi-17.14.md index e054921cf..460b5a83e 100644 --- a/content/changelog/2026/03-30-otoroshi-17.14.md +++ b/content/changelog/2026/03-30-otoroshi-17.14.md @@ -15,7 +15,7 @@ authors: excludeSearch: true --- -[Otoroshi v17.14](https://github.com/MAIF/otoroshi/releases/tag/v17.14.0) is available with significant enhancements to [remote catalogs](https://maif.github.io/otoroshi/manual/topics/remote-catalogs.html). They now support organisation scanning, additional GitHub-like providers, pattern-based and YAML-formatted descriptor files, as well as Kubernetes-like manifests. This release also introduces PostgreSQL as a data exporter target and adds Redis Sentinel password support with the Lettuce driver. +[Otoroshi v17.14](https://github.com/MAIF/otoroshi/releases/tag/v17.14.0) is available with significant enhancements to [remote catalogs](https://maif.github.io/otoroshi/manual/docs/topics/remote-catalogs). They now support organisation scanning, additional GitHub-like providers, pattern-based and YAML-formatted descriptor files, as well as Kubernetes-like manifests. This release also introduces PostgreSQL as a data exporter target and adds Redis Sentinel password support with the Lettuce driver. New mandatory flags are available on client certificate plugins and OIDC JWT verification for APIs, offering finer control over authentication requirements. The expression language has been improved with path-based read support for deep structures such as user profiles, with complex structure stringification. Several fixes address tunnel handler plugin visibility, Kafka data exporter host validation, and the "Override Location header" plugin behaviour. diff --git a/content/doc/addons/otoroshi.md b/content/doc/addons/otoroshi.md index 25719c2fa..72e94a4dd 100644 --- a/content/doc/addons/otoroshi.md +++ b/content/doc/addons/otoroshi.md @@ -107,7 +107,7 @@ clever otoroshi open webui myOtoroshi The first time you connect, change the initial password (Security -> Administrators -> Edit user). -* [Learn how to use Otoroshi](https://maif.github.io/otoroshi/manual/how-to-s/index.html) +* [Learn how to use Otoroshi](https://maif.github.io/otoroshi/manual/docs/index.html) ## Underlying resources @@ -155,11 +155,11 @@ The integration provides advanced security capabilities through OWASP CRS implem ### Enterprise Capabilities Designed for production environments, the Coraza WAF plugin offers flexible configuration options, supporting both detection and prevention modes. It enables customized rule sets per domain and provides detailed security event tracking through Otoroshi's event management system. The implementation is optimized for minimal performance impact while maintaining robust security controls. -- [Otoroshi Coraza WAF documentation](https://maif.github.io/otoroshi/manual/how-to-s/instantiate-waf-coraza.html) +- [Otoroshi Coraza WAF documentation](https://maif.github.io/otoroshi/manual/docs/tutorials/instantiate-waf-coraza) ## Manage Otoroshi from its API -Otoroshi exposes a comprehensive REST API that enables programmatic control over all operations available through the Otoroshi dashboard. The dashboard itself operates as a client of this API. It gives you full control over your Otoroshi instances, enabling you to build custom integrations and extensions tailored to your infrastructure needs. [A Swagger UI detailing available endpoints is available](https://maif.github.io/otoroshi/swagger-ui/index.html). +Otoroshi exposes a comprehensive REST API that enables programmatic control over all operations available through the Otoroshi dashboard. The dashboard itself operates as a client of this API. It gives you full control over your Otoroshi instances, enabling you to build custom integrations and extensions tailored to your infrastructure needs. [A Swagger UI detailing available endpoints is available](https://maif.github.io/otoroshi/manual/api-reference). An OpenAPI descriptor is available from your instance: diff --git a/content/doc/applications/python/servers.md b/content/doc/applications/python/servers.md index 947453d9f..695167877 100644 --- a/content/doc/applications/python/servers.md +++ b/content/doc/applications/python/servers.md @@ -41,7 +41,7 @@ To enable [uWSGI asynchronous](https://uwsgi-docs.readthedocs.io/en/latest/Async | Name | Description | Default | |------|-------------|---------| -| `CC_GUNICORN_WORKER_CLASS` | Type of worker to use. [Available workers](https://docs.gunicorn.org/en/stable/settings.html#worker-class) | `sync` | +| `CC_GUNICORN_WORKER_CLASS` | Type of worker to use. [Available workers](https://gunicorn.org/reference/settings/#worker_class) | `sync` | | `CC_GUNICORN_TIMEOUT` | Gunicorn timeout (in seconds) | `30` | | `CC_GUNICORN_LOGLEVEL` | Gunicorn log level | `info` | diff --git a/content/guides/astro.md b/content/guides/astro.md index f58127925..9b47cbb60 100644 --- a/content/guides/astro.md +++ b/content/guides/astro.md @@ -25,7 +25,7 @@ Clever Cloud supports deploying both [fully static and on-demand rendered](https - The `static` output mode is ideal for most content-oriented website, for which you have no need for per-visitor server-side customization. Consider using a [Static runtime](/doc/applications/static/) when using this output mode, with the automatic site generation. - The `server` or `hybrid` output modes: consider using a [Node.js runtime](/doc/applications/nodejs) with [Astro’s Node adapter](https://docs.astro.build/en/guides/integrations-guide/node/) -If you need an example source code, get [Astrowind](https://github.com/onwidget/astrowind) (you'll need [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [Node.js](https://nodejs.org/en/learn/getting-started/how-to-install-nodejs)): +If you need an example source code, get [Astrowind](https://github.com/onwidget/astrowind) (you'll need [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [Node.js](https://nodejs.org/en/download)): ```bash git clone https://github.com/onwidget/astrowind myStaticApp ``` From 726642765ea29fd74da16ac50b0f5fae10720bd8 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 28 Apr 2026 12:17:22 +0200 Subject: [PATCH 084/180] changelog: images updates, 2026W18 --- content/changelog/2026/04-28-images-update.md | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 content/changelog/2026/04-28-images-update.md diff --git a/content/changelog/2026/04-28-images-update.md b/content/changelog/2026/04-28-images-update.md new file mode 100644 index 000000000..ae6183706 --- /dev/null +++ b/content/changelog/2026/04-28-images-update.md @@ -0,0 +1,26 @@ +--- +title: "Images update: Ruby 4.0.3, Git 2.54, Yarn 4.14" +description: All runtimes updated except PHP, with security and library refreshes across the board +date: 2026-04-28 +tags: + - images + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated all our images, except PHP. Deployment is in progress for all our users. + +* **Common:** + * Linux kernel 6.19.14 + * CA certificates 20260223 + * Git 2.54.0 +* **Node.js & Bun:** + * Yarn 4.14.1 +* **Ruby:** + * Update to 3.2.11 + * Update to 3.4.9 + * Update to 4.0.3 From 9800a3e0982c332721030880c26288e84dc949ec Mon Sep 17 00:00:00 2001 From: Julien Durillon Date: Wed, 29 Apr 2026 08:27:04 +0200 Subject: [PATCH 085/180] administraite(ssl) update gpg public key for sending encrypted emails --- content/doc/administrate/ssl.md | 90 +++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 39 deletions(-) diff --git a/content/doc/administrate/ssl.md b/content/doc/administrate/ssl.md index fecc3c287..7e406d90a 100644 --- a/content/doc/administrate/ssl.md +++ b/content/doc/administrate/ssl.md @@ -158,12 +158,11 @@ If your are a Keybase.io user, you can find us at [keybase.io/clevercloud](https Email can be a secure way to transfer your certificates **when using a signed and encrypted email with GPG**. Our dedicated email for receiving certificates is [ssl@clever-cloud.com](mailto:ssl@clever-cloud.com). -- fingerprint: `03943517934C1FA5ED4E2F61218B86BD5278470F` -- 64-bit: `218B86BD5278470F` +- fingerprint: `0394 3517 934C 1FA5 ED4E 2F61 218B 86BD 5278 460F` +- 64-bit: `218B 86BD 5278 460F` ```bash -----BEGIN PGP PUBLIC KEY BLOCK----- -Comment: GPGTools - https://gpgtools.org mQINBFa4bJMBEAC21vsfJ1ay5iVUcKsP8X8GziZu8daV5G4Lqpns54zN/GB05f9+ 3jV1RYRMweq7RC6XU/GDZo20ksvDw0N963/WCswCt0MyzM1O105tZ/ZbYyVV/w5w @@ -177,42 +176,55 @@ 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 -=DNbu +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 +SboMCws= +=VOPS -----END PGP PUBLIC KEY BLOCK----- ``` From eea236642074ad90c38bd21cfc3924ba9c982d1f Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 29 Apr 2026 15:27:28 +0200 Subject: [PATCH 086/180] changelog: API update, GitHub webhook secret rotation (#929) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 What does this PR do? This PR introduces a new "API update" style of changelog entry, starting with the new CC API endpoint that rotates the GitHub webhook secret of an application and its siblings. --- ## 🧪 Type of Change - [ ] ⚠️ Bug fix - [x] 📅 Changelog update - [x] 📚 Documentation update - [ ] ✨ New content/feature - [ ] 🔧 Technical/maintenance --- ## ✅ Quick Checklist - [x] I have read the [contributing guidelines](https://github.com/CleverCloud/documentation/blob/main/CONTRIBUTING.md) - [x] The content is accurate and links work - [x] The site builds without errors --- .../04-21-github-webhook-secret-rotation.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 content/changelog/2026/04-21-github-webhook-secret-rotation.md diff --git a/content/changelog/2026/04-21-github-webhook-secret-rotation.md b/content/changelog/2026/04-21-github-webhook-secret-rotation.md new file mode 100644 index 000000000..56c3448cd --- /dev/null +++ b/content/changelog/2026/04-21-github-webhook-secret-rotation.md @@ -0,0 +1,30 @@ +--- +title: "API update: rotate GitHub webhook secrets for your applications" +date: 2026-04-21 +description: A new CC API endpoint lets you rotate the GitHub webhook secret of an application and its linked siblings, without recreating the hook. +tags: + - api + - api-update + - github +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +The Clever Cloud API now exposes a dedicated endpoint to rotate the GitHub webhook secret of an application linked to a GitHub repository. + +The rotation applies to the target application and its siblings sharing the same webhook, so a single call keeps every linked deployment in sync with the new secret. Until now, rotating a webhook secret meant creating a new application, which was cumbersome and error-prone. The new endpoint replaces that workflow with a single authenticated call. + +## How to use it + +You can call this endpoint through any [authenticated request to the Clever Cloud API](/doc/cli/#tokens), or through the [`clever curl` command](/doc/cli/#curl) of Clever Tools, which signs the request using the active user profile: + +```bash +clever curl -X POST https://api.clever-cloud.com/v2/organisations/{orgId}/applications/{appId}/github-webhook/secret +``` + +The response confirms the rotation. Any existing GitHub push event already in flight keeps working with the previous secret until GitHub picks up the new configuration; subsequent deliveries use the rotated secret automatically. + +Refer to the [v2 API reference](/api/v2) for the full request and response schema. From 3da1160d9328892d3ba496206a077a30fa1be793 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Thu, 23 Apr 2026 09:43:30 +0200 Subject: [PATCH 087/180] changelog: Otoroshi 17.15 --- .../changelog/2026/04-23-otoroshi-17.15.md | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 content/changelog/2026/04-23-otoroshi-17.15.md diff --git a/content/changelog/2026/04-23-otoroshi-17.15.md b/content/changelog/2026/04-23-otoroshi-17.15.md new file mode 100644 index 000000000..c902e3555 --- /dev/null +++ b/content/changelog/2026/04-23-otoroshi-17.15.md @@ -0,0 +1,46 @@ +--- +title: Otoroshi 17.15 brings agents with tools and memory, OAuth2 token exchange, PluginPresets and requires Java 25 +description: Agents' built-in tools and persistent memory, new embedding and memory storage backends, Kreuzberg OCR support, OAuth2 token exchange plugin, PluginPresets and workflow auth modules +date: 2026-04-23 +tags: + - addons + - otoroshi +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Otoroshi v17.15.1](https://github.com/MAIF/otoroshi/releases/tag/v17.15.1) is available. It introduces an OAuth2 token exchange plugin implementing [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693) for secure service-to-service API communication, a new `PluginPreset` kind to package and reuse plugin configurations, a `MandatoryConsumerPreset` plugin and a workflow-based authentication module. The `OAuth2Caller` plugin can now rely on an authentication module for the client credentials flow, and `$2b$` / `$2y$` Bcrypt hashes are now accepted. + +### A redesigned admin interface and manual + +The admin interface has been redesigned with refreshed themes, a new home page, a reworked API editor with sticky actions and form views capped at 1000 px to improve readability. The UI also now only displays entities a user is actually authorized to see. The full manual has been reworked as well, with ditaa schemas replaced by mermaid ones, and the OpenAPI specification and Swagger UI can be protected behind an optional access secret. + +### API management, data exporters and plugin development + +API management gains direct subscription to a plan and a new consistency service. The PostgreSQL data exporter now supports retention and plugin developers can rely on a reusable stateful clients internal API. Several fixes address a race condition in data exporters loading, legacy audit event layouts, body consumption handling and the `OAuth2Caller` password grant type. + +### LLM extension: agents, memory and Kreuzberg + +This release includes LLM extension [0.0.75](https://github.com/cloud-apim/otoroshi-llm-extension/releases/tag/0.0.75), which significantly expands agent capabilities with built-in tools (`system_exec`, `file_read`, `file_write`, `http_call`), a per-session working memory, autonomous persistent memory on Redis and PostgreSQL and a new agent OpenAI proxy plugin. Embedding stores and persistent memories now support many backends (PostgreSQL, Redis, Elasticsearch, OpenSearch, Qdrant, Weaviate, Pinecone, ChromaDB), a semantic cache on Redis with a custom embedding model and MCP audit events. The Anthropic provider exposes version and beta settings, and [Kreuzberg](https://kreuzberg.dev) is now available as a content-to-markdown and OCR backend. + +### Java 25 is now required + +Otoroshi 17.15 requires Java 25 to load the Kreuzberg content-to-markdown and OCR backend. Starting with this release, all new Otoroshi add-ons are created with `CC_JAVA_VERSION=25`, and any update to Otoroshi 17.15 automatically switches the underlying Java application to Java 25. If you prefer to handle this manually, you can set `CC_JAVA_VERSION` to `25` on the Java application backing your Otoroshi add-on and rebuild it before updating Otoroshi itself. + +You can update through your add-on dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_OTOROSHI_VERSION` of the underlying Java application to `v17.15.1_1776891995` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever otoroshi version check yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId v17.15.1_1776891995 +``` + +- [Learn more about Otoroshi with LLM on Clever Cloud](/doc/addons/otoroshi/) From 8d50ed9eabe050ef5ca57695fbfe0d97a396eafc Mon Sep 17 00:00:00 2001 From: Anis Nielsen Date: Tue, 5 May 2026 16:15:57 +0200 Subject: [PATCH 088/180] reference(env vars): rename VPN add-on env vars to CC_VPN_* --- .../reference-environment-variables.md | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/content/doc/reference/reference-environment-variables.md b/content/doc/reference/reference-environment-variables.md index 67860ea5b..e55b5132b 100644 --- a/content/doc/reference/reference-environment-variables.md +++ b/content/doc/reference/reference-environment-variables.md @@ -607,20 +607,23 @@ When your Python application doesn't use one of the supported backends, with `CC ### VPN The VPN add-on provides a fixed-ip outgoing node. This can be used to work -with services protected by ip address filtering. `VPN_ADDON_*` variables will +with services protected by ip address filtering. `CC_VPN_*` variables will be provided by Clever Cloud upon setup, the only configuration you have to provide is a list of CIDRs (eg. 1.2.3.0/24) for which you want the traffic to be routed through the exit node. +> [!NOTE] +> New setups use the `CC_VPN_*` names listed below. The previous `VPN_ADDON_*` names (and `VPN_TARGETS`) are deprecated and kept only for backward compatibility with existing setups. + | Name | Description | Default value | Read Only | | --------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------ | -------------------------- | | `CC_VPN_DNS_OVERRIDES` | Comma-separated list of DNS IP | | | -| `VPN_ADDON_CACRT` | Server CA certificate | | | -| `VPN_ADDON_CIPHER`| Cipher to use CIPHER, can be either {cipher_suite}:{hmac_alg} or only {cipher_suite} | DES-EDE3-CBC:SHA1 | | -| `VPN_ADDON_CRT` | Client certificate | | | -| `VPN_ADDON_DEVTYPE` | Kernel virtual interface kind to use ("tap" or "tun") | tap | | -| `VPN_ADDON_HOST` | Server host or IP address | | | -| `VPN_ADDON_KEY` | Client certificate private key | | | -| `VPN_ADDON_PORT` | Server port | | | -| `VPN_ADDON_TAKEY` | Pre-shared secret | | | -| `VPN_TARGETS` | Comma-separated list of CIDRs for which you want the traffic to be routed through the exit node | | | +| `CC_VPN_CACRT` | Server CA certificate | | | +| `CC_VPN_CIPHER`| Cipher to use CIPHER, can be either {cipher_suite}:{hmac_alg} or only {cipher_suite} | DES-EDE3-CBC:SHA1 | | +| `CC_VPN_CRT` | Client certificate | | | +| `CC_VPN_DEVTYPE` | Kernel virtual interface kind to use ("tap" or "tun") | tap | | +| `CC_VPN_HOST` | Server host or IP address | | | +| `CC_VPN_KEY` | Client certificate private key | | | +| `CC_VPN_PORT` | Server port | | | +| `CC_VPN_TAKEY` | Pre-shared secret | | | +| `CC_VPN_TARGETS` | Comma-separated list of CIDRs for which you want the traffic to be routed through the exit node | | | From 147efa43b3d13d23c7daff6477693ab5e2c215b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Nivolle?= Date: Wed, 6 May 2026 09:33:51 +0200 Subject: [PATCH 089/180] docs: add note on ES about migrations --- content/doc/addons/elastic.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/content/doc/addons/elastic.md b/content/doc/addons/elastic.md index 0bcd0f0d3..42bb17741 100644 --- a/content/doc/addons/elastic.md +++ b/content/doc/addons/elastic.md @@ -197,3 +197,7 @@ Most settings are available for modifications and update from Kibana or by API, - Backups destination If you think your system might require some customization (like some plugins activation), contact Clever Cloud support to explain your use case and we will work with you to find a solution. + +## Migrations and upgrades + +When migrating or upgrading an Elasticsearch instance — especially when it's deployed as a cluster with multiple VMs and nodes — contact [our support team](https://console.clever-cloud.com/ticket-center-choice) beforehand to confirm feasibility and coordinate the operation without disruption. From b09dbe4c6dee05efdb2a60d2f671b53ca1fe6bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Nivolle?= Date: Tue, 5 May 2026 10:56:04 +0200 Subject: [PATCH 090/180] applications: add note about runtime extensions support --- content/doc/applications/_index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/content/doc/applications/_index.md b/content/doc/applications/_index.md index c1df96762..a80416d9d 100644 --- a/content/doc/applications/_index.md +++ b/content/doc/applications/_index.md @@ -58,6 +58,10 @@ Refer to the [Docker](docker) section of this documentation to know how to deplo If you are out of options, contact our support team and we'll come up with a solution with you. +## Custom Runtime Extensions + +Some runtimes can be extended with additional modules (such as extra PHP extensions). To request one, contact [our support team](https://console.clever-cloud.com/ticket-center-choice) with your use case so they can assess feasibility on the target operating system. + ## Environment Variables You can control deployments and set your application configuration with environment variables: From 30600802a2da5faa65f95486d9fce6d229e54b32 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 09:56:26 +0200 Subject: [PATCH 091/180] applications: update options for unsupported runtimes --- content/doc/applications/_index.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/content/doc/applications/_index.md b/content/doc/applications/_index.md index a80416d9d..3a50c87c5 100644 --- a/content/doc/applications/_index.md +++ b/content/doc/applications/_index.md @@ -49,14 +49,11 @@ Find here specific instructions related to your application's language. {{< card link="/developers/doc/applications/v" title="V (Vlang)" icon="v" >}} {{< /cards >}} -## How To Deploy X if It Isn't Natively Supported +## Deploying a Non-native Runtime -If your favorite runtime is not available, you can deploy it on Clever Cloud by Dockerizing it and make it run in a Docker instance. -You will probably find a basic Docker file for your technology on the Docker hub. +If your runtime isn't available natively, the [Linux runtime](/developers/doc/applications/linux) with [Mise](https://mise.jdx.dev/) can run many languages and tools — see examples in [our GitHub examples and demos](https://github.com/CleverCloud/examples-and-demos). Otherwise, use the [Docker runtime](/developers/doc/applications/docker) with a Dockerfile from [Docker Hub](https://hub.docker.com/) or your own. -Refer to the [Docker](docker) section of this documentation to know how to deploy your Dockerized application. - -If you are out of options, contact our support team and we'll come up with a solution with you. +If none of these options fit, contact [our support team](https://console.clever-cloud.com/ticket-center-choice) to find a solution together. ## Custom Runtime Extensions From 05edd57c281e3049b4e6307017d40d45005c0a9b Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:31:00 +0200 Subject: [PATCH 092/180] chore(icons): add npm in new Package managers section --- data/icons.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/icons.yaml b/data/icons.yaml index c5a439214..17af536d2 100644 --- a/data/icons.yaml +++ b/data/icons.yaml @@ -124,3 +124,6 @@ mdbook: kubernetes: + +# Package managers +npm: From 16b10610b8597efa99adc48cd113ee76979e42a5 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:31:09 +0200 Subject: [PATCH 093/180] chore(icons): add pnpm --- data/icons.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/data/icons.yaml b/data/icons.yaml index 17af536d2..6f656e7a1 100644 --- a/data/icons.yaml +++ b/data/icons.yaml @@ -127,3 +127,4 @@ kubernetes: +pnpm: From 4df747815aa3519485eae601e3eb78fd9f0731ec Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:31:21 +0200 Subject: [PATCH 094/180] chore(icons): add yarn --- data/icons.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/data/icons.yaml b/data/icons.yaml index 6f656e7a1..3dc827ac0 100644 --- a/data/icons.yaml +++ b/data/icons.yaml @@ -128,3 +128,4 @@ kubernetes: pnpm: +yarn: From 4c750f67b66567018ad7db396ed0c0d7639e5414 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:31:44 +0200 Subject: [PATCH 095/180] chore(icons): add github in new Brands and services section --- data/icons.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/icons.yaml b/data/icons.yaml index 3dc827ac0..be496f5b9 100644 --- a/data/icons.yaml +++ b/data/icons.yaml @@ -129,3 +129,6 @@ kubernetes: pnpm: yarn: + +# Brands and services +github: From 7d313d2a7f3c800d1769180b7979d2f4cb921f10 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:31:55 +0200 Subject: [PATCH 096/180] chore(icons): add aws --- data/icons.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/data/icons.yaml b/data/icons.yaml index be496f5b9..c401492e9 100644 --- a/data/icons.yaml +++ b/data/icons.yaml @@ -132,3 +132,4 @@ yarn: +aws: From 4a050072cd5ef26c93474e4c2cc30c5934a4de99 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:32:01 +0200 Subject: [PATCH 097/180] chore(icons): add minio --- data/icons.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/data/icons.yaml b/data/icons.yaml index c401492e9..663263682 100644 --- a/data/icons.yaml +++ b/data/icons.yaml @@ -133,3 +133,4 @@ yarn: aws: +minio: From fc478db4b3bfd94799885f69fdce3e37381c59cb Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:32:27 +0200 Subject: [PATCH 098/180] chore(icons): add apple in new Operating systems section --- data/icons.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/icons.yaml b/data/icons.yaml index 663263682..1dfd92343 100644 --- a/data/icons.yaml +++ b/data/icons.yaml @@ -134,3 +134,6 @@ yarn: aws: minio: + +# Operating systems +apple: From 3d2b46b2e846088b150590b9a262a47f348351c8 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:32:34 +0200 Subject: [PATCH 099/180] chore(icons): add windows --- data/icons.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/data/icons.yaml b/data/icons.yaml index 1dfd92343..d0db593dd 100644 --- a/data/icons.yaml +++ b/data/icons.yaml @@ -136,4 +136,5 @@ aws: # Operating systems +windows: apple: From d4c8c304a329cf242af2a694859aa24edfadd0b0 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:32:54 +0200 Subject: [PATCH 100/180] chore(icons): add json in new Data formats section --- data/icons.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/icons.yaml b/data/icons.yaml index d0db593dd..800525cda 100644 --- a/data/icons.yaml +++ b/data/icons.yaml @@ -138,3 +138,6 @@ minio: apple: + +# Data formats +json: From 5ac9cf3d0b604bf2c45cc5c02c0d9379d9af6fde Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:33:01 +0200 Subject: [PATCH 101/180] chore(icons): add yaml --- data/icons.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/data/icons.yaml b/data/icons.yaml index 800525cda..34a22050d 100644 --- a/data/icons.yaml +++ b/data/icons.yaml @@ -141,3 +141,4 @@ apple: +yaml: From 4cca2323daf9ef10c19bd564c32b4a8a11d7b431 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:33:06 +0200 Subject: [PATCH 102/180] chore(icons): add toml --- data/icons.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/data/icons.yaml b/data/icons.yaml index 34a22050d..498fd8c5c 100644 --- a/data/icons.yaml +++ b/data/icons.yaml @@ -142,3 +142,4 @@ apple: yaml: +toml: From 0cb0b3b3ea19b2c7ee44a3ad9da1ea4eea359e2a Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:33:22 +0200 Subject: [PATCH 103/180] chore(icons): add envelope in new UI utility section --- data/icons.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/icons.yaml b/data/icons.yaml index 498fd8c5c..b13ee5db3 100644 --- a/data/icons.yaml +++ b/data/icons.yaml @@ -139,6 +139,9 @@ minio: apple: +# UI utility +envelope: + # Data formats json: yaml: From 9eb62dd0956f00742eef4cc49d84c36e6f6f02d2 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:56:21 +0200 Subject: [PATCH 104/180] chore(icons): add mistral in new AI assisstants section --- data/icons.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/icons.yaml b/data/icons.yaml index b13ee5db3..6a4d414d2 100644 --- a/data/icons.yaml +++ b/data/icons.yaml @@ -146,3 +146,6 @@ envelope: yaml: toml: + +# AI assistants +mistral: From a2becd1ef7d5ec665ace28fbf36b4d8b8d4b059b Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:56:29 +0200 Subject: [PATCH 105/180] chore(icons): add perplexity --- data/icons.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/data/icons.yaml b/data/icons.yaml index 6a4d414d2..35ce9c087 100644 --- a/data/icons.yaml +++ b/data/icons.yaml @@ -149,3 +149,4 @@ toml: +perplexity: From f501f07617a2d376b07c782a271cf53f11f6bcf3 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:35:09 +0200 Subject: [PATCH 106/180] fix: update tabs syntax, add icons --- content/doc/account/ssh-keys-management.md | 6 +++--- content/doc/addons/cellar.md | 24 +++++++++++----------- content/doc/administrate/log-management.md | 6 +++--- content/doc/ci-cd/custom-scripts.md | 6 +++--- content/doc/quickstart.md | 20 +++++++++--------- content/guides/astro.md | 14 ++++++------- content/guides/hugo-static-s3.md | 8 ++++---- 7 files changed, 42 insertions(+), 42 deletions(-) diff --git a/content/doc/account/ssh-keys-management.md b/content/doc/account/ssh-keys-management.md index e9e52830b..9954b2cc8 100644 --- a/content/doc/account/ssh-keys-management.md +++ b/content/doc/account/ssh-keys-management.md @@ -101,9 +101,9 @@ You can add any key already present in your GitHub account by clicking on the im You may already have an SSH key and so do not need to generate a new one. To check if you have one, follow these steps: -{{< tabs items="Linux and macOS,Windows" >}} +{{< tabs >}} -{{< tab >}} +{{< tab name="Linux and macOS" icon="linux" >}} 1. Whether you use macOS or Linux, open your Terminal application. 2. Run `cd ~/.ssh/` in your Terminal. @@ -115,7 +115,7 @@ If you can find them, you do not need to generate a new one, simply go to the fo {{< /tab >}} -{{< tab >}}1. If you don't have it, download [Git for Windows](https://git-for-windows.GitHub.io/) and install it. +{{< tab name="Windows" icon="windows" >}}1. If you don't have it, download [Git for Windows](https://git-for-windows.GitHub.io/) and install it. 2. Run **Git Bash** (from the *Start Menu* or from the *Explorer* with the contextual menu (right click)). 3. Run `cd ~/.ssh/` in your Terminal. 4. If the folder exists, run `ls` and check if a pair of key exists : *id_ed25519* and *id_ed25519.pub* or *id_rsa* and *id_rsa.pub*. We would recommend using *ed25519* keys. Smaller to copy and way stronger than 2048-bit RSA keys. If you can find them, you do not need to generate a new one, simply go to the following "Add your key on Clever Cloud" part! diff --git a/content/doc/addons/cellar.md b/content/doc/addons/cellar.md index 327b6bdaf..b25240667 100644 --- a/content/doc/addons/cellar.md +++ b/content/doc/addons/cellar.md @@ -118,9 +118,9 @@ This list isn't exhaustive. Feel free to [suggest other clients that you would l `s3cmd` allows you to manage your buckets using its commands, after [configuring it on your machine](#with-s3cmd) -{{< tabs items="Upload,List" >}} +{{< tabs >}} - {{< tab >}} + {{< tab name="Upload" >}} You can upload files (`--acl-public` makes the file publicly readable) with: ```bash @@ -130,7 +130,7 @@ This list isn't exhaustive. Feel free to [suggest other clients that you would l The file is then publicly available at `https://.cellar-c2.services.clever-cloud.com/image.jpg`. {{< /tab >}} - {{< tab >}} + {{< tab name="List" >}} You can list the files in your bucket, you should see the `image.png` file: ```bash @@ -156,9 +156,9 @@ Then, create a CNAME record on your domain pointing to `cellar-c2.services.cleve To use Cellar from your applications, you can use the [AWS SDK](https://aws.amazon.com/tools/#sdk) or any S3-compatible client. You only need to specify a custom endpoint (e.g. `cellar-c2.services.clever-cloud.com`). -{{< tabs items="Bun,Node.js,Java,Python,Ruby" >}} +{{< tabs >}} - {{< tab >}} + {{< tab name="Bun" icon="bun" >}} **Bun (native S3 client)** [Bun](https://bun.sh) includes a [native S3 client](https://bun.sh/docs/api/s3) with no external dependency. It works with any S3-compatible service, including Cellar. @@ -206,7 +206,7 @@ You only need to specify a custom endpoint (e.g. `cellar-c2.services.clever-clou {{< /tab >}} - {{< tab >}} + {{< tab name="Node.js" icon="node" >}} **Node.js** Using AWS SDK for JavaScript v3 (recommended): @@ -263,7 +263,7 @@ You only need to specify a custom endpoint (e.g. `cellar-c2.services.clever-clou {{< /tab >}} - {{< tab >}} + {{< tab name="Java" icon="java" >}} **Java** Import the AWS SDK S3 library. Maven uses the following dependency: @@ -345,7 +345,7 @@ You only need to specify a custom endpoint (e.g. `cellar-c2.services.clever-clou See the [AWS Java SDK code examples for S3](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/s3) for more example use cases. {{< /tab >}} -{{< tab >}} + {{< tab name="Python" icon="python" >}} **Python** This script uses boto3, the AWS SDK for Python. @@ -399,7 +399,7 @@ You only need to specify a custom endpoint (e.g. `cellar-c2.services.clever-clou {{< /tab >}} - {{< tab >}} + {{< tab name="Ruby" icon="ruby" >}} **Active Storage (Ruby On Rails)** [Active Storage](https://guides.rubyonrails.org/active_storage_overview.html) can manage various cloud storage services like Amazon S3, Google Cloud Storage, or Microsoft Azure Storage. To use Cellar, @@ -684,9 +684,9 @@ For that reason, we do recommend you to enable versioning when creating a new bu > [!WARNING] > Versioning can quickly take up a lot of space since multiple version of an object are stored in the bucket. -{{< tabs items="MinIO, AWS CLI" >}} +{{< tabs >}} - {{< tab >}} + {{< tab name="MinIO" icon="minio" >}} To use [minIO](https://min.io/docs/minio/linux/reference/minio-mc.html#command-mc), you must create an alias. @@ -747,7 +747,7 @@ When versioning is enabled, the newly added object is automatically provided wit {{< /tab >}} - {{< tab >}} + {{< tab name="AWS CLI" icon="aws" >}} The following command assumes you have configured your AWS CLI and added an alias as shown earlier in the section [Creating a bucket with AWS CLI](/doc/addons/cellar/#with-aws-cli) diff --git a/content/doc/administrate/log-management.md b/content/doc/administrate/log-management.md index 63980ef74..da653c0e9 100644 --- a/content/doc/administrate/log-management.md +++ b/content/doc/administrate/log-management.md @@ -240,9 +240,9 @@ To export logs from an application or an add-on to [OVHcloud Logs Data Platform] On your terminal, use the following command: -{{< tabs items="Application,Add-on" >}} +{{< tabs >}} - {{< tab >}}**Exporting logs from an application**: + {{< tab name="Application" >}}**Exporting logs from an application**: ```shell clever drain create ovh-tcp tcp://:514 -app --sd-params="X-OVH-TOKEN=\"\"" @@ -256,7 +256,7 @@ On your terminal, use the following command: {{< /tab >}} - {{< tab >}}**Exporting logs from an add-on**: + {{< tab name="Add-on" >}}**Exporting logs from an add-on**: ```shell clever drain create ovh-tcp tcp://:514 -addon --sd-params="X-OVH-TOKEN=\"\"" diff --git a/content/doc/ci-cd/custom-scripts.md b/content/doc/ci-cd/custom-scripts.md index 23da63433..3e2ef919b 100644 --- a/content/doc/ci-cd/custom-scripts.md +++ b/content/doc/ci-cd/custom-scripts.md @@ -20,9 +20,9 @@ keywords: You can write your own pipeline to deploy from either GitHub or GitLab. Use [Clever Cloud CLI](https://github.com/CleverCloud/clever-tools) with either Docker or Node image. Place the following snippets at the top of your `.gitlab-ci.yml` file: -{{< tabs items="Docker image, Node image" >}} +{{< tabs >}} - {{< tab >}}**Docker image**: + {{< tab name="Docker image" icon="docker" >}}**Docker image**: ```yaml variables: @@ -35,7 +35,7 @@ image: {{< /tab >}} - {{< tab >}}**Node image**: + {{< tab name="Node image" icon="node" >}}**Node image**: ```yaml variables: diff --git a/content/doc/quickstart.md b/content/doc/quickstart.md index ba454ac59..cafd38ef7 100644 --- a/content/doc/quickstart.md +++ b/content/doc/quickstart.md @@ -32,14 +32,14 @@ aliases: The API of Clever Cloud uses OAuth 1 to perform authentication actions. There are two ways to sign up for Clever Cloud: **email** or **GitHub login**. -{{< tabs items="Email Auth, GitHub Auth" >}} +{{< tabs >}} - {{< tab >}} + {{< tab name="Email Auth" icon="envelope" >}} This kind of auth requires a valid and non-temporary disposable email, and a password having at least 6 characters. Do not forget to validate your email by clicking the link you will receive. {{< /tab >}} - {{< tab >}} + {{< tab name="GitHub Auth" icon="github" >}} The GitHub sign up allows you to create an account or link your existing one to GitHub, in one click. This process asks the following permissions: @@ -152,8 +152,8 @@ Enter the name and the description of your application. #### Choose How to Deploy -{{< tabs items="Git,GitHub, FTP" >}} - {{< tab >}} +{{< tabs >}} + {{< tab name="Git" icon="git" >}} *To deploy via Git, you need it installed on your machine. You can find more information on Git website: [git-scm.com](https://git-scm.com)* *Note:* during the deployment, the .git folder is automatically deleted to avoid security problems. If you need to know which version is used on the server please use the `COMMIT_ID` [environment variable](/doc/reference/reference-environment-variables/). @@ -186,7 +186,7 @@ git push :master {{< /tab >}} - {{< tab >}} + {{< tab name="GitHub" icon="github" >}} Once you have created your application with GitHub, each push on the `master` branch trigger a deployment. To deploy an other branch than `master`, go to the `information` panel of your application and select the default branch to use. ![GitHub deployment branch select](/images/github-deployment-branch.png "Github deployment branch select") @@ -200,7 +200,7 @@ git push :master However, if you set up an organisation, create the repository under the aegis of the organisation, and then add the collaborator, you have much more fine-grained control (including giving read-only access to a private repository). {{< /tab >}} - {{< tab >}} + {{< tab name="FTP" >}} You can deploy via FTP with PHP applications. To deploy via FTP, you need an FTP software installed on your machine. [Filezilla](https://filezilla-project.org/) is one of them. @@ -320,8 +320,8 @@ Clever Cloud provides multiple add-ons to work with your applications: **If your add-on:** -{{< tabs items="Doesn't exist yet,Already exists" >}} - {{< tab >}} +{{< tabs >}} + {{< tab name="Doesn't exist yet" >}} Here we will assume you want to create a new add-on and link it to your application. 1. Go to the [Clever Cloud Console](https://console.clever-cloud.com/). @@ -336,7 +336,7 @@ Clever Cloud provides multiple add-ons to work with your applications: The add-on will now be available in your organisation, and corresponding environment variables will be available for the applications linked to the add-on you just created. {{< /tab >}} - {{< tab >}} + {{< tab name="Already exists" >}} To link an already existing add-on with your application, just follow these steps: 1. Go in the organisation of your application. diff --git a/content/guides/astro.md b/content/guides/astro.md index 9b47cbb60..eb2f0cd5d 100644 --- a/content/guides/astro.md +++ b/content/guides/astro.md @@ -79,13 +79,13 @@ To deploy an Astro project with Server-Side Rendering (SSR), use a **Node.js** a Depending on your package manager, use the following environment variables: -{{< tabs items="npm,pnpm,yarn" >}} - {{< tab >}} +{{< tabs >}} + {{< tab name="npm" icon="npm" >}} ```shell CC_POST_BUILD_HOOK="npm run build" ``` {{< /tab >}} - {{< tab >}} + {{< tab name="pnpm" icon="pnpm" >}} ```shell CC_NODE_BUILD_TOOL="custom" CC_PRE_BUILD_HOOK="npm install -g pnpm && pnpm install" @@ -93,7 +93,7 @@ Depending on your package manager, use the following environment variables: CC_RUN_COMMAND="pnpm run preview" ``` {{< /tab >}} - {{< tab >}} + {{< tab name="yarn" icon="yarn" >}} ```shell CC_NODE_BUILD_TOOL="yarn" CC_PRE_BUILD_HOOK="yarn && yarn run astro telemetry disable && yarn build" @@ -109,8 +109,8 @@ Depending on your package manager, use the following environment variables: As you manage the server, ensure to configure your application to listen on port **8080** as required by Clever Cloud. Set your port and host in your `astro dev` script for development mode, and/or configure it directly for production: -{{< tabs items="development,production" >}} - {{< tab >}} +{{< tabs >}} + {{< tab name="development" >}} To quickly deploy on development mode: ```json {filename="package.json"} @@ -123,7 +123,7 @@ As you manage the server, ensure to configure your application to listen on port } ``` {{< /tab >}} - {{< tab >}} + {{< tab name="production" >}} When deploying for production: ```javascript {filename="astro.config.mjs"} diff --git a/content/guides/hugo-static-s3.md b/content/guides/hugo-static-s3.md index e9641c099..b67491610 100644 --- a/content/guides/hugo-static-s3.md +++ b/content/guides/hugo-static-s3.md @@ -105,9 +105,9 @@ In the folder of your website, you’ll find Hugo’s configuration file: Open it, then add the following (according to the programing language): -{{< tabs items="JSON,YAML,TOML" >}} +{{< tabs >}} - {{< tab >}}**JSON** + {{< tab name="JSON" icon="json" >}}**JSON** ```json {filename="hugo.json"} { @@ -125,7 +125,7 @@ Open it, then add the following (according to the programing language): {{< /tab >}} - {{< tab >}}**YAML** + {{< tab name="YAML" icon="yaml" >}}**YAML** ```json {filename="hugo.yaml"} deployment: @@ -137,7 +137,7 @@ Open it, then add the following (according to the programing language): {{< /tab >}} - {{< tab >}}**TOML** + {{< tab name="TOML" icon="toml" >}}**TOML** ```json {filename="hugo.toml"} [deployment] [[deployment.targets]] From b60e1b1bc11926e39a7bedc3d34e46bb47b3cf76 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:35:28 +0200 Subject: [PATCH 107/180] feat: enable contextMenu with AI services --- hugo.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/hugo.yaml b/hugo.yaml index c56da2fcc..82ac18ff3 100644 --- a/hugo.yaml +++ b/hugo.yaml @@ -175,6 +175,21 @@ params: width: full tabs: sync: true + contextMenu: + enable: true + links: + - name: Open in ChatGPT + icon: chatgpt + url: "https://chatgpt.com/?hints=search&q=Read+this+Clever+Cloud+documentation+page+and+help+me+understand+and+apply+it+to+my+project%3A+{url}%0ARaw+Markdown+source%3A+{markdown_url}" + - name: Open in Claude + icon: claude + url: "https://claude.ai/new?q=Read+this+Clever+Cloud+documentation+page+and+help+me+understand+and+apply+it+to+my+project%3A+{url}%0ARaw+Markdown+source%3A+{markdown_url}" + - name: Open in Le Chat + icon: mistral + url: "https://chat.mistral.ai/chat?q=Read+this+Clever+Cloud+documentation+page+and+help+me+understand+and+apply+it+to+my+project%3A+{url}%0ARaw+Markdown+source%3A+{markdown_url}" + - name: Open in Perplexity + icon: perplexity + url: "https://www.perplexity.ai/search?q=Read+this+Clever+Cloud+documentation+page+and+help+me+understand+and+apply+it+to+my+project%3A+{url}%0ARaw+Markdown+source%3A+{markdown_url}" search: enable: true From 735baeb48da777c07b04bdaaecc57afcd8d6e850 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:35:40 +0200 Subject: [PATCH 108/180] feat: enable imageZoom --- hugo.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hugo.yaml b/hugo.yaml index 82ac18ff3..412e91dde 100644 --- a/hugo.yaml +++ b/hugo.yaml @@ -191,6 +191,9 @@ params: icon: perplexity url: "https://www.perplexity.ai/search?q=Read+this+Clever+Cloud+documentation+page+and+help+me+understand+and+apply+it+to+my+project%3A+{url}%0ARaw+Markdown+source%3A+{markdown_url}" + imageZoom: + enable: true + search: enable: true type: flexsearch From 9afa62b4c21d14d4ea41bc4734e100e466cb0acf Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:44:10 +0200 Subject: [PATCH 109/180] fix: language Hugo values deprecations --- hugo.yaml | 2 +- layouts/openapi/baseof.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hugo.yaml b/hugo.yaml index 412e91dde..34604903d 100644 --- a/hugo.yaml +++ b/hugo.yaml @@ -18,7 +18,7 @@ enableGitInfo: true enableInlineShortcodes: true hasCJKLanguage: true -languageCode: en-US +locale: en-US markup: highlight: diff --git a/layouts/openapi/baseof.html b/layouts/openapi/baseof.html index 7c04b27e6..52e1570da 100644 --- a/layouts/openapi/baseof.html +++ b/layouts/openapi/baseof.html @@ -1,5 +1,5 @@ - + From 8d2692ba6064e73b2637b8e9ffdab4bb34fa0397 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:44:43 +0200 Subject: [PATCH 110/180] fix: replace deprecated .Site.Data. with hugo.Data --- layouts/shortcodes/kubernetes_version.html | 2 +- layouts/shortcodes/runtime_version.html | 2 +- layouts/shortcodes/runtimes_versions.html | 2 +- layouts/shortcodes/software_versions_shared_dedicated.html | 2 +- layouts/shortcodes/tooltip.html | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/layouts/shortcodes/kubernetes_version.html b/layouts/shortcodes/kubernetes_version.html index 0da98df40..a564e2a68 100644 --- a/layouts/shortcodes/kubernetes_version.html +++ b/layouts/shortcodes/kubernetes_version.html @@ -1,3 +1,3 @@ {{- $key := .Get 0 -}} -{{- $value := index .Site.Data.kubernetes_versions $key -}} +{{- $value := index hugo.Data.kubernetes_versions $key -}} {{- if reflect.IsSlice $value -}}{{ delimit $value ", " }}{{- else -}}{{ $value }}{{- end -}} diff --git a/layouts/shortcodes/runtime_version.html b/layouts/shortcodes/runtime_version.html index 084945473..8ca5e4880 100644 --- a/layouts/shortcodes/runtime_version.html +++ b/layouts/shortcodes/runtime_version.html @@ -1 +1 @@ -{{ $software := .Get 0 }}{{ $key := or (.Get 1) "default" }}{{ with index .Site.Data.runtime_versions $software }}{{ with index . $key }}{{ index . 0 }}{{ end }}{{ end }} \ No newline at end of file +{{ $software := .Get 0 }}{{ $key := or (.Get 1) "default" }}{{ with index hugo.Data.runtime_versions $software }}{{ with index . $key }}{{ index . 0 }}{{ end }}{{ end }} \ No newline at end of file diff --git a/layouts/shortcodes/runtimes_versions.html b/layouts/shortcodes/runtimes_versions.html index beea16c45..5d5b163b7 100644 --- a/layouts/shortcodes/runtimes_versions.html +++ b/layouts/shortcodes/runtimes_versions.html @@ -1,5 +1,5 @@ {{ $software := or (.Get 0) }} -{{ $version_list := .Site.Data.runtime_versions }} +{{ $version_list := hugo.Data.runtime_versions }} {{ with index $version_list $software }} {{ $default := .default | default (slice) }} diff --git a/layouts/shortcodes/software_versions_shared_dedicated.html b/layouts/shortcodes/software_versions_shared_dedicated.html index 448e93013..cb2876c9b 100644 --- a/layouts/shortcodes/software_versions_shared_dedicated.html +++ b/layouts/shortcodes/software_versions_shared_dedicated.html @@ -1,5 +1,5 @@ {{ $software := or (.Get 0) }} -{{ $version_list := .Site.Data.software_versions_shared_dedicated }} +{{ $version_list := hugo.Data.software_versions_shared_dedicated }} {{ $software_versions := index $version_list $software }} {{ $has_dev := false }} diff --git a/layouts/shortcodes/tooltip.html b/layouts/shortcodes/tooltip.html index 807e5e763..b1e112d88 100644 --- a/layouts/shortcodes/tooltip.html +++ b/layouts/shortcodes/tooltip.html @@ -3,7 +3,7 @@ {{- errorf "missing tooltip title" -}} {{- end -}} {{ .Scratch.Set "title" $title }} -{{ $def := index .Site.Data.tooltips (.Scratch.Get "title") }} +{{ $def := index hugo.Data.tooltips (.Scratch.Get "title") }} {{- if not $def -}} {{- errorf "%s not in tooltips" $title -}} From 0420f0dc4e9d21d4ab7d0119a8d104898e655cd7 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 10:41:29 +0200 Subject: [PATCH 111/180] chore: update Hextra theme to 0.12.3 --- go.mod | 2 +- go.sum | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 990a24e38..b63284dd5 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/CleverCloud/documentation go 1.26 -require github.com/imfing/hextra v0.12.2 // indirect +require github.com/imfing/hextra v0.12.3 // indirect diff --git a/go.sum b/go.sum index 72fad666f..afa868093 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,2 @@ -github.com/imfing/hextra v0.11.1 h1:8pTc4ReYbzGTHAnyiebmlT3ijFfIXiGu1r7tM/UGjFI= -github.com/imfing/hextra v0.11.1/go.mod h1:cEfel3lU/bSx7lTE/+uuR4GJaphyOyiwNR3PTqFTXpI= -github.com/imfing/hextra v0.12.0 h1:f6y35hW/WDJEcx9S0dOmbICOBxYE0PmP6IJFsTUgVyY= -github.com/imfing/hextra v0.12.0/go.mod h1:YAv8XRNSmcqjieFwI7fVQK1AoY2Do+45DO9HGqxSGu4= -github.com/imfing/hextra v0.12.1 h1:3t1n0bmJbDzSTVfht93UDcfF1BXMRjeFojA071ri2l8= -github.com/imfing/hextra v0.12.1/go.mod h1:vi+yhpq8YPp/aghvJlNKVnJKcPJ/VyAEcfC1BSV9ARo= -github.com/imfing/hextra v0.12.2 h1:qa+cHQ1LC/7ys9EhRNnHrRBAHu83Tm8rhh1oWO2a7cc= -github.com/imfing/hextra v0.12.2/go.mod h1:vi+yhpq8YPp/aghvJlNKVnJKcPJ/VyAEcfC1BSV9ARo= +github.com/imfing/hextra v0.12.3 h1:DZHY2rUWYteyzjlHi9r4n7Bb5e2Q+6LXe4C1Dqn0ZjM= +github.com/imfing/hextra v0.12.3/go.mod h1:vi+yhpq8YPp/aghvJlNKVnJKcPJ/VyAEcfC1BSV9ARo= From 4184e91af67a765efcb04eee8ca0ad77aaff1c9a Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 13:46:37 +0200 Subject: [PATCH 112/180] chore: update Hugo version --- mise.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 mise.toml diff --git a/mise.toml b/mise.toml new file mode 100644 index 000000000..ab6cd305c --- /dev/null +++ b/mise.toml @@ -0,0 +1,2 @@ +[tools] +hugo-extended = "latest" From 51867b04e1fc23a511e50efb2df4e178951f6f88 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 17:56:36 +0200 Subject: [PATCH 113/180] changelog: Metabase 60 RAM requirements --- .../changelog/2026/05-06-metabase-60-ram.md | 23 +++++++++++++++++++ content/doc/addons/metabase.md | 6 ++--- 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 content/changelog/2026/05-06-metabase-60-ram.md diff --git a/content/changelog/2026/05-06-metabase-60-ram.md b/content/changelog/2026/05-06-metabase-60-ram.md new file mode 100644 index 000000000..cb720d63f --- /dev/null +++ b/content/changelog/2026/05-06-metabase-60-ram.md @@ -0,0 +1,23 @@ +--- +title: "Metabase 60 and RAM requirements" +description: New Metabase add-ons are deployed on a S Java instance, and existing add-ons will be upgraded to S when they'll move to 60. +date: 2026-05-06 +tags: + - addons + - metabase +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +Starting with its `x.60` branch, [Metabase requires at least 2 GB of RAM](https://github.com/metabase/metabase/issues/72942) to run. The XS Java instance currently used by default for the Metabase add-on on Clever Cloud only provides 1 GB of RAM, which is no longer enough. To make sure your Metabase instance keeps deploying and running smoothly, we are adapting the default sizing: + +- Metabase add-ons created from today onwards use a **S Java instance** instead of XS +- When the update to version 60 will be offered, existing add-ons will **automatically be migrated to a S Java instance** during the upgrade + +If you want to keep an XS instance, stay on the `x.59` branch by setting `CC_METABASE_VERSION` to `0.59` (or `1.59` for the Enterprise Edition) on the underlying Java application. Keep in mind that staying on x.59 means you will not receive the new features shipped with x.60 and beyond; security patches and some fixes will still be provided as long as the 0.59 branch is maintained. We recommend moving to a S instance and the latest branch as soon as possible. + +- [Learn more about Metabase on Clever Cloud](/doc/addons/metabase/) +- [Pricing of Java instances on Clever Cloud](https://www.clever-cloud.com/pricing/) diff --git a/content/doc/addons/metabase.md b/content/doc/addons/metabase.md index 566a2d285..8aa66bbe0 100644 --- a/content/doc/addons/metabase.md +++ b/content/doc/addons/metabase.md @@ -120,12 +120,12 @@ When you create the Metabase add-on, Clever Cloud automatically deploys: ## Plan sizing -By default, Metabase on Clever Cloud uses small-size resources, i.e: +Metabase on Clever Cloud uses small-size resources, i.e: -- XS Java +- S Java - XXS Small Space PostgreSQL -They are dimensioned to suit a majority of needs. You can however manage and adjust them directly [in the Console](https://console.clever-cloud.com/). For example when you have multiple users loading large dashboards concurrently or if your instance experiences crashes due to Out of Memory (OOM) issues, you should use a larger flavor for the Java application or activate auto-scalability. The PostgreSQL database is not likely to be needed a larger plan, but should this happen you can migrate it using Clever Cloud's Console. +These defaults are dimensioned to suit a majority of needs. You can however manage and adjust them directly [in the Console](https://console.clever-cloud.com/). For example when you have multiple users loading large dashboards concurrently or if your instance experiences crashes due to Out of Memory (OOM) issues, you should use a larger flavor for the Java application or activate auto-scalability. The PostgreSQL database is not likely to be needed a larger plan, but should this happen you can migrate it using Clever Cloud's Console. ## Version management, Security and Updates From d79d48b2d05010b735d9797c91b60828cd3cbddb Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 6 May 2026 18:51:27 +0200 Subject: [PATCH 114/180] changelog: Kubernetes 1.36 by default --- .../2026/05-05-kubernetes-1.36-default.md | 33 +++++++++++++++++++ content/doc/kubernetes/_index.md | 4 +-- data/kubernetes_versions.yml | 2 +- 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 content/changelog/2026/05-05-kubernetes-1.36-default.md diff --git a/content/changelog/2026/05-05-kubernetes-1.36-default.md b/content/changelog/2026/05-05-kubernetes-1.36-default.md new file mode 100644 index 000000000..d90a76944 --- /dev/null +++ b/content/changelog/2026/05-05-kubernetes-1.36-default.md @@ -0,0 +1,33 @@ +--- +title: Kubernetes 1.36 is now used by default +description: Kubernetes 1.36 is now the default version for new clusters on Clever Kubernetes Engine +date: 2026-05-05 +tags: + - kubernetes + - release +authors: + - name: Gilles Biannic + link: https://github.com/GillesBIANNIC + image: https://github.com/GillesBIANNIC.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +Kubernetes 1.36 "Haru" is available on Clever Kubernetes Engine [since its release](/changelog/2026/04-24-kubernetes-1.36/). It's now the default version deployed when no `--cluster-version` is specified. New clusters will run on v1.36 unless you explicitly pick another supported one. + +You can still target v1.35 or v1.34 with `--cluster-version`, as long as they remain supported (n-2 from the current release, mirroring the official Kubernetes support policy): + +```bash +clever k8s create myCluster --cluster-version 1.35 +``` + +Existing clusters are not migrated automatically. To move one to v1.36, use [Clever Tools](/doc/cli/kubernetes/): + +```bash +clever k8s version update myClusterNameOrId 1.36 +``` + +- [Learn more about Kubernetes 1.36](/changelog/2026/04-24-kubernetes-1.36/) +- [Learn more about Kubernetes on Clever Cloud](/doc/kubernetes/) diff --git a/content/doc/kubernetes/_index.md b/content/doc/kubernetes/_index.md index 6a239be84..a4e44ee99 100644 --- a/content/doc/kubernetes/_index.md +++ b/content/doc/kubernetes/_index.md @@ -285,8 +285,8 @@ example-nodegroup 2 2 M Synced 2m kubectl get nodes NAME STATUS ROLES AGE VERSION -example-nodegroup-node0 Ready 6d17h v1.35.4 -example-nodegroup-node1 Ready 3d18h v1.35.4 +example-nodegroup-node0 Ready 6d17h v1.36.0 +example-nodegroup-node1 Ready 3d18h v1.36.0 ``` `DESIREDNODECOUNT` is the number of nodes you asked for, `CURRENTNODECOUNT` is the number of nodes currently in the node group. When creating a node group, `CURRENTNODECOUNT` starts at `0` and increases until it reaches `DESIREDNODECOUNT`. diff --git a/data/kubernetes_versions.yml b/data/kubernetes_versions.yml index b2e2d7cd6..7585f268f 100644 --- a/data/kubernetes_versions.yml +++ b/data/kubernetes_versions.yml @@ -3,7 +3,7 @@ # Update this file when a new Kubernetes version becomes available or when the # platform default rolls forward. current: "1.36" -default: "1.35" +default: "1.36" supported: - "1.36" - "1.35" From 4ec8ef9444ea372ebdf7a2145c11cf99b885154c Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 12 May 2026 10:17:28 +0200 Subject: [PATCH 115/180] changelog: Matomo 5.10 --- content/changelog/2026/05-04-matomo-5.10.md | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 content/changelog/2026/05-04-matomo-5.10.md diff --git a/content/changelog/2026/05-04-matomo-5.10.md b/content/changelog/2026/05-04-matomo-5.10.md new file mode 100644 index 000000000..2e31e53ea --- /dev/null +++ b/content/changelog/2026/05-04-matomo-5.10.md @@ -0,0 +1,25 @@ +--- +title: Matomo 5.10 is available with dark mode and a refreshed interface +description: Switch to a new dark mode, enjoy a modernized interface and redesigned selectors for websites, segments and dashboards +date: 2026-05-04 +tags: + - addons + - matomo +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +Our [Matomo](https://matomo.org/) add-on has been updated to version `5.10.0` which is now used by default. This release introduces a new dark mode, available from your personal settings, to reduce brightness in low-light environments. The interface has also been modernized with updated widget styling, refreshed navigation bars, redesigned selectors for websites, segments or dashboards. + +This version also improves the Goals submenu ordering, lets actions use configured delimiters for flat report labels, prevents modern content tables from overflowing their card containers and keeps action buttons properly styled during focus states. + +You can deploy this release from our [Console](https://console.clever-cloud.com) or [Clever Tools](/doc/cli/). Existing customers' add-ons are already up-to-date. + +- [Learn more about Matomo 5.10](https://matomo.org/changelog/matomo-5-10-0/) +- [Learn more about Matomo on Clever Cloud](/doc/addons/matomo/) From ac0da9e9e5bde4b92a3383fb205ca885f9f41de3 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 12 May 2026 10:35:23 +0200 Subject: [PATCH 116/180] changelog: Clever Tools 4.10 --- .../changelog/2026/05-07-clever-tools-4.10.md | 42 +++++++++++++++++++ content/doc/reference/cli.md | 8 ++-- 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 content/changelog/2026/05-07-clever-tools-4.10.md diff --git a/content/changelog/2026/05-07-clever-tools-4.10.md b/content/changelog/2026/05-07-clever-tools-4.10.md new file mode 100644 index 000000000..247892a8e --- /dev/null +++ b/content/changelog/2026/05-07-clever-tools-4.10.md @@ -0,0 +1,42 @@ +--- +title: "Clever Tools 4.10: organisation-scoped add-on providers" +date: 2026-05-07 +description: Clever Tools 4.10 adds an --org option to filter add-on providers, regions and plans for a specific organisation +tags: + - clever-tools + - cli + - addons +authors: + - name: Hubert Sablonnière + link: https://github.com/hsablonniere + image: https://github.com/hsablonniere.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Clever Tools 4.10.0](https://github.com/CleverCloud/clever-tools/releases/tag/4.10.0) is available. This release makes add-on provider discovery organisation-aware, so you can see exactly which providers, regions and plans are available for a given organisation before creating a new add-on. + +## Organisation-scoped add-on providers + +The `clever addon providers` and `clever addon providers show` commands now accept an `--org` option. When set, results are filtered to match the providers, zones and plans exposed by the `addonproviders` API for that organisation, instead of the global catalogue. + +```bash +# List add-on providers available for a specific organisation +clever addon providers --org orga_xxx + +# Inspect a single provider, with org-specific plans and regions +clever addon providers show postgresql-addon --org orga_xxx +``` + +The `clever addon create` command also uses `--org` to validate the requested region. Without it, region availability checks were not organisation-aware, which could let creation calls through in regions not allowed for your organisation. With this fix, the CLI rejects the call up front when the target region is not available in the selected organisation. + +## How to upgrade + +To upgrade Clever Tools, [use your favourite package manager](/doc/cli/install/). For example with `npm`: + +```bash +npm update -g clever-tools +clever version +``` diff --git a/content/doc/reference/cli.md b/content/doc/reference/cli.md index 7122e2a47..d86e9b68a 100644 --- a/content/doc/reference/cli.md +++ b/content/doc/reference/cli.md @@ -422,7 +422,8 @@ clever addon providers [options] **Options** ``` --F, --format Output format (human, json) (default: human) +-F, --format Output format (human, json) (default: human) +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) ``` #### addon providers show @@ -438,12 +439,13 @@ clever addon providers show [options] **Arguments** ``` -addon-provider Add-on provider +addon-provider Add-on provider ``` **Options** ``` --F, --format Output format (human, json) (default: human) +-F, --format Output format (human, json) (default: human) +-o, --org, --owner Organisation to target by its ID (or name, if unambiguous) ``` ### addon rename From 906e3a4c79620afcfa506fca6f6ee8294d2098c1 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 12 May 2026 11:48:10 +0200 Subject: [PATCH 117/180] changelog: Keycloak 26.6 with per-realm IP filtering --- content/changelog/2026/05-12-keycloak-26.6.md | 71 +++++++++++++++++++ content/doc/addons/keycloak.md | 37 +++++++++- 2 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 content/changelog/2026/05-12-keycloak-26.6.md diff --git a/content/changelog/2026/05-12-keycloak-26.6.md b/content/changelog/2026/05-12-keycloak-26.6.md new file mode 100644 index 000000000..ccc9f9fe2 --- /dev/null +++ b/content/changelog/2026/05-12-keycloak-26.6.md @@ -0,0 +1,71 @@ +--- +title: Keycloak 26.6 with per-realm IP filtering +description: Keycloak 26.6 is available on Clever Cloud and adds IP filtering to restrict admin, public and SCIM endpoints per realm +date: 2026-05-12 +tags: + - addons + - keycloak +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[The release 26.6.1](https://github.com/keycloak/keycloak/releases/tag/26.6.1) of Keycloak is available on Clever Cloud, bringing bug fixes on top of the new features introduced in [Keycloak 26.6.0](https://github.com/keycloak/keycloak/releases/tag/26.6.0). This version graduates several features from preview and adds new ones: + +- Step-up authentication for SAML (preview) +- Zero-downtime patch releases supported, with rolling updates for minor versions +- New Groups scope for user membership changes and Vault SPI lookup for client secrets +- JWT Authorization Grant, Federated client authentication and Workflows promoted to supported +- Identity Brokering APIs V2 (preview), the successor to legacy Token Exchange V1 for retrieving external IdP tokens +- OAuth Client ID Metadata Document (experimental), enabling Keycloak as an authorization server for the Model Context Protocol +- New `KCRAW_` environment variable prefix to preserve literal values, dedicated HTTP access log file, configurable log file rotation, graceful HTTP shutdown + +## Organization groups + +[Organization groups](https://www.keycloak.org/2026/04/org-groups) give each organization its own isolated, nestable group hierarchy. Two organizations can now have a `/Engineering/Backend` group each without sharing members, attributes or identifiers, which removes the need to namespace groups across the realm. + +Identity providers can assign users to organization groups automatically through two new mappers: Hardcoded Group, which adds every brokered user to a specific group, and Advanced Claim to Group, which routes users based on the value of an external IdP claim. Group memberships appear in the `organization` claim of OIDC tokens and as attributes in SAML assertions, so applications can authorize on them without an extra round-trip. Full automation is available through the new endpoints under `/admin/realms//organizations/{orgId}/groups`. + +## SCIM Realm API (experimental) + +This release also introduces the [SCIM Realm API](https://www.keycloak.org/2026/04/scim-as-experimental-feature) (System for Cross-domain Identity Management) as an experimental feature, disabled by default. It exposes POST, GET, PATCH, PUT and DELETE operations for users and groups, the core user, enterprise user and group schemas, and SCIM filtering and pagination on search endpoints. Bulk operations, password management, sorting and custom schemas and attributes are not supported yet. Two security fixes ship in this release, addressing an [IDOR on the SCIM PUT endpoint](https://github.com/keycloak/keycloak/issues/46658) and an [authorization bypass on user group management](https://github.com/keycloak/keycloak/issues/47536). + +To expose `/realms//scim/*` endpoints, add `scim-api` to the `KC_FEATURES` environment variable of the Java application and rebuild it. `KC_FEATURES` is a build-time setting, so a simple restart is not enough. Once the rebuild completes, enable SCIM on each target realm from the Keycloak admin console (Realm Settings, *SCIM API Enabled* toggle) where the SCIM base URL is also displayed. + +## Per-realm IP filtering + +On top of upstream features, this version of the Keycloak add-on deployed on Clever Cloud extends its IP filtering capabilities. In addition to the existing in-realm authenticator flow, you can now restrict access to administration, public and SCIM endpoints on a per-realm basis through environment variables of the underlying Java application. Four families of variables control these filters, each accepting a comma-separated list of IP addresses: + +- `CC_KEYCLOAK_ADMIN_IPS_`: restricts `/admin//*` and `/admin/realms//*` for a given realm +- `CC_KEYCLOAK_PUBLIC_IPS_`: restricts `/realms//*` (login pages, user authentication, tokens) +- `CC_KEYCLOAK_SCIM_IPS_`: restricts `/realms//scim/*` provisioning endpoints +- `CC_KEYCLOAK_ADMIN_IPS`: global fallback for any `/admin/*` endpoint not covered by a per-realm admin rule + +The realm name in the variable suffix must match the realm name as it appears in URLs (case-sensitive). Per-realm filters take precedence over the global admin filter. Blocked requests receive an `HTTP 403` response. If no IP filtering variable is set, Keycloak keeps its standard public behavior. + +For example, to allow only two office IPs to reach the `master` realm admin console and a dedicated server to call SCIM on the `production` realm: + +```bash +CC_KEYCLOAK_ADMIN_IPS_master="203.0.113.10,203.0.113.11" +CC_KEYCLOAK_SCIM_IPS_production="198.51.100.42" +``` + +## Updating + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_KEYCLOAK_VERSION` of the underlying Java application to `26.6.1` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever keycloak version check yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId 26.6.1 +``` + +- [Learn more about IP filtering on the Keycloak add-on](/doc/addons/keycloak#ip-filtering) +- [Learn more about Keycloak on Clever Cloud](/doc/addons/keycloak) diff --git a/content/doc/addons/keycloak.md b/content/doc/addons/keycloak.md index c56e98be1..6bafd8f74 100644 --- a/content/doc/addons/keycloak.md +++ b/content/doc/addons/keycloak.md @@ -182,15 +182,46 @@ Uploading previously exported data in `realms/import` folder in the associated F Keycloak uses an [FSBucket](/doc/addons/fs-bucket) to install themes and plugins. To deploy a custom theme or custom plugin, simply download them into the respective `themes` or `providers` folder in your FSBucket. -## Add IP filtering in Keycloak for admin console +## IP filtering -Two specific authentication flows with an IP addresses based filter are especially created and affected as default to clients `security-admin-console` and `admin-cli`. To use them (do not forget to make the same on each realm you want to protect): +You can restrict who can reach your Keycloak instance with two complementary mechanisms: a per-endpoint filter that blocks traffic before authentication runs, and an in-realm authentication flow that filters at sign-in time. They can be used together. + +### Per-realm, on admin, public and SCIM endpoints + +Starting with version `26.6`, the Keycloak add-on can filter incoming requests based on the client's public IP, with separate rules for each realm and for each endpoint category (admin console and admin API, public endpoints, SCIM provisioning). Filtering is configured through environment variables of the underlying Java application. Each variable accepts a comma-separated list of IP addresses and blocked requests receive an `HTTP 403` response. + +| Variable | Scope | Protected paths | +|----------|-------|-----------------| +| `CC_KEYCLOAK_ADMIN_IPS_` | Admin endpoints of a given realm | `/admin//*`, `/admin/realms//*` | +| `CC_KEYCLOAK_PUBLIC_IPS_` | Public endpoints of a given realm (login, tokens, user authentication) | `/realms//*` | +| `CC_KEYCLOAK_SCIM_IPS_` | SCIM provisioning endpoints of a given realm (requires the `scim-api` feature, see below) | `/realms//scim/*` | +| `CC_KEYCLOAK_ADMIN_IPS` | Global fallback for admin endpoints not covered by a per-realm rule | `/admin/*` | + +The realm name in the variable suffix must match the realm name as it appears in URLs (case-sensitive). Per-realm rules take precedence over the global admin filter. If none of these variables is set, Keycloak keeps its standard public behavior. + +For example, to allow only two office IPs to reach the `master` realm admin console, restrict the `production` realm to your application servers and reserve its SCIM endpoints for your identity sync server: + +```bash +CC_KEYCLOAK_ADMIN_IPS_master="203.0.113.10,203.0.113.11" +CC_KEYCLOAK_PUBLIC_IPS_production="198.51.100.10,198.51.100.11" +CC_KEYCLOAK_SCIM_IPS_production="198.51.100.42" +CC_KEYCLOAK_ADMIN_IPS="203.0.113.10" +``` + +Filters compare the client IP to the literal values you provide, so use individual IP addresses rather than CIDR ranges. If you configure a custom HTTP path through `KC_HTTP_RELATIVE_PATH`, the prefix is automatically prepended to the protected paths. + +> [!NOTE] Enabling the SCIM endpoints +> SCIM was introduced in Keycloak `26.6` as an [experimental feature](https://www.keycloak.org/2026/04/scim-as-experimental-feature) and is disabled by default. The `CC_KEYCLOAK_SCIM_IPS_` filter only takes effect once SCIM is enabled. Add `scim-api` (comma-separated if you already have other entries) to the `KC_FEATURES` environment variable of the Java application and rebuild it — `KC_FEATURES` is a build-time setting, so a simple restart is not enough. Once the rebuild completes, enable SCIM on each target realm from the Keycloak admin console (Realm Settings, *SCIM API Enabled* toggle) where the SCIM base URL is also displayed. + +### At the realm authentication flow level + +Two specific authentication flows with an IP-address-based filter are created by default and assigned to the `security-admin-console` and `admin-cli` clients. They run at sign-in time, inside Keycloak. To use them (do this on each realm you want to protect): - Enable "PLEASE-OPEN.IT Authenticator IP Range" to "Required" - Click on the crank to access parameters - Set IPs with authorized access -Those flows could be affected to your own clients if you need. +You can also assign these flows to your own clients if needed. ## Grafana dashboard & Metrics From 5abd15a57ec118057dcc0577737fead710bf7972 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 12 May 2026 11:06:04 +0200 Subject: [PATCH 118/180] changelog: Metabase 60 released --- content/changelog/2026/05-12-metabase-60.md | 44 +++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 content/changelog/2026/05-12-metabase-60.md diff --git a/content/changelog/2026/05-12-metabase-60.md b/content/changelog/2026/05-12-metabase-60.md new file mode 100644 index 000000000..63a3187cf --- /dev/null +++ b/content/changelog/2026/05-12-metabase-60.md @@ -0,0 +1,44 @@ +--- +title: "Metabase 60 is available, with Metabot, MCP server and split panel charts" +description: Metabot, MCP server integration, Slack querying, metrics explorer, split panel charts, frozen columns, transform inspector and more +date: 2026-05-12 +tags: + - addons + - metabase +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +The `x.60` branch of Metabase is now available on Clever Cloud. It brings Metabot with "bring your own key" support for Anthropic models, an official MCP server to connect Claude, Cursor and other tools, and a Slack integration to query data directly from a channel. + +It also introduces a metrics explorer to compare multiple metrics side-by-side and surface trends across dimensions, split panel charts to display multiple series as faceted panels, frozen columns and rows in tables, a transform inspector to compare data before and after a transform runs, model-to-transform migration in a few clicks, OpenID Connect (OIDC) authentication, remote sync with GitLab and Bitbucket in addition to GitHub, and multiple enhancements and bug fixes. + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_METABASE_VERSION` of the underlying Java application to `0.60` or `1.60` for the enterprise edition (EE) and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever metabase version check yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId 0.60 +``` + +## New RAM requirements + +Starting with the `x.60` branch, [Metabase requires at least 2 GB of RAM](https://github.com/metabase/metabase/issues/72942) to run. Because the XS Java instance previously used by default only provides 1 GB of RAM, the default sizing has been adapted: new Metabase add-ons are deployed on a **S Java instance**, and existing add-ons are **automatically migrated to a S Java instance** when upgrading to version 60. + +This new branch is not yet the default if you use `community-latest`, we'll move to it in the next few weeks. To prepare for the automatic move to x.60, we recommend scaling the underlying Java application from XS (the current default) to a S instance now from the **Scalability** link in your Metabase add-on dashboard. + +If you want to keep an XS instance, stay on the `x.59` branch by setting `CC_METABASE_VERSION` to `0.59` (or `1.59` for the Enterprise Edition) on the underlying Java application. Keep in mind that staying on x.59 means you will not receive the new features shipped with x.60 and beyond; security patches and some fixes will still be provided as long as the 0.59 branch is maintained. We recommend moving to a S instance and the latest branch as soon as possible. + +- [Learn more about Metabase 60](https://www.metabase.com/releases/metabase-60) +- [Learn more about Metabase on Clever Cloud](/doc/addons/metabase/) +- [Pricing of Java instances on Clever Cloud](https://www.clever-cloud.com/pricing/) + +{{< youtube id="IrQeCFHHU3A" >}} From 7c5327deec5ffbc3b1f22d57a4d37b9570a06483 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 12 May 2026 14:47:18 +0200 Subject: [PATCH 119/180] changelog: Materia KV GraphQL layer --- .../2026/05-11-materia-kv-graphql.md | 30 ++++ content/doc/addons/materia-kv.md | 170 ++++++++++++++++-- 2 files changed, 186 insertions(+), 14 deletions(-) create mode 100644 content/changelog/2026/05-11-materia-kv-graphql.md diff --git a/content/changelog/2026/05-11-materia-kv-graphql.md b/content/changelog/2026/05-11-materia-kv-graphql.md new file mode 100644 index 000000000..a15af9a89 --- /dev/null +++ b/content/changelog/2026/05-11-materia-kv-graphql.md @@ -0,0 +1,30 @@ +--- +title: "Materia KV: query your data through GraphQL" +description: Read your Materia KV add-on through a typed GraphQL endpoint, in addition to the Redis API. Same keyspace, same token, served from a shared per-region URL. +date: 2026-05-11 +tags: + - addons + - materia + - kv + - graphql +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Materia KV](/doc/addons/materia-kv/) gains a second compatibility layer: a **GraphQL** endpoint that reads from the same keyspace as the Redis API. Every key you write through a Redis/Valkey compatible client is immediately queryable through GraphQL, with no synchronization in between. Both interfaces authenticate with the same token (`$KV_TOKEN` / `$REDIS_PASSWORD`), passed as a bearer token. + +As Materia KV is a distributed cluster, the GraphQL endpoint is a single URL shared by every add-on in a given region. For Paris: + +``` +https://materiakv-graphql.eu-fr-1.services.clever-cloud.com/graphql +``` + +The schema exposes a single root type, `MateriaKvQuery`, with queries for strings, hashes and sets — single-key lookups, batched reads, pattern matching, and server-side set algebra (`setIntersection`, `setDifference`, `setUnion`). Introspection is enabled, so you can browse the full schema in the embedded **GraphiQL** playground (open the URL in a browser) or pull it as SDL with any introspection tool. + +The GraphQL layer is **read-only** for now — mutations aren't supported yet. Use the Redis API for writes. Have a look at [this end-to-end example](https://github.com/CleverCloud/kv-graphql-example) combining both layers: writes via the Redis API, reads via GraphQL. + +- [Learn more about the GraphQL compatibility layer](/doc/addons/materia-kv/#using-the-graphql-compatibility-layer) +- [Materia KV write via Redis API, read via GraphQL](https://github.com/CleverCloud/kv-graphql-example) diff --git a/content/doc/addons/materia-kv.md b/content/doc/addons/materia-kv.md index 0eb806de9..f9272c962 100644 --- a/content/doc/addons/materia-kv.md +++ b/content/doc/addons/materia-kv.md @@ -30,9 +30,14 @@ You don't have to configure leaders, followers: high availability is included, b ## Compatibility layers -We didn’t want this Materia KV to come at the cost of complex configuration, requiring the use of special clients and ORMs. That’s why we’ve developed its compatibility layers. To “talk” to it, you don’t need a special API or tools specific to Clever Cloud. You'll be able to use it with existing solutions for **DynamoDB, GraphQL or Redis**. The first available layer is compatible with Redis API (and its variants as Reddict or Valkey). +We didn’t want this Materia KV to come at the cost of complex configuration, requiring the use of special clients and ORMs. That’s why we’ve developed its compatibility layers: each one lets you talk to Materia KV through an existing protocol, with the clients, CLIs and ORMs you already use — no Clever Cloud-specific SDK required. -Thus, you can use a Materia KV add-on with any compatible client within your applications, `redis-cli` or alternatives such as [iredis](https://github.com/laixintao/iredis). You can also use it with graphical interface (GUI). We tested many of them with success: +Two layers are available: + +- [**Redis API**](#using-the-redis-api-compatible-layer) (and variants such as Redict and Valkey) — full read/write access, the primary way to interact with Materia KV. +- [**GraphQL**](#using-the-graphql-compatibility-layer) — a typed, read-oriented view of the same keyspace, served from a standard GraphQL endpoint. + +Both layers operate on the same underlying data, so any key written through the Redis API is immediately visible through GraphQL. For the Redis API layer specifically, you can use `redis-cli`, `valkey-cli` or alternatives such as [iredis](https://github.com/laixintao/iredis), as well as graphical clients we've tested successfully: - [Another Redis Desktop Client](https://goanother.com/) - [PX3 Redis UI](https://github.com/patrikx3/redis-ui) @@ -44,7 +49,7 @@ Thus, you can use a Materia KV add-on with any compatible client within your app You can create a Materia KV add-on as simply as any other Clever Cloud service in the Console, [following this link](https://console.clever-cloud.com/users/me/addons/new). Select the plan (free during Beta testing phase), an application to link to (or none), give it a name, and you'll get access to its dashboard giving you connection details. Environment variables shared with a linked application are listed in the `Service dependencies` section. -We included them with the `REDIS_` format. Thus, you can just try to replace a Redis instance by Materia KV. It's as simple as linking the new add-on, unlinking the old one and restarting your application! (Check commands you'll need first). +We included them with the `REDIS_` format. Thus, you can just try to replace a Redis or Valkey instance by Materia KV. It's as simple as linking the new add-on, unlinking the old one and restarting your application! (Check commands you'll need first). You can also use clever tools to create a Materia KV add-on and set environment variables to test it with a `PING` command: @@ -84,7 +89,7 @@ You can also deploy Materia KV add-ons with [Terraform provider](https://registr ### Environment variables and CLI usage -To connect to a Materia KV add-on, you need 3 parameters: the host, the port and a ([biscuit](https://biscuitsec.org) based) token. You can set these parameters as environment variables by doing `source <(clever addon env addon ADDON_ID -F shell)`. The variables set are: +To connect to a Materia KV add-on, you need 3 parameters: the host, the port and a token. You can set these parameters as environment variables by doing `source <(clever addon env addon ADDON_ID -F shell)`. The variables set are: * `$KV_HOST` and its alias `$REDIS_HOST` * `$KV_PORT` and its alias `$REDIS_PORT` @@ -119,15 +124,6 @@ We're exploring how [Clever Tools](https://github.com/CleverCloud/clever-tools/) * [Learn more about Clever KV](/doc/cli/kv-stores/) -### Demos and examples - -We've prepared a few examples to help you get started with Materia KV: - -* [Materia KV Go client](https://github.com/CleverCloud/mkv-go-cli) -* [Materia KV raw TCP V demo](https://github.com/CleverCloud/mkv-raw-tcp-v) -* [Materia KV raw TCP Ruby demo](https://github.com/CleverCloud/mkv-raw-tcp-ruby) -* [Materia KV PHP sessions with TTL demo](https://github.com/CleverCloud/php-sessions-kv-example) - ### Supported types and commands Supported value types are: @@ -141,7 +137,7 @@ Find below the list of currently supported commands: |
Commands
| Description | | ------- | ----------- | | `APPEND` | If `key` already exists and is a string, this command appends the value at the end of the string. If `key` doesn't exist it is created and set as an empty string, so `APPEND` will be similar to `SET` in this special case. | -| `AUTH` | Authenticate the current connection using the biscuit token as `password`. | +| `AUTH` | Authenticate the current connection using the token as `password`. | | `CLIENT ID` | Returns the `ID` of the current connection. A connection ID has is never repeated and is monotonically incremental. | | `COMMAND` | Return an array with details about every supported command. | | `COMMAND COUNT` | Return the number of supported commands. | @@ -262,3 +258,149 @@ OK - `JSON.SET` can't create new fields in existing documents - Nested path creation is not supported (e.g., `$.new.child.field`) - Keys in your JSON must not contains characters like `..`, `*`, `[?(` + +## Using the GraphQL compatibility layer + +In addition to the Redis API, Materia KV exposes a **GraphQL** endpoint. It reads from the same keyspace as the Redis API layer — every key you write through the Redis API is immediately queryable through GraphQL, with no synchronization layer in between. Both interfaces authenticate with the same token, and that token scopes each request to your add-on's data on the shared cluster. + +The GraphQL layer is **read-only** today — mutations aren't supported yet. Use the Redis API for writes. + +### Endpoint and authentication + +Materia KV is a distributed cluster: the GraphQL endpoint is a single URL shared by every add-on in a given region, over HTTPS on the standard port. For the Paris region, it is: + +``` +https://materiakv-graphql.eu-fr-1.services.clever-cloud.com/graphql +``` + +Authentication uses the same token as the Redis API (`$KV_TOKEN` / `$REDIS_PASSWORD`), passed as a bearer token: + +```bash +curl -X POST "https://materiakv-graphql.eu-fr-1.services.clever-cloud.com/graphql" \ + -H "Authorization: Bearer $KV_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"query":"{ __typename }"}' +``` + +Missing or invalid credentials return **HTTP 401** with the error in the GraphQL `errors` array. This endpoint returns **HTTP 200** for all other errors (wrong-type reads, unknown fields, mutation requests, validation errors), with the details in `errors`. + +### GraphiQL playground + +Opening the GraphQL URL in a browser (plain `GET`) serves an embedded **GraphiQL** playground: an in-browser IDE with autocompletion, query history and a documentation explorer. Every request — including the schema introspection that powers autocomplete and the docs explorer — needs your token, otherwise GraphiQL displays `Error fetching schema`. + +**Initial setup, once per browser session:** + +1. At the **bottom of the query panel**, click the **Headers** tab (next to `Variables`). +2. Paste your token as a JSON object: + + ```json + { + "Authorization": "Bearer " + } + ``` + +3. Click the **Re-fetch GraphQL schema** icon at the bottom of the **left sidebar** (the circular-arrow icon, keyboard shortcut `Ctrl` + `Shift` + `R`). Introspection runs with your header, and the full schema becomes browsable. + +Once the header is set, the **Docs Explorer** (book icon, also in the left sidebar) shows the full type tree: every query, every argument, every return type, with the descriptions baked into the schema. + +### Fetching the schema + +Introspection is enabled, so you can pull the schema directly from the endpoint in three common ways. + +1. **Browse it in GraphiQL** — follow the setup above (headers + re-fetch), then click the Docs Explorer icon in the left sidebar. + +2. **Raw JSON introspection via `curl`** — useful for scripts and CI: + + ```bash + curl -X POST "https://materiakv-graphql.eu-fr-1.services.clever-cloud.com/graphql" \ + -H "Authorization: Bearer $KV_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"query":"{ __schema { queryType { name fields { name } } types { name kind } } }"}' + ``` + +3. **Download as SDL** (the classic `.graphql` schema file) using any introspection tool, such as `get-graphql-schema`: + + ```bash + npx -y get-graphql-schema \ + -h "Authorization=Bearer $KV_TOKEN" \ + "https://materiakv-graphql.eu-fr-1.services.clever-cloud.com/graphql" > schema.graphql + ``` + +The resulting file plugs straight into code generators, IDE plugins, and schema-aware editors. + +### Queries + +The schema exposes a single root type, `MateriaKvQuery`, with queries for strings, hashes and sets — including single-key lookups, batched reads, pattern matching and server-side set algebra (`setIntersection`, `setDifference`, `setUnion`). Fetch the full list of queries and their signatures through introspection (see [Fetching the schema](#fetching-the-schema) above) or browse them in the GraphiQL Docs Explorer. + +Single-key queries (`string`, `hash`, `hashField`, `getSetMembers`) return a **nullable** object — `null` when the key doesn't exist. Pattern and batch queries return **non-null lists** (possibly empty). + +### Query examples + +Fetch a single string, including its expiration: + +```graphql +query ReadSession($key: String!) { + string(key: $key) { + key + value + expireAt + } +} +``` + +Read a structured record in one round-trip: + +```graphql +query GetUser($key: String!) { + hash(key: $key) { + key + fields { name value } + } +} +``` + +Combine several reads into one request using [aliases](https://graphql.org/learn/queries/#aliases): + +```graphql +query Dashboard { + admins: getSetMembers(key: "group:admins") { members } + active: getSetMembers(key: "group:active") { members } + overlap: setIntersection(keys: ["group:admins", "group:active"]) +} +``` + +Always pass user input through **GraphQL variables** rather than string interpolation: the server type-checks every variable, which reduces injection risk and avoids query string interpolation issues: + +```bash +curl -X POST "https://materiakv-graphql.eu-fr-1.services.clever-cloud.com/graphql" \ + -H "Authorization: Bearer $KV_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "query": "query($k:String!){ string(key:$k){ key value } }", + "variables": { "k": "session:xyz" } + }' +``` + +### JSON values read through GraphQL + +JSON documents written via `JSON.SET` are stored on top of strings. The schema has no dedicated GraphQL type for JSON: read the document back through `string(key)` as the serialized payload and parse it client-side. Partial JSON paths (`JSON.GET key $.field`) are only available through the Redis API. + +### Current behaviors and limitations + +- Queries on a key of the wrong type (e.g. `hash(key)` on a string key) return a GraphQL error — `Database operation failed: Operation against a key holding the wrong kind of value` — rather than `null`. Make sure your query type matches the Redis type at the same key. +- `stringsByPattern(pattern)` rejects a call where the pattern matches **more than 100 keys** with an error (`Database operation failed: Max batch size exceeded: N > 100`) — results are not silently truncated. Narrow the pattern (or walk the keyspace through several tighter prefixes) when the match count is larger. +- `strings(keys: [...])` has the same hard limit: **at most 100 keys** per call. Chunk larger batches on the client side. +- Pattern-based queries (`hashesByPattern`, `setsByPattern`, `hashFieldsByPattern`) are not paginated: each call returns its full result set in one response. Keep patterns focused to avoid oversized payloads. +- Glob patterns follow the Redis `KEYS`/`SCAN` syntax (`*`, `?`, `[abc]`). +- The `expireAt` field is an absolute instant (ISO 8601), not a remaining TTL — parse it as an ISO 8601 timestamp in your client, then subtract the current time to compute a countdown. +- Authentication errors return HTTP 401 with the message in the `errors` array. Every other GraphQL error (wrong-type reads, mutation requests, validation errors) returns HTTP 200 with `errors` populated. + +## Demos and examples + +We've prepared a few examples to help you get started with Materia KV: + +* [Materia KV Go client](https://github.com/CleverCloud/mkv-go-cli) +* [Materia KV raw TCP V demo](https://github.com/CleverCloud/mkv-raw-tcp-v) +* [Materia KV raw TCP Ruby demo](https://github.com/CleverCloud/mkv-raw-tcp-ruby) +* [Materia KV PHP sessions with TTL demo](https://github.com/CleverCloud/php-sessions-kv-example) +* [Materia KV write via Redis API, read via GraphQL](https://github.com/CleverCloud/kv-graphql-example) From 12ffbfbf079aa27ff936409954b4c1cca623a7ab Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 12 May 2026 16:02:04 +0200 Subject: [PATCH 120/180] changelog: kernel 7.0.6 --- .../2026/05-12-linux-kernel-7.0.6.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 content/changelog/2026/05-12-linux-kernel-7.0.6.md diff --git a/content/changelog/2026/05-12-linux-kernel-7.0.6.md b/content/changelog/2026/05-12-linux-kernel-7.0.6.md new file mode 100644 index 000000000..e3538fe68 --- /dev/null +++ b/content/changelog/2026/05-12-linux-kernel-7.0.6.md @@ -0,0 +1,21 @@ +--- +title: "Linux kernel 7.0.6 available" +description: The Linux kernel can now be updated independently from the rest of our images. Restart your application to run on 7.0.6. +date: 2026-05-12 +tags: + - images + - update + - kernel +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +Over the last few weeks, we worked on decoupling the Linux kernel from the rest of our runtime images. This work is now complete: kernel upgrades no longer require a full image refresh and can roll out on their own cadence, which means faster delivery of security fixes and hardware support improvements without touching the language runtimes, build tools, or system libraries shipped in each image. + +As a first step on this new pipeline, **Linux 7.0.6** is now available. To run your application on this kernel, restart it from the [Clever Cloud Console](https://console.clever-cloud.com) or with `clever restart`. Newly deployed applications pick up the new kernel automatically. + +- [What's new in Linux 7.0](https://kernelnewbies.org/Linux_7.0) +- [Linux 7.0.6 changelog](https://cdn.kernel.org/pub/linux/kernel/v7.x/ChangeLog-7.0.6) From 009f3f8d62c5f48480699bb92383f1592c4904be Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 12 May 2026 19:53:19 +0200 Subject: [PATCH 121/180] changelog: Cellar Explorer & NG in Console --- .../2026/05-12-cellar-dashboard-explorer.md | 27 +++++++++++++++++++ .../2026/05-12-network-groups-console.md | 26 ++++++++++++++++++ content/doc/addons/cellar.md | 10 ++++++- 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 content/changelog/2026/05-12-cellar-dashboard-explorer.md create mode 100644 content/changelog/2026/05-12-network-groups-console.md diff --git a/content/changelog/2026/05-12-cellar-dashboard-explorer.md b/content/changelog/2026/05-12-cellar-dashboard-explorer.md new file mode 100644 index 000000000..76c2fd4ae --- /dev/null +++ b/content/changelog/2026/05-12-cellar-dashboard-explorer.md @@ -0,0 +1,27 @@ +--- +title: "Cellar: new dashboard and Cellar Explorer" +description: A new Cellar add-on dashboard surfaces access information, consumption, the s3cfg configuration file, and the Cellar Explorer to browse buckets and objects directly from the Console. +date: 2026-05-12 +tags: + - addons + - cellar + - console +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Cellar](/doc/addons/cellar/), our S3-compatible object storage service, gets a new dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). It puts the information you need to connect and operate your add-on within reach: access key ID and secret, endpoint, region, current storage and bandwidth consumption, plan and billing details. The pre-filled `s3cfg` configuration file is one click away, ready to drop into your home directory and use with `s3cmd` or any compatible tool. + +## Cellar Explorer + +The new dashboard also opens the door to the **Cellar Explorer**, a browser-side file manager for your Cellar buckets, built in the spirit of the [KV Explorer](/doc/addons/materia-kv/#clever-cloud-kv-explorer) we shipped for Materia KV and Redis® add-ons. It lists every bucket of the add-on, lets you navigate objects as folders, and supports the operations you reach for most often: download an object, upload one or several files, copy a public URL, inspect object metadata, all without leaving the Console or installing a third-party S3 client. + +Under the hood, the Cellar Explorer relies on the new Cellar APIs we've been rolling out, which also power upcoming features around versioning and usage reporting. Expect the tool to keep evolving over the coming months, with more operations exposed directly in the interface. + +The Cellar Explorer is available in Beta for every Cellar add-on. Share your feedback and feature requests on our [GitHub Community](https://github.com/CleverCloud/Community/discussions/155). + +- [Learn more about Cellar on Clever Cloud](/doc/addons/cellar/) +- [Share your feedback on the Cellar Explorer](https://github.com/CleverCloud/Community/discussions/155) diff --git a/content/changelog/2026/05-12-network-groups-console.md b/content/changelog/2026/05-12-network-groups-console.md new file mode 100644 index 000000000..ecad87f0f --- /dev/null +++ b/content/changelog/2026/05-12-network-groups-console.md @@ -0,0 +1,26 @@ +--- +title: "Network Groups are now available in the Console" +description: Create, browse and delete Network Groups directly from the Clever Cloud Console, link applications and add-ons, inspect members and peers without leaving the browser. +date: 2026-05-12 +tags: + - console + - network-groups + - wireguard +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Network Groups](/doc/develop/network-groups/) — our [WireGuard](https://www.wireguard.com/)-based private networks between Clever Cloud resources — are now first-class citizens in the [Clever Cloud Console](https://console.clever-cloud.com). Until now, creating and managing them required the [public API](/api/v4/#network-groups) or [Clever Tools](/doc/cli/network-groups/). The same operations are now available directly in the Console. + +From the Network Groups section of your organisation, you can create a new group with its label, description and tags, browse the groups already in place, and delete the ones you don't need anymore. Opening a group shows its CIDR, the members linked to it (applications, add-ons or external resources) and the peers currently connected, with domain name and IP inside the private network. + +Linking resources is also done from the Console: pick an application or an add-on from your organisation and add it as a member of the group in a few clicks. Removing a member works the same way, and the change is reflected immediately on the peers list as instances reconnect to the network. + +This rounds out the Network Groups experience across all our interfaces — API, CLI, and now the Console — so you can pick the one that fits your workflow. Share your feedback and feature requests on our [GitHub Community](https://github.com/CleverCloud/Community/discussions/156). + +- [Learn more about Network Groups](/doc/develop/network-groups/) +- [How to use Network Groups from Clever Tools](/doc/cli/network-groups/) +- [Share your feedback on Network Groups in the Console](https://github.com/CleverCloud/Community/discussions/156) diff --git a/content/doc/addons/cellar.md b/content/doc/addons/cellar.md index b25240667..092b36ec1 100644 --- a/content/doc/addons/cellar.md +++ b/content/doc/addons/cellar.md @@ -101,9 +101,17 @@ alias aws="aws --endpoint-url https://cellar-c2.services.clever-cloud.com" There are several ways to manage your buckets, find in this section a list of options. +### Cellar Explorer + +Browse your buckets, upload, download and inspect objects directly from the [Clever Cloud Console](https://console.clever-cloud.com) with the **Cellar Explorer** tool, part of the all-included Clever Cloud experience. + +From the add-on dashboard, the Cellar Explorer lists every bucket of the add-on and lets you navigate objects as folders. You can upload or download an object, copy its public URL and inspect its metadata without installing a third-party S3 client. The tool relies on the new Cellar APIs and will keep gaining features over the coming months. + +Cellar Explorer is in Beta testing phase. Share your feedback and feature requests on our [GitHub Community](https://github.com/CleverCloud/Community/discussions/155). + ### Using S3 clients -Some clients allows you to upload files, list them, delete them, etc, like: +Some clients allow you to upload files, list them, delete them, etc, like: - [Cyberduck](https://cyberduck.io) - [Filestash](https://www.filestash.app/) From b2cdcbc15cdc7ff0a85d6ad1a2111e0a25ef94ab Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 19 May 2026 18:32:50 +0200 Subject: [PATCH 122/180] fix: OAuth2 Proxy branding --- content/changelog/2026/03-25-images-update.md | 2 +- content/changelog/2026/04-07-images-update.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/changelog/2026/03-25-images-update.md b/content/changelog/2026/03-25-images-update.md index 4b7d41bea..353d5795d 100644 --- a/content/changelog/2026/03-25-images-update.md +++ b/content/changelog/2026/03-25-images-update.md @@ -22,7 +22,7 @@ We updated all our images, except PHP. Deployment is in progress for all our use * cURL 8.19.0 * FFmpeg 8.1 * Ghostscript 10.07 - * OAuth2Proxy 7.14.3 + * OAuth2 Proxy 7.14.3 * Poppler 26.03 * SQLite 3.52.0 * Varnish 8.0.1 diff --git a/content/changelog/2026/04-07-images-update.md b/content/changelog/2026/04-07-images-update.md index 9124590c8..16827c560 100644 --- a/content/changelog/2026/04-07-images-update.md +++ b/content/changelog/2026/04-07-images-update.md @@ -17,7 +17,7 @@ We updated all our images, except PHP. Deployment is in progress for all our use * **Common:** * Linux kernel 6.19.11 * nginx 1.28.3 - * OAuth2Proxy 7.15.1 + * OAuth2 Proxy 7.15.1 * Tailscale 1.96.3 * **Python:** * uv 0.11.2 From 71f9b0bab0dda31bf77d1c5fb553f1888257dcfd Mon Sep 17 00:00:00 2001 From: Corentin BARAULT Date: Mon, 11 May 2026 15:25:55 +0200 Subject: [PATCH 123/180] addons(postgresql): customized backups mention and pgbackrest --- content/doc/addons/postgresql.md | 5 +++++ shared/db-backup.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/content/doc/addons/postgresql.md b/content/doc/addons/postgresql.md index e24e4d387..a2fbb7945 100644 --- a/content/doc/addons/postgresql.md +++ b/content/doc/addons/postgresql.md @@ -30,6 +30,11 @@ PostgreSQL is an object-relational database management system (ORDBMS) with an e {{% content "db-backup" %}} +## Point In Time Recovery + +The PostgreSQL add-on supports pgBackRest, enabling incremental backups and point-in-time data restoration. +It's not installed by default on a PostgreSQL add-on, if you need it, you can contact [Clever Cloud Support](https://console.clever-cloud.com/ticket-center-choice). + ## Migrating from an old database Some applications require a non-empty database to run properly. If you want to import your **SQL** dump, you can use several methods: diff --git a/shared/db-backup.md b/shared/db-backup.md index 367e169de..fd10fc0d4 100644 --- a/shared/db-backup.md +++ b/shared/db-backup.md @@ -1,5 +1,5 @@ ## Database Daily Backup and Retention -By default, Clever Cloud performs a free backup every day, with a retention of seven days. Retention and frequency can be customized for Premium customers. +By default, Clever Cloud performs a free backup every day, with a retention of seven days. Retention and frequency can be customized by contacting [Clever Cloud Support](https://console.clever-cloud.com/ticket-center-choice). Each backup can be found in the add-on dashboard in the web console, along with the credentials. From 4d1b669767b668cec85a535e78d228ce6c915762 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 19 May 2026 18:43:08 +0200 Subject: [PATCH 124/180] changelog: images updates, 2026W21 --- content/changelog/2026/05-22-images-update.md | 72 +++++++++++++++++++ content/doc/applications/frankenphp.md | 2 +- .../applications/scala/play-framework-1.md | 3 +- content/doc/applications/static.md | 2 +- data/runtime_versions.yml | 2 +- 5 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 content/changelog/2026/05-22-images-update.md diff --git a/content/changelog/2026/05-22-images-update.md b/content/changelog/2026/05-22-images-update.md new file mode 100644 index 000000000..889bfb504 --- /dev/null +++ b/content/changelog/2026/05-22-images-update.md @@ -0,0 +1,72 @@ +--- +title: "Images update: PHP 8.5.6, Java security patches, Python 3.14.5, Hugo 0.161" +description: All images updated with security patches across every runtime, PHP included. Hugo support narrows to 0.160 and later from June 15th. +date: 2026-05-22 +tags: + - images + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated all our images. Deployment is in progress for all our users. + +* **Common:** + * Apache 2.4.67 + * Chromium 148.0.7778.167 + * Clever Tools 4.10.0 + * cURL 8.20.0 + * Mise 2026.5.3 + * NGINX 1.30.1 + * OAuth2 Proxy 7.15.2 + * Redis 8.6.3 + * SQLite 3.53.1 +* **FrankenPHP:** + * Update to 1.12.3 (For `CC_PHP_VERSION=8.5`) +* **Go:** + * Go 1.26.3 +* **Java:** + * Update to 1.8.0.492_p09 + * Update to 11.0.31_p11 + * Update to 17.0.19_p10 + * Update to 21.0.11_p10 + * Update to 25.0.3_p9 + * Gradle 8.14.5 + * Gradle 9.5.1 +* **Node.js & Bun:** + * Bun 1.3.14 +* **PHP:** + * Update to 8.2.31 + * Update to 8.3.31 + * Update to 8.4.21 + * Update to 8.5.6 + * Composer 2.2.27 + * Composer 2.9.7 +* **Python:** + * Update to 3.14.5 + * uv 0.11.15 +* **Ruby:** + * Update to 4.0.5 +* **Scala:** + * Play Framework 1.2.7.2 + * Play Framework 1.11.0 +* **Static:** + * Hugo 0.152.2 + * Hugo 0.159.2 + * Hugo 0.160.1 + * Hugo 0.161.1 + +## Linux Kernel + +Kernel is [now updated independently](/changelog/2026/05-12-linux-kernel-7.0.6). Current version is 7.0.9. + +## Fixes for Java, Node.js & Bun, PHP + +This image includes multiple fixes for bug we encountered for Java applications using Maven, PHP applications using `apcu` or `zip` extensions, Node.js 25/26 applications using `pnpm` or `yarn`. + +## Hugo version update + +Starting June 15th, we will only support Hugo 0.160 release and later. If you are using an older version, update your application or [use Mise](/doc/reference/reference-environment-variables#install-tools-with-mise-package-manager) to download it during deployment. diff --git a/content/doc/applications/frankenphp.md b/content/doc/applications/frankenphp.md index 50e9576a5..f04c1b906 100644 --- a/content/doc/applications/frankenphp.md +++ b/content/doc/applications/frankenphp.md @@ -49,7 +49,7 @@ FrankenPHP runtime only requires a working web application, with an `index.php` FrankenPHP currently deployed version on Clever Cloud is `{{< runtime_version frankenphp >}}` based on PHP `{{< runtime_version frankenphp php >}}` and Caddy server `{{< runtime_version frankenphp caddy >}}`. Virtual machine image includes multiple tools from the PHP ecosystem such as Composer or Symfony CLI. The `php` command available in hooks and scripts uses `frankenphp php-cli` under the hood. -You can use FrankenPHP 1.12.1 with PHP 8.5 and Caddy 2.11.2, by setting the `CC_PHP_VERSION` environment variable to `8.5`. +You can use FrankenPHP 1.12.3 with PHP 8.5 and Caddy 2.11.3, by setting the `CC_PHP_VERSION` environment variable to `8.5`. - [FrankenPHP PHP 8.4 info](https://frankenphpinfo-8.4.cleverapps.io/) - [FrankenPHP PHP 8.5 info](https://frankenphpinfo-8.5.cleverapps.io/) diff --git a/content/doc/applications/scala/play-framework-1.md b/content/doc/applications/scala/play-framework-1.md index c5e12ec61..461989296 100644 --- a/content/doc/applications/scala/play-framework-1.md +++ b/content/doc/applications/scala/play-framework-1.md @@ -31,7 +31,7 @@ Clever Cloud supports Play 1.x applications natively. The present guide explains ### Select Play! 1.x version -Clever Cloud supports Play! **1.2**, **1.3**, **1.4**, **1.5**. You can select the Play! version for your application by setting the `PLAY1_VERSION` [environment variable](#setting-up-environment-variables-on-clever-cloud) (or by putting it in a file named `clevercloud/play1_version`). +Clever Cloud supports Play! **1.2** to **1.11**. You can select the Play! version for your application by setting the `PLAY1_VERSION` [environment variable](#setting-up-environment-variables-on-clever-cloud) (or by putting it in a file named `clevercloud/play1_version`). The `PLAY1_VERSION` environment variable can contain one of the following values: @@ -44,6 +44,7 @@ The `PLAY1_VERSION` environment variable can contain one of the following values * `1.8` or `18` for **Play! 1.8** * `1.9` or `19` for **Play! 1.9** * `1.10` or `110` for **Play! 1.10** +* `1.11` or `111` for **Play! 1.11** ### Play! configuration with application.conf diff --git a/content/doc/applications/static.md b/content/doc/applications/static.md index 6de99f7fc..229b5364c 100644 --- a/content/doc/applications/static.md +++ b/content/doc/applications/static.md @@ -110,7 +110,7 @@ Supported Static Site Generators (SSG) are: - Detected file: `hugo.toml`, `hugo.yaml`, `hugo.json` > [!TIP] Set the Hugo version ->Use a specific Hugo version by setting the `CC_HUGO_VERSION` environment variable to `0.147`, `0.148`, `0.149` (default), `0.150`, `0.151` or `0.152` +>Use a specific Hugo version by setting the `CC_HUGO_VERSION` environment variable to `0.147`, `0.148`, `0.149` (default), `0.150`, `0.151`, `0.152`, `0.159`, `0.160` or `0.161`. From June 15th, only `0.160` and later are supported. ### mdBook diff --git a/data/runtime_versions.yml b/data/runtime_versions.yml index a5521476d..cda42bcde 100644 --- a/data/runtime_versions.yml +++ b/data/runtime_versions.yml @@ -1,7 +1,7 @@ bun: eol_source: https://github.com/oven-sh/bun/releases default: - - 1.3.12 + - 1.3.14 caddy: eol_source: https://github.com/caddyserver/caddy/releases From 0afd6e6e274b2e48f9d3a80347db944c6a65be76 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Fri, 22 May 2026 13:55:15 +0200 Subject: [PATCH 125/180] addons(postgresql): PostgreSQL 18.4, 17.10, 16.14, 15.18, 14.23 --- .../changelog/2026/05-21-pg-update-18.4.md | 26 +++++++++++++++++++ data/software_versions_shared_dedicated.yml | 12 ++++----- 2 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 content/changelog/2026/05-21-pg-update-18.4.md diff --git a/content/changelog/2026/05-21-pg-update-18.4.md b/content/changelog/2026/05-21-pg-update-18.4.md new file mode 100644 index 000000000..ddaf0ef91 --- /dev/null +++ b/content/changelog/2026/05-21-pg-update-18.4.md @@ -0,0 +1,26 @@ +--- +title: PostgreSQL 18.4, 17.10, 16.14, 15.18, 14.23 are available (security update) +description: Security patches, bug fixes and improvements for PostgreSQL 14 to 18 +date: 2026-05-21 +tags: + - addons + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +New PostgreSQL versions are available for new add-ons and migration: +* PostgreSQL 18.4 +* PostgreSQL 17.10 +* PostgreSQL 16.14 +* PostgreSQL 15.18 +* PostgreSQL 14.23 + +These versions include [over 60 bug fixes and improvements](https://www.postgresql.org/about/news/postgresql-184-1710-1614-1518-and-1423-released-3297), with security patches for 11 CVEs: [CVE-2026-6472](https://nvd.nist.gov/vuln/detail/CVE-2026-6472), [CVE-2026-6473](https://nvd.nist.gov/vuln/detail/CVE-2026-6473), [CVE-2026-6474](https://nvd.nist.gov/vuln/detail/CVE-2026-6474), [CVE-2026-6475](https://nvd.nist.gov/vuln/detail/CVE-2026-6475), [CVE-2026-6476](https://nvd.nist.gov/vuln/detail/CVE-2026-6476), [CVE-2026-6477](https://nvd.nist.gov/vuln/detail/CVE-2026-6477), [CVE-2026-6478](https://nvd.nist.gov/vuln/detail/CVE-2026-6478), [CVE-2026-6479](https://nvd.nist.gov/vuln/detail/CVE-2026-6479), [CVE-2026-6575](https://nvd.nist.gov/vuln/detail/CVE-2026-6575), [CVE-2026-6637](https://nvd.nist.gov/vuln/detail/CVE-2026-6637) and [CVE-2026-6638](https://nvd.nist.gov/vuln/detail/CVE-2026-6638). + +PostgreSQL 14 reaches its end of life on 12 November 2026. Plan a migration to a newer version before that date to keep receiving security updates. Also note that PostgreSQL 11 [is not available anymore](/changelog/2025/03-24-postgresql-11-12-eol/) for migrations on Clever Cloud. + +* [Learn more about PostgreSQL on Clever Cloud](/doc/addons/postgresql/) diff --git a/data/software_versions_shared_dedicated.yml b/data/software_versions_shared_dedicated.yml index 770c206f4..1a5a027f2 100644 --- a/data/software_versions_shared_dedicated.yml +++ b/data/software_versions_shared_dedicated.yml @@ -22,13 +22,13 @@ mysql: pg: dedicated: - - v14.22 - - v15.17 - - v16.13 - - v17.9 - - v18.3 + - v14.23 + - v15.18 + - v16.14 + - v17.10 + - v18.4 dev: - - v15.17 + - v15.18 redis: dedicated: From 99ffdc386fe36e8c0c942433454c2d0916797280 Mon Sep 17 00:00:00 2001 From: Julien Durillon Date: Wed, 27 May 2026 10:57:56 +0200 Subject: [PATCH 126/180] reference(env vars): add `CC_CACHE_DEPENDENCIES_EXTRA_PATHS` variable in reference --- content/doc/reference/reference-environment-variables.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/doc/reference/reference-environment-variables.md b/content/doc/reference/reference-environment-variables.md index e55b5132b..4e564dbd2 100644 --- a/content/doc/reference/reference-environment-variables.md +++ b/content/doc/reference/reference-environment-variables.md @@ -69,6 +69,7 @@ You can set some tools' version in any runtime (except Docker): | Name | Description | Default value | |-----------------------|------------------------------|--------------------------------| |`CC_CACHE_DEPENDENCIES` | Enable caching of your build dependencies to speed up following builds. | false | +|`CC_CACHE_DEPENDENCIES_EXTRA_PATHS` | Add custom paths to the dependencies cache. Expected format: Colon-separated PATH-style string. Paths are considered relative to APP_HOME. One level of `../` is allowed. E.g. `../.cargo:lib:vendor` will select `/home/bas/.cargo`, `/home/bas/{app_id}/lib` and `/home/bas/{app_id}/vendor` | | |[`CC_DISABLE_BUILD_CACHE_UPLOAD`](/doc/develop/env-variables/#settings-you-can-define-using-environment-variables "Settings you can define using environment variables") | Disable creation and upload of cache archive. Restarts won't be speeded up. | `false` | |[`CC_IGNORE_FROM_BUILDCACHE`](/doc/develop/env-variables/#settings-you-can-define-using-environment-variables "Settings you can define using environment variables") | Allows to specify paths to ignore when the build cache archive is created. | | |[`IGNORE_FROM_BUILDCACHE`](/doc/develop/env-variables/#settings-you-can-define-using-environment-variables "Settings you can define using environment variables") | (Deprecated) Allows to specify paths to ignore when the build cache archive is created. | | From 9064698969ef9052d885d239d738eb9f3b6af094 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 27 May 2026 12:41:47 +0200 Subject: [PATCH 127/180] chore: update Hugo minimum supported version --- hugo.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hugo.yaml b/hugo.yaml index 34604903d..7ab573b17 100644 --- a/hugo.yaml +++ b/hugo.yaml @@ -7,7 +7,7 @@ module: - path: github.com/imfing/hextra hugoVersion: extended: true - min: 0.151.0 + min: 0.154.5 disableKinds: - taxonomy From 4905f22af87ecd00a64cace06f627dbbb3a0d0de Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 27 May 2026 12:42:31 +0200 Subject: [PATCH 128/180] chore: use Hugo 0.161 as example version --- .github/copilot-instructions.md | 2 +- .github/workflows/deploy.yml | 2 +- CLAUDE.md | 2 +- README.md | 2 +- content/doc/reference/reference-environment-variables.md | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 839f8222b..74b9c7cd4 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -273,7 +273,7 @@ The site is configured for Clever Cloud hosting with the `static` runtime and th - `CC_WEBROOT="public"` - `CC_STATIC_AUTOBUILD_OUTDIR="public/developers"` - `SERVER_ERROR_PAGE_404="developers/404.html"` -- Optional: `CC_HUGO_VERSION="0.152"` to specify Hugo version (example value) +- Optional: `CC_HUGO_VERSION="0.161"` to specify Hugo version (example value) ## Quality Assurance Requirements diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 007363036..e8bc5bc1d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,7 +35,7 @@ jobs: CLEVER_SECRET: ${{ secrets.CLEVER_SECRET }} CLEVER_TOKEN: ${{ secrets.CLEVER_TOKEN }} ORGA_ID: ${{ secrets.ORGA_ID }} - GH_CC_HUGO_VERSION: 0.152 + GH_CC_HUGO_VERSION: 0.161 GH_CC_STATIC_AUTOBUILD_OUTDIR: public/developers GH_CC_WEBROOT: public GH_SERVER_ERROR_PAGE_404: developers/404.html diff --git a/CLAUDE.md b/CLAUDE.md index f017e46fb..6f15fe59a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -90,7 +90,7 @@ The site is configured for Clever Cloud hosting with the `static` runtime and th - `CC_WEBROOT="public"` - `CC_STATIC_AUTOBUILD_OUTDIR="public/developers"` - `SERVER_ERROR_PAGE_404="developers/404.html"` -- Optional: `CC_HUGO_VERSION="0.152"` to specify Hugo version (example value) +- Optional: `CC_HUGO_VERSION="0.161"` to specify Hugo version (example value) ## Data Management Runtime versions and software compatibility information is maintained in `/data/runtime_versions.yml` and should be kept current with platform capabilities. The site generates various output formats including standard HTML and a special LLMS output format at `/llms.txt` for AI consumption. diff --git a/README.md b/README.md index 2d3a7a6f9..417b5b276 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ SERVER_ERROR_PAGE_404="developers/404.html" ``` > [!TIP] -> You can set the Hugo version with `CC_HUGO_VERSION` with a value like `0.152` +> You can set the Hugo version with `CC_HUGO_VERSION` with a value like `0.161` ## Contributing diff --git a/content/doc/reference/reference-environment-variables.md b/content/doc/reference/reference-environment-variables.md index 4e564dbd2..66c2d0c22 100644 --- a/content/doc/reference/reference-environment-variables.md +++ b/content/doc/reference/reference-environment-variables.md @@ -61,7 +61,7 @@ You can set some tools' version in any runtime (except Docker): | Name | Description | |------|-------------| -| `CC_HUGO_VERSION` | Set the Hugo version, for example `0.152` | | +| `CC_HUGO_VERSION` | Set the Hugo version, for example `0.161` | | | `CC_NODE_VERSION` | Set Node.js version, for example `24`, `23.11` or `22.15.1` | | #### Control build and dependencies cache @@ -386,7 +386,7 @@ When your Python application doesn't use one of the supported backends, with `CC | Name | Description | Default value | |------|-------------|---------------| -| `CC_HUGO_VERSION` | Set the Hugo version, for example `0.152` | | +| `CC_HUGO_VERSION` | Set the Hugo version, for example `0.161` | | | `CC_BUILD_COMMAND` | The command to run during the build phase | | | `CC_OVERRIDE_BUILDCACHE` | Files and path to put in the build cache, separated by a `:` | | | `CC_STATIC_AUTOBUILD_OUTDIR` | The output directory of the static site generator (SSG) auto-build, relative to the root of your application | `/cc_static_autobuilt` | From 29e8a5ea13a8ffefc3a4594a86510cf7e01e24ef Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 27 May 2026 12:43:07 +0200 Subject: [PATCH 129/180] chore: remove temporary mise.toml file --- mise.toml | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 mise.toml diff --git a/mise.toml b/mise.toml deleted file mode 100644 index ab6cd305c..000000000 --- a/mise.toml +++ /dev/null @@ -1,2 +0,0 @@ -[tools] -hugo-extended = "latest" From 7930afdd4b1f7196dd54494566e43f370f8f1550 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Fri, 29 May 2026 15:15:45 +0200 Subject: [PATCH 130/180] changelog: Keycloak 26.6.2 --- .../changelog/2026/05-27-keycloak-26.6.2.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 content/changelog/2026/05-27-keycloak-26.6.2.md diff --git a/content/changelog/2026/05-27-keycloak-26.6.2.md b/content/changelog/2026/05-27-keycloak-26.6.2.md new file mode 100644 index 000000000..47e03a8dc --- /dev/null +++ b/content/changelog/2026/05-27-keycloak-26.6.2.md @@ -0,0 +1,32 @@ +--- +title: Keycloak 26.6.2 (security update) +description: Keycloak 26.6.2 ships on Clever Cloud with the 26.6.1 fixes and addresses seventeen CVEs +date: 2026-05-27 +tags: + - addons + - keycloak +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[The release 26.6.2](https://github.com/keycloak/keycloak/releases/tag/26.6.2) of Keycloak is available on Clever Cloud. It brings some enhancements, but mainly security fixes. It also includes those from [26.6.1](https://github.com/keycloak/keycloak/releases/tag/26.6.1). + +Together these releases address seventeen security vulnerabilities: [CVE-2026-4366](https://nvd.nist.gov/vuln/detail/CVE-2026-4366), [CVE-2026-4633](https://nvd.nist.gov/vuln/detail/CVE-2026-4633), [CVE-2026-33870](https://nvd.nist.gov/vuln/detail/CVE-2026-33870), [CVE-2026-33871](https://nvd.nist.gov/vuln/detail/CVE-2026-33871), [CVE-2026-4628](https://nvd.nist.gov/vuln/detail/CVE-2026-4628), [CVE-2026-4630](https://nvd.nist.gov/vuln/detail/CVE-2026-4630), [CVE-2026-5588](https://nvd.nist.gov/vuln/detail/CVE-2026-5588), [CVE-2026-6856](https://nvd.nist.gov/vuln/detail/CVE-2026-6856), [CVE-2026-7307](https://nvd.nist.gov/vuln/detail/CVE-2026-7307), [CVE-2026-7504](https://nvd.nist.gov/vuln/detail/CVE-2026-7504), [CVE-2026-7507](https://nvd.nist.gov/vuln/detail/CVE-2026-7507), [CVE-2026-7571](https://nvd.nist.gov/vuln/detail/CVE-2026-7571), [CVE-2026-37978](https://nvd.nist.gov/vuln/detail/CVE-2026-37978), [CVE-2026-37979](https://nvd.nist.gov/vuln/detail/CVE-2026-37979), [CVE-2026-37980](https://nvd.nist.gov/vuln/detail/CVE-2026-37980), [CVE-2026-37981](https://nvd.nist.gov/vuln/detail/CVE-2026-37981) and [CVE-2026-37982](https://nvd.nist.gov/vuln/detail/CVE-2026-37982). + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_KEYCLOAK_VERSION` of the underlying Java application to `26.6.2` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever keycloak version check yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId 26.6.2 +``` + +- [Learn more about Keycloak on Clever Cloud](/doc/addons/keycloak) From 07cd04454d02c63fc969a52d46b78143ca8991bc Mon Sep 17 00:00:00 2001 From: David Legrand Date: Fri, 29 May 2026 15:00:28 +0200 Subject: [PATCH 131/180] changelog: Otoroshi 17.16 --- .../changelog/2026/05-29-otoroshi-17.16.md | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 content/changelog/2026/05-29-otoroshi-17.16.md diff --git a/content/changelog/2026/05-29-otoroshi-17.16.md b/content/changelog/2026/05-29-otoroshi-17.16.md new file mode 100644 index 000000000..81586c3e9 --- /dev/null +++ b/content/changelog/2026/05-29-otoroshi-17.16.md @@ -0,0 +1,65 @@ +--- +title: Otoroshi 17.16 brings user analytics, distributed rate limiting and new HTTP standards plugins +description: User analytics dashboards and alerts, distributed rate limiting, RFC 9421/9440/9728 plugins, reworked Kubernetes deployments and an LLM AI Assistant +date: 2026-05-29 +tags: + - addons + - otoroshi +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Otoroshi v17.16.0](https://github.com/MAIF/otoroshi/releases/tag/v17.16.0) is available. This release focuses on observability, API lifecycle management, Kubernetes deployments and a set of brand-new HTTP standards plugins. It also ships an updated LLM extension featuring an AI Assistant for the admin interface and an updated WAF extension. The Otoroshi documentation has been substantially expanded as well. + +### User analytics, dashboards and a global event stream + +Otoroshi gains a complete user analytics stack. A PostgreSQL-based analytics exporter stores and queries events at scale, and you can now build your own dashboards with a graphical widget editor, drill-down support and a set of default dashboards shipped out of the box. User-defined alerts are built from analytics queries through a graphical condition editor, with a dedicated alerting interface and a scheduled evaluation job. Dashboards, alerts and analytics queries are all integrated with RBAC. + +A new global node event stream page in the back office streams audit, alert and analytics events from current node, with pagination, capped buffers and per-type filtering. Data exporters also accept pluggable custom filters and a project phase, and no longer generate one event per filtered event. + +- [Learn more about dashboards, analytics queries and alerts](https://maif.github.io/otoroshi/manual/docs/topics/user-analytics/) + +### API management and new HTTP standards plugins + +API management hardens the split between draft and production: a clear version banner, read-only production views and plan lifecycle enforcement that locks the `Api` entity fields once a plan is published. A getting-started stepper guides newcomers, and the draft subscription listing, top bar search and API key plan editor have all been cleaned up. + +This release introduces three plugins implementing recent HTTP standards. The HTTP Message Signatures plugins ([RFC 9421](https://datatracker.ietf.org/doc/html/rfc9421)) sign and verify requests and responses, the `NgRfc9440ClientCertHeader` plugin forwards client certificates through the standard `Client-Cert` and `Client-Cert-Chain` headers ([RFC 9440](https://datatracker.ietf.org/doc/html/rfc9440)), and an OAuth 2.0 Protected Resource Metadata plugin implements [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728). New JSON validator plugins based on JSON Schemas are also available, and the `OAuth2Caller` plugin can now target a specific resource for the generated token or rely on an OAuth2 authentication module. + +- [HTTP Message Signatures (RFC 9421) tutorial](https://maif.github.io/otoroshi/manual/docs/tutorials/http-message-signatures-rfc9421/) +- [Learn more about RFC 9440 Client-Cert headers](https://maif.github.io/otoroshi/manual/docs/topics/tls/#rfc-9440--client-cert--client-cert-chain-headers) + +### Distributed rate limiting, mailers and exporters + +A new distributed rate-limiting strategy backed by atomic Lua scripts on Redis provides predictable, cluster-aware quotas across nodes. It is a drop-in alternative to the previous distributed strategy and is configurable per route. Scaleway TEM and MailPace join the supported mailers. + +### Reworked Kubernetes deployments + +The Helm chart has been almost entirely rewritten with first-class cluster mode (leader and worker deployments), a Redis secured by default based on the CloudPirates Redis chart, and proper HPA, PDB, NetworkPolicies, RBAC, webhooks and certificate templates. The Kustomize manifests reach feature parity with Helm through a component-based split, and the generated CRDs now expose previously missing entities, including APIs, dashboards, alerts and the new analytics resources. + +### LLM extension: an AI Assistant and Meta MCP + +This release includes LLM extension [0.0.76](https://github.com/cloud-apim/otoroshi-llm-extension/releases/tag/0.0.76), which adds an AI Assistant to the Otoroshi admin interface. The assistant can search the documentation, query the admin API and run administrative tasks through its Search, Doc and Execute tools, with a conversational chat window, streaming responses, light and dark themes and API key-based access. A hybrid semantic and lexical search engine backs documentation queries. + +The extension also introduces a Meta MCP connector that aggregates and proxies multiple MCP servers with dynamic capabilities, an Otoroshi MCP server plugin exposing the assistant tools, and image generation support in the `Response` and `OpenResponse` plugins. The Monaco editor is now used across all administration pages of the extension. + +### Updated bundled plugins + +Every plugin delivered with Otoroshi on Clever Cloud has been rebuilt against 17.16.0. The WAF extension ([0.0.9](https://github.com/cloud-apim/otoroshi-waf-extension/releases/tag/0.0.9)) upgrades the OWASP Core Rule Set to v4.26.0. + +You can update through your add-on dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_OTOROSHI_VERSION` of the underlying Java application to `v17.16.0_1779982854` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever otoroshi version check yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId v17.16.0_1779982854 +``` + +- [Learn more about Otoroshi with LLM on Clever Cloud](/doc/addons/otoroshi/) From 4765a09638e02e1826c1a04e5441edc0c961c6af Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 10 Jun 2026 09:06:00 +0200 Subject: [PATCH 132/180] =?UTF-8?q?changelog:=20S=C5=8Dzu=202.1.0=20(#965)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 What does this PR do? This PR adds an entry about Sōzu 2.1.0 --- ## 🧪 Type of Change - [ ] ⚠️ Bug fix - [x] 📅 Changelog update - [ ] 📚 Documentation update - [ ] ✨ New content/feature - [ ] 🔧 Technical/maintenance --- ## ✅ Quick Checklist - [x] I have read the [contributing guidelines](https://github.com/CleverCloud/documentation/blob/main/CONTRIBUTING.md) - [x] The content is accurate and links work - [x] The site builds without errors --- ## 👥 Reviewers @FlorentinDUBOIS --- content/changelog/2026/06-05-sozu-2.1.0.md | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 content/changelog/2026/06-05-sozu-2.1.0.md diff --git a/content/changelog/2026/06-05-sozu-2.1.0.md b/content/changelog/2026/06-05-sozu-2.1.0.md new file mode 100644 index 000000000..3977c2227 --- /dev/null +++ b/content/changelog/2026/06-05-sozu-2.1.0.md @@ -0,0 +1,24 @@ +--- +title: Sōzu 2.1.0 is available with HTTP/2 support +date: 2026-06-05 +tags: + - sozu +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 + - name: Florentin Dubois + link: https://github.com/FlorentinDUBOIS + image: https://github.com/FlorentinDUBOIS.png?size=40 +description: UDP load balancing joins the HTTP/2 stack, security hardening and operator tooling introduced with Sōzu 2.0 +excludeSearch: true +--- + +[Sōzu](https://www.sozu.io) [2.1.0](https://github.com/sozu-proxy/sozu/releases/tag/2.1.0) is available. It consolidates every improvement shipped since the [2.0.0 milestone](https://github.com/sozu-proxy/sozu/releases/tag/2.0.0): a rewritten HTTP/2 frontend stack supporting the full HTTP/1 and HTTP/2 protocol matrix, a `sozu top` TUI for live monitoring, pluggable cryptographic providers, per-IP rate limiting, frontend routing features such as HSTS, redirects and URL rewrites, control-plane audit logging and a comprehensive metrics rewrite. It also hardens the proxy against HTTP/2 attacks such as Rapid Reset, CONTINUATION flood and MadeYouReset. + +Sōzu 2.1.0 introduces first-class UDP load balancing as an opt-in listener type, with HRW and Maglev source-hash algorithms, flow affinity, PROXY protocol v2 to backends, active health checks and dedicated metrics. Patch releases since 2.0.0 added a wall-clock `start_time` field in access logs for OpenTelemetry span reconstruction and further HTTP/2 flow-control protections. This release also fixes a hot-reconfiguration bug that silently deactivated listeners, and adds about 1,100 debug assertions across the codebase for stronger correctness validation. + +This new version currently serves `cleverapps.io` domains as a testing phase, before a wider rollout to custom domains. + +- [Read about Sōzu 2.0, turning a reverse proxy into a programmable edge](https://www.clever.cloud/blog/engineering/2026/05/29/sozu-2-0-reverse-proxy-programmable-edge/) +- [Learn more about Sōzu](https://github.com/sozu-proxy/sozu/releases) {{< icon "github" >}} From 4444fa9afa072986ca4aa933306f5ed27bf36226 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 10 Jun 2026 08:28:17 +0200 Subject: [PATCH 133/180] changelog: Redis 8.8 --- content/changelog/2026/06-04-redis-8.8.md | 24 +++++++++++++++++++++ data/software_versions_shared_dedicated.yml | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 content/changelog/2026/06-04-redis-8.8.md diff --git a/content/changelog/2026/06-04-redis-8.8.md b/content/changelog/2026/06-04-redis-8.8.md new file mode 100644 index 000000000..ea1dd7527 --- /dev/null +++ b/content/changelog/2026/06-04-redis-8.8.md @@ -0,0 +1,24 @@ +--- +title: Redis 8.8 is available with new commands and field-level notifications +description: A new INCREX rate limiting command, XNACK for Streams, hash field notifications and query engine improvements come with Redis 8.8 +date: 2026-06-04 +tags: + - addons + - redis +authors: + - name: Aurélien Hébert + link: https://github.com/aurrelhebert + image: https://github.com/aurrelhebert.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated Redis™ to [release 8.8.0](https://github.com/redis/redis/releases/tag/8.8.0). It introduces the `INCREX` command, a window counter rate limiter combining increment, bounds and expiration in a single operation, and the `XNACK` command letting Streams consumers explicitly release pending messages. Keyspace notifications now work at field level for hashes, so you can subscribe to changes on individual hash fields. + +This release also adds a `COUNT` aggregator to `ZUNION`, `ZINTER` and their store variants, support for multiple aggregators in a single TimeSeries range command, and query engine improvements with profiling support for hybrid searches. It ships bug fixes for memory tracking, cluster topology handling and module memory leaks, along with performance optimizations. + +Redis™ 8.8 is available for new add-ons. Those already deployed can upgrade through migration. + +- [Learn more about Redis™ on Clever Cloud](/doc/addons/redis/) diff --git a/data/software_versions_shared_dedicated.yml b/data/software_versions_shared_dedicated.yml index 1a5a027f2..de6bc9310 100644 --- a/data/software_versions_shared_dedicated.yml +++ b/data/software_versions_shared_dedicated.yml @@ -32,4 +32,4 @@ pg: redis: dedicated: - - v8.4.0 + - v8.8.0 From aeeed6b4deaff9ffd905d2a799e7958fd56ab7d9 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 10 Jun 2026 08:25:46 +0200 Subject: [PATCH 134/180] changelog: Otoroshi 17.16.1 --- .../changelog/2026/06-04-otoroshi-17.16.1.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 content/changelog/2026/06-04-otoroshi-17.16.1.md diff --git a/content/changelog/2026/06-04-otoroshi-17.16.1.md b/content/changelog/2026/06-04-otoroshi-17.16.1.md new file mode 100644 index 000000000..1dfd44e57 --- /dev/null +++ b/content/changelog/2026/06-04-otoroshi-17.16.1.md @@ -0,0 +1,32 @@ +--- +title: Otoroshi 17.16.1 is available with AlphaEdge and OCR support +description: A patch release fixing workflows and OAuth2Caller, shipped with LLM extension 0.0.78 introducing OCR models and the AlphaEdge provider +date: 2026-06-04 +tags: + - addons + - otoroshi +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Otoroshi v17.16.1](https://github.com/MAIF/otoroshi/releases/tag/v17.16.1) is available. This patch release fixes the workflow backend response parsing, an `OAuth2Caller` bug where the plugin always tried to read `auth_ref`, and a Monaco editor issue swallowing scroll events. It also cleans up the HTTP client workflow node interface. + +This release ships with LLM extension [0.0.78](https://github.com/cloud-apim/otoroshi-llm-extension/releases/tag/0.0.78), which introduces OCR models as a first-class entity alongside audio, image, embedding and moderation models. You can expose them through a dedicated plugin, the unified OpenAI-compatible API or the `ocr_call` workflow function, with Mistral models support. The French provider [AlphaEdge](https://www.alphaedge-ai.com/) joins the extension for both speech transcription, with diarization and linguistic post-correction options, and optical character recognition. + +You can update through your add-on dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_OTOROSHI_VERSION` of the underlying Java application to `v17.16.1_1780575469` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever otoroshi version check yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId v17.16.1_1780575469 +``` + +- [Learn more about Otoroshi with LLM on Clever Cloud](/doc/addons/otoroshi/) From f1534d1a928fa55e9e9b672fc98f536a6a5e8e73 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 10 Jun 2026 17:37:15 +0200 Subject: [PATCH 135/180] addon(materia-kv): fix GraphQL expireAt --- content/doc/addons/materia-kv.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/content/doc/addons/materia-kv.md b/content/doc/addons/materia-kv.md index f9272c962..251efdc56 100644 --- a/content/doc/addons/materia-kv.md +++ b/content/doc/addons/materia-kv.md @@ -263,7 +263,7 @@ OK In addition to the Redis API, Materia KV exposes a **GraphQL** endpoint. It reads from the same keyspace as the Redis API layer — every key you write through the Redis API is immediately queryable through GraphQL, with no synchronization layer in between. Both interfaces authenticate with the same token, and that token scopes each request to your add-on's data on the shared cluster. -The GraphQL layer is **read-only** today — mutations aren't supported yet. Use the Redis API for writes. +The GraphQL layer is **read-only** today — the schema exposes no mutation type, and any mutation request is rejected with a `Schema is not configured for mutations.` error. Use the Redis API for writes. ### Endpoint and authentication @@ -334,6 +334,12 @@ The schema exposes a single root type, `MateriaKvQuery`, with queries for string Single-key queries (`string`, `hash`, `hashField`, `getSetMembers`) return a **nullable** object — `null` when the key doesn't exist. Pattern and batch queries return **non-null lists** (possibly empty). +### Key expiration + +String, hash and set results all expose the key's expiration through an `expireAt` field, typed with the schema's `DateTime` scalar: an absolute UTC instant serialized as an RFC 3339 (ISO 8601) string, such as `2026-06-10T15:40:54.134+00:00`. Keys without a time to live return `null`. + +The Redis API remains the place where you set, update or clear a TTL (`EXPIRE`, `PEXPIRE`, `PERSIST`, `SET ... EX`): GraphQL reflects the resulting absolute instant on the next read. To compute a countdown in your client, parse `expireAt` as a timestamp and subtract the current time. + ### Query examples Fetch a single string, including its expiration: @@ -392,7 +398,7 @@ JSON documents written via `JSON.SET` are stored on top of strings. The schema h - `strings(keys: [...])` has the same hard limit: **at most 100 keys** per call. Chunk larger batches on the client side. - Pattern-based queries (`hashesByPattern`, `setsByPattern`, `hashFieldsByPattern`) are not paginated: each call returns its full result set in one response. Keep patterns focused to avoid oversized payloads. - Glob patterns follow the Redis `KEYS`/`SCAN` syntax (`*`, `?`, `[abc]`). -- The `expireAt` field is an absolute instant (ISO 8601), not a remaining TTL — parse it as an ISO 8601 timestamp in your client, then subtract the current time to compute a countdown. +- The `expireAt` field is an absolute instant (a `DateTime` scalar in RFC 3339 / ISO 8601 format), not a remaining TTL — see [Key expiration](#key-expiration) above. - Authentication errors return HTTP 401 with the message in the `errors` array. Every other GraphQL error (wrong-type reads, mutation requests, validation errors) returns HTTP 200 with `errors` populated. ## Demos and examples From a52e0ad1c02bad94b292a9dcfa961b7d1517a9bb Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 10 Jun 2026 08:30:27 +0200 Subject: [PATCH 136/180] changelog: Matomo 5.11 --- content/changelog/2026/06-09-matomo-5.11.md | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 content/changelog/2026/06-09-matomo-5.11.md diff --git a/content/changelog/2026/06-09-matomo-5.11.md b/content/changelog/2026/06-09-matomo-5.11.md new file mode 100644 index 000000000..ca75e2ad4 --- /dev/null +++ b/content/changelog/2026/06-09-matomo-5.11.md @@ -0,0 +1,25 @@ +--- +title: Matomo 5.11 is available +description: Add descriptions to your sites and custom dimensions, send scheduled reports over custom date ranges +date: 2026-06-09 +tags: + - addons + - matomo +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +Our [Matomo](https://matomo.org/) add-on has been updated to version `5.11.0` which is now used by default. This release lets you document your analytics setup directly in Matomo: sites accept a description of up to 255 characters, and custom dimensions a description of up to 1000 characters, both available through the interface and the API. + +Scheduled reports can now be sent over a custom date range, in addition to the usual periods. Reports gain finer control over flattening and exports, CSV and TSV exports now replace carriage return characters to keep files consistent, and themes benefit from a new alternative border color variable. As usual, this version ships its batch of bug fixes. + +You can deploy this release from our [Console](https://console.clever-cloud.com) or [Clever Tools](/doc/cli/). Existing customers' add-ons are already up-to-date. + +- [Learn more about Matomo 5.11](https://matomo.org/changelog/matomo-5-11-0/) +- [Learn more about Matomo on Clever Cloud](/doc/addons/matomo/) From 6f8ff275a18ef0e6143202f52cef56e5928729b3 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Thu, 11 Jun 2026 10:22:41 +0200 Subject: [PATCH 137/180] changelog: Metabase 61 --- content/changelog/2026/05-30-metabase-61.md | 35 +++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 content/changelog/2026/05-30-metabase-61.md diff --git a/content/changelog/2026/05-30-metabase-61.md b/content/changelog/2026/05-30-metabase-61.md new file mode 100644 index 000000000..f592a804e --- /dev/null +++ b/content/changelog/2026/05-30-metabase-61.md @@ -0,0 +1,35 @@ +--- +title: "Metabase 61 is available, with AI governance, dashboards-as-code and Security Center" +description: AI access controls, token limits, Metabot customization, AI usage analytics, dashboards-as-code, metrics math, Security Center and more +date: 2026-05-30 +tags: + - addons + - metabase +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +The `x.61` branch of Metabase is now available on Clever Cloud. It focuses on AI governance: control which user groups can access AI features such as Metabot, SQL generation and auto-generated transforms, set token limits per instance or per user group with daily, weekly or monthly resets, customize Metabot with your own name, icon and system prompts, and track token spend and feature usage through a pre-made AI usage analytics dashboard. + +It also introduces dashboards-as-code to create dashboards from an AI terminal such as Claude Code or Cursor with git-backed validation, arithmetic expressions across metrics in the metrics explorer, custom expressions written by Metabot in the query builder, and a Security Center with targeted alerts for your instance configuration. Embedded analytics gains usage analytics, guest token auto-renewal, a themes editor, mobile-optimized SDK components and a `useMetabot` React hook. Some of these features require the enterprise edition (EE). + +This branch is not the default for now if you use `community-latest`. You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_METABASE_VERSION` of the underlying Java application to `0.61` or `1.61` for the enterprise edition (EE) and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever metabase version check yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId 0.61 +``` + +- [Learn more about Metabase 61](https://www.metabase.com/releases/metabase-61) +- [Learn more about Metabase on Clever Cloud](/doc/addons/metabase/) + +{{< youtube id="jU8ua9OHvTY" >}} From 7658324b814b5a6cde2b64ce129c034c63bd96b1 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Thu, 11 Jun 2026 10:22:46 +0200 Subject: [PATCH 138/180] changelog: Metabase 62 --- content/changelog/2026/06-11-metabase-62.md | 33 +++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 content/changelog/2026/06-11-metabase-62.md diff --git a/content/changelog/2026/06-11-metabase-62.md b/content/changelog/2026/06-11-metabase-62.md new file mode 100644 index 000000000..31e13a5f2 --- /dev/null +++ b/content/changelog/2026/06-11-metabase-62.md @@ -0,0 +1,33 @@ +--- +title: "Metabase 62 is available, with custom visualizations, schema viewer and a CLI" +description: Custom visualizations, schema viewer, Metabase CLI, centralized alert management, subcollections, new MCP server tools and more +date: 2026-06-11 +tags: + - addons + - metabase +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +The `x.62` branch of Metabase is now available on Clever Cloud. It brings custom visualizations with built-in drills and tooltips, a schema viewer to display entity-relationship diagrams of your databases, a Metabase CLI built on the API to create dashboards, transforms and metrics from the terminal or an AI agent, centralized alert management, and subcollections in the Library to organize tables and metrics with inherited permissions. Some of these features require the enterprise edition (EE). + +On the AI side, this release adds official connectors for the OpenAI Codex and Claude marketplaces, new MCP server tools to read entities, create collections and execute SQL, and interactive Metabase charts rendered directly in AI clients. The official Claude connector only works with Metabase Cloud instances: for a Metabase running on Clever Cloud, add a custom Claude connector pointing to your instance's MCP server URL. Embedding security improves with JWT tokens passed via POST instead of GET, programmatic filter control through a new `parameters` prop, and a streamlined embedding wizard. This release also includes multiple enhancements and bug fixes. + +This branch is not the default for now if you use `community-latest`. You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_METABASE_VERSION` of the underlying Java application to `0.62` or `1.62` for the enterprise edition (EE) and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever metabase version check yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId 0.62 +``` + +- [Learn more about Metabase 62](https://www.metabase.com/changelog/62) +- [Learn more about Metabase on Clever Cloud](/doc/addons/metabase/) From 2d5db85026dd4f6f305299d263cc994c14068279 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Thu, 11 Jun 2026 10:23:26 +0200 Subject: [PATCH 139/180] changelog: Keycloak 26.6.3 --- .../changelog/2026/06-11-keycloak-26.6.3.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 content/changelog/2026/06-11-keycloak-26.6.3.md diff --git a/content/changelog/2026/06-11-keycloak-26.6.3.md b/content/changelog/2026/06-11-keycloak-26.6.3.md new file mode 100644 index 000000000..eec510bba --- /dev/null +++ b/content/changelog/2026/06-11-keycloak-26.6.3.md @@ -0,0 +1,32 @@ +--- +title: Keycloak 26.6.3 (security update) +description: Keycloak 26.6.3 ships on Clever Cloud with enhancements and addresses sixteen CVEs +date: 2026-06-11 +tags: + - addons + - keycloak +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[The release 26.6.3](https://github.com/keycloak/keycloak/releases/tag/26.6.3) of Keycloak is available on Clever Cloud. It brings some enhancements and bug fixes, but mainly security fixes. + +This release addresses sixteen security vulnerabilities: [CVE-2026-0707](https://nvd.nist.gov/vuln/detail/CVE-2026-0707), [CVE-2026-4800](https://nvd.nist.gov/vuln/detail/CVE-2026-4800), [CVE-2026-4874](https://nvd.nist.gov/vuln/detail/CVE-2026-4874), [CVE-2026-7500](https://nvd.nist.gov/vuln/detail/CVE-2026-7500), [CVE-2026-8830](https://nvd.nist.gov/vuln/detail/CVE-2026-8830), [CVE-2026-8922](https://nvd.nist.gov/vuln/detail/CVE-2026-8922), [CVE-2026-9087](https://nvd.nist.gov/vuln/detail/CVE-2026-9087), [CVE-2026-9088](https://nvd.nist.gov/vuln/detail/CVE-2026-9088), [CVE-2026-9704](https://nvd.nist.gov/vuln/detail/CVE-2026-9704), [CVE-2026-9791](https://nvd.nist.gov/vuln/detail/CVE-2026-9791), [CVE-2026-9792](https://nvd.nist.gov/vuln/detail/CVE-2026-9792), [CVE-2026-9794](https://nvd.nist.gov/vuln/detail/CVE-2026-9794), [CVE-2026-9801](https://nvd.nist.gov/vuln/detail/CVE-2026-9801), [CVE-2026-9802](https://nvd.nist.gov/vuln/detail/CVE-2026-9802), [CVE-2026-37977](https://nvd.nist.gov/vuln/detail/CVE-2026-37977) and [CVE-2026-42581](https://nvd.nist.gov/vuln/detail/CVE-2026-42581). + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_KEYCLOAK_VERSION` of the underlying Java application to `26.6.3` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever keycloak version check yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId 26.6.3 +``` + +- [Learn more about Keycloak on Clever Cloud](/doc/addons/keycloak) From 8ab103afa5662cf4e44e9ca8c20b6b2acf828d12 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 16 Jun 2026 16:57:07 +0200 Subject: [PATCH 140/180] changelog: Otoroshi 17.16.1 with LLM Extension 0.0.79 --- .../2026/06-16-otoroshi-17.16.1-llm-0.0.79.md | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 content/changelog/2026/06-16-otoroshi-17.16.1-llm-0.0.79.md diff --git a/content/changelog/2026/06-16-otoroshi-17.16.1-llm-0.0.79.md b/content/changelog/2026/06-16-otoroshi-17.16.1-llm-0.0.79.md new file mode 100644 index 000000000..542037e30 --- /dev/null +++ b/content/changelog/2026/06-16-otoroshi-17.16.1-llm-0.0.79.md @@ -0,0 +1,36 @@ +--- +title: Otoroshi 17.16.1 is available with web search (including Staan) and AI router support +description: A new build of Otoroshi 17.16.1 shipped with LLM extension 0.0.79, introducing search engines, AI routers, circuit breaking and OpenRouter multi-modal support +date: 2026-06-16 +tags: + - addons + - otoroshi +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Otoroshi v17.16.1](https://github.com/MAIF/otoroshi/releases/tag/v17.16.1) is available on Clever Cloud with the same core as the previous [release](/changelog/2026/06-04-otoroshi-17.16.1/), but with a bumped LLM extension [0.0.79](https://github.com/cloud-apim/otoroshi-llm-extension/releases/tag/0.0.79). + +This version introduces search engines as a first-class entity, with seven providers including [Staan](https://staan.ai/), the sovereign search engine [announced today](https://x.com/Qwant_FR/status/2066539860022718695) by Qwant, alongside Tavily, Brave Search, SearXNG, Google Custom Search and DuckDuckGo. You can use them as LLM tools, expose them through an HTTP API or call them from a workflow function. + +The new AI router adds three routing strategies: a code-router that selects the cheapest model meeting a quality threshold, an auto-router that relies on a judge LLM for prompt-aware routing, and a fusion-router that synthesises responses from several candidates. + +The extension also adds a per-provider circuit breaker, with configurable failure thresholds and cooldown windows for better fallback handling, and a plugin exposing call metadata such as model, provider, token usage, latency and cost through `x-otoroshi-llm-*` response headers. A mock-response decorator lets you short-circuit calls to test provider fallbacks without real API requests, OpenRouter now supports audio, image and video beyond chat, and the dashboard gains budget reset buttons. + +You can update through your add-on dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_OTOROSHI_VERSION` of the underlying Java application to `v17.16.1_1781607455` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever otoroshi version check yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId v17.16.1_1781607455 +``` + +- [Learn more about Otoroshi with LLM on Clever Cloud](/doc/addons/otoroshi/) From 7fb82ad44fa1c4e85000b1c96ad120339aff046c Mon Sep 17 00:00:00 2001 From: David Legrand Date: Thu, 25 Jun 2026 09:07:20 +0200 Subject: [PATCH 141/180] changelog: images updates, 2026W25 --- content/changelog/2026/06-17-images-update.md | 53 +++++++++++++++++++ content/doc/applications/frankenphp.md | 2 +- content/doc/applications/static.md | 2 +- data/runtime_versions.yml | 6 +-- 4 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 content/changelog/2026/06-17-images-update.md diff --git a/content/changelog/2026/06-17-images-update.md b/content/changelog/2026/06-17-images-update.md new file mode 100644 index 000000000..5a9c21fc1 --- /dev/null +++ b/content/changelog/2026/06-17-images-update.md @@ -0,0 +1,53 @@ +--- +title: "Images update: Static Web Server 2.43, Yarn 4.16, Rust 1.96, Hugo 0.162" +description: All runtimes updated, PHP included, with fresh Rust, Yarn and Static Web Server releases. Hugo now requires 0.160 or newer. +date: 2026-06-17 +tags: + - images + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated all our images. Deployment is in progress for all our users. + +* **Common:** + * Apache 2.4.68 + * Chromium 149.0.7827.114 + * Mise 2026.6.0 + * NGINX 1.30.2 + * OAuth2 Proxy 7.15.3 + * Redis 8.8.0 +* **Docker:** + * Update to 29.5.2 +* **Erlang/Elixir:** + * Rebar 3.27.0 +* **FrankenPHP:** + * Update to 1.12.4 (For `CC_PHP_VERSION=8.5`) +* **Go:** + * Go 1.26.4 +* **Node.js & Bun:** + * Yarn 4.16.0 +* **PHP:** + * Update to 8.4.22 + * Update to 8.5.7 + * Composer 1.20.28 + * Composer 2.2.28 + * Composer 2.10.1 +* **Python:** + * Update to 3.13.14 + * Update to 3.14.6 + * pip 26.1.2 + * uv 0.11.21 +* **Rust:** + * Rust 1.96.0 +* **Static:** + * Hugo 0.162.1 + * Static Web Server 2.43.0 + +## Hugo version update + +Starting with next release, we will only support Hugo 0.160 and newer. If you are using an older version, update your application or [use Mise](/doc/reference/reference-environment-variables#install-tools-with-mise-package-manager) to download it during deployment. diff --git a/content/doc/applications/frankenphp.md b/content/doc/applications/frankenphp.md index f04c1b906..f340037b3 100644 --- a/content/doc/applications/frankenphp.md +++ b/content/doc/applications/frankenphp.md @@ -49,7 +49,7 @@ FrankenPHP runtime only requires a working web application, with an `index.php` FrankenPHP currently deployed version on Clever Cloud is `{{< runtime_version frankenphp >}}` based on PHP `{{< runtime_version frankenphp php >}}` and Caddy server `{{< runtime_version frankenphp caddy >}}`. Virtual machine image includes multiple tools from the PHP ecosystem such as Composer or Symfony CLI. The `php` command available in hooks and scripts uses `frankenphp php-cli` under the hood. -You can use FrankenPHP 1.12.3 with PHP 8.5 and Caddy 2.11.3, by setting the `CC_PHP_VERSION` environment variable to `8.5`. +You can use FrankenPHP 1.12.4 with PHP 8.5.7 and Caddy 2.11.4, by setting the `CC_PHP_VERSION` environment variable to `8.5`. - [FrankenPHP PHP 8.4 info](https://frankenphpinfo-8.4.cleverapps.io/) - [FrankenPHP PHP 8.5 info](https://frankenphpinfo-8.5.cleverapps.io/) diff --git a/content/doc/applications/static.md b/content/doc/applications/static.md index 229b5364c..972e7e3c8 100644 --- a/content/doc/applications/static.md +++ b/content/doc/applications/static.md @@ -110,7 +110,7 @@ Supported Static Site Generators (SSG) are: - Detected file: `hugo.toml`, `hugo.yaml`, `hugo.json` > [!TIP] Set the Hugo version ->Use a specific Hugo version by setting the `CC_HUGO_VERSION` environment variable to `0.147`, `0.148`, `0.149` (default), `0.150`, `0.151`, `0.152`, `0.159`, `0.160` or `0.161`. From June 15th, only `0.160` and later are supported. +>Use a specific Hugo version by setting the `CC_HUGO_VERSION` environment variable to `0.147`, `0.148`, `0.149` (default), `0.150`, `0.151`, `0.152`, `0.159`, `0.160`, `0.161` or `0.162`. ### mdBook diff --git a/data/runtime_versions.yml b/data/runtime_versions.yml index cda42bcde..80b5c7b19 100644 --- a/data/runtime_versions.yml +++ b/data/runtime_versions.yml @@ -56,7 +56,7 @@ java: node: eol_source: https://nodejs.org/en/about/releases/ default: - - 24.15.0 (npm 11.12.1) + - 24.16.0 (npm 11.13.0) php: eol_source: https://www.php.net/supported-versions.php @@ -80,7 +80,7 @@ php: sws: eol_source: https://github.com/static-web-server/static-web-server/releases default: - - "2.42.0" + - "2.43.0" varnish: eol_source: https://varnish-cache.org/releases/ @@ -90,7 +90,7 @@ varnish: varnish-modules: eol_source: https://github.com/varnish/varnish-modules/releases default: - - "0.27" + - "0.28" v: eol_source: https://github.com/vlang/v/releases From d4cbe01a9230ac4836229caa97b03eb223f9f617 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 1 Jul 2026 12:52:24 +0200 Subject: [PATCH 142/180] ci: move to Hugo 0.163 --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e8bc5bc1d..b3dfb7bd1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,7 +35,7 @@ jobs: CLEVER_SECRET: ${{ secrets.CLEVER_SECRET }} CLEVER_TOKEN: ${{ secrets.CLEVER_TOKEN }} ORGA_ID: ${{ secrets.ORGA_ID }} - GH_CC_HUGO_VERSION: 0.161 + GH_CC_HUGO_VERSION: 0.163 GH_CC_STATIC_AUTOBUILD_OUTDIR: public/developers GH_CC_WEBROOT: public GH_SERVER_ERROR_PAGE_404: developers/404.html From 1957d9f587bcbe5401153890742a7fd89d397a5e Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 1 Jul 2026 13:04:18 +0200 Subject: [PATCH 143/180] changelog: images updates, 2026W27 --- content/changelog/2026/07-01-images-update.md | 34 +++++++++++++++++++ content/doc/applications/static.md | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 content/changelog/2026/07-01-images-update.md diff --git a/content/changelog/2026/07-01-images-update.md b/content/changelog/2026/07-01-images-update.md new file mode 100644 index 000000000..73a5464e9 --- /dev/null +++ b/content/changelog/2026/07-01-images-update.md @@ -0,0 +1,34 @@ +--- +title: "Images update: Gradle 9.6, Hugo 0.163, Docker 29.6" +description: A tools bump release, Hugo 0.163 is now the default for static applications +date: 2026-07-01 +tags: + - images + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated all our images. Deployment is in progress for all our users. + +* **Common:** + * cURL 8.21.0 + * Mise 2026.6.14 + * NGINX 1.30.3 +* **Docker:** + * Update to 29.6.1 +* **Java:** + * Gradle 9.6.1 +* **PHP:** + * `mod_ssl` module is now loaded +* **Python:** + * uv 0.11.25 +* **Static:** + * Hugo 0.163.3 + +## Hugo version update + +Starting with this release, we only support Hugo 0.160 and newer. If you are using an older version, update your application or [use Mise](/doc/reference/reference-environment-variables#install-tools-with-mise-package-manager) to download it during deployment. If you don't set `CC_HUGO_VERSION`, `0.163` is now the default. diff --git a/content/doc/applications/static.md b/content/doc/applications/static.md index 972e7e3c8..d088e0b03 100644 --- a/content/doc/applications/static.md +++ b/content/doc/applications/static.md @@ -110,7 +110,7 @@ Supported Static Site Generators (SSG) are: - Detected file: `hugo.toml`, `hugo.yaml`, `hugo.json` > [!TIP] Set the Hugo version ->Use a specific Hugo version by setting the `CC_HUGO_VERSION` environment variable to `0.147`, `0.148`, `0.149` (default), `0.150`, `0.151`, `0.152`, `0.159`, `0.160`, `0.161` or `0.162`. +>Use a specific Hugo version by setting the `CC_HUGO_VERSION` environment variable to `0.160`, `0.161` , `0.162` or `0.163` (default). ### mdBook From 1b8e6ba023c112686a4c0a7bd30b8f35a44cc0fd Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 1 Jul 2026 14:38:11 +0200 Subject: [PATCH 144/180] changelog: Otoroshi 17.17 with LLM extension 0.0.82 --- .../changelog/2026/06-30-otoroshi-17.17.md | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 content/changelog/2026/06-30-otoroshi-17.17.md diff --git a/content/changelog/2026/06-30-otoroshi-17.17.md b/content/changelog/2026/06-30-otoroshi-17.17.md new file mode 100644 index 000000000..f130e33bb --- /dev/null +++ b/content/changelog/2026/06-30-otoroshi-17.17.md @@ -0,0 +1,49 @@ +--- +title: Otoroshi 17.17 brings RFC 7662 token introspection and an LLM extension with A2A and MCP virtual servers +description: Otoroshi 17.17.0 adds RFC 7662 token introspection, hardened API key validation and Elasticsearch 8 compatibility, and ships LLM extension 0.0.82 with A2A, MCP virtual servers, gateway discovery and local PII redaction +date: 2026-06-30 +tags: + - addons + - otoroshi +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Otoroshi v17.17.0](https://github.com/MAIF/otoroshi/releases/tag/v17.17.0) is available on Clever Cloud. This release adds standards-based token introspection, hardens API key validation and improves Elasticsearch compatibility. It also ships LLM extension [0.0.82](https://github.com/cloud-apim/otoroshi-llm-extension/releases/tag/0.0.82), which builds on [0.0.81](https://github.com/cloud-apim/otoroshi-llm-extension/releases/tag/0.0.81) and [0.0.80](https://github.com/cloud-apim/otoroshi-llm-extension/releases/tag/0.0.80). + +### RFC 7662 token introspection and admin bootstrap + +Otoroshi now implements the [RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662) introspection flow, so you can validate opaque access tokens against an introspection endpoint. The `OIDCJwtVerifier` also reads the audience from an array, aligning it with providers that emit `aud` as a list. + +The initial admin password can now be generated and stored in a temporary file at first startup, which makes automated and reproducible bootstrap of a fresh instance easier. + +### Hardened API key validation + +Several fixes tighten how API keys are accepted. Disabled API keys are no longer accepted in client-id-only mode, and a bearer signature validation bypass in that same mode has been corrected. Legacy quota declarations are now reported on the API key quotas endpoint, so quota reporting stays consistent across key formats. + +### Elasticsearch 8 and Expression Language + +Writes to Elasticsearch 8 no longer include the deprecated `_type` field, and the cluster version can be auto-filled from the interface. The Expression Language handles default values more reliably and fixes the ordering of `ctx.geolocation.*` and `ctx.useragent.*` expressions. The `NgErrorRewriter` plugin also receives several improvements, and ingress fetching now targets the correct API group. + +### LLM extension: A2A, MCP virtual servers and gateway discovery + +The bundled LLM extension reaches [0.0.82](https://github.com/cloud-apim/otoroshi-llm-extension/releases/tag/0.0.82) and includes the features introduced in 0.0.81 and 0.0.80. The provider and capability catalogs are now exposed through the authenticated admin API, and a new `RampartBodyRedactionBackend` terminal plugin reads the request body, redacts PII with the local Rampart model and returns the redacted body directly. The Mistral provider adopts the OpenAI content representation for chat messages, improving support for structured and multi-part content. + +The extension also gains a self-describing gateway: two endpoints, `GET /providers` and `GET /model-capabilities`, let clients discover available providers and model types at runtime, with filtering across text, audio, image, OCR, embedding, moderation and video capabilities. Both are also available as standalone plugins. Embeddings now work with OpenAI-compatible endpoints and OVH AI Endpoints, and the OpenAI-compatible plugin extends to image, audio and moderation on top of chat and embeddings. + +The 0.0.80 release added bidirectional Agent-to-Agent (A2A) support, so Otoroshi agents can be published as standards-compliant services and consume remote A2A agents as tools. MCP virtual servers bundle tool functions, filtering, OAuth security, rate limiting and Zero-Trust controls into a single persisted entity that can be published to the MCP registry. Zero-Trust controls add anti-rug-pull pinning to detect tool definition changes, along with tool-poisoning and prompt-injection scanning. A local Rampart PII guardrail redacts personally identifiable information offline through an ONNX model, and OAuth hardening brings audience validation ([RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707)) and opaque token introspection ([RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662)). An Exa search engine provider and real-time MCP and LLM gateway metrics round out the release. + +You can update through your add-on dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_OTOROSHI_VERSION` of the underlying Java application to `v17.17.0_1783064693` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever otoroshi version check yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId +clever otoroshi version update yourOtoroshiNameOrId v17.17.0_1783064693 +``` + +- [Learn more about Otoroshi with LLM on Clever Cloud](/doc/addons/otoroshi/) From 239a8c738dbce9af728e6e9595b4eed2bc5a4430 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Fri, 10 Jul 2026 13:18:47 +0200 Subject: [PATCH 145/180] changelog: Keycloak 26.6.4 and 26.7.0 --- .../2026/07-10-keycloak-26.6.4-26.7.0.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 content/changelog/2026/07-10-keycloak-26.6.4-26.7.0.md diff --git a/content/changelog/2026/07-10-keycloak-26.6.4-26.7.0.md b/content/changelog/2026/07-10-keycloak-26.6.4-26.7.0.md new file mode 100644 index 000000000..d9655775a --- /dev/null +++ b/content/changelog/2026/07-10-keycloak-26.6.4-26.7.0.md @@ -0,0 +1,32 @@ +--- +title: Keycloak 26.6.4 and 26.7.0 are available (security updates) +description: Keycloak 26.6.4 and 26.7.0 fix twelve vulnerabilities, while 26.7.0 adds SCIM provisioning, multi-cluster improvements and SAML step-up authentication +date: 2026-07-10 +tags: + - addons + - keycloak +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Keycloak 26.6.4](https://github.com/keycloak/keycloak/releases/tag/26.6.4) and [Keycloak 26.7.0](https://github.com/keycloak/keycloak/releases/tag/26.7.0) are available on Clever Cloud. Keycloak 26.6.4 addresses eight security vulnerabilities: [CVE-2026-9099](https://nvd.nist.gov/vuln/detail/CVE-2026-9099), [CVE-2026-9083](https://nvd.nist.gov/vuln/detail/CVE-2026-9083), [CVE-2026-9086](https://nvd.nist.gov/vuln/detail/CVE-2026-9086), [CVE-2026-9705](https://nvd.nist.gov/vuln/detail/CVE-2026-9705), [CVE-2026-9795](https://nvd.nist.gov/vuln/detail/CVE-2026-9795), [CVE-2026-9799](https://nvd.nist.gov/vuln/detail/CVE-2026-9799), [CVE-2026-9800](https://nvd.nist.gov/vuln/detail/CVE-2026-9800) and [CVE-2026-11800](https://nvd.nist.gov/vuln/detail/CVE-2026-11800). + +Keycloak 26.7.0 fixes four additional vulnerabilities: [CVE-2026-9796](https://nvd.nist.gov/vuln/detail/CVE-2026-9796), [CVE-2026-9689](https://nvd.nist.gov/vuln/detail/CVE-2026-9689), [CVE-2026-9798](https://nvd.nist.gov/vuln/detail/CVE-2026-9798) and [CVE-2026-11986](https://nvd.nist.gov/vuln/detail/CVE-2026-11986). It also promotes the SCIM API to preview, adds a preview of simplified multi-cluster high availability without external caches and supports step-up authentication for SAML clients. + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_KEYCLOAK_VERSION` of the underlying Java application to `26.7.0` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever keycloak version check yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId 26.7.0 +``` + +- [Learn more about Keycloak on Clever Cloud](/doc/addons/keycloak) From f6aedf53194778d64411a29834815b19aeb1599f Mon Sep 17 00:00:00 2001 From: David Legrand Date: Fri, 24 Jul 2026 21:42:00 +0200 Subject: [PATCH 146/180] changelog: Redis 8.8.1 --- content/changelog/2026/07-24-redis-8.8.1.md | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 content/changelog/2026/07-24-redis-8.8.1.md diff --git a/content/changelog/2026/07-24-redis-8.8.1.md b/content/changelog/2026/07-24-redis-8.8.1.md new file mode 100644 index 000000000..388225538 --- /dev/null +++ b/content/changelog/2026/07-24-redis-8.8.1.md @@ -0,0 +1,22 @@ +--- +title: Redis 8.8.1 is available (security update) +description: Redis 8.8.1 patches out-of-bounds writes triggered by crafted RESTORE payloads in the Probabilistic and TDigest modules +date: 2026-07-24 +tags: + - addons + - redis +authors: + - name: Aurélien Hébert + link: https://github.com/aurrelhebert + image: https://github.com/aurrelhebert.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated Redis™ to [release 8.8.1](https://github.com/redis/redis/releases/tag/8.8.1). It is a security-only release: crafted `RESTORE` payloads targeting the Probabilistic (RedisBloom) and TDigest data structures could trigger out-of-bounds writes, potentially leading to remote code execution. No other change ships with this version. + +Redis™ 8.8.1 is available for new add-ons. Those already deployed can upgrade through migration. + +- [Learn more about Redis™ on Clever Cloud](/doc/addons/redis/) From 25179519d34e5a0da78b37891d4b7ee599dc5573 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 29 Jul 2026 14:09:16 +0200 Subject: [PATCH 147/180] changelog: images updates, 2026W31 --- content/changelog/2026/07-28-images-update.md | 56 +++++++++++++++++++ content/doc/applications/static.md | 2 +- data/runtime_versions.yml | 4 +- 3 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 content/changelog/2026/07-28-images-update.md diff --git a/content/changelog/2026/07-28-images-update.md b/content/changelog/2026/07-28-images-update.md new file mode 100644 index 000000000..e0e0c5696 --- /dev/null +++ b/content/changelog/2026/07-28-images-update.md @@ -0,0 +1,56 @@ +--- +title: "Images update: Rust 1.97, Yarn 4.17, Hugo 0.164" +description: All runtimes updated, PHP and Node.js included, with fresh Rust and Yarn releases. Hugo 0.164 is now available for static applications +date: 2026-07-28 +tags: + - images + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated all our images. Deployment is in progress for all our users. + +* **Common:** + * Chromium 150.0.7871.186 + * Clever Tools 4.11.0 + * Git 2.55.0 + * Nano 9.1 + * NGINX 1.30.4 + * OpenSSH 10.4_p1 + * Perl 5.44.0 + * Poppler 26.07.0 + * Zellij 0.43.1 +* **Docker:** + * Update to 29.6.2 +* **Go:** + * Go 1.26.5 +* **Node.js & Bun:** + * Node.js 24.18.0 (npm 11.16.0) + * Yarn 4.17.1 +* **Java:** + * Update to 11.0.32_p9 + * Update to 21.0.12_p8 +* **PHP:** + * Update to 8.2.32 + * Update to 8.3.32 + * Update to 8.4.23 + * Update to 8.5.8 + * Composer 2.10.2 + * Elastic APM agent 1.17.0 + * New Relic extension 12.8.0.37 +* **Python:** + * uv 0.11.29 +* **Rust:** + * Update to 1.97.1 +* **Static:** + * Caddy 2.11.4 + * Hugo 0.164.0 + +## Linux Kernel + +Kernel is [now updated independently](/changelog/2026/05-12-linux-kernel-7.0.6). Current version is 7.1.5. + diff --git a/content/doc/applications/static.md b/content/doc/applications/static.md index d088e0b03..43cc854e9 100644 --- a/content/doc/applications/static.md +++ b/content/doc/applications/static.md @@ -110,7 +110,7 @@ Supported Static Site Generators (SSG) are: - Detected file: `hugo.toml`, `hugo.yaml`, `hugo.json` > [!TIP] Set the Hugo version ->Use a specific Hugo version by setting the `CC_HUGO_VERSION` environment variable to `0.160`, `0.161` , `0.162` or `0.163` (default). +>Use a specific Hugo version by setting the `CC_HUGO_VERSION` environment variable to `0.160`, `0.161`, `0.162`, `0.163` (default) or `0.164`. ### mdBook diff --git a/data/runtime_versions.yml b/data/runtime_versions.yml index 80b5c7b19..70525e52b 100644 --- a/data/runtime_versions.yml +++ b/data/runtime_versions.yml @@ -6,7 +6,7 @@ bun: caddy: eol_source: https://github.com/caddyserver/caddy/releases default: - - "2.11.2" + - "2.11.4" dotnet: eol_source: https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core @@ -56,7 +56,7 @@ java: node: eol_source: https://nodejs.org/en/about/releases/ default: - - 24.16.0 (npm 11.13.0) + - 24.18.0 (npm 11.6.0) php: eol_source: https://www.php.net/supported-versions.php From 77daf277e87d691b3f2bd394bef7f37e76f1a649 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 29 Jul 2026 16:07:16 +0200 Subject: [PATCH 148/180] changelog: MySQL 8.0.46 and 8.4.10 --- .../2026/07-29-mysql-8.0.46-8.4.10.md | 23 +++++++++++++++++++ content/doc/addons/mysql.md | 2 +- data/software_versions_shared_dedicated.yml | 6 ++--- 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 content/changelog/2026/07-29-mysql-8.0.46-8.4.10.md diff --git a/content/changelog/2026/07-29-mysql-8.0.46-8.4.10.md b/content/changelog/2026/07-29-mysql-8.0.46-8.4.10.md new file mode 100644 index 000000000..45f3ddf03 --- /dev/null +++ b/content/changelog/2026/07-29-mysql-8.0.46-8.4.10.md @@ -0,0 +1,23 @@ +--- +title: MySQL 8.0.46 and 8.4.10 are available +description: MySQL 8.4.10 ships on Clever Cloud with eight security fixes, alongside 8.0.46, the final Percona Server 8.0 release now that this branch reached its end of life +date: 2026-07-29 +tags: + - addons + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated MySQL images for releases 8.4 (8.4.10-10) and 8.0 (8.0.46-37). They're now available for add-ons creation and migration. + +The 8.4 release addresses eight security vulnerabilities: [CVE-2026-46850](https://nvd.nist.gov/vuln/detail/CVE-2026-46850), [CVE-2026-46860](https://nvd.nist.gov/vuln/detail/CVE-2026-46860), [CVE-2026-46861](https://nvd.nist.gov/vuln/detail/CVE-2026-46861), [CVE-2026-46862](https://nvd.nist.gov/vuln/detail/CVE-2026-46862), [CVE-2026-46863](https://nvd.nist.gov/vuln/detail/CVE-2026-46863), [CVE-2026-46869](https://nvd.nist.gov/vuln/detail/CVE-2026-46869), [CVE-2026-46870](https://nvd.nist.gov/vuln/detail/CVE-2026-46870) and [CVE-2026-46871](https://nvd.nist.gov/vuln/detail/CVE-2026-46871). Only [CVE-2026-46863](https://nvd.nist.gov/vuln/detail/CVE-2026-46863) affects the database server itself, the others concern MySQL Shell, MySQL Router and NDB Cluster. + +MySQL 8.0 [reached its end of life on 30 April 2026](https://www.mysql.com/support/eol-notice.html), and 8.0.46-37 is [the final release of the Percona Server 8.0 series](https://docs.percona.com/new/2026/06/10/percona-server-for-mysql-8046-37-has-been-released/). If you run a MySQL 8.0 add-on, we recommend you migrate it to the 8.4 LTS branch. + +* [Learn more about MySQL on Clever Cloud](/doc/addons/mysql/) +* [Learn more about MySQL 8.0.46](https://docs.percona.com/percona-server/8.0/release-notes/8.0.46-37.html) +* [Learn more about MySQL 8.4.10](https://docs.percona.com/percona-server/8.4/release-notes/8.4.10-10.html) diff --git a/content/doc/addons/mysql.md b/content/doc/addons/mysql.md index 48e2b3fc2..d678df651 100644 --- a/content/doc/addons/mysql.md +++ b/content/doc/addons/mysql.md @@ -23,7 +23,7 @@ MySQL is an open source relational database management system (RDBMS). Clever Cl ## Supported Versions -MySQL is available in regular versions and `early` for 8.4. That means it's the first release (8.4.0) of this long term support (LTS) branch, so you should consider it mostly to make some tests and discover what's new. But we recommend waiting a bit before using a new branch in production. +Use the 8.4 long term support (LTS) branch for new add-ons. MySQL 8.0 [reached its end of life on 30 April 2026](https://www.mysql.com/support/eol-notice.html) and [Percona Server 8.0 had its final release in June 2026](https://docs.percona.com/new/2026/06/10/percona-server-for-mysql-8046-37-has-been-released/), so we recommend you migrate your existing 8.0 add-ons to 8.4. {{< software_versions_shared_dedicated mysql>}} diff --git a/data/software_versions_shared_dedicated.yml b/data/software_versions_shared_dedicated.yml index de6bc9310..e9ff752e1 100644 --- a/data/software_versions_shared_dedicated.yml +++ b/data/software_versions_shared_dedicated.yml @@ -15,10 +15,10 @@ mongo: mysql: dedicated: - 5.7 (EOL) - - 8.0.45 - - 8.4.8 + - 8.0.46 (EOL) + - 8.4.10 dev: - - 8.0.45 + - 8.0.46 (EOL) pg: dedicated: From 1a2ae6ffde2ad7d9f800e5c9acb2e816667f638c Mon Sep 17 00:00:00 2001 From: Corentin BARAULT Date: Thu, 18 Jun 2026 14:44:51 +0200 Subject: [PATCH 149/180] addons(postgresql): add pg_partman to the extension that can be installed --- content/doc/addons/postgresql.md | 1 + 1 file changed, 1 insertion(+) diff --git a/content/doc/addons/postgresql.md b/content/doc/addons/postgresql.md index a2fbb7945..49c7d795f 100644 --- a/content/doc/addons/postgresql.md +++ b/content/doc/addons/postgresql.md @@ -146,6 +146,7 @@ Extension | Description pg_cron | Job scheduler for PostgreSQL pg_ivm | Incremental view maintenance for PostgreSQL pg_net | Enables asynchronous (non-blocking) HTTP/HTTPS requests with SQL +pg_partman | Extension to manage partitioned tables by time or ID pg_repack | Reorganize tables in PostgreSQL databases with minimal locks pgaudit | Provides detailed session and/or object audit logging via the standard PostgreSQL logging facility pgsql-http | HTTP client for PostgreSQL From 5c2bae67ad701ed2f135d7ac5bb62c4e200f0ed2 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 5 Aug 2026 11:24:18 +0200 Subject: [PATCH 150/180] changelog: images updates, 2026W32 --- content/changelog/2026/08-05-images-update.md | 45 +++++++++++++++++++ data/runtime_versions.yml | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 content/changelog/2026/08-05-images-update.md diff --git a/content/changelog/2026/08-05-images-update.md b/content/changelog/2026/08-05-images-update.md new file mode 100644 index 000000000..2a0daf17e --- /dev/null +++ b/content/changelog/2026/08-05-images-update.md @@ -0,0 +1,45 @@ +--- +title: "Images update: Blackfire agent 2026.7, Yarn 4.18, uv 0.12" +description: Runtime updates for .NET, Java, Node.js and PHP, with Blackfire agent 2026.7, Yarn 4.18 and uv 0.12 +date: 2026-08-05 +tags: + - images + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated all our images. Deployment is in progress for all our users. + +* **Common:** + * Chromium 151.0.7922.71 + * Redis 8.10.0 + * Ripgrep 15.2.0 +* **.NET:** + * Update to 8.0.129 + * Update to 9.0.119 + * Update to 10.0.110 +* **Java:** + * Update to 1.8.0.502_p07 + * Update to 17.0.20_p8 + * Update to 25.0.4_p7 +* **Node.js & Bun:** + * Node.js 24.18.1 (npm 11.16.0) + * Yarn 4.18.0 +* **PHP:** + * Update to 8.2.33 + * Update to 8.3.33 + * Update to 8.4.24 + * Update to 8.5.9 + * Blackfire agent 2026.7.0 + * Blackfire extension 2026.7.1 +* **Python:** + * pip 26.2 + * uv 0.12.0 + +## Linux Kernel + +Kernel is [now updated independently](/changelog/2026/05-12-linux-kernel-7.0.6). Current version is 7.1.8. diff --git a/data/runtime_versions.yml b/data/runtime_versions.yml index 70525e52b..28f3c73b9 100644 --- a/data/runtime_versions.yml +++ b/data/runtime_versions.yml @@ -56,7 +56,7 @@ java: node: eol_source: https://nodejs.org/en/about/releases/ default: - - 24.18.0 (npm 11.6.0) + - 24.18.1 (npm 11.16.0) php: eol_source: https://www.php.net/supported-versions.php From b54ba039eca0e8eae2c55e753cc186bccb431f44 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Fri, 7 Aug 2026 18:22:16 +0200 Subject: [PATCH 151/180] changelog: Metabase password reset endpoint blocked --- .../08-07-metabase-security-reset-password.md | 62 +++++++++++++++++++ content/doc/addons/metabase.md | 3 + 2 files changed, 65 insertions(+) create mode 100644 content/changelog/2026/08-07-metabase-security-reset-password.md diff --git a/content/changelog/2026/08-07-metabase-security-reset-password.md b/content/changelog/2026/08-07-metabase-security-reset-password.md new file mode 100644 index 000000000..41cb1c459 --- /dev/null +++ b/content/changelog/2026/08-07-metabase-security-reset-password.md @@ -0,0 +1,62 @@ +--- +title: "Metabase critical security update, password reset endpoint blocked" +description: Metabase 0.58.24, 0.59.21, 0.60.17, 0.61.11, 0.62.9 and 0.63.5 are available on Clever Cloud, requests to /api/session/reset_password are blocked on outdated instances +date: 2026-08-07 +tags: + - addons + - metabase +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +Metabase [published a security advisory](https://www.metabase.com/blog/security-update) about a critical vulnerability affecting the `x.58` branch and above. It could be exploited through the unauthenticated `POST /api/session/reset_password` endpoint, which allows an attacker to get an authenticated session on a vulnerable instance. Branches below `x.58` are not affected. Technical details are available in the [GHSA-vwf4-m7j8-wcjf advisory](https://github.com/metabase/metabase/security/advisories/GHSA-vwf4-m7j8-wcjf). + +Metabase versions `0.58.24`, `0.59.21`, `0.60.17`, `0.61.11`, `0.62.9` and `0.63.5` fix this vulnerability. They're available on Clever Cloud (versions starting with `1` for the enterprise edition). Update your add-on as soon as possible, through its dashboard in the [Clever Cloud Console](https://console.clever-cloud.com), by setting `CC_METABASE_VERSION` of the underlying Java application to the latest patch of your branch and rebuilding it, or with [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever metabase version check yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId 0.63 +``` + +## Requests to the vulnerable endpoint are blocked + +As long as your Metabase add-on runs an affected version, Clever Cloud blocks requests to `/api/session/reset_password`. Your instance stays protected against this attack, but users can't ask for a password reset email until you update. Once your add-on runs a patched version, requests reach Metabase again. + +You can control this behavior with the `CC_METABASE_BLOCK_RESET_PASSWORD` environment variable of the underlying Java application. Set it to `true` to keep blocking the endpoint, even on a patched version, or to `false` to allow requests on a version that's not up to date. Restart the application for the change to take effect. Keep the default behavior unless you have a specific reason to change it. + +## Detect an attack in your access logs + +The attack pattern described by Metabase is a call to `POST /api/session/reset_password` answered with a `400` status code, immediately followed by a call to `GET /api/user/current` answered with a `200` status code, from the same IP address. You can look for this sequence in the access logs of the Java application of your Metabase add-on, kept for 7 days: + +```bash +clever accesslogs --app yourMetabaseJavaAppId --since 7d --format json-stream \ + | jq -r 'select(.http.request.path == "/api/session/reset_password" or .http.request.path == "/api/user/current") + | "\(.date) \(.source.ip) \(.http.request.method) \(.http.request.path) \(.http.response.statusCode)"' +``` + +Access logs are also available in the [Clever Cloud Console](https://console.clever-cloud.com). A `200` on `/api/user/current` right after a rejected password reset means the attacker got a valid session on your instance: consider it compromised and apply the steps below. + +## What to do after the update + +Metabase recommends the following actions once your instance runs a patched version: + +- Revoke all active sessions by deleting the rows of the `core_session` table in the PostgreSQL database of your add-on +- Review your API keys and delete the ones you don't recognize +- Review administrator accounts and check that they weren't modified +- Rotate the credentials of every database connected to your Metabase instance +- Review the logs of your data warehouses to detect unauthorized access +- Review Metabase activity and query history to detect unexpected queries or exports + +## Users on an XS Java instance + +If your add-on uses `community-latest` as its `CC_METABASE_VERSION` and still runs on an XS Java instance, you must move to a S instance to update. Metabase requires at least 2 GB of RAM starting for releases after x.59.9, as detailed in [a previous changelog entry](/changelog/2026/05-06-metabase-60-ram/): 1 GB provided by the XS instance is no longer enough for recent versions. Update your add-on to a patched version x.60 or above, it will automatically move to a S instance. + +If you need help with these database actions, contact the [Clever Cloud support team](https://console.clever-cloud.com/ticket-center-choice). + +- [Learn more about Metabase on Clever Cloud](/doc/addons/metabase/) diff --git a/content/doc/addons/metabase.md b/content/doc/addons/metabase.md index 8aa66bbe0..c7b32824a 100644 --- a/content/doc/addons/metabase.md +++ b/content/doc/addons/metabase.md @@ -77,6 +77,9 @@ Once you created your add-on, open the management URL or look for `MB_SITE_URL` ## Password reset +> [!WARNING] +> Requests to `/api/session/reset_password` are blocked on `x.58` and above until you update to a version patching a [critical security vulnerability](/changelog/2026/08-07-metabase-security-reset-password/). Use `CC_METABASE_BLOCK_RESET_PASSWORD=true/false` to control this behavior. + To be able to reset your password, you must have [set up an active SMTP server](#configuring-a-smtp-server) in the `e-mail` section of the administrator settings. You can also do it [using a Mailpace add-on](#using-a-mailpace-add-on). Once done, the forgot password procedure will ask you the user email address and send a reset link to it. If you don't have an active SMTP server configured, there is a manual procedure to get the reset link: From 0c8cab1f1ef98224a886b2bcaae9bade7c0c8f85 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Fri, 7 Aug 2026 19:14:52 +0200 Subject: [PATCH 152/180] changelog(metabase): Metabase 63 --- content/changelog/2026/08-07-metabase-63.md | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 content/changelog/2026/08-07-metabase-63.md diff --git a/content/changelog/2026/08-07-metabase-63.md b/content/changelog/2026/08-07-metabase-63.md new file mode 100644 index 000000000..d494e2bb4 --- /dev/null +++ b/content/changelog/2026/08-07-metabase-63.md @@ -0,0 +1,36 @@ +--- +title: "Metabase 63 is available, with treemaps, two-factor authentication and PDF subscriptions" +description: Treemap charts, two-factor authentication, PDF attachments, more Metabot providers, one-step dashboard sharing and more +date: 2026-08-07 +tags: + - addons + - metabase +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +The `x.63` branch of Metabase is now available on Clever Cloud with version `0.63.5`. It introduces treemap charts for hierarchical data, two-factor authentication for users signing in with a password, and PDF attachments in dashboard subscriptions. Two-factor authentication is available with Pro and Enterprise plans. + +Metabot can now use OpenAI, AWS Bedrock and Microsoft Azure models in addition to Anthropic, including on the open source edition with your own API key. This release also adds one-step invitations from dashboards and questions, CSV uploads to Snowflake, custom visualizations in the Modular Embedding SDK, and audit logs for MCP authorizations. The Sample Database now uses SQLite instead of H2, and Metabase introduces a predictable support policy with at least 60 days of support for every version and regular Long Term Support releases. + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_METABASE_VERSION` of the underlying Java application to `0.63` or `1.63` for the enterprise edition (EE) and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever metabase version check yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId 0.63 +``` + +- [Learn more about Metabase 63](https://www.metabase.com/releases/metabase-63) +- [Watch the Metabase 63 video playlist](https://www.youtube.com/playlist?list=PLTC-ts2h37r4) +- [Learn more about Metabase on Clever Cloud](/doc/addons/metabase/) + +{{< youtube id="ZZ-KSyG7OVc" >}} From 89855dcfbdfb3c439eadbde07865b4b879f7e390 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Tue, 11 Aug 2026 17:26:14 +0200 Subject: [PATCH 153/180] changelog: Keycloak 26.7.1 --- .../changelog/2026/08-11-keycloak-26.7.1.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 content/changelog/2026/08-11-keycloak-26.7.1.md diff --git a/content/changelog/2026/08-11-keycloak-26.7.1.md b/content/changelog/2026/08-11-keycloak-26.7.1.md new file mode 100644 index 000000000..be6645606 --- /dev/null +++ b/content/changelog/2026/08-11-keycloak-26.7.1.md @@ -0,0 +1,32 @@ +--- +title: Keycloak 26.7.1 (security update) +description: Keycloak 26.7.1 brings the 26.7 features to Clever Cloud and fixes twelve vulnerabilities affecting OIDC, SAML, LDAP and fine-grained permissions +date: 2026-08-11 +tags: + - addons + - keycloak +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[The release 26.7.1](https://github.com/keycloak/keycloak/releases/tag/26.7.1) of Keycloak is available on Clever Cloud. It includes the new capabilities introduced in [Keycloak 26.7.0](https://github.com/keycloak/keycloak/releases/tag/26.7.0), such as the SCIM API promoted to preview, simplified multi-cluster high availability without external caches, step-up authentication for SAML clients and the experimental Admin API v2 for declarative client management. + +This security update addresses twelve vulnerabilities: [CVE-2026-9793](https://nvd.nist.gov/vuln/detail/CVE-2026-9793), [CVE-2026-4629](https://nvd.nist.gov/vuln/detail/CVE-2026-4629), [CVE-2026-14209](https://nvd.nist.gov/vuln/detail/CVE-2026-14209), [CVE-2026-14614](https://nvd.nist.gov/vuln/detail/CVE-2026-14614), [CVE-2026-14615](https://nvd.nist.gov/vuln/detail/CVE-2026-14615), [CVE-2026-15573](https://nvd.nist.gov/vuln/detail/CVE-2026-15573), [CVE-2026-15572](https://nvd.nist.gov/vuln/detail/CVE-2026-15572), [CVE-2026-16100](https://nvd.nist.gov/vuln/detail/CVE-2026-16100), [CVE-2026-16442](https://nvd.nist.gov/vuln/detail/CVE-2026-16442), [CVE-2026-16443](https://nvd.nist.gov/vuln/detail/CVE-2026-16443), [CVE-2026-16071](https://nvd.nist.gov/vuln/detail/CVE-2026-16071) and [CVE-2026-16102](https://nvd.nist.gov/vuln/detail/CVE-2026-16102). + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_KEYCLOAK_VERSION` of the underlying Java application to `26.7.1` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever keycloak version check yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId 26.7.1 +``` + +- [Learn more about Keycloak on Clever Cloud](/doc/addons/keycloak) From 1df1d6e2dd2d360d029bc14e7778ac24debeabe3 Mon Sep 17 00:00:00 2001 From: vballu Date: Thu, 13 Aug 2026 18:06:05 +0200 Subject: [PATCH 154/180] metrics: remove metrics beta notice --- content/doc/metrics/_index.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/content/doc/metrics/_index.md b/content/doc/metrics/_index.md index 460bea482..6e8d1a578 100644 --- a/content/doc/metrics/_index.md +++ b/content/doc/metrics/_index.md @@ -19,10 +19,6 @@ aliases: - /doc/tools/metrics - /metrics --- -{{< callout type="warning" >}} -Clever Cloud Metrics is still in beta. -{{< /callout >}} - In addition to logs, you can have access to metrics to know how your application behaves. By default, system metrics like CPU and RAM use are available, as well as application-level metrics when available (apache or nginx status for instance). From 327b234456b30b5f74ad140cbb0ba9d14784dcfe Mon Sep 17 00:00:00 2001 From: Corentin BARAULT Date: Thu, 18 Jun 2026 12:15:51 +0200 Subject: [PATCH 155/180] addons(cellar): add a section about pre-signed URL and checksum validation --- content/doc/addons/cellar.md | 104 +++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/content/doc/addons/cellar.md b/content/doc/addons/cellar.md index 092b36ec1..21747bb28 100644 --- a/content/doc/addons/cellar.md +++ b/content/doc/addons/cellar.md @@ -819,6 +819,110 @@ When versioning is enabled, the newly added object is automatically provided wit {{< /tabs >}} +## Uploading objects with presigned URLs and checksum validation + +Pre-signed URLs allow the client to upload files directly to S3 without ever exposing your credentials. You simply generate a temporary, time-limited URL and the client uses it to upload their file to your cellar. +To ensure the integrity of the uploaded file, you can add a checksum as part of the URL parameters. This checksum acts as a fingerprint for the expected file. + +Cellar supports pre-signed URLs and MD5 checksum validation. If you pre-sign your URL with an MD5 checksum as a parameter, Cellar validates the uploaded file against the expected checksum by verifying both the request headers and the file content. The upload fail if either value doesn't match the expected checksum. + +{{< tabs items="Python, Node.js" >}} + + {{< tab >}} + + ```python + import boto3 + import hashlib + import base64 + from botocore.config import Config + from botocore.exceptions import ClientError + + # --- Configuration Cellar --- + cellar_host = "https://cellar-c2.services.clever-cloud.com" + access_key = "" + secret_key = "" + bucket_name = "" + object_key = "/" + + # --- Client S3 --- + s3 = boto3.client( + "s3", + endpoint_url=cellar_host, + aws_access_key_id=access_key, + aws_secret_access_key=secret_key, + config=Config( + signature_version="s3v4", + s3={"addressing_style": "path"}, + ) + ) + + # MD5 de "hello world" + md5 = base64.b64encode(hashlib.md5(b"hello world").digest()).decode() + + url = s3.generate_presigned_url( + ClientMethod="put_object", + Params={ + "Bucket": bucket_name, + "Key": object_key, + "ContentMD5": md5, + }, + ExpiresIn=3600, + ) + + print(url) + ``` + + {{< /tab >}} + + {{< tab >}} + + ```js + import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; + import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; + import { createHash } from "crypto"; + + // --- Configuration --- + const s3 = new S3Client({ + endpoint: "https://cellar-c2.services.clever-cloud.com", + region: "us-east-1", + credentials: { + accessKeyId: "", + secretAccessKey: "", + }, + forcePathStyle: true, + }); + + const bucket = ""; + const key = "/"; + + // --- Calcul du checksum MD5 (base64) --- + const fileContent = Buffer.from("Hello World"); + const md5Base64 = createHash("md5").update(fileContent).digest("base64"); + + // --- Génération de l'URL présignée --- + const command = new PutObjectCommand({ + Bucket: bucket, + Key: key, + ContentMD5: md5Base64, + }); + + const presignedUrl = await getSignedUrl(s3, command, { + expiresIn: 3600, + // ← Force le header Content-MD5 à rester dans la signature (non hoistable) + unhoistableHeaders: new Set(["content-md5"]), + }); + + console.log("URL présignée :", presignedUrl); + ``` + + {{< /tab >}} + +{{< /tabs >}} + +{{< callout type="info">}} + If you use SHA-256 for the checksum, Cellar verifies that the SHA-256 checksum specified in the URL matches the value provided in the request headers. However, unlike MD5, it doesn't validate the file content against the checksum, meaning data integrity isn't verified during upload. +{{< /callout >}} + ## Troubleshooting {{% details title="SSL error with s3cmd" closed="true" %}} From cbf777bf71251b8afa7ba4fbdcd659d80a53b525 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Sat, 15 Aug 2026 16:34:27 +0200 Subject: [PATCH 156/180] changelog(metabase): add 0.61.18, 0.62.16 and 0.63.13 --- .../08-15-metabase-0.61.18-0.62.16-0.63.13.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 content/changelog/2026/08-15-metabase-0.61.18-0.62.16-0.63.13.md diff --git a/content/changelog/2026/08-15-metabase-0.61.18-0.62.16-0.63.13.md b/content/changelog/2026/08-15-metabase-0.61.18-0.62.16-0.63.13.md new file mode 100644 index 000000000..1aa596941 --- /dev/null +++ b/content/changelog/2026/08-15-metabase-0.61.18-0.62.16-0.63.13.md @@ -0,0 +1,33 @@ +--- +title: "Metabase 0.61.18, 0.62.16 and 0.63.13 are available (security update)" +description: Metabase 0.61.18, 0.62.16 and 0.63.13 are available on Clever Cloud with security hardening for the supported 61, 62 and 63 branches +date: 2026-08-15 +tags: + - addons + - metabase +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +Metabase versions `0.61.18`, `0.62.16` and `0.63.13` are now available on Clever Cloud. These patch releases harden security on the supported `x.61`, `x.62` and `x.63` branches and should be applied as soon as possible. Versions starting with `1` provide the same updates for the enterprise edition. + +If you use `latest` as your `CC_METABASE_VERSION`, restart your instance to deploy the latest patched version. If you use a specific branch, update `CC_METABASE_VERSION` of the underlying Java application to its latest patch and rebuild it. + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com), or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever metabase version check yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId 0.63 +``` + +- [Read the Metabase security-focused release announcement](https://www.metabase.com/blog/security-focused-release-announcement-2026-08-12) +- [Read the Metabase 61 changelog](https://www.metabase.com/changelog/61) +- [Read the Metabase 62 changelog](https://www.metabase.com/changelog/62) +- [Read the Metabase 63 changelog](https://www.metabase.com/changelog/63) +- [Learn more about Metabase on Clever Cloud](/doc/addons/metabase/) From b90293c36cc138067b89a37cace5e8ff0b932f5a Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 19 Aug 2026 10:51:36 +0200 Subject: [PATCH 157/180] changelog: images updates, 2026W34 --- content/changelog/2026/08-19-images-update.md | 49 +++++++++++++++++++ data/runtime_versions.yml | 2 +- 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 content/changelog/2026/08-19-images-update.md diff --git a/content/changelog/2026/08-19-images-update.md b/content/changelog/2026/08-19-images-update.md new file mode 100644 index 000000000..4477cafc2 --- /dev/null +++ b/content/changelog/2026/08-19-images-update.md @@ -0,0 +1,49 @@ +--- +title: "Images update: FFmpeg 9, Node.js 24.19, OpenSSH 10.5, rsync 3.5" +description: Runtime updates for Go, Node.js, Python and Ruby, with FFmpeg 9, OpenSSH 10.5, rsync 3.5 and new PDF tooling +date: 2026-08-19 +tags: + - images + - update +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated all our images. Deployment is in progress for all our users. + +* **Common:** + * Anubis 1.26.2 + * ClamAV 1.5.4 + * Chromium 151.0.7922.137 + * FFmpeg 9.0 + * Nano 9.2 + * OpenSSH 10.5_p1 + * Poppler 26.08.0 + * pdfio 1.6.4 is now available + * qpdf is no longer included + * rsync 3.5.0 +* **Go:** + * Update to 1.26.6 +* **Node.js & Bun:** + * Node.js 24.19.0 (npm 11.17.0) +* **Python:** + * Update to 3.10.21 + * Update to 3.11.16 + * Update to 3.12.14 + * Update to 3.13.15 + * Update to 3.14.7 +* **Ruby:** + * Update to 3.3.12 + * Update to 3.4.10 + * Update to 4.0.6 + +## Health Check and Request Flow improvements + +This release also improves the management of Health Checks and Request Flow across our application images. See the [Request Flow documentation](/doc/develop/request-flow/) to learn how to configure middleware chaining and automatic port allocation. + +## Linux Kernel + +Kernel is [now updated independently](/changelog/2026/05-12-linux-kernel-7.0.6). Current version is 7.1.10. diff --git a/data/runtime_versions.yml b/data/runtime_versions.yml index 28f3c73b9..571deb350 100644 --- a/data/runtime_versions.yml +++ b/data/runtime_versions.yml @@ -56,7 +56,7 @@ java: node: eol_source: https://nodejs.org/en/about/releases/ default: - - 24.18.1 (npm 11.16.0) + - 24.19.0 (npm 11.17.0) php: eol_source: https://www.php.net/supported-versions.php From 3eea2208aabba2c922a249b1a224c2889a402e27 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 19 Aug 2026 16:17:38 +0200 Subject: [PATCH 158/180] changelog: Redis 8.10.1 --- content/changelog/2026/08-19-redis-8.10.1.md | 22 ++++++++++++++++++++ data/software_versions_shared_dedicated.yml | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 content/changelog/2026/08-19-redis-8.10.1.md diff --git a/content/changelog/2026/08-19-redis-8.10.1.md b/content/changelog/2026/08-19-redis-8.10.1.md new file mode 100644 index 000000000..4f0118f65 --- /dev/null +++ b/content/changelog/2026/08-19-redis-8.10.1.md @@ -0,0 +1,22 @@ +--- +title: Redis 8.10.1 is available (security update) +description: Redis 8.10.1 introduces compact hashes and new commands, and fixes multiple memory safety issues +date: 2026-08-19 +tags: + - addons + - redis +authors: + - name: Aurélien Hébert + link: https://github.com/aurrelhebert + image: https://github.com/aurrelhebert.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +We updated Redis™ to [release 8.10.1](https://github.com/redis/redis/releases/tag/8.10.1). It includes the features introduced in [Redis™ 8.10](https://github.com/redis/redis/releases/tag/8.10.0), such as compact hashes that reduce memory usage for keys sharing a schema. It also adds `HIMPORT` for high-throughput compact hash insertion, `LMOVEM` and `BLMOVEM` to move multiple list elements, and `SUNIONCARD` and `SDIFFCARD` to return set cardinalities. + +This security release fixes multiple memory safety issues affecting RDB loading, Vector Sets, TLS client certificate authentication and blocked clients. Redis™ 8.10.1 is available for new add-ons. Those already deployed can upgrade through migration. + +- [Learn more about Redis™ on Clever Cloud](/doc/addons/redis/) diff --git a/data/software_versions_shared_dedicated.yml b/data/software_versions_shared_dedicated.yml index e9ff752e1..2e90e282f 100644 --- a/data/software_versions_shared_dedicated.yml +++ b/data/software_versions_shared_dedicated.yml @@ -32,4 +32,4 @@ pg: redis: dedicated: - - v8.8.0 + - v8.10.1 From 713cfb83419fcff40ba008c39f8fc40be4b16982 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Fri, 21 Aug 2026 10:27:43 +0200 Subject: [PATCH 159/180] changelog(metabase): add 0.61.19, 0.62.17 and 0.63.14 --- .../08-21-metabase-0.61.19-0.62.17-0.63.14.md | 40 +++++++++++++++++++ content/doc/addons/metabase.md | 12 +++--- 2 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 content/changelog/2026/08-21-metabase-0.61.19-0.62.17-0.63.14.md diff --git a/content/changelog/2026/08-21-metabase-0.61.19-0.62.17-0.63.14.md b/content/changelog/2026/08-21-metabase-0.61.19-0.62.17-0.63.14.md new file mode 100644 index 000000000..d0dbd8c4b --- /dev/null +++ b/content/changelog/2026/08-21-metabase-0.61.19-0.62.17-0.63.14.md @@ -0,0 +1,40 @@ +--- +title: "Metabase 0.61.19, 0.62.17 and 0.63.14 are available (security update)" +description: Metabase 0.61.19, 0.62.17 and 0.63.14 harden security and introduce breaking changes to API parameters, dependency permissions and serialization +date: 2026-08-21 +tags: + - addons + - metabase +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +Metabase versions [0.61.19](https://github.com/metabase/metabase/releases/tag/v0.61.19), [0.62.17](https://github.com/metabase/metabase/releases/tag/v0.62.17) and [0.63.14](https://github.com/metabase/metabase/releases/tag/v0.63.14) are now available on Clever Cloud. These releases focus on hardening Metabase security and include the changes from private patch releases published for each branch. Versions starting with `1` provide the same updates for the enterprise edition. + +## Breaking changes + +These security measures introduce breaking changes. Metabase no longer supports undocumented API usage, and the `/api/card/{card-id}/query/{export-format}` endpoint now requires a non-blank `id` for each item in the `parameters` array. Users also need **View** collection permissions for every question dependency, including models, metrics and nested questions. Database secrets are no longer included in serialization exports, but existing exports containing secrets can still be imported. + +## `community-latest` on XS instances + +All Metabase add-ons that used `community-latest` on an XS Java instance have been pinned to `0.59.9`, the latest version that can start with the 1 GB of RAM provided by this instance size. Metabase `0.60` and later require at least 2 GB of RAM, as detailed in the previous changelog entries about [the new RAM requirements](/changelog/2026/05-06-metabase-60-ram/) and [the availability of Metabase 60](/changelog/2026/05-12-metabase-60/). To upgrade, set `CC_METABASE_VERSION` to `latest` and rebuild the underlying Java application. The update automatically resizes it from XS to S because `latest` resolves to a version newer than `0.60`. Keep `latest` afterward so each new deployment uses the most recent Metabase version available on Clever Cloud. + +Back up your Metabase application database and review your API integrations and collection permissions before updating. If you use `latest` on an S instance or larger, restart your instance to get the latest version. If you use a specific branch, update `CC_METABASE_VERSION` of the underlying Java application to its latest patch and rebuild it. + +You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com), or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever metabase version check yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId +clever metabase version update yourMetabaseNameOrId 0.63 +``` + +- [Read the Metabase 61 changelog](https://www.metabase.com/changelog/61) +- [Read the Metabase 62 changelog](https://www.metabase.com/changelog/62) +- [Read the Metabase 63 changelog](https://www.metabase.com/changelog/63) +- [Learn more about Metabase on Clever Cloud](/doc/addons/metabase/) diff --git a/content/doc/addons/metabase.md b/content/doc/addons/metabase.md index c7b32824a..860207b32 100644 --- a/content/doc/addons/metabase.md +++ b/content/doc/addons/metabase.md @@ -156,16 +156,16 @@ clever metabase version update myMetabase If you use `CC_METABASE_VERSION` it can contain a value that is either a special keyword or a [SemVer](https://semver.org/) version requirement (the only difference with SemVer is that `x.y.z` is interpreted as `=x.y.z` instead of `^x.y.z`.): -- `community-latest` (_default_): use the latest version of the Community Edition (_same as `0`, `0.*`, `^0` or empty_) -- `0.55.1`: use the `0.55.1` version (_same as `=0.55.1`_) -- `0.55`: use the latest available version starting with `0.55` (_same as `^0.55.0`, `~0.55.0`_) +- `latest` (_default_): use the latest version of the Community Edition (_same as `0`, `0.*`, `^0` or empty_) +- `0.63.14`: use the `0.63.14` version (_same as `=0.63.14`_) +- `0.63`: use the latest available version starting with `0.63` (_same as `^0.63.0`, `~0.63.0`_) To update Metabase manually, you **should** restart the Java application without the build cache, using the `re-build and restart` button in the [Console](https://console.clever-cloud.com/) or the `clever restart --without-cache` command of [Clever Tools](/doc/cli/applications/deployment-lifecycle/#restart). The Metabase JAR is stored in the build cache so that no time is wasted re-downloading it every time you restart the application (or it is restarting as part of a scaling event). This also makes the service more resilient: should the download be temporarily failing for any reason, this would not prevent restarting/scaling your add-on. {{< callout type="warning" >}} -**With great power comes great responsibility.** If you choose to fix your add-on to a specific version (for example, `0.55.3`) or a specific "branch" (for example, `0.55`), you must make sure that this version/branch does not become obsolete (new Metabase versions that patch critical security issues may be released but not used in your add-on because you specified otherwise). +**With great power comes great responsibility.** If you choose to fix your add-on to a specific version (for example, `0.63.14`) or a specific "branch" (for example, `0.63`), you must make sure that this version/branch does not become obsolete (new Metabase versions that patch critical security issues may be released but not used in your add-on because you specified otherwise). {{< /callout >}} - [The Atom feed (XML) of latest versions and their changelog](https://cc-metabase.cellar-c2.services.clever-cloud.com/metabase_releases.xml) @@ -175,7 +175,7 @@ The Metabase JAR is stored in the build cache so that no time is wasted re-downl Metabase provides an Enterprise Edition (EE) that offers [more features](https://www.metabase.com/docs/latest/paid-features/) but requires a license key that must be purchased through their website (see the [pricing page](https://www.metabase.com/pricing/)) EE versions are usually released at the same time as Community Edition (CE) versions, starting with a `1` instead of a `0`. -If you wish to deploy an EE version on your Clever Cloud add-on, `CC_METABASE_VERSION` environment variable to either use a fixed version/branch that starts with `1` (for example: `CC_METABASE_VERSION=1.55`) or `CC_METABASE_VERSION=enterprise-latest`. +If you wish to deploy an EE version on your Clever Cloud add-on, `CC_METABASE_VERSION` environment variable to either use a fixed version/branch that starts with `1` (for example: `CC_METABASE_VERSION=1.63`) or `CC_METABASE_VERSION=enterprise-latest`. You must then add your license key in Metabase's settings (see [documentation](https://www.metabase.com/docs/latest/installation-and-operation/activating-the-enterprise-edition#if-youre-self-hosting-metabase)). @@ -243,4 +243,4 @@ Here is how you can do it: 8. _(optional)_ Configure the Java application domain and update your DNS record accordingly; if you use a custom domain, you should also update the `MB_SITE_URL` environment variable (it defines the base URL used by links in Metabase emails, among other things) 9. Start the Java application of your Clever Cloud add-on (without build cache) -If everything seems OK, set `CC_METABASE_VERSION` to the value you wish (for example, `community-latest`) in the Java application of your Clever Cloud add-on and restart it. [Contact the Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice) if you need advice or help doing that. +If everything seems OK, set `CC_METABASE_VERSION` to the value you wish (for example, `latest`) in the Java application of your Clever Cloud add-on and restart it. [Contact the Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice) if you need advice or help doing that. From 97caa10ce3901d79a1bc17b2971b07256b1bffda Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 24 Aug 2026 10:00:18 +0200 Subject: [PATCH 160/180] docs: callouts, commits message recommandations --- .github/copilot-instructions.md | 37 ++++++++++++++++++++++++++++----- CLAUDE.md | 26 ++++++++++++++++++++++- CONTRIBUTING.md | 36 ++++++++++++++++++++++++++++++-- 3 files changed, 91 insertions(+), 8 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 74b9c7cd4..f8d541562 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -105,12 +105,19 @@ aliases: - Always specify **current versions** of software/tools - Include **environment variables** with exact names and examples - Provide **command-line examples** with proper syntax -- Use **callouts for important information**: +- Prefer **GitHub-style callouts for important information**. They render in both GitHub and the documentation site: ```markdown - > [!NOTE] Context about new features - > [!TIP] Helpful suggestions - > [!WARNING] Important considerations + > [!NOTE] + > Context about new features. + + > [!TIP] + > Helpful suggestions. + + > [!WARNING] + > Important considerations. ``` +- Use the Hugo `{{< callout >}}` shortcode only when GitHub-style syntax can't provide the required rendering or behaviour +- Limit callouts to one or two per page ### Guide-Specific Rules @@ -223,7 +230,7 @@ clever keycloak version update yourKeycloakNameOrId - [ ] Examples use realistic project names and values - [ ] No first-person pronouns (I, we, us, our) - [ ] Short, clear sentences under 25 words -- [ ] Proper callouts for important information +- [ ] GitHub-style callouts used for important information where needed ### Before Publishing Changelog - [ ] Clear benefit/impact stated upfront @@ -277,6 +284,26 @@ The site is configured for Clever Cloud hosting with the `static` runtime and th ## Quality Assurance Requirements +### Commit Message Convention +Use `section(page): commit message` for content updates. The section and page must identify the documentation area being changed: + +```text +addons(postgresql): document pg_partman support +applications(nodejs): clarify pnpm configuration +changelog(metabase): announce 0.63.14 security update +``` + +Use standard Conventional Commits for documentation structure, Hugo configuration or templates, deployment, CI, tooling, and dependency changes: + +```text +feat(hugo): add a shortcode for version tables +fix(ci): run Vale on shared content +refactor(layouts): simplify changelog rendering +chore(deps): update the Hextra theme +``` + +Split content and structural changes into separate commits when possible. + ### Build Verification Always test changes with the `hugo` command before committing to ensure the build is functional. Fix any build errors immediately as they prevent deployment. Verify that all links, references, image paths, and shortcode syntax work correctly in the generated output. diff --git a/CLAUDE.md b/CLAUDE.md index 6f15fe59a..063018b21 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -78,6 +78,30 @@ For changelog entries, also include: - **Structure**: Use 2-4 well-developed paragraphs per section, minimize bullet lists - **Paragraphs**: Aim for 3-6 lines for optimal readability +### Callouts +- Prefer GitHub-style callouts because they render in both GitHub and the documentation site: + ```markdown + > [!NOTE] + > This information helps readers understand the current behaviour. + + > [!WARNING] + > Back up your application database before upgrading. + ``` +- Use the Hugo `{{< callout >}}` shortcode only when GitHub-style syntax can't provide the required rendering or behaviour +- Limit callouts to one or two per page + +### Commit Messages +- For content updates, use `section(page): commit message`, for example: + - `addons(postgresql): document pg_partman support` + - `applications(nodejs): clarify pnpm configuration` + - `changelog(metabase): announce 0.63.14 security update` +- For documentation structure, Hugo, deployment, CI, tooling, or dependency changes, use standard Conventional Commits, for example: + - `feat(hugo): add a shortcode for version tables` + - `fix(ci): run Vale on shared content` + - `refactor(layouts): simplify changelog rendering` + - `chore(deps): update the Hextra theme` +- Split content and structural changes into separate commits when possible + ### Code and Technical Examples - Always provide complete, runnable code examples - Use exact environment variable names: `CC_WEBROOT`, `CC_NODE_BUILD_TOOL`, etc. @@ -103,7 +127,7 @@ Runtime versions and software compatibility information is maintained in `/data/ - `{{% steps %}}` - Create step-by-step instructions for guides - `{{< tabs items="npm,yarn,pnpm" >}}` - Create tabbed content sections - `{{< cards >}}` - Display card layouts for related resources -- `{{< callout >}}` - Create note, tip, warning callouts +- `{{< callout >}}` - Create a callout only when GitHub-style syntax isn't sufficient - `{{< hextra/hero-subtitle >}}` - Add engaging subtitles in guides ### Hugo Content Types diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 71c384682..ddd345641 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,13 +40,24 @@ Sources for quality content are currently being updated. - Using phrases like _simply_, _It's that simple_, _It's easy_, or _quickly_ in a procedure. - Over-politeness with the use of _please_: go straight to the point. -#### 💡 Shortcodes +#### 💡 Shortcodes and callouts This doc uses Hugo with [Hextra theme](https://imfing.github.io/hextra/), which provides a variety of [shortcodes](https://imfing.github.io/hextra/docs/guide/shortcodes/) to enhance it and improve its readability. For example : - [Steps](https://imfing.github.io/hextra/docs/guide/shortcodes/steps/) are well suited for the `/guides/` section, or for any tutorial. -- [Callouts](https://imfing.github.io/hextra/docs/guide/shortcodes/callout/) draw attention to an important information in the page. However, don't overuse them, as too many callouts can miss their point and make the page crowded. Limit callouts to one or two per page. + +Use GitHub-style callouts whenever possible. They remain readable on GitHub and don't depend on Hugo-specific rendering: + +```markdown +> [!NOTE] +> This information helps readers understand the current behaviour. + +> [!WARNING] +> Back up your application database before upgrading. +``` + +Use the [Hextra callout shortcode](https://imfing.github.io/hextra/docs/guide/shortcodes/callout/) only when a GitHub-style callout can't provide the required rendering or behaviour. Don't overuse callouts: limit them to one or two per page. ### 💅 Style guide @@ -68,6 +79,27 @@ Priority goes to PRs that reference a problem addressed in an issue fitting the - **Keep it small:** The quality of the review is inversely proportional to the size of the PR. Smaller PRs simplify the reviewing process and increase the chances of getting constructive feedback. - **Accept the feedback:** If reviewers ask you to make changes, do it. If you disagree, explain why. If you aren't sure, ask for clarification. Don't nitpick on the feedback, and don't take it personally. +#### Commit messages + +For content updates, use `section(page): commit message`. The section and page identify the documentation area you changed: + +```text +addons(postgresql): document pg_partman support +applications(nodejs): clarify pnpm configuration +changelog(metabase): announce 0.63.14 security update +``` + +For changes to the documentation structure, Hugo configuration or templates, deployment, CI, tooling, or dependencies, use the standard Conventional Commits format `type(scope): commit message`: + +```text +feat(hugo): add a shortcode for version tables +fix(ci): run Vale on shared content +refactor(layouts): simplify changelog rendering +chore(deps): update the Hextra theme +``` + +Keep content and structural changes in separate commits when possible so each commit can follow the appropriate convention. + ### 🥸 When reviewing a PR - **Latency:** Long PR review latency can be disappointing for the authors, and make merge conflicts arise in their branch. Long latency kills productivity and morale, so make sure to review PRs in a timely manner. From a566f526afa967e24a93d627353e40b45d185d0e Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 24 Aug 2026 08:14:37 +0200 Subject: [PATCH 161/180] changelog(keycloak): Keycloak 26.7.2 --- .../changelog/2026/08-24-keycloak-26.7.2.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 content/changelog/2026/08-24-keycloak-26.7.2.md diff --git a/content/changelog/2026/08-24-keycloak-26.7.2.md b/content/changelog/2026/08-24-keycloak-26.7.2.md new file mode 100644 index 000000000..4ca5e8daa --- /dev/null +++ b/content/changelog/2026/08-24-keycloak-26.7.2.md @@ -0,0 +1,42 @@ +--- +title: "Keycloak critical security update, 26.7.2 is available" +description: Keycloak 26.7.2 fixes CVE-2026-18963, a critical unauthenticated account takeover vulnerability in the password reset flow +date: 2026-08-24 +tags: + - addons + - keycloak +authors: + - name: Sébastien Allemand + link: https://github.com/allemas + image: https://github.com/allemas.png?size=40 + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 +excludeSearch: true +--- + +[Keycloak 26.7.2](https://github.com/keycloak/keycloak/releases/tag/26.7.2) is available on Clever Cloud. It fixes [CVE-2026-18963](https://access.redhat.com/security/cve/CVE-2026-18963), a critical vulnerability with a CVSS score of 9.1. An unauthenticated attacker can bypass the email verification step of the password reset flow, set new credentials for any user and take control of their account without user interaction. + +## Update or temporarily disable password resets + +Update every Keycloak add-on to version 26.7.2 as soon as possible. You can update through the add-on's dashboard in the [Clever Cloud Console](https://console.clever-cloud.com). You can also set `CC_KEYCLOAK_VERSION` of the underlying Java application to `26.7.2` and rebuild it, or use [Clever Tools](/doc/cli/operators/): + +```bash +clever features enable operators + +clever keycloak version check yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId +clever keycloak version update yourKeycloakNameOrId --target 26.7.2 +``` + +If you can't update immediately, temporarily disable password resets in every realm. In the Keycloak Admin Console, select a realm, open **Realm settings**, then **Login**, switch **Forgot password** to **Off** and repeat for every realm. This prevents legitimate users from requesting a password reset; re-enable the feature only after the instance runs version 26.7.2. + +If you suspect that an account was compromised before the update, reset its credentials, revoke its active sessions and review its recent activity, roles and permissions for unexpected changes. + +## Other security fixes + +Keycloak 26.7.2 addresses seven other vulnerabilities: [CVE-2026-45292](https://nvd.nist.gov/vuln/detail/CVE-2026-45292), [CVE-2026-14613](https://nvd.nist.gov/vuln/detail/CVE-2026-14613), [CVE-2026-59888](https://nvd.nist.gov/vuln/detail/CVE-2026-59888), [CVE-2026-59889](https://nvd.nist.gov/vuln/detail/CVE-2026-59889), [CVE-2026-15945](https://nvd.nist.gov/vuln/detail/CVE-2026-15945), [CVE-2026-17048](https://nvd.nist.gov/vuln/detail/CVE-2026-17048) and [CVE-2026-15571](https://nvd.nist.gov/vuln/detail/CVE-2026-15571). They cover account takeover risks in account linking flows, permission bypasses and information disclosure in fine-grained admin permissions, exposure of rotated client secrets, unbounded memory allocation in OpenTelemetry baggage processing and vulnerabilities in Jackson Databind. The release also prevents `show-config` from displaying the Vault keystore password in clear text and fixes bugs affecting SCIM, OIDC, WebAuthn, stateless clusters and the Admin UI. + +- [Read the CVE-2026-18963 advisory](https://access.redhat.com/security/cve/CVE-2026-18963) +- [Read the Keycloak 26.7.2 release notes](https://www.keycloak.org/2026/08/keycloak-2672-released) +- [Learn more about Keycloak on Clever Cloud](/doc/addons/keycloak) From 60b74a094f7dffd96a27e6271e5593ecde0299fa Mon Sep 17 00:00:00 2001 From: hcaumeil Date: Fri, 21 Aug 2026 22:49:02 +0200 Subject: [PATCH 162/180] reference(env vars): update Node.js build tools --- content/doc/reference/reference-environment-variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/doc/reference/reference-environment-variables.md b/content/doc/reference/reference-environment-variables.md index 66c2d0c22..0cedc10b5 100644 --- a/content/doc/reference/reference-environment-variables.md +++ b/content/doc/reference/reference-environment-variables.md @@ -262,7 +262,7 @@ Use Linux runtime with [Mise package manager](#install-tools-with-mise-package-m |[`CC_NODE_VERSION`](/doc/applications/nodejs#set-nodejs-version)| Set Node.js version, for example `24`, `23.11` or `22.15.1` | | |`CC_NODE_DEV_DEPENDENCIES` | Control if development dependencies are installed or not. Values are either `install` or `ignore` | `ignore` | |`CC_RUN_COMMAND` | Define a custom command. Example for Meteor: `node .build/bundle/main.js ` | | -|`CC_NODE_BUILD_TOOL` | Choose your build tool between npm, npm-ci, yarn, yarn2 and custom | npm | +|`CC_NODE_BUILD_TOOL` | Choose your build tool between bun, npm, npm-ci, pnpm, yarn, yarn2, yarn-berry and custom | npm | |`CC_CUSTOM_BUILD_TOOL`| A custom command to run (with `CC_NODE_BUILD_TOOL` set to `custom`) | | |`CC_NPM_REGISTRY` | The host of your private repository, available values: `github` or the registry host. | registry.npmjs.org | |`CC_NPM_BASIC_AUTH`| Private repository credentials, in the form `user:password`. You can't use this if `CC_NPM_TOKEN` is set | | From 95a8f4e4dc38db251ae73041c8f330977da1be83 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 24 Aug 2026 11:23:51 +0200 Subject: [PATCH 163/180] guides(astro): use native pnpm build tool --- content/guides/astro.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/content/guides/astro.md b/content/guides/astro.md index eb2f0cd5d..d7594934c 100644 --- a/content/guides/astro.md +++ b/content/guides/astro.md @@ -87,9 +87,8 @@ Depending on your package manager, use the following environment variables: {{< /tab >}} {{< tab name="pnpm" icon="pnpm" >}} ```shell - CC_NODE_BUILD_TOOL="custom" - CC_PRE_BUILD_HOOK="npm install -g pnpm && pnpm install" - CC_CUSTOM_BUILD_TOOL="pnpm run astro telemetry disable && pnpm build" + CC_NODE_BUILD_TOOL="pnpm" + CC_POST_BUILD_HOOK="pnpm run astro telemetry disable && pnpm build" CC_RUN_COMMAND="pnpm run preview" ``` {{< /tab >}} From da406d432c2fc59fa256edfbe1f2d2d86025a97b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Brunat?= Date: Wed, 19 Aug 2026 20:35:12 +0200 Subject: [PATCH 164/180] develop: List the missing pages on the section index The Develop section index only linked five of its nine pages. Clever Tasks, Request Flow, Varnish and Network Groups were reachable through the sidebar and search only. --- content/doc/develop/_index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/content/doc/develop/_index.md b/content/doc/develop/_index.md index b6ea2f89b..1bb08f165 100644 --- a/content/doc/develop/_index.md +++ b/content/doc/develop/_index.md @@ -20,5 +20,9 @@ aliases: {{< card link="/developers/doc/reference/reference-environment-variables" title="Environment variables reference" icon="creds" >}} {{< card link="/developers/doc/develop/env-variables" title="How environment variables work" icon="question-mark-circle" >}} {{< card link="/developers/doc/develop/workers" title="Workers" icon="arrow-path" >}} + {{< card link="/developers/doc/develop/tasks" title="Clever Tasks" icon="play-circle" >}} {{< card link="/developers/doc/develop/healthcheck" title="Deployment healthcheck path" icon="check" >}} + {{< card link="/developers/doc/develop/request-flow" title="Request Flow" icon="traffic-light" >}} + {{< card link="/developers/doc/develop/varnish" title="Varnish as HTTP cache" icon="arrow-trending-up" >}} + {{< card link="/developers/doc/develop/network-groups" title="Network Groups" icon="tcp-ip-service" >}} {{< /cards >}} From 573ccb6aa642992d48eec3545b6672165ea6df0f Mon Sep 17 00:00:00 2001 From: Mehdi <26483210+mehdi653@users.noreply.github.com> Date: Thu, 13 Aug 2026 00:07:28 +0000 Subject: [PATCH 165/180] applications(java): document Java 21 as the current default --- content/doc/find-help/faq.md | 2 +- data/runtime_versions.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/doc/find-help/faq.md b/content/doc/find-help/faq.md index 940d03489..4114faf5b 100644 --- a/content/doc/find-help/faq.md +++ b/content/doc/find-help/faq.md @@ -139,7 +139,7 @@ As an example, if a Spring Boot application was compiled with Java `17` and run java.lang.UnsupportedClassVersionError: org/springframework/boot/loader/JarLauncher has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0 ``` -By default, Java apps on Clever Cloud use Java `11`, but you can change it. Please head [over here](/doc/applications/java/java-jar/#available-java-versions "Java versions") for more information. +By default, Java apps on Clever Cloud use Java `21`, but you can change it. Please head [over here](/doc/applications/java/java-jar/#available-java-versions "Java versions") for more information. For reference, the table below lists the class file version for each major Java version ([official doc](https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-4.html)) : diff --git a/data/runtime_versions.yml b/data/runtime_versions.yml index 571deb350..f01eb91e9 100644 --- a/data/runtime_versions.yml +++ b/data/runtime_versions.yml @@ -41,7 +41,7 @@ frankenphp: java: eol_source: https://adoptium.net/fr/support/ default: - - 11 + - 21 accepted: - 25 - 24 (EOL) From 5532c7ba71e06724361c341ec15b986fad9926a8 Mon Sep 17 00:00:00 2001 From: Mehdi <26483210+mehdi653@users.noreply.github.com> Date: Thu, 13 Aug 2026 00:06:59 +0000 Subject: [PATCH 166/180] find-help(faq): update regions, runtimes, flavors and Kubernetes --- content/doc/find-help/faq.md | 40 +++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/content/doc/find-help/faq.md b/content/doc/find-help/faq.md index 4114faf5b..d8faa9391 100644 --- a/content/doc/find-help/faq.md +++ b/content/doc/find-help/faq.md @@ -16,9 +16,9 @@ aliases: ## What is a Scaler? -A _scaler_ is an individual instance hosting your app. You can attribute one or more scalers to your apps. scalers come in many sizes based on each language requirements from pico to XL. +A _scaler_ is an individual instance hosting your application. You can allocate one or more scalers to each application. Available flavors are `pico`, `nano`, `XS`, `S`, `M`, `L`, `XL`, `2XL` and `3XL`, depending on the runtime. -A fixed set of resources supports each scaler. +Each scaler flavor provides a fixed set of resources. When enabling auto-scalability, you have to set a minimum and a maximum of active scalers in your apps settings. This way you can precisely control your monthly fee. @@ -26,19 +26,27 @@ When enabling auto-scalability, you have to set a minimum and a maximum of activ Nano and pico instances operate with **reduced CPU priority** on the host system. As a result, during periods of high load on the hypervisor, these instances may experience performance degradation (since they yield processing power to higher-priority workloads). {{< /callout >}} -## What languages and frameworks are supported by Clever Cloud? -Currently Clever Cloud supports: +## Which application runtimes are supported by Clever Cloud? +Currently, Clever Cloud supports: -* Java (Play Framework 1 & 2, Maven, War files… ) -* Node.js -* PHP ([see frameworks and CMS](/guides)) -* Python (Django) -* Ruby +* .NET +* Docker +* Elixir * Go * Haskell -* Scala +* Java (Jar, Maven, Gradle, War/Ear, Play Framework 1 & 2) and Groovy with Gradle +* Linux +* Meteor.js +* Node.js & Bun +* PHP and FrankenPHP +* Python +* Ruby * Rust -* Docker +* Scala (SBT, Play Framework 1 & 2) +* Static sites (with or without Apache) +* V (Vlang) + +See the full list in the [applications documentation](/doc/applications/), or browse the [guides](/guides/) for frameworks and services you can deploy on Clever Cloud. ## How many applications can I create? As many as you want. We've not set a limited number of apps by developer. @@ -157,20 +165,19 @@ Clever Cloud does not give you access to a server or a VPS, it makes your applic If however, you still need SSH access for debugging purposes, please have a look at [SSH access](/doc/cli/applications/deployment-lifecycle/#ssh), but keep in mind that changes made on an instance are not persistent across deployments. -## I want to user Clever Cloud on my own premises, is that possible? +## I want to use Clever Cloud on my own premises, is that possible? Yes, since 2016 Clever Cloud is packaged for private data center. This offer called "Clever Cloud On Premises" is available upon request: you can send a mail to [sales@clever-cloud.com](mailto:sales@clever-cloud.com) or visit [https://www.clever.cloud/on-premises](https://www.clever.cloud/on-premises) for more info. ## Where are my applications and add-ons located? -Applications and add-ons are located in either _Paris, France_ or _Montreal, Canada_. You can choose where you want it to be when you create an application -and a Clever Cloud add-on. +Applications and add-ons are deployed in multiple regions across Europe, North America and Asia-Pacific, including Paris, Roubaix, Gravelines, London, Warsaw, Montreal, Singapore and Sydney, plus HDS-certified zones in France for healthcare data. When creating an application or an add-on, you can choose among the deployment zones available for that product. Clever Cloud is based in Nantes, France. -## I want to run Kubernetes on top of Clever CLoud, is that possible? +## I want to run Kubernetes on top of Clever Cloud, is that possible? -It's currently not possible to use Kubernetes on our platform. It is however on our Roadmap. +Yes. [Clever Kubernetes Engine](/doc/kubernetes/) (CKE), currently in public beta, provides a managed Kubernetes control plane. See the [product page](https://www.clever.cloud/product/kubernetes/) for more information. ## How to setup a firewall on Clever Cloud? @@ -229,4 +236,3 @@ DEV plan is a free-tier plan available for some databases, designed to let custo Some features such as extensions, simultaneous connections numbers, functions… might be reduced or unavailable. Support is not able to provide help in case of DEV plan. - From 252eb7c0af3ab02bb510a876c6ba264e2ab2a395 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 24 Aug 2026 14:47:51 +0200 Subject: [PATCH 167/180] addons(keycloak): remove legacy mention --- content/doc/addons/keycloak.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/content/doc/addons/keycloak.md b/content/doc/addons/keycloak.md index 6bafd8f74..32077077f 100644 --- a/content/doc/addons/keycloak.md +++ b/content/doc/addons/keycloak.md @@ -47,13 +47,6 @@ When you create the Keycloak add-on, Clever Cloud automatically deploys: - A [PostgreSQL](/doc/addons/postgresql) database - A [FS Bucket](/doc/addons/fs-bucket) used for themes, plugins, and import/export storage needs -## Security and updates -Since the Keycloak add-on is a fully managed application, you don't have to select a particular version. It's automatically upgraded and updated both for features and security. - -An add-on update might require a rebuild. - -> Required actions are notified by email - ## Plan sizing By default, Keycloak on Clever Cloud uses small-size resources, i.e: From 11de165b14a9cc535260916fd53602796c1aa957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hubert=20SABLONNI=C3=88RE?= Date: Wed, 24 Jun 2026 17:31:43 +0200 Subject: [PATCH 168/180] administrate(log-management): update access log formats for Clever Tools 4.11 --- content/doc/administrate/log-management.md | 107 ++++++++++++--------- 1 file changed, 59 insertions(+), 48 deletions(-) diff --git a/content/doc/administrate/log-management.md b/content/doc/administrate/log-management.md index da653c0e9..11426da47 100644 --- a/content/doc/administrate/log-management.md +++ b/content/doc/administrate/log-management.md @@ -57,15 +57,7 @@ clever logs --addon ### Access logs -It contains all incoming requests to your application. Here is an example: - -```txt -255.255.255.255 - - [06/Feb/2020:07:59:22 +0100] "GET /aget/to/your/beautiful/website -" 200 1453 -``` - -They are available in different formats, the most common is CLF which stands for Common Log Format. - -You can see access logs with the following command: +It contains all incoming HTTP requests to your application. You can see access logs with the following command: ```bash clever accesslogs @@ -74,48 +66,67 @@ clever accesslogs As with the `logs` command, you can specify `--before` and `--after` flags. If you don't specify any options, the logs display continuously. -To change the output, specify the `--format` flag with one of these values: +To change the output, specify the `--format` (`-F`) flag with one of these values: -- simple: `2021-06-25T10:11:35.358Z 255.255.255.255 GET /` -- extended: `2021-06-25T10:11:35.358Z [ 255.255.255.255 - Nantes, FR ] GET www.clever.cloud / 200` -- clf: `255.255.255.255 - - [25/Jun/2021:12:11:35 +0200] "GET / -" 200 562` -- json: +- `human` (default): a human-readable, colored table + + ```txt + 2026-06-24T08:05:43.880Z 255.255.255.255 FR/Nantes 200 GET / + ``` + +- `clf`: [Common Log Format](https://en.wikipedia.org/wiki/Common_Log_Format) + + ```txt + 255.255.255.255 - - [24/Jun/2026:08:05:43 +0000] "GET /" 200 562 + ``` + + The HTTP protocol version isn't part of the access log payload, so the request line is limited to `method path` (no `HTTP/x.y` token). + +- `json`: a JSON array of log objects (requires a bounding flag such as `--before`) +- `json-stream`: one JSON log object per line + + Both JSON formats share the same object shape: ```json - { - "t":"2021-06-25T10:11:35.358209Z", - "a":"app_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "adc":"clevercloud-adc-nX", - "o":"orga_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "i":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "ipS":"255.255.255.255", - "pS":58477, - "s":{ - "lt":50.624, - "lg":3.0511, - "ct":"Nantes", - "co":"FR" - }, - "ipD":"46.252.181.17", - "pD":14001, - "d":{ - "lt":45.7059, - "lg":4.7444, - "ct":"Chaponost", - "co":"FR" - }, - "vb":"GET", - "path":"/", - "bIn":658,"bOut":562, - "h":"www.clever.cloud", - "rTime":"31ms", - "sTime":"75μs", - "scheme":"HTTPS", - "sC":200,"sT":"OK", - "w":"WRK-01", - "r":"01F91AEG8Z9RJKYB7JY7H56FNB", - "tlsV":"TLS1.3" - } + { + "id": "01F91AEG8Z9RJKYB7JY7H56FNB", + "date": "2026-06-24T08:05:43.880Z", + "applicationId": "app_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "instanceId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "region": "par", + "zone": "par", + "requestId": "01F91AEG8Z9RJKYB7JY7H56FNB", + "bytesIn": 658, + "bytesOut": 562, + "source": { + "ip": "255.255.255.255", + "port": 58477, + "city": "Nantes", + "countryCode": "FR", + "geoLocation": { "latitude": 50.624, "longitude": 3.0511 } + }, + "destination": { + "ip": "46.252.181.17", + "port": 14001, + "city": "Chaponost", + "countryCode": "FR", + "geoLocation": { "latitude": 45.7059, "longitude": 4.7444 } + }, + "http": { + "request": { + "method": "GET", + "path": "/", + "host": "www.clever-cloud.com", + "scheme": "https" + }, + "response": { + "statusCode": 200, + "serviceTime": null, + "time": 31 + } + }, + "tls": null + } ``` ## Exporting logs to an external tool From 91aa97a69a2e8e763e5044e0d721644a2dbbcb0c Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 24 Aug 2026 12:05:10 +0200 Subject: [PATCH 169/180] reference(cli): update Clever Tools commands reference --- content/doc/reference/cli.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/content/doc/reference/cli.md b/content/doc/reference/cli.md index d86e9b68a..f296e8ee3 100644 --- a/content/doc/reference/cli.md +++ b/content/doc/reference/cli.md @@ -279,7 +279,7 @@ clever accesslogs [options] -a, --alias Short name for the application --app Application to manage by its ID (or name, if unambiguous) --before, --until Fetch logs before this date/time (ISO8601 date, positive number in seconds or duration, e.g.: 1h) --F, --format Output format (human, json, json-stream) (default: human) +-F, --format Output format (human, json, json-stream, clf) (default: human) ``` ## activity @@ -1075,6 +1075,30 @@ clever drain [options] -F, --format Output format (human, json) (default: human) ``` +### drain check + +**Description:** Check that a drain's recipient is reachable and accepts deliveries + +**Since:** 4.11.0 + +**Usage** +``` +clever drain check [options] +``` + +**Arguments** +``` +drain-id Drain ID +``` + +**Options** +``` + --addon Add-on ID or real ID +-a, --alias Short name for the application + --app Application to manage by its ID (or name, if unambiguous) +-F, --format Output format (human, json) (default: human) +``` + ### drain create **Description:** Create a drain @@ -1088,7 +1112,7 @@ clever drain create [options] **Arguments** ``` -drain-type Drain type (datadog, elasticsearch, newrelic, ovh-tcp, raw-http, syslog-tcp, syslog-udp) +drain-type Drain type (betterstack, datadog, elasticsearch, newrelic, ovh-tcp, raw-http, syslog-tcp, syslog-udp) drain-url Drain URL ``` @@ -1101,6 +1125,7 @@ drain-url Drain URL -i, --index-prefix Optional index prefix (for elasticsearch), `logstash` value is used if not set -p, --password Basic auth password (for elasticsearch or raw-http) -s, --sd-params RFC5424 structured data parameters (for ovh-tcp), e.g.: `X-OVH-TOKEN=\"REDACTED\"` +-t, --source-token Source token (for betterstack) -u, --username Basic auth username (for elasticsearch or raw-http) ``` From 6bc9b57ebafbc6c859703a7e634dc6b4a88d1331 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Mon, 24 Aug 2026 12:05:54 +0200 Subject: [PATCH 170/180] administrate(log-management): clarify JSON stream output --- content/doc/administrate/log-management.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/doc/administrate/log-management.md b/content/doc/administrate/log-management.md index 11426da47..959860278 100644 --- a/content/doc/administrate/log-management.md +++ b/content/doc/administrate/log-management.md @@ -83,7 +83,7 @@ To change the output, specify the `--format` (`-F`) flag with one of these value The HTTP protocol version isn't part of the access log payload, so the request line is limited to `method path` (no `HTTP/x.y` token). - `json`: a JSON array of log objects (requires a bounding flag such as `--before`) -- `json-stream`: one JSON log object per line +- `json-stream`: a stream of JSON log objects Both JSON formats share the same object shape: From daa0dc5a300ce76725da37dffea17653cde101f7 Mon Sep 17 00:00:00 2001 From: David Legrand Date: Wed, 10 Jun 2026 08:32:00 +0200 Subject: [PATCH 171/180] changelog: Clever Kubernetes Operator 0.8.0 --- .../2026/06-09-kubernetes-operator-0.8.0.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 content/changelog/2026/06-09-kubernetes-operator-0.8.0.md diff --git a/content/changelog/2026/06-09-kubernetes-operator-0.8.0.md b/content/changelog/2026/06-09-kubernetes-operator-0.8.0.md new file mode 100644 index 000000000..559aac99d --- /dev/null +++ b/content/changelog/2026/06-09-kubernetes-operator-0.8.0.md @@ -0,0 +1,20 @@ +--- +title: Clever Kubernetes Operator v0.8.0 is available +date: 2026-06-09 +tags: + - kubernetes + - operator +authors: + - name: David Legrand + link: https://github.com/davlgd + image: https://github.com/davlgd.png?size=40 + - name: Gilles Biannic + link: https://github.com/GillesBIANNIC + image: https://github.com/GillesBIANNIC.png?size=40 +description: The operator gets a new name, an updated Kubernetes stack and refreshed documentation +excludeSearch: true +--- + +[Clever Kubernetes Operator v0.8.0](https://github.com/CleverCloud/clever-kubernetes-operator/releases/tag/v0.8.0) is available. The project, formerly known as `clever-operator`, is now named `clever-kubernetes-operator` to better reflect its purpose. This release updates the Kubernetes stack, the `schemars` library and [clevercloud-sdk-rust v1.0.1](https://github.com/CleverCloud/clevercloud-sdk-rust/releases/tag/v1.0.1). Documentation now includes a dedicated credentials section covering the available authentication options, and reflects current add-on versions. + +- [Learn more about Clever Kubernetes Operator](/guides/kubernetes-operator) From 9dc8fce2870462a18678e87886c8c137c9255e94 Mon Sep 17 00:00:00 2001 From: Julie POUNY Date: Thu, 21 May 2026 16:04:21 +0200 Subject: [PATCH 172/180] addons(databases): document custom server configuration --- content/doc/addons/elastic.md | 14 +++++++------- content/doc/addons/mongodb.md | 10 +++++----- content/doc/addons/mysql.md | 10 +++++----- content/doc/addons/postgresql.md | 10 +++++----- content/doc/addons/redis.md | 10 +++++----- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/content/doc/addons/elastic.md b/content/doc/addons/elastic.md index 42bb17741..6e5476788 100644 --- a/content/doc/addons/elastic.md +++ b/content/doc/addons/elastic.md @@ -187,16 +187,16 @@ It's important here to set `number_of_replicas` to zero to avoid triggering clus ## 🔑 Rights and permissions -Elastic Stack add-ons are **managed services**, with Clever Cloud in charge of configuring and maintaining native configuration files. Some operations like adding an oauth source to connect to Kibana can't be added, as well as some native settings modifications. This ensures optimal performances and security for managed services as configured by Clever Cloud. +Elastic Stack add-ons are **managed services**. Clever Cloud configures and maintains their native configuration files. You can change most settings from Kibana or by API, but some native settings, such as adding an OAuth source to Kibana, aren't available directly. This ensures optimal performance and security. -Most settings are available for modifications and update from Kibana or by API, for example: +Settings you can change include: -- Managing users permissions -- Frequencies of back-ups -- The lifecycle of backups indexes -- Backups destination +- Manage user permissions +- Set backup frequency +- Manage the lifecycle of backup indexes +- Configure the backup destination -If you think your system might require some customization (like some plugins activation), contact Clever Cloud support to explain your use case and we will work with you to find a solution. +If your use case requires changing a native configuration file or another unavailable setting, contact [Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice) to discuss feasibility. ## Migrations and upgrades diff --git a/content/doc/addons/mongodb.md b/content/doc/addons/mongodb.md index 6b8ba9bf7..afa7a8a4c 100644 --- a/content/doc/addons/mongodb.md +++ b/content/doc/addons/mongodb.md @@ -82,20 +82,20 @@ Note that these features are available for all our databases add-ons, in additio ## 🔑 Rights and permissions -Add-ons are managed services, meaning that users have **standard access** to the database (role **owner**). Some operations like databases and users creation, as well as some settings modifications aren't available by default. This ensures optimal performances and security for managed services as configured by Clever Cloud. +Clever Cloud configures and maintains the MongoDB server. You have **standard access** to the database through the **owner** role, but some administrative operations and server settings aren't available directly. This ensures optimal performance and security. Authorized actions: + - Manage collections (create, delete…). - Manage indexes. - Manage documents. -If you think your system might require more advanced administrative access, [contact Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice) to explain your use case, and we will work with you to find a solution. +The following actions aren't available directly: -Here is the list of actions that you won't be able to perform: - Database administration (for example you won't be able to create new databases). -- Users administration (you won't be able to create other users than the one handled with our control plane, ie the base owner and read-only users). +- Users administration (you won't be able to create other users than the one handled with our control plane, i.e. the base owner and read-only users). - Server configuration update. - Cluster creation. - Backup frequency or retention control. -Ask Clever Cloud support if you want to perform one of these actions. +If your use case requires specific server parameters or one of these restricted operations, contact [Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice) to discuss feasibility. diff --git a/content/doc/addons/mysql.md b/content/doc/addons/mysql.md index d678df651..fbfbe619b 100644 --- a/content/doc/addons/mysql.md +++ b/content/doc/addons/mysql.md @@ -70,21 +70,21 @@ As Shared databases (DEV) are shared between multiple applications and delays co ## 🔑 Rights and permissions -Add-ons are managed services, meaning that users have **standard access** to the database (**ALL privileges**). Some operations like databases and users creation, as well as some settings modifications aren't available by default. This ensures optimal performances and security for managed services as configured by Clever Cloud. +Clever Cloud configures and maintains the MySQL server. You have **standard access** to the database with **ALL privileges**, but some administrative operations and server settings aren't available directly. This ensures optimal performance and security. Authorized actions: + - Manage tables (create, delete…). - Manage indexes. -If you think your system might require more advanced administrative access, contact [Clever Cloud Support](https://console.clever-cloud.com/ticket-center-choice) to explain your use case, and we will work with you to find a solution. +The following actions aren't available directly: -Here is the list of actions that you won't be able to perform: - Database administration (for example you won't be able to create new databases). - Users administration (you won't be able to create other users than the one handled with our control plane, i.e. the base owner and read-only users). - Server configuration update. - Plugins installation. - Replica creation. - Backup frequency or retention control. -- Create Trigger or Function (Only on DEV plan) +- Create triggers or functions (DEV plans only). -Ask Clever Cloud support if you want to perform one of these actions. +If your use case requires specific server parameters or one of these restricted operations, contact [Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice) to discuss feasibility. diff --git a/content/doc/addons/postgresql.md b/content/doc/addons/postgresql.md index 49c7d795f..7407861b8 100644 --- a/content/doc/addons/postgresql.md +++ b/content/doc/addons/postgresql.md @@ -170,23 +170,23 @@ If you want to use [pg_activity](https://github.com/dalibo/pg_activity) on a Pos ## 🔑 Rights and permissions -Add-ons are managed services, meaning that users have **standard access** to the database (role **owner**). Some operations like databases and users creation, as well as some settings modifications aren't available by default. This ensures optimal performances and security for managed services as configured by Clever Cloud. +Clever Cloud configures and maintains the PostgreSQL server. You have **standard access** to the database through the **owner** role, but some administrative operations and server settings aren't available directly. This ensures optimal performance and security. Authorized actions: + - Manage tables (create, delete…). - Manage schemas. - Manage indexes. - Access information from **pg_catalog** (except **pg_database** on DEV plan). - Access to basic maintenance operations such as *VACUUM* and *ANALYZE*. -If you think your system might require more advanced administrative access, [contact Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice) to explain your use case, and we will work with you to find a solution. +The following actions aren't available directly: -Here is the list of actions that you won't be able to perform: - Database administration (for example you won't be able to create new databases). - Users administration (you won't be able to create other users than the one handled with our control plane, i.e. the base owner and read-only users). - Server configuration update. - Extensions installation. - Replica creation. -- Back-up frequency or retention control. +- Backup frequency or retention control. -Ask Clever Cloud support if you want to perform one of these actions. +If your use case requires specific server parameters or one of these restricted operations, contact [Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice) to discuss feasibility. diff --git a/content/doc/addons/redis.md b/content/doc/addons/redis.md index f90fa7f0a..877249b03 100644 --- a/content/doc/addons/redis.md +++ b/content/doc/addons/redis.md @@ -59,18 +59,18 @@ please contact the support to change its policy. ## 🔑 Rights and permissions -Add-ons are managed services, meaning that users have **controlled access** to the server. They are granted access to all proposed operations except changing the server configuration. Based on the plan, they are granted access to a fix amount of databases. This ensures optimal performances and security for managed services as configured by Clever Cloud. +Clever Cloud configures and maintains the Redis server. You have **controlled access** to its operations, except those that change the server configuration. Your plan determines the number of databases you can access. This ensures optimal performance and security. Authorized actions: + - Access to one or more databases depending on your plan. - Access to all Redis operations except *CONFIG* and *CLUSTER*. -- Set up replica via clever cloud console. +- Set up a replica from the Clever Cloud Console. -If you think your system might require more advanced administrative access, [contact Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice) to explain your use case, and we will work with you to find a solution. +The following actions aren't available directly: -Here is the list of actions that you won't be able to perform: - Server configuration update. - Modules installation. - Backup frequency or retention control. -Ask Clever Cloud support if you want to perform one of these actions. +If your use case requires specific server parameters or one of these restricted operations, contact [Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice) to discuss feasibility. From e22cae111bbdde724f6ca2ca4533215c779ed594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Brunat?= Date: Tue, 12 May 2026 15:19:33 +0200 Subject: [PATCH 173/180] administrate(network): remove outdated Unique IP pricing --- content/doc/administrate/network.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/doc/administrate/network.md b/content/doc/administrate/network.md index 0966c52e6..2f6152902 100644 --- a/content/doc/administrate/network.md +++ b/content/doc/administrate/network.md @@ -27,7 +27,7 @@ This service allows your queries to some external services to come from a fixed This service does not appear in the Console at the moment. The best is to ask the support team that will set it up for you and provide you with the needed information. -At the time of writing this doc, this service was billed 30€/month. +For pricing details, contact your sales representative or [Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice). The price does not change with the number of applications that will use it. The IP depends on the zone, so ask the support about it. From b88aa5281820932c9e15bc86e4c8067606c216ae Mon Sep 17 00:00:00 2001 From: Julie POUNY Date: Tue, 19 May 2026 11:32:24 +0200 Subject: [PATCH 174/180] develop(build cache): warn against disabling uploads on nano and pico --- content/doc/administrate/apps-management.md | 5 ++--- content/doc/develop/build-hooks.md | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/content/doc/administrate/apps-management.md b/content/doc/administrate/apps-management.md index ebfc53a04..1ffd6fd54 100644 --- a/content/doc/administrate/apps-management.md +++ b/content/doc/administrate/apps-management.md @@ -28,9 +28,8 @@ Stop functionality is useful during the development of the application to limit ![Manage your application from the Console](/images/app-management.png) -{{< callout type="info" >}} - If you set [`CC_DISABLE_BUILD_CACHE_UPLOAD`](/doc/develop/env-variables/#settings-you-can-define-using-environment-variables) environment variable to `true`, the cache archive won't be created nor uploaded. -{{< /callout >}} +> [!NOTE] +> If you set [`CC_DISABLE_BUILD_CACHE_UPLOAD`](/doc/develop/env-variables/#settings-you-can-define-using-environment-variables) environment variable to `true`, the cache archive won't be created nor uploaded. Don't use it with `nano` and `pico` plans which always use a build instance to create the build cache. ## Deploy an old commit diff --git a/content/doc/develop/build-hooks.md b/content/doc/develop/build-hooks.md index 2390cde7e..099613657 100644 --- a/content/doc/develop/build-hooks.md +++ b/content/doc/develop/build-hooks.md @@ -97,9 +97,8 @@ This hook is perfect for: - extra build steps that you want to cache (eg bundling your frontend assets) -{{< callout type="info" >}} - If you set [\`CC_DISABLE_BUILD_CACHE_UPLOAD\`](/doc/develop/env-variables/#settings-you-can-define-using-environment-variables) environment variable to `true`, the cache archive won't be created nor uploaded. -{{< /callout >}} +> [!NOTE] +> If you set [`CC_DISABLE_BUILD_CACHE_UPLOAD`](/doc/develop/env-variables/#settings-you-can-define-using-environment-variables) environment variable to `true`, the cache archive won't be created nor uploaded. Don't use it with `nano` and `pico` plans which always use a build instance to create the build cache. ### Pre Run From a83dcbb77273a1bb5884953d2ba86cb26339ddc2 Mon Sep 17 00:00:00 2001 From: Rachel Nascimento Date: Wed, 6 May 2026 18:07:38 +0200 Subject: [PATCH 175/180] administrate(network): document dedicated load balancers --- content/doc/administrate/network.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/content/doc/administrate/network.md b/content/doc/administrate/network.md index 2f6152902..50590f26c 100644 --- a/content/doc/administrate/network.md +++ b/content/doc/administrate/network.md @@ -47,6 +47,24 @@ We provide three kinds of VPN technologies: If you are interested, please ask the support / your sales contact for a quote. +### Dedicated load balancers + +By default, incoming traffic to your applications goes through Clever Cloud's shared load balancers, powered by [Sōzu](https://www.sozu.io/). For workloads that need isolated capacity, fixed inbound IP addresses or additional redundancy, you can request **dedicated load balancers**. + +Dedicated load balancers are especially relevant if you need to: + +- **Isolate your traffic** from other customers +- **Use fixed inbound IP addresses** for allowlists or compliance requirements +- **Handle high traffic volumes** without sharing load balancer capacity with other organisations +- **Add redundancy** to the network entry point of critical architectures + +Two configurations are available: + +- **Single load balancer**: one load balancer dedicated to your organisation. +- **High availability**: two dedicated load balancers make the network entry layer redundant, so it doesn't depend on a single load balancer. This configuration is recommended for critical architectures. + +This is a custom, quote-based option. To discuss your requirements and pricing, contact your sales representative or [Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice) with your use case and target region. + ## The "Paris" region The Paris region is owned and handled by Clever Cloud. We own or entrust the associated AS's and From 67dae36bd9f2cf7450cf6a5d03993d1a5e68b9f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Brunat?= Date: Tue, 12 May 2026 15:25:26 +0200 Subject: [PATCH 176/180] addons(mysql): document replication --- content/doc/addons/mysql.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/content/doc/addons/mysql.md b/content/doc/addons/mysql.md index fbfbe619b..ce765a61c 100644 --- a/content/doc/addons/mysql.md +++ b/content/doc/addons/mysql.md @@ -42,6 +42,15 @@ If you need to import a very large dump, contact [Clever Cloud Support](https:// {{% content "db-migration" %}} +## Replication + +You can add up to two replicas to an existing MySQL database on Clever Cloud to enhance performance and reliability. Replication is available for MySQL 5.7 and later versions. Read-only replicas use [logical replication based on the binary log](https://dev.mysql.com/doc/refman/8.4/en/binlog-replication-configuration-overview.html) and can be deployed in a different availability zone (AZ) or region on request. + +If a primary server isn't available, a replica can be promoted as a standalone server and linked to applications. + +> [!NOTE] +> Replica creation and promotion aren't yet available through the API or the Console. To create or configure replicas, or to promote one, contact your sales representative or [Clever Cloud support](https://console.clever-cloud.com/ticket-center-choice). + ## Direct access {{< callout type="warning">}} From 6b2ec174ed0c3335369f02b09e038a16fff59e6b Mon Sep 17 00:00:00 2001 From: Pierre Zemb Date: Mon, 30 Mar 2026 10:03:14 +0200 Subject: [PATCH 177/180] docs(kv): add distributed behaviour and usage guide Co-Authored-By: Claude Opus 4.6 (1M context) --- content/doc/addons/materia-kv.md | 96 ++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 6 deletions(-) diff --git a/content/doc/addons/materia-kv.md b/content/doc/addons/materia-kv.md index 251efdc56..82a523fee 100644 --- a/content/doc/addons/materia-kv.md +++ b/content/doc/addons/materia-kv.md @@ -11,6 +11,9 @@ keywords: - distributed storage - nosql database - high availability +- transaction conflicts +- hot-spotting +- foundationdb draft: false aliases: - /doc/addons/materia-db-kv/ @@ -77,12 +80,6 @@ redis-cli -h $KV_HOST -p $KV_PORT --tls You can also deploy Materia KV add-ons with [Terraform provider](https://registry.terraform.io/providers/CleverCloud/clevercloud/latest/docs/resources/materiadb_kv) (OpenTofu compatible). -{{< callout type="info" >}} - -**Materia KV is in Beta testing phase** Each add-on is limited to 128 MB of storage, requests sent to the server can't exceed 5 MB. - -{{< /callout >}} - {{% content "kv-explorer" %}} ## Using the Redis API compatible layer @@ -410,3 +407,90 @@ We've prepared a few examples to help you get started with Materia KV: * [Materia KV raw TCP Ruby demo](https://github.com/CleverCloud/mkv-raw-tcp-ruby) * [Materia KV PHP sessions with TTL demo](https://github.com/CleverCloud/php-sessions-kv-example) * [Materia KV write via Redis API, read via GraphQL](https://github.com/CleverCloud/kv-graphql-example) + +## How Materia KV differs from Redis + +Materia KV supports many Redis commands, but runs them on a distributed FoundationDB cluster instead of Redis's single-threaded engine. This changes how concurrent writes, key expiration and size limits behave. + +### Concurrent execution instead of a single thread + +FoundationDB uses a concurrency model called **optimistic concurrency control** (OCC). Every Redis command you send to Materia KV executes inside its own FoundationDB transaction. When a command runs, it starts a transaction that reads from a consistent snapshot of the database and buffers all writes locally. At commit time, the system checks whether another transaction has modified any key that the current transaction read since the snapshot. If so, the transaction aborts and retries automatically. If not, the writes apply atomically. + +This means multiple clients can operate on the database in parallel without locks. Most of the time, transactions commit on the first attempt and latency stays low. Contention only arises when two transactions touch overlapping keys within a short time window—and when it does, the system resolves it through retries rather than blocking. + +Because each command is its own transaction, two clients issuing `INCR counter` at the same time create two independent transactions that both read and write the same key—and one of them conflicts. This is the fundamental difference from Redis, where commands are queued and executed one at a time. + +A useful mental model: **your reads determine whether you can conflict, and your writes determine what other transactions conflict with.** A transaction that only reads never conflicts. A transaction that only writes (without reading first) never conflicts either. Conflicts arise specifically from read-then-write patterns on the same key range, which is exactly what commands like `INCR`, `HSET`, and `SADD` do internally. + +### Conflicts and automatic retries + +When two concurrent transactions read and write overlapping keys, FoundationDB detects a **conflict** at commit time. The system retries the losing transaction automatically—your Redis client receives a normal response if a retry succeeds, but the operation takes longer because it runs more than once behind the scenes. + +A transaction can't commit more than **5 seconds** after its first read. If a transaction can't commit within this window due to repeated conflicts or a long-running operation, it fails and returns an error to the client. Normal-priority operations retry up to **5 times** with a maximum delay of **500 ms** between attempts. If all retries are exhausted, the client receives an error. + +Under heavy contention, this retry mechanism causes **tail latency spikes**. Materia KV can serve concurrent requests across the cluster, but individual requests may occasionally take tens of milliseconds when retries occur. Monitoring your p99 latency is a good way to detect emerging contention in your workload. + +### Hot-spotting: the main pitfall + +A **hot spot** occurs when many concurrent operations target the same key or a narrow range of keys. Because all those transactions read and write overlapping data, they serialise through repeated conflicts and retries—effectively reducing throughput to sequential execution, but with the added overhead of each failed attempt. + +In Redis, hot keys constrain the throughput of its single execution thread. In Materia KV, a hot key can also trigger retries and increase latency for operations that touch it. + +#### Counters: `INCR` and `DECR` + +The `INCR` command reads the current value, adds one, and writes the result. This is a textbook read-modify-write cycle. When many clients increment the same counter concurrently, every transaction reads the same value and attempts to write a new one. Only one succeeds per commit round; the rest retry. Under high concurrency, most attempts fail on each round, and throughput drops significantly. + +If you need a high-throughput counter, consider **sharding** it across multiple keys. For example, maintain `counter:{0}` through `counter:{N}` and have each client pick a shard at random. To read the total, sum all shards. This trades read convenience for write scalability—a standard distributed systems technique known as **partition-local counters**. + +#### Concurrent writes to a Hash or Set + +When you call `HSET myhash field1 value1` and another client calls `HSET myhash field2 value2` at the same time, you might expect no conflict because the fields are different. However, Materia KV maintains **internal cardinality indexes** to support commands like `HLEN` and `SCARD`. Every `HSET` or `SADD` on the same key updates this shared counter, causing conflicts between concurrent writers even when they target different fields or members. + +This means a single Hash or Set key that receives rapid concurrent writes becomes a hot spot, even if each writer touches a distinct field. If write throughput to a collection matters more than having a single logical key, consider splitting the collection across multiple keys (for example, `users:a-m` and `users:n-z`). + +#### Large batch operations + +Commands like `MSET` with many keys or `DEL` with many keys execute within a single FoundationDB transaction. The wider the key range touched, the larger the **conflict surface**—the set of keys that can cause other concurrent transactions to fail. Additionally, `DEL` caps at **100 keys per call**, and `MSET` with thousands of keys may exceed the **10 MB transaction size limit**. + +Break large batch operations into smaller chunks when working with many keys. This reduces the conflict window and keeps each transaction within size limits. + +#### Time to Live (TTL) refresh storms + +Web frameworks commonly call `EXPIRE` on a session key for every HTTP request to keep the session alive. When a user has multiple browser tabs open, each tab generates its own requests. The number of concurrent `EXPIRE` calls on the same session key multiplies with every open tab, and each `EXPIRE` is a write transaction. Concurrent calls conflict and retry, and under sustained load the retry budget runs out and the client receives errors. + +Before calling `EXPIRE`, call `TTL` or `PTTL` to read the remaining TTL. Read-only operations don't conflict. Only issue the `EXPIRE` if the TTL has dropped by a meaningful threshold, such as 60 seconds. This reduces redundant writes and the risk of conflicts. + +#### General guidance + +The common thread across all these scenarios is **write concentration**. Distribute your writes across the keyspace rather than funnelling them through a single key or a narrow prefix. When multiple clients need to update related data, partition the work so that each transaction touches a different subset of keys. If a write might be redundant—refreshing a TTL that has barely changed, or setting a value identical to the current one—guard it with a read first. Reads never cause conflicts and cost far less in a distributed transaction engine than unnecessary writes that trigger retries. + +### Key expiration on access only + +Redis expires keys using two mechanisms: an active background process that periodically samples keys with a TTL and deletes expired ones, and a lazy check that removes expired keys when they're accessed. Materia KV uses **lazy deletion only**. An expired key leaves the database when a client attempts to read or write it—not before. + +This has a few practical consequences. Expired keys continue to consume storage until they're accessed. The `DBSIZE` command may report a count that includes expired-but-not-yet-deleted keys. If your workload creates many short-lived keys that are never read again, those keys accumulate until accessed or until the database is flushed. + +The TTL itself is stored as an absolute Unix timestamp in milliseconds. The server clock is authoritative—clock differences between your application and Materia KV don't affect correctness. + +Active background cleanup of expired keys is under active development. Future releases include a background process that reclaims storage from expired keys without waiting for client access. + +### Stricter size limits + +Materia KV enforces stricter size limits than Redis due to the constraints of the underlying distributed transaction engine. + +| Limit | Redis | Materia KV | +|-------|-------|------------| +| Max key size | 512 MB | 8 KB | +| Max value size | 512 MB | 5 MB | +| Max transaction size | N/A | 10 MB (reads + writes) | +| Transaction duration | N/A | 5 seconds | +| Max keys per `DEL` | No limit | 100 | +| Storage per add-on | Plan-dependent | 128 MB (during Beta, can be increased) | + +The system enforces the 8 KB key limit and 5 MB value limit at the application level. The 10 MB transaction limit and 5-second duration are FoundationDB constraints that apply to the sum of all data read and written within a single command's transaction. + +### Stronger durability by default + +Materia KV keeps each command atomic through a serialisable FoundationDB transaction. Successful writes are synchronously replicated across three data centres in Paris, reducing exposure to a single-site failure. + +Redis durability depends on its persistence and replication configuration. Materia KV doesn't expose `fsync`, `appendonly` or replication settings: durability is part of the managed service. The trade-off is the conflict and retry mechanism described above. Distributed coordination under contention costs latency. From 7c739587a90891eb3a573a3477aaeb33ac1a5833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Brunat?= Date: Wed, 19 Aug 2026 19:56:24 +0200 Subject: [PATCH 178/180] develop(request-flow): document OAuth2 Proxy configuration Co-authored-by: David Legrand --- content/doc/applications/python/uv.md | 7 +- content/doc/develop/_index.md | 9 +- content/doc/develop/oauth2-proxy.md | 156 ++++++++++++++++++ content/doc/develop/request-flow.md | 46 +++--- .../reference-environment-variables.md | 2 + shared/request-flow.md | 10 +- 6 files changed, 200 insertions(+), 30 deletions(-) create mode 100644 content/doc/develop/oauth2-proxy.md diff --git a/content/doc/applications/python/uv.md b/content/doc/applications/python/uv.md index 51fe77282..2933ad892 100644 --- a/content/doc/applications/python/uv.md +++ b/content/doc/applications/python/uv.md @@ -40,22 +40,23 @@ The uv cache (`~/.cache/uv`) is included in the build cache to speed up subseque ## Run phase -The application starts with the command defined in `CC_PYTHON_UV_RUN_COMMAND`. It must start an HTTP server listening on `0.0.0.0:8080`. +The application starts with the command defined in `CC_PYTHON_UV_RUN_COMMAND`. It must start an HTTP server listening on `0.0.0.0:$PORT`. Clever Cloud sets `PORT` to `8080` by default. Set it to `9000` when you enable at least one [Request Flow middleware](/doc/develop/request-flow/#port-management). `CC_RUN_COMMAND` takes precedence over `CC_PYTHON_UV_RUN_COMMAND` if both are set. | Name | Description | Required | Default | -|------|-------------|----------|---------| +| ---- | ----------- | -------- | ------- | | `CC_PYTHON_UV_RUN_COMMAND` | Command to start the application (e.g. `uv run python app.py`) | Yes | - | | `CC_RUN_COMMAND` | Overrides `CC_PYTHON_UV_RUN_COMMAND` if set | No | - | | `ENVIRONMENT` | Set to `development` to include dev dependencies during build | No | `production` | +| `PORT` | Port used by your HTTP server | No | `8080` | ## Differences with legacy Python deployment With native uv deployment: - No Nginx, uWSGI, or Gunicorn is involved -- Your application listens on port `8080` (not `9000`) +- Your application manages its HTTP server and listens on the port defined by `PORT` - `CC_PYTHON_MODULE` and `CC_PYTHON_BACKEND` are ignored - [Redirection.io, Varnish and custom proxies](/doc/develop/request-flow/) are configured through Request Flow, not Nginx - [Server configuration](/doc/applications/python/servers/) settings do not apply diff --git a/content/doc/develop/_index.md b/content/doc/develop/_index.md index 1bb08f165..c59fd4718 100644 --- a/content/doc/develop/_index.md +++ b/content/doc/develop/_index.md @@ -16,13 +16,14 @@ aliases: --- {{< cards >}} + {{< card link="/developers/doc/develop/tasks" title="Clever Tasks" icon="play-circle" >}} + {{< card link="/developers/doc/develop/healthcheck" title="Deployment healthcheck path" icon="check" >}} {{< card link="/developers/doc/develop/build-hooks" title="Deployment hooks" icon="rocket-launch" >}} {{< card link="/developers/doc/reference/reference-environment-variables" title="Environment variables reference" icon="creds" >}} {{< card link="/developers/doc/develop/env-variables" title="How environment variables work" icon="question-mark-circle" >}} - {{< card link="/developers/doc/develop/workers" title="Workers" icon="arrow-path" >}} - {{< card link="/developers/doc/develop/tasks" title="Clever Tasks" icon="play-circle" >}} - {{< card link="/developers/doc/develop/healthcheck" title="Deployment healthcheck path" icon="check" >}} + {{< card link="/developers/doc/develop/network-groups" title="Network Groups" icon="tcp-ip-service" >}} + {{< card link="/developers/doc/develop/oauth2-proxy" title="OAuth2 Proxy" icon="lock-closed" >}} {{< card link="/developers/doc/develop/request-flow" title="Request Flow" icon="traffic-light" >}} {{< card link="/developers/doc/develop/varnish" title="Varnish as HTTP cache" icon="arrow-trending-up" >}} - {{< card link="/developers/doc/develop/network-groups" title="Network Groups" icon="tcp-ip-service" >}} + {{< card link="/developers/doc/develop/workers" title="Workers" icon="arrow-path" >}} {{< /cards >}} diff --git a/content/doc/develop/oauth2-proxy.md b/content/doc/develop/oauth2-proxy.md new file mode 100644 index 000000000..a7f29c82f --- /dev/null +++ b/content/doc/develop/oauth2-proxy.md @@ -0,0 +1,156 @@ +--- +type: docs +linkTitle: OAuth2 Proxy +title: OAuth2 Proxy +description: Add OAuth2 Proxy authentication to applications through Request Flow on Clever Cloud without changing application code +keywords: +- oauth2-proxy +- authentication +- oidc +- sso +- request flow +- reverse proxy +--- + +## Overview + +[Request Flow](/doc/develop/request-flow/) puts authentication in front of any application through [OAuth2 Proxy](https://oauth2-proxy.github.io/oauth2-proxy/), with no change to your code: a few environment variables are enough. Clever Cloud starts the middleware, allocates its port, points it at your application, and keeps it in the chain alongside Varnish, Redirection.io or a proxy of your own with the same flexibility. It's available on every runtime that supports Request Flow. + +OAuth2 Proxy redirects unauthenticated visitors to the identity provider you configure, and only authenticated requests reach your application. This suits a staging environment, an internal dashboard or an administration interface. + +Clever Cloud sets the port OAuth2 Proxy listens on and the address of your application. Every other setting comes from `OAUTH2_PROXY_*` environment variables, which map one to one to the [OAuth2 Proxy configuration options](https://oauth2-proxy.github.io/oauth2-proxy/configuration/overview). + +## Enable OAuth2 Proxy + +Add `oauth2-proxy` to the `CC_REQUEST_FLOW` environment variable: + +```bash +CC_REQUEST_FLOW="oauth2-proxy" +``` + +Request Flow allows you to use OAuth2 Proxy with other middleware such as [Varnish](/doc/develop/varnish/) or [Redirection.io](https://redirection.io/) for example. List every middleware in the order you want, from the public port to your application: + +```bash +CC_REQUEST_FLOW="oauth2-proxy,varnish" +``` + +## Minimal configuration + +OAuth2 Proxy validates its configuration at startup and exits when a required setting is missing, which fails your deployment. The following variables are the smallest working set for a GitHub identity provider, and the same structure applies to every provider. Replace every value with your own: + +```bash +CC_REQUEST_FLOW="oauth2-proxy" +OAUTH2_PROXY_PROVIDER="github" +OAUTH2_PROXY_CLIENT_ID="" +OAUTH2_PROXY_CLIENT_SECRET="" +OAUTH2_PROXY_COOKIE_SECRET="" +OAUTH2_PROXY_EMAIL_DOMAINS="example.com" +OAUTH2_PROXY_REDIRECT_URL="https://app.example.com/oauth2/callback" +``` + +The redirect URL must point to the public domain of your application, followed by `/oauth2/callback`. Declare that exact URL in your identity provider as an authorised callback, otherwise the provider rejects the login with a `redirect_uri mismatch` error. + +For an OpenID Connect provider such as Okta or Microsoft Entra ID, set the provider to `oidc` and give the issuer URL. The `email` scope feeds the address that authorization rules check, so request it explicitly: + +```bash +OAUTH2_PROXY_PROVIDER="oidc" +OAUTH2_PROXY_OIDC_ISSUER_URL="https://sso.example.com" +OAUTH2_PROXY_SCOPE="openid email profile" +``` + +Keycloak has a provider of its own, `keycloak-oidc`, pointing at the realm you want: + +```bash +OAUTH2_PROXY_PROVIDER="keycloak-oidc" +OAUTH2_PROXY_OIDC_ISSUER_URL="https://sso.example.com/realms/internal" +OAUTH2_PROXY_SCOPE="openid email profile" +``` + +> [!NOTE] +> The [OAuth2 Proxy client installation provider](https://github.com/please-openit/keycloak-oauth2proxy-client-installation-provider) writes these variables for you from your Keycloak client settings. Install the extension in the `providers` folder of your [Keycloak add-on](/doc/addons/keycloak/#custom-themes-and-plugins), then download "Oauth2-proxy environment variables" from the Installation tab of your client. [This video](https://www.youtube.com/watch?v=Jo-Njxsxq-8) presents it, in French. + +## Generate the cookie secret + +OAuth2 Proxy encrypts session cookies with an AES cipher and accepts a secret of 16, 24, or 32 bytes only. A hexadecimal string generated with `openssl rand -hex 32` counts as 64 bytes and gets rejected at startup. Generate a valid secret with: + +```bash +openssl rand -base64 32 | tr '+/' '-_' +``` + +Use a different secret for each application. Changing the secret invalidates every existing session and signs all users out. + +## Authorize users + +Authenticating a visitor and authorizing them are two different steps. Once the identity provider confirms who the visitor is, OAuth2 Proxy compares the email address it received against your authorization rules, and answers `403 Forbidden` when no rule matches, even though the login succeeded. + +One of these rules is mandatory at startup, which prevents an accidentally open proxy. Set `OAUTH2_PROXY_EMAIL_DOMAINS` to a comma-separated list of domains, prefixing a domain with a dot to include its subdomains, or set `OAUTH2_PROXY_AUTHENTICATED_EMAILS_FILE` to the path of a file listing one authorized address per line: + +```bash +OAUTH2_PROXY_EMAIL_DOMAINS="example.com,.corp.example.com" +``` + +The value `*` authorizes any address the provider validates, which means you rely entirely on the provider for identity checks. With an identity provider restricted to your organisation, this is the expected setting. With a public provider such as GitHub or Google, it accepts every account of that provider, so pick the scope you want: a domain list, an authenticated emails file, or a provider-level rule such as `OAUTH2_PROXY_GITHUB_ORG`. + +## Pass the identity to your application + +OAuth2 Proxy forwards the visitor identity to your application by default through `X-Forwarded-User`, `X-Forwarded-Groups`, `X-Forwarded-Email` and `X-Forwarded-Preferred-Username` headers. + +To also forward the OpenID Connect ID token in the `Authorization` header, enable: + +```bash +OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER="true" +``` + +This suits an application that validates the token itself. Only trust these identity headers on requests that have passed through OAuth2 Proxy. + +## Listen on the right port + +When your application manages its own HTTP server, it must listen on port `9000` while Request Flow is active. Clever Cloud handles the backend configuration for runtimes with a managed web server or port. In every other runtime, set the port yourself: + +```bash +PORT="9000" +``` + +> [!NOTE] +> This works as long as your application listens on `0.0.0.0:$PORT` rather than on a hardcoded port. Read the [Request Flow port management](/doc/develop/request-flow/#port-management) section for the details of the whole chain. + +## Keep the health check working + +The platform health check requests your application through the public port, which OAuth2 Proxy now answers. A redirect to the login page still counts as a healthy answer, so the default health check keeps working. + +This changes as soon as you configure [`CC_HEALTH_CHECK_PATH`](/doc/develop/healthcheck/), which expects a `2xx` status. An authenticated path never returns one to the health check, so exclude it from authentication: + +```bash +CC_HEALTH_CHECK_PATH="/health" +OAUTH2_PROXY_SKIP_AUTH_ROUTES="^/health$" +``` + +This exclusion makes the health check path publicly accessible. Limit its response to the application's health status and don't expose sensitive data. + +## Scale to several instances + +OAuth2 Proxy stores sessions in the cookie by default, so every instance of your application validates them without shared state. Horizontal scaling and instance replacement need no extra configuration. + +Tokens with many claims, particularly Microsoft Entra ID tokens carrying group memberships, can make sessions exceed the 4 kB limit for a single cookie. OAuth2 Proxy splits large sessions across several cookies, but the resulting headers can still exceed browser or proxy limits. In that case, store sessions in a [Redis add-on](/doc/addons/redis/) and copy its connection URL into the variable: + +```bash +OAUTH2_PROXY_SESSION_STORE_TYPE="redis" +OAUTH2_PROXY_REDIS_CONNECTION_URL="redis://:password@host:port" +``` + +## Troubleshooting + +OAuth2 Proxy logs its configuration errors and exits, so read the deployment logs from the bottom up. The health check reports the public port as closed, which is a consequence of the middleware being down rather than a problem with your application: + +| Log message | Cause | +| ----------- | ----- | +| `cookie_secret must be 16, 24, or 32 bytes` | The secret has the wrong length, see [Generate the cookie secret](#generate-the-cookie-secret) | +| `missing setting for email validation` | The configuration carries neither `OAUTH2_PROXY_EMAIL_DOMAINS` nor `OAUTH2_PROXY_AUTHENTICATED_EMAILS_FILE` | +| `Your application is not listening on 8080` | OAuth2 Proxy failed to start, the preceding message holds the reason | +| `Some software are not listening as expected: 9000` | OAuth2 Proxy runs, your application doesn't listen on port `9000` | + +A login that loops between your application and the identity provider points to `OAUTH2_PROXY_REDIRECT_URL` not matching the callback declared in the provider. A `403 Forbidden` after a successful login points to an email address outside your authorization rules, or to a missing `email` scope. + +- [Learn more about Request Flow](/doc/develop/request-flow/) +- [Learn more about OAuth2 Proxy configuration](https://oauth2-proxy.github.io/oauth2-proxy/) +- [Configure your health check](/doc/develop/healthcheck/) diff --git a/content/doc/develop/request-flow.md b/content/doc/develop/request-flow.md index fe63d3464..34ef2f009 100644 --- a/content/doc/develop/request-flow.md +++ b/content/doc/develop/request-flow.md @@ -23,10 +23,10 @@ Request Flow is Clever Cloud's automatic middleware chaining mechanism. It confi ## Supported services | Service | Activation | Description | -|---------|-----------|-------------| +| ------- | ---------- | ----------- | | `block` | `CC_REQUEST_FLOW="block"` | Blocks public access with a `200 OK` response. Other ports remain accessible through [Network Groups](/doc/develop/network-groups/) | -| `custom` | `CC_REQUEST_FLOW_CUSTOM` | Any custom reverse proxy | -| `oauth2-proxy` | `CC_REQUEST_FLOW="oauth2-proxy"` | Authentication proxy using [OAuth2 Proxy](https://oauth2-proxy.github.io/oauth2-proxy/) | +| `custom` | `CC_REQUEST_FLOW="custom"` | Any custom reverse proxy, started with `CC_REQUEST_FLOW_CUSTOM` | +| `oauth2-proxy` | `CC_REQUEST_FLOW="oauth2-proxy"` | Authentication proxy using [OAuth2 Proxy](/doc/develop/oauth2-proxy/) | | `otoroshi-challenge` | `OTOROSHI_CHALLENGE_SECRET` | [Otoroshi](/doc/addons/otoroshi/) challenge verification proxy | | `redirectionio` | `CC_REDIRECTIONIO_PROJECT_KEY` | HTTP redirects, rewrites, SEO | | `varnish` | `clevercloud/varnish.vcl` file or `CC_VARNISH_FILE` | HTTP cache accelerator | @@ -39,20 +39,26 @@ When no `CC_REQUEST_FLOW` is set, Clever Cloud detects and activates services au - If a `clevercloud/varnish.vcl` file exists (or `CC_VARNISH_FILE` is set), Varnish is activated - If `CC_REDIRECTIONIO_PROJECT_KEY` is set, Redirection.io is activated -All three can be active simultaneously. Default order: Otoroshi Challenge first, then Varnish, then Redirection.io. +When automatically detected, Otoroshi Challenge, Varnish and Redirection.io can run simultaneously, in this order: Otoroshi Challenge, Varnish, then Redirection.io. + +No automatic detection exists for `oauth2-proxy` and `custom`. Only `CC_REQUEST_FLOW` activates them. Setting this variable also replaces automatic detection for the whole chain, so a `CC_REQUEST_FLOW="oauth2-proxy"` on an application holding a `clevercloud/varnish.vcl` file starts OAuth2 Proxy alone. List every middleware you need: `CC_REQUEST_FLOW="oauth2-proxy,varnish"`. ## Port management -Request Flow allocates ports in a chain from port `8080` (public) down to the application: +Request Flow allocates middleware ports in a chain from port `8080` (public) down to your application. For runtimes where you configure the application HTTP server yourself: - With no middleware: your application listens directly on port `8080` - With one middleware: the middleware listens on `8080`, forwards to your application on port `9000` -- With two middleware: first listens on `8080`, forwards to second on `8081`, which forwards to the application on `9000` - -Your application must listen on port `8080` when no middleware is active, or on port `9000` when at least one middleware is configured. +- With two middleware services: the first listens on `8080`, forwards to the second on `8081`, which forwards to the application on `9000` > [!NOTE] -> In runtimes where Clever Cloud manages the port configuration (FrankenPHP, Java, PHP, Static), port allocation is handled transparently with no additional configuration. +> FrankenPHP, Java, PHP, legacy Python, Ruby and Static applications need no additional configuration, as Clever Cloud manages their web server or port transparently. Python applications using native uv support manage their own HTTP server and follow the port rule above. + +In every runtime where your application manages its own HTTP server, have it listen on `0.0.0.0:$PORT` and set `PORT` to `9000` when a middleware is active: + +```bash +PORT="9000" +``` ## Explicit configuration with CC_REQUEST_FLOW @@ -62,7 +68,7 @@ To control the order or selection of middleware, set `CC_REQUEST_FLOW` to a comm CC_REQUEST_FLOW="redirectionio,varnish" ``` -This inverts the default order: Redirection.io listens on `8080`, forwards to Varnish on `8081`, which forwards to the application on `9000`. +For an application that manages its own HTTP server, this inverts the default order: Redirection.io listens on `8080`, forwards to Varnish on `8081`, which forwards to the application on `9000`. ### Disable Request Flow @@ -84,12 +90,7 @@ CC_REQUEST_FLOW="block" ### Health check with block mode -By default, `block` responds `200 OK` regardless of your application's actual state. If [`CC_HEALTH_CHECK_PATH` or `CC_HEALTH_CHECK_PATH_0` to `CC_HEALTH_CHECK_PATH_5`](/doc/develop/healthcheck/) are configured, the blocking service also checks these paths on your application and responds accordingly: - -- `200 OK` if all configured paths return a `2xx` status -- `503 Service Unavailable` if the application is down or any path returns a non-`2xx` status - -This way, the platform's health check still reflects the actual state of your application even when public traffic is blocked. +With `block` enabled, the deployment health check only verifies that the blocking service listens on port `8080` and responds `200 OK`. It doesn't send an HTTP request to your application, including when you configure [`CC_HEALTH_CHECK_PATH` or `CC_HEALTH_CHECK_PATH_0` to `CC_HEALTH_CHECK_PATH_5`](/doc/develop/healthcheck/). ## Custom middleware @@ -100,7 +101,8 @@ CC_REQUEST_FLOW="redirectionio,custom,varnish" CC_REQUEST_FLOW_CUSTOM="./my-proxy --listen @@LISTEN_PORT@@ --forward @@FORWARD_PORT@@" ``` -In this example: +For an application that manages its own HTTP server, this example produces the following chain: + - Redirection.io listens on `8080`, forwards to custom middleware on `8081` - Custom middleware listens on `8081`, forwards to Varnish on `8082` - Varnish listens on `8082`, forwards to the application on `9000` @@ -108,15 +110,21 @@ In this example: ## Environment variables reference | Name | Description | -|------|-------------| +| ---- | ----------- | | `CC_REQUEST_FLOW` | Comma-separated list of middleware to chain (e.g. `varnish,redirectionio`). Special values: `disable`, `block` | | `CC_REQUEST_FLOW_CUSTOM` | Command to start a custom middleware. Must contain `@@LISTEN_PORT@@` and `@@FORWARD_PORT@@` placeholders | | `CC_REDIRECTIONIO_PROJECT_KEY` | Redirection.io project key. Activates Redirection.io in the request flow | | `CC_VARNISH_FILE` | Path to a custom Varnish VCL file (default: `clevercloud/varnish.vcl`) | | `OTOROSHI_CHALLENGE_SECRET` | Otoroshi challenge secret. Activates Otoroshi Challenge verification in the request flow | +## Troubleshooting + +A middleware that fails to start leaves the public port closed, and the deployment ends with `Your application is not listening on 8080`. This message names the public port of the chain, which belongs to the first middleware rather than to your application. Read the preceding deployment logs: the middleware logs its own error before exiting. + +The message `Some software are not listening as expected: 9000` means the opposite. Every middleware runs, and your application doesn't listen on the port the chain forwards to. Check the [port management](#port-management) section for the port your runtime expects. + - [Learn more about Varnish on Clever Cloud](/doc/develop/varnish/) - [Learn more about Redirection.io](https://redirection.io/) -- [Learn more about OAuth2 Proxy](https://oauth2-proxy.github.io/oauth2-proxy/) +- [Learn more about OAuth2 Proxy on Clever Cloud](/doc/develop/oauth2-proxy/) - [Learn more about Otoroshi on Clever Cloud](/doc/addons/otoroshi/) - [Learn more about Network Groups](/doc/develop/network-groups/) diff --git a/content/doc/reference/reference-environment-variables.md b/content/doc/reference/reference-environment-variables.md index 0cedc10b5..98ca417b8 100644 --- a/content/doc/reference/reference-environment-variables.md +++ b/content/doc/reference/reference-environment-variables.md @@ -111,6 +111,8 @@ Use these to define [commands to run](/doc/develop/build-hooks) between various |[`CC_METRICS_PROMETHEUS_PORT`](/doc/metrics/#publish-your-own-metrics "Publish your own metrics") | Define the port on which the Prometheus endpoint is available | 9100 | |[`CC_METRICS_PROMETHEUS_RESPONSE_TIMEOUT`](/doc/metrics/#publish-your-own-metrics "Publish your own metrics") | Define the timeout in seconds to collect the application metrics. This value **must** be below 60 seconds as data are collected every minutes | 3 | |[`CC_METRICS_PROMETHEUS_USER`](/doc/metrics/#publish-your-own-metrics "Publish your own metrics") | Define the user for the basic auth of the Prometheus endpoint | | +|[`CC_REQUEST_FLOW`](/doc/develop/request-flow "Request Flow") | Comma-separated list of middleware to chain between the public port and your application (`block`, `custom`, `oauth2-proxy`, `otoroshi-challenge`, `redirectionio`, `varnish`). Special values: `disable`, `block` | | +|[`CC_REQUEST_FLOW_CUSTOM`](/doc/develop/request-flow/#custom-middleware "Request Flow") | Command starting a custom middleware, with the `@@LISTEN_PORT@@` and `@@FORWARD_PORT@@` placeholders | | |[`CC_VARNISH_FILE`](/doc/develop/varnish "Cache") | The path to the Varnish configuration file, relative to your application root | `/clevercloud/varnish.vcl` | |[`CC_VARNISH_STORAGE_SIZE`](/doc/develop/varnish "Cache") | Configure the size of the Varnish cache. | 1G | |[`CC_WORKER_COMMAND`](/doc/develop/workers "Workers") | Command to run in background as a worker process. You can run multiple workers. | | diff --git a/shared/request-flow.md b/shared/request-flow.md index 1fe40c7f3..1aca34131 100644 --- a/shared/request-flow.md +++ b/shared/request-flow.md @@ -1,15 +1,17 @@ -## Request Flow: Varnish, Redirection.io, custom proxy +## Request Flow: Varnish, Redirection.io, OAuth2 Proxy, custom proxy -Request Flow automatically chains reverse proxies between port `8080` (public) and your application, managing port allocation with no manual configuration. Supported services are activated by their presence in your project: +Request Flow automatically chains reverse proxies between port `8080` (public) and your application, allocating middleware ports automatically. Some services are detected from your configuration, while others must be listed explicitly in `CC_REQUEST_FLOW`: - **Otoroshi Challenge**: set `OTOROSHI_CHALLENGE_SECRET` - **Varnish**: add a `clevercloud/varnish.vcl` file or set `CC_VARNISH_FILE` - **Redirection.io**: set `CC_REDIRECTIONIO_PROJECT_KEY` +- **OAuth2 Proxy**: set `CC_REQUEST_FLOW=oauth2-proxy` and its `OAUTH2_PROXY_*` settings -All three can be active simultaneously. To control the order, set `CC_REQUEST_FLOW` (e.g. `redirectionio,varnish`). To add a custom middleware, include `custom` in the chain and define `CC_REQUEST_FLOW_CUSTOM` with `@@LISTEN_PORT@@` and `@@FORWARD_PORT@@` placeholders. To block public access, set `CC_REQUEST_FLOW=block`. +Multiple services can run simultaneously. Setting `CC_REQUEST_FLOW` replaces automatic detection, so list every service you need in order (e.g. `oauth2-proxy,varnish`). To add a custom middleware, include `custom` in the chain and define `CC_REQUEST_FLOW_CUSTOM` with `@@LISTEN_PORT@@` and `@@FORWARD_PORT@@` placeholders. To block public access, set `CC_REQUEST_FLOW=block`. -When at least one middleware is active, your application must listen on port `9000` instead of `8080`. +If your application manages its own HTTP server, configure it to listen on port `9000` instead of `8080` when at least one middleware is active. Clever Cloud handles this automatically for runtimes with a managed web server. - [Learn more about Request Flow](/doc/develop/request-flow/) - [Learn more about Varnish on Clever Cloud](/doc/develop/varnish/) +- [Learn more about OAuth2 Proxy on Clever Cloud](/doc/develop/oauth2-proxy/) - [Learn more about Redirection.io](https://redirection.io/) From 5f859fbfac734fe8315d431a1c393b26484d6d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Brunat?= Date: Mon, 24 Aug 2026 14:59:33 +0200 Subject: [PATCH 179/180] chore(vale): update documentation vocabulary --- .github/styles/config/vocabularies/Doc/accept.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/styles/config/vocabularies/Doc/accept.txt b/.github/styles/config/vocabularies/Doc/accept.txt index 148795ae7..bbc33985b 100644 --- a/.github/styles/config/vocabularies/Doc/accept.txt +++ b/.github/styles/config/vocabularies/Doc/accept.txt @@ -32,6 +32,7 @@ DNS Dockerfile downtimes eg +Entra EOL ESLint failover @@ -45,6 +46,7 @@ Glassfish Gradle gradle Grafana +hardcoded healthcheck Heptapod Hextra @@ -65,6 +67,7 @@ Materia Matomo maven Metabase +middleware monolog monorepo monorepository @@ -76,6 +79,7 @@ nmap npm Nuxt OAuth +Okta Otoroshi packageManager Payara From 4b86c5d2d51c5415535af7f0d93bafe7ee5d3369 Mon Sep 17 00:00:00 2001 From: Julie POUNY Date: Wed, 4 Feb 2026 22:33:35 +0100 Subject: [PATCH 180/180] docs: add Otoroshi advanced features guide (WAF, reverse proxy, rate limiting, canary) --- content/guides/otoroshi.md | 304 +++++++++++++++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 content/guides/otoroshi.md diff --git a/content/guides/otoroshi.md b/content/guides/otoroshi.md new file mode 100644 index 000000000..87c5f8578 --- /dev/null +++ b/content/guides/otoroshi.md @@ -0,0 +1,304 @@ +--- +type: docs +linkTitle: Otoroshi Advanced Features +title: Otoroshi Advanced Features Guide +description: Configure and use Otoroshi advanced features including WAF, Reverse Proxy, Rate Limiting, and Canary Deployments with detailed tutorials and best practices +keywords: +- otoroshi +- api gateway +- waf +- reverse proxy +- canary deployment +- rate limiting +- load balancing +--- + +Otoroshi is a modern API Gateway that provides powerful features to secure, manage, and optimize your services. + +This guide covers the main features tested and validated for deployment on Clever Cloud. + +--- + +## WAF (Web Application Firewall) + +Otoroshi integrates **Coraza**, an open-source WAF compatible with ModSecurity rules and compliant with OWASP recommendations. It filters and blocks malicious requests before they reach your applications. + +### Configuration + +#### Step 1: Create a WAF Item + +1. Navigate to **Categories → WAF → WAF Config** +2. Click **Add item** +3. Add your security directives + +#### Example Directives + +``` +SecRuleEngine On +SecRule REQUEST_HEADERS:X-Forwarded-For "@rx " "id:1001,deny,status:403,log,msg:'IP bloquee'" +``` + +**Directive Explanations:** + +- **`SecRuleEngine On`** : Enables the Coraza rules engine. Without this directive, no rules are evaluated +- **`SecRule REQUEST_HEADERS:X-Forwarded-For`** : Rule applied in phase 1 (header analysis) that reads the real client IP from the `X-Forwarded-For` header, required because requests go through Clever Cloud load balancers +- **`@rx `** : Regex operator that matches the header value against the provided IPv4 address +- **`id:1001`** : Mandatory unique identifier for the rule +- **`phase:1`** : Evaluation during the header analysis phase, before request body processing +- **`deny`** : Disruptive action that rejects the request +- **`status:403`** : HTTP status code returned to the blocked client +- **`log`** : Records the event in the logs +- **`msg:'IP blocked'`** : Message associated with the event in the logs + +**Further Reading:** +- 📖 [Coraza SecLang Documentation](https://coraza.io/docs/seclang/) — Full reference for all directives, operators, variables and actions +- 🛡️ [OWASP Core Rule Set with Coraza](https://coraza.io/docs/tutorials/coreruleset/) — How to enable and configure the OWASP CRS for broader WAF protection + +{{< callout type="warning" >}} +**Important** : Each directive must be on a **separate line** in the directives field. Mixing multiple directives in a single entry will cause a WASM runtime crash and may lead to Out of Memory errors on your Otoroshi instance. +{{< /callout >}} + +{{< callout type="info" >}} +**Note** : Using `REQUEST_HEADERS:X-Forwarded-For` instead of `REMOTE_ADDR` is mandatory behind a load balancer. `REMOTE_ADDR` would contain the load balancer's IP address instead of the real client IP. + +Additionally, **IPv4 addresses are required**. IPv6 addresses are not reliably supported by the Coraza WASM module embedded in Otoroshi. Use `curl -4 ifconfig.io` to retrieve your IPv4 address. +{{< /callout >}} + +#### Step 2: Create and Configure a Route + +1. Navigate to **Shortcuts → Routes → Create new route** +2. Configure the **Frontend** (public entry point): + - Public URL used by your clients (set on Otoroshi addon) +3. Configure the **Backend** (actual service): + - Internal URL of your application that will process requests (set on your application) +4. Add the **WAF** in the "Plugins" section + +{{< callout type="info" >}} +The frontend DNS must point to Otoroshi, not directly to your application. The backend URL is used by Otoroshi to proxy requests after applying WAF rules. +{{< /callout >}} + +--- + +## Reverse Proxy + +### Overview + +Otoroshi natively supports multiple protocols: + +- **HTTP/HTTPS**: Standard web requests +- **TCP**: Raw TCP connections (databases, custom services) +- **gRPC**: Modern RPC protocol based on HTTP/2 + +### Configuring a Simple Reverse Proxy + +Otoroshi's versatile protocol support allows it to act as a reverse proxy for various types of services: + +1. **Create a route** in Otoroshi +2. **Configure the Frontend**: + - Public hostname, port, and path (set on Otoroshi addon) +3. **Configure the Backend**: + - IP + port or URL of your actual service (set on your application) + +Otoroshi intercepts incoming requests, applies your rules (security, rate limiting, etc.), and forwards them to the backend. + +**Use Cases:** +- Proxy HTTP/HTTPS requests to web applications +- Forward TCP connections to databases or custom services +- Route gRPC calls to microservices + +### Frontend vs Backend + +**Frontend**: The public entry point that clients use to access your service. This is the URL your users will call. + +**Backend**: The actual internal address of your application/service that processes requests. Otoroshi proxies requests to this address after filtering through WAF and other plugins. + +{{< callout type="info" >}} +Your DNS must point the frontend domain to Otoroshi, not to your backend application. The backend URL is used internally by Otoroshi to forward filtered traffic. +{{< /callout >}} + +--- + +## Rate Limiting & Custom Quotas + +### Overview + +Rate limiting protects your services from abuse by controlling the number of requests allowed per time period. Otoroshi provides flexible quota management based on various criteria. + +### Implementation + +To limit the number of requests per IP or per user: + +1. Navigate to your route's configuration +2. Add the **"Custom quotas"** plugin in the "Plugins" section +3. Configure the following parameters: + - **Quota**: Number of authorized requests + - **Period**: Time window (per second, minute, hour, day) + - **Criteria**: IP address, API key, user, etc. + +**Common Use Cases:** +- Protect your APIs against abuse and brute force attacks +- Manage differentiated quotas for commercial tiers +- Mitigate DDoS attempts +- Implement fair usage policies (free vs paid tiers) + +### Configuration Example + +```json +{ + "throttling_quota": "1000", + "throttling_period": "3600", + "throttling_by": "ip" +} +``` + +This configuration allows **1000 requests per hour per IP address**. + +### Additional Options + +You can customize rate limiting based on: +- **IP address**: Limit per visitor +- **API key**: Different quotas per client +- **User**: Authenticated user limits +- **Custom header**: Advanced filtering + +--- + +## Canary Deployments + +Canary mode allows you to test a new version of your application on a percentage of traffic before a complete deployment. This technique reduces risk by gradually exposing new code to production users. + +### Method 1: Route-Level Configuration with Plugin (Recommended) + +This method provides more granular control and is easier to configure for single routes. + +#### Configuration Steps + +1. **Create a new route** +2. **Select the "Canary Mode" plugin** +3. **Configure the following parameters**: + - **Frontend**: Public URL (set on Otoroshi addon) + - **Canary mode**: + - **Traffic**: 0.2 for 20% redirection to canary (or 0.5 for 50%) + - **Targets**: + - Hostname: `app-canary.cleverapps.io` + - Port: `443` + - Weight: `1` + - **Backend**: Stable application URL (e.g., `app-stable.cleverapps.io`) + +#### Complete Test Script + +```sh +#!/bin/bash + +echo "=== Otoroshi Canary Mode Test ===" +echo "Date: $(date)" +echo "" + +STABLE=0 +CANARY=0 +UNKNOWN=0 + +# Replace with your actual Otoroshi frontend URL +FRONTEND_URL="http://myapp.mydomain.com" + +for i in {1..100}; do + RESPONSE=$(curl -L -s "$FRONTEND_URL") + + if echo "$RESPONSE" | grep -q "STABLE"; then + ((STABLE++)) + elif echo "$RESPONSE" | grep -q "CANARY"; then + ((CANARY++)) + else + ((UNKNOWN++)) + fi + + echo -n "." +done + +echo "" +echo "" +echo "Results over 100 requests:" +echo " → Stable: $STABLE" +echo " → Canary: $CANARY" +echo " → Unknown: $UNKNOWN" +echo "" + +PERCENT_CANARY=$((CANARY)) +echo "Canary rate: ${PERCENT_CANARY}%" +``` + +#### Expected Results + +- Traffic at **0.2** (20%): ~20% to CANARY, ~80% to STABLE +- Traffic at **0.5** (50%): ~50% to CANARY, ~50% to STABLE + +{{< callout type="warning" >}} +Always monitor error rates and latency when testing canary deployments. Start with a low percentage (5-10%) and gradually increase if metrics remain healthy. +{{< /callout >}} + +--- + +### Method 2: Service-Level Configuration + +This method is useful for managing multiple routes with the same canary configuration. + +#### Configuration Steps + +1. **Create a new Service**: + ``` + Name: "My Service" + Description: "Service with Canary" + ``` + +2. **Service Exposition Settings**: + ``` + Exposed domain: myapp.mydomain.com + Legacy domain: true + Strip path: true + ``` + +3. **Service Targets**: + ``` + Load balancing: WeightBestResponseTime + Weight ratio: 0.2 + + Target 1: app-stable.cleverapps.io (STABLE) + Target 2: app-canary.cleverapps.io (CANARY) + ``` + +4. **URL Patterns**: + ``` + Public patterns: "/.*" + ``` + +#### Testing + +```sh +# Replace with your actual frontend URL +out=$(for i in {1..200}; do curl -s https://myapp.mydomain.com | grep Version; done); +echo "CANARY: $(echo "$out" | grep -c CANARY)" +echo "STABLE: $(echo "$out" | grep -c STABLE)" +``` + +Expected Result: ~40 requests to CANARY, ~160 to STABLE (20/80 ratio) +{{< callout type=“info” >}} +Traffic Flow: Client → your-domain.com → Otoroshi → { stable / canary } +{{< /callout >}} + +### Load Balancing Strategies + +When using canary deployments, you can choose from several load balancing algorithms: +- WeightBestResponseTime: Routes traffic based on response time and weights +- RoundRobin: Distributes requests evenly across targets +- Random: Randomly selects a target for each request +- IpAddressHash: Routes based on client IP (sticky sessions) + +--- +## Resources + +- Official Otoroshi Documentation : https://maif.github.io/otoroshi/ +- Clever Cloud Documentation : https://www.clever.cloud/developers/doc/ +- OWASP ModSecurity Core Rule Set : https://owasp.org/www-project-modsecurity-core-rule-set/ +- Coraza WAF Documentation : https://coraza.io/ +- API Gateway Patterns : https://microservices.io/patterns/apigateway.html +