# Install

Convinced by Onyxia? Let's see how you can get your own instance today!

{% hint style="warning" %}
If you are only interested in deploying Onyxia for it's S3 explorer, start [here](/admin-doc/readme/s3-explorer-standalone-deployment).
{% endhint %}

{% hint style="info" %}

## Oneliner

If you are already familiar with Kubernetes and Helm, here's how you can get an Onyxia instance up and running in just a matter of seconds.

```bash
helm repo add onyxia https://inseefrlab.github.io/onyxia

cat << EOF > ./onyxia-values.yaml
ingress:
  enabled: true
  hosts:
    - host: onyxia.my-domain.net
EOF

helm install onyxia onyxia/onyxia -f onyxia-values.yaml

# Navigate to https://onyxia.my-domain.net
```

With this minimal configuration, you'll have an Onyxia instance operating in a degraded mode, which lacks features such as authentication, S3 explorer, secret management, etc. However, you will still retain the capability to launch services from the catalog.
{% endhint %}

Whether you are a Kubernetes veteran or a beginner with cloud technologies, this guide aims to guide you through the instantiation and configuration of an Onyxia instance with it's full range of features enabled. Let's dive right in! 🤿

First let's make sure we have a suitable deployment environement to work with!

{% content-ref url="/pages/LvC5vcZc9pkCe267bM3D" %}
[Kubernetes](/admin-doc/readme/kubernetes)
{% endcontent-ref %}


# Kubernetes

Provision a Kubernetes cluster

First you'll need a Kubernetes cluster. If you have one already you can skip and directly go to [the Onyxia installation section](/admin-doc/readme/gitops).

{% tabs %}
{% tab title="Provisioning a cluster on AWS, GCP or Azure" %}
[Hashicorp](https://www.hashicorp.com/) maintains great tutorials for [terraforming](https://www.terraform.io/) Kubernetes clusters on [AWS](https://aws.amazon.com/what-is-aws/), [GCP](https://cloud.google.com/) or [Azure](https://acloudguru.com/videos/acg-fundamentals/what-is-microsoft-azure).

Pick one of the three and follow the guide.

You can stop after the [configure kubectl section](https://learn.hashicorp.com/tutorials/terraform/eks#configure-kubectl).

{% embed url="<https://developer.hashicorp.com/terraform/tutorials/kubernetes/eks>" %}

{% embed url="<https://developer.hashicorp.com/terraform/tutorials/kubernetes/gke?in=terraform%2Fkubernetes>" %}

{% embed url="<https://developer.hashicorp.com/terraform/tutorials/kubernetes/aks?in=terraform%2Fkubernetes>" %}

**Ingress controller**

Let's install ingress-ngnix on our newly created cluster:

{% hint style="warning" %}
The following command is [for AWS](https://kubernetes.github.io/ingress-nginx/deploy/#aws).

For GCP use [this command](https://kubernetes.github.io/ingress-nginx/deploy/#gce-gke).

For Azure use [this command](https://kubernetes.github.io/ingress-nginx/deploy/#azure).
{% endhint %}

```bash
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.2.0/deploy/static/provider/aws/deploy.yaml
```

**DNS**

Let's assume you own the domain name **my-domain.net**, for the rest of the guide you should replace **my-domain.net** by a domain you actually own.

Now you need to get the external address of your cluster, run the command

```bash
kubectl get services -n ingress-nginx
```

and write down the `External IP` assigned to the `LoadBalancer`.

Depending on the cloud provider you are using it can be an IPv4, an IPv6 or a domain. On AWS for example, it will be a domain like **xxx.elb.eu-west-1.amazonaws.com**.

If you see `<pending>`, wait a few seconds and try again.

Once you have the address, create the following DNS records:

```dns-zone-file
datalab.my-domain.net CNAME xxx.elb.eu-west-1.amazonaws.com. 
*.lab.my-domain.net   CNAME xxx.elb.eu-west-1.amazonaws.com. 
```

If the address you got was an IPv4 (`x.x.x.x`), create a `A` record instead of a CNAME.

If the address you got was ans IPv6 (`y:y:y:y:y:y:y:y`), create a `AAAA` record.

**<https://datalab.my-domain.net>** will be the URL for your instance of Onyxia. The URL of the services created by Onyxia are going to look like: **https\://\<something>.lab.my-domain.net**

{% hint style="info" %}
You can customise "**datalab**" and "**lab**" to your liking, for example you could chose **onyxia.my-domain.net** and **\*.kub.my-domain.net**.
{% endhint %}

**SSL**

In this section we will obtain a TLS certificate issued by [LetsEncrypt](https://letsencrypt.org/) using the [certbot](https://certbot.eff.org/) commend line tool then get our ingress controller to use it.

If you are already familiar with `certbot` you're probably used to run it on a remote host via SSH. In this case you are expected to run it on your own machine, we'll use the DNS chalenge instead of the HTTP chalenge.

```bash
brew install certbot #On Mac, lookup how to install certbot for your OS

#Because we need a wildcard certificate we have to complete the DNS callange.  
sudo certbot certonly --manual --preferred-challenges dns

# When asked for the domains you wish to optains a certificate for enter:
#   datalab.my-domain.net *.lab.my-domain.net
```

{% hint style="info" %}
The obtained certificate needs to be renewed every three month.

To avoid the burden of having to remember to re-run the `certbot` command periodically you can setup [cert-manager](https://cert-manager.io/) and configure a [DNS01 challenge provider](https://cert-manager.io/docs/configuration/acme/dns01/) on your cluster but that's out of scope for Onyxia.

You may need to delegate your DNS Servers to one of the supported [DNS service provider](https://cert-manager.io/docs/configuration/acme/dns01/#supported-dns01-providers).
{% endhint %}

Now we want to create a Kubernetes secret containing our newly obtained certificate:

```bash
DOMAIN=my-domain.net
sudo kubectl create secret tls onyxia-tls \
    -n ingress-nginx \
    --key /etc/letsencrypt/live/datalab.$DOMAIN/privkey.pem \
    --cert /etc/letsencrypt/live/datalab.$DOMAIN/fullchain.pem
```

Lastly, we want to tell our ingress controller to use this TLS certificate, to do so run:

```bash
kubectl edit deployment ingress-nginx-controller -n ingress-nginx
```

This command will open your configured text editor, go to containers -> args and add:

```
      - --default-ssl-certificate=ingress-nginx/onyxia-tls
      - --watch-ingress-without-class
```

<figure><img src="/files/37IXE3fdFzoMK74lbsYZ" alt=""><figcaption></figcaption></figure>

Save and quit. Done :tada:\
We installed the ingress-nginx in our cluster, (but note that any other ingress controller could have been used as well). The configuration was adjusted to handle all ingress objects, even those lacking a specified class, and to employ our SSL certificate for our wildcard certificate. This strategy facilitated an effortless SSL termination, managed by the reverse proxy for both **\*.lab.my-domain.net** and **datalab.my-domain.net**, thus removing any additional SSL configuration concerns.
{% endtab %}

{% tab title="Test on your machine" %}
If you are on a Mac or Window computer you can install [Docker desktop](https://www.docker.com/products/docker-desktop/) then enable Kubernetes.

<figure><img src="/files/963SPSYgl9OctTv2c3bl" alt=""><figcaption><p>Enabling Kubernetes in the Docker desktop App</p></figcaption></figure>

{% hint style="warning" %}
WARNING: If you are folowing this installating guide on an Apple Sillicon Mac, be aware that many of the services that comes by default with Onyxia like Jupyter RStudio and VSCode won't run because we do not yet compile our datacience stack for the ARM64 architecture.\
If you would like to see this change please [sumit an issue about it](https://github.com/InseeFrLab/helm-charts-interactive-services/issues).
{% endhint %}

{% hint style="info" %}
Docker desktop isn't available on Linux, you can use [Kind](https://kind.sigs.k8s.io/) instead.
{% endhint %}

**Port Forwarding**

You'll need to [forward the TCP ports 80 and 443 to your local machine](https://user-images.githubusercontent.com/6702424/174459930-23fb577c-11a2-49ef-a082-873f4139aca1.png). It's done from the administration panel of your domestic internet Box. If you're on a corporate network you'll have to [test onyxia on a remote Kubernetes cluster](#provisioning-a-cluster-on-aws-gcp-or-azure).

**DNS**

Let's assume you own the domain name **my-domain.net,** for the rest of the guide you should replace **my-domain.net** by a domain you actually own.

Get [your internet box routable IP](http://monip.org/) and create the following DNS records:

```dns-zone-file
datalab.my-domain.net A <YOUR_IP>
*.lab.my-domain.net   A <YOUR_IP>
```

{% hint style="success" %}
If you have DDNS domain you can create `CNAME` instead example:

```
datalab.my-domain.net CNAME jhon-doe-home.ddns.net.
*.lab.my-domain.net   CNAME jhon-doe-home.ddnc.net.
```

{% endhint %}

***<https://datalab.my-domain.net>*** will be the URL for your instance of Onyxia.

The URL of the services created by Onyxia are going to look like: ***<https://xxx.lab.my-domain.net>***

{% hint style="info" %}
You can customise "**datalab**" and "**lab**" to your liking, for example you could chose **onyxia.my-domain.net** and **\*.kub.my-domain.net**.
{% endhint %}

**SSL**

In this section we will obtain a TLS certificate issued by [LetsEncrypt](https://letsencrypt.org/) using the [certbot](https://certbot.eff.org/) commend line tool.

```bash
brew install certbot #On Mac, lookup how to install certbot for your OS

# Because we need a wildcard certificate we have to complete the DNS callange.  
sudo certbot certonly --manual --preferred-challenges dns

# When asked for the domains you wish to optains a certificate for enter:
#   datalab.my-domain.net *.lab.my-domain.net
```

{% hint style="info" %}
The obtained certificate needs to be renewed every three month.

To avoid the burden of having to remember to re-run the `certbot` command periodically you can setup [cert-manager](https://cert-manager.io/) and configure a [DNS01 challenge provider](https://cert-manager.io/docs/configuration/acme/dns01/) on your cluster but that's out of scope for Onyxia.

You may need to delegate your DNS Servers to one of the supported [DNS service provider](https://cert-manager.io/docs/configuration/acme/dns01/#supported-dns01-providers).
{% endhint %}

Now we want to create a Kubernetes secret containing our newly obtained certificate:

```bash
# First let's make sure we connect to our local Kube cluser
kubectl config use-context docker-desktop

kubectl create namespace ingress-nginx
DOMAIN=my-domain.net
sudo kubectl create secret tls onyxia-tls \
    -n ingress-nginx \
    --key /etc/letsencrypt/live/datalab.$DOMAIN/privkey.pem \
    --cert /etc/letsencrypt/live/datalab.$DOMAIN/fullchain.pem
```

**Ingress controller**

We will install ingress-nginx in our cluster, although any other ingress controller would be suitable as well. The configuration will be set up to handle all ingress objects, including those without a specified class, and to utilize our SSL certificate for our wildcard certificate. This approach ensures a straightforward SSL termination managed by the reverse proxy for both **\*.lab.my-domain.net** and **datalab.my-domain.net**, eliminating any further concerns regarding SSL setup.

```bash
cat << EOF > ./ingress-nginx-values.yaml
controller:
  extraArgs:
    default-ssl-certificate: "ingress-nginx/onyxia-tls"
  watchIngressWithoutClass: true
EOF

helm install ingress-nginx ingress-nginx \
    --repo https://kubernetes.github.io/ingress-nginx \
    --version 4.9.1 \
    --namespace ingress-nginx \
    -f ./ingress-nginx-values.yaml
```

{% endtab %}
{% endtabs %}

Now that we have a Kubernetes cluster ready to use let's levrage ArgoCD and GitOps practices to deploy and monitor the core services of our Onyxia Datalab.

{% content-ref url="/pages/l0ZKsb6EVc5JlIZJBecZ" %}
[GitOps](/admin-doc/readme/gitops)
{% endcontent-ref %}


# GitOps

Let's install ArgoCD to manage and monitor our Onyxia Datalab deployment!

{% hint style="info" %}
At this stage of this installation process we assumes that:

* You have a Kubernetes cluster and `kubectl` configured
* **datalab.my-domain.net** and **\*.lab.my-domain.net**'s DNS are pointing to your cluster's external address. **my-domain.net** being a domain that you own.
* Your ingress-nginx is set up with a default TLS certificate that covers both **datalab.my-domain.net** and **\*.lab.my-domain.net**, processing all ingress objects, [even those that do not have a class specified](#user-content-fn-1)[^1].
  {% endhint %}

We can proceed with manually installing various services via Helm to set up the datalab. However, it's more convenient and reproducible to maintain a Git repository that outlines the required services that we need for our datalab, allowing [ArgoCD](https://argo-cd.readthedocs.io/en/stable/) to handle the deployment for us.

To clarify, using ArgoCD is merely an approach that we recommend, but it is by no means a requirement. Feel free to manually helm install the different services using the `values.yaml` from [InseeFrLab/onyxia-ops](https://github.com/InseeFrLab/onyxia-ops)!

Let's install ArgoCD on the cluster.

```bash
DOMAIN=my-domain.net

cat << EOF > ./argocd-values.yaml
server:
  extraArgs:
    - --insecure
  ingress:
    #ingressClassName: nginx
    enabled: true
    hostname: argocd.lab.$DOMAIN
    extraTls:
      - hosts:
          - argocd.lab.$DOMAIN
EOF

helm install argocd argo-cd \
  --repo https://argoproj.github.io/argo-helm \
  --version 6.0.9 \
  -f ./argocd-values.yaml
```

Now you have to get the password that have been automatically generated to protect ArgoCD's admin console.\
Allow some time for ArgoCD to start, you can follow the progress by running `kubectl get pods` and making sure that all pod are ready 1/1. After that running this command will print the password:

```bash
kubectl get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d
```

You can now login to **<https://argocd.lab.my-domain.net>** using:

* username: **admin**
* password: **\<the output of the previous command (without the `%` at the end)>**

<figure><img src="/files/pVxWR2E8N2yjIcqnY1nt" alt=""><figcaption></figcaption></figure>

Now that we have an ArgoCD we want to connect it to a Git repository that will describe what services we want to be running on our cluster.

Let's fork the onyxia-ops GitHub repo and use it to deploy an Onyxia instance!

{% hint style="info" %}
Note that in this guide, we use GitHub, but feel free to fork the [InseeFrLab/onyxia-ops](https://github.com/InseeFrLab/onyxia-ops) repository on GitLab or any other forge. You'll need to slightly adapt the instructions, but you should be able to follow along!
{% endhint %}

{% embed url="<https://app.tango.us/app/embed/55af08f3-43b0-4b5d-84b7-dfb75f6983c9>" %}

At this point you should have a very bare bone Onyxia instance that you can use to launch services.

What's great, is that now, if you want to update the configuration of your Onyxia instance you only have to commit the change to your GitOps repo, ArgoCD will takes charge of restarting the service for you with the new configuration.\
To put that to the test try to modify your Onyxia configuration by setting up a global alert that will be shown as a banner to all users!

{% code title="apps/onyxia/values.yaml" %}

```diff
 onyxia:
   ingress:
     enabled: true
     hosts:
       - host: datalab.demo-domain.ovh
   web:
     env:
+      GLOBAL_ALERT: |
+       {
+         severity: "success",
+         message: {
+           en: "A **big** announcement! [Check it out](https://example.com)!",
+           fr: "Une annonce **importante**! [Regardez](https://example.com)!"
+         }
+       }
   api:
     regions: [...]
```

{% endcode %}

After a few seconds, if you reload **<https://datalab.my-domain.net>** you should see the message!\\

<figure><img src="/files/XPFW8px8SO1yryTFYLGa" alt="" width="354"><figcaption></figcaption></figure>

Next step is to see how to enable your user to authenticate themselvs to your datalab!

{% content-ref url="/pages/1rEWljYFN5WjJKGt73DO" %}
[User authentication](/admin-doc/readme/user-authentication)
{% endcontent-ref %}

[^1]: This simplifies the process but is not a requirement of Onyxia. Should your ingress controller filter ingress objects based on a specific class name, be mindful of the various `ingressClassName: nginx` entries commented out in the chart configurations. To adapt to this setup, simply edit/uncomment those lines.


# User authentication

Using Keycloak to enable user authentication

Let's setup Keycloak to enable users to create account and login to our Onyxia instance.

Note that in this installation guide we make you use Keycloak but you can use any OIDC compliant provider like Entra ID or Auth0. See the following gide for specific instructions for different provider and detailed authentication related configuration options.

{% content-ref url="/pages/DwA18GQM35k1Kr67zkoU" %}
[OpenID Connect Configuration](/admin-doc/openid-connect-configuration)
{% endcontent-ref %}

### Deploying Keycloak

We're going to install Keycloak just like we installed Onyxia.

Before anything open [`apps/keycloak/values.yaml`](https://github.com/InseeFrLab/onyxia-ops/blob/main/apps/keycloak/values.yaml) in your onyxia-ops repo and [change the passwords](#user-content-fn-1)[^1]. Also write down the [`keycloak.auth.adminPassword`](https://github.com/InseeFrLab/onyxia-ops/blob/bad75636d72c20c48f1b34ec08593df83ee6c9a6/apps/keycloak/values.yaml#L11), you'll need it to connect to the Keycloak console.

{% embed url="<https://app.tango.us/app/embed/dbb21e90-db2c-41f4-b2ab-5f8b9f4d33c0>" %}

{% hint style="info" %}
Try to remember, when you [update Onyxia in `apps/onyxia/Chart.yaml`](https://github.com/InseeFrLab/onyxia-ops/blob/bad75636d72c20c48f1b34ec08593df83ee6c9a6/apps/onyxia/Chart.yaml#L6) to also update [the Onyxia theme in `apps/keycloak/values.yaml`](https://github.com/InseeFrLab/onyxia-ops/blob/bad75636d72c20c48f1b34ec08593df83ee6c9a6/apps/keycloak/values.yaml#L69).
{% endhint %}

### Configuring Keycloak

You can now login to the **administration console** of **<https://auth.lab.my-domain.net/auth/>** and login using username: keycloak and password: \<the one you've wrote down earlier>.

1. Create a realm called "datalab" (or something else), go to **Realm settings**
   1. On the tab General
      1. *User Profile Enabled*: **On**
   2. On the tab **login**
      1. *User registration*: **On**
      2. *Forgot password*: **On**
      3. *Remember me*: **On**
   3. On the tab **email,** we give an example with [AWS SES](https://aws.amazon.com/ses/), if you don't have a SMTP server at hand you can skip this by going to **Authentication** (on the left panel) -> Tab **Required Actions** -> Uncheck "set as default action" **Verify Email**. Be aware that with email verification disable, anyone will be able to sign up to your service.
      1. *From*: **<noreply@lab.my-domain.net>**
      2. *Host*: **email-smtp.us-east-2.amazonaws.com**
      3. *Port*: **465**
      4. *Authentication*: **enabled**
      5. *Username*: **\*\*\*\*\*\*\*\*\*\*\*\*\*\***
      6. *Password*: **\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\***
      7. When clicking "save" you'll be asked for a test email, you have to provide one that correspond **to a pre-existing user** or you will get a silent error and the credentials won't be saved.
   4. On the tab **Themes**
      1. *Login theme*: **onyxia-web** (you can also select the login theme on a per client basis)
      2. *Email theme*: **onyxia-web**
   5. On the tab **Localization**
      1. *Internationalization*: **Enabled**
      2. *Supported locales*: \<Select the languages you wish to support>
   6. On the tab **Session**.
      * Users **without** "Remember Me" will need to log in **every 2 weeks**:
        * Set **Session idle timeout**: `14 days`.
        * Set **Session max idle timeout**: `14 days`.
      * Users **who checked "Remember Me"** should stay logged in for **1 year**:
        * Set **Session idle timeout (Remember Me)**: `365 days`.
        * Set **Session max idle timeout (Remember Me)**: `365 days`.
2. Create a client with client ID "onyxia"
   1. *Root URL*: **<https://datalab.my-domain.net/>**
   2. *Valid redirect URIs*: **<https://datalab.my-domain.net/>**
   3. Login theme: **onyxia-web**
3. In **Authentication** (on the left panel) -> Tab **Required Actions** enable and set as default action **Therms and Conditions.**

Now you want to ensure that the username chosen by your users complies with Onyxia requirement (only alphanumerical characters) and define a list of email domain allowed to register to your service.

Go to **Realm Settings** (on the left panel) -> Tab **User Profile** -> **JSON Editor**.

Now you can edit the file as suggested in the following DIFF snippet. Be mindful that in this example we only allow emails @gmail.com and @hotmail.com to register you want to edit that.

```diff
{
  "attributes": [
    {
      "name": "username",
      "displayName": "${username}",
      "validations": {
        "length": {
          "min": 3,
          "max": 255
        },
+       "pattern": {
+         "error-message": "${lowerCaseAlphanumericalCharsOnly}",
+         "pattern": "^[a-z0-9]*$"
+       },
        "username-prohibited-characters": {}
      }
    },
    {
      "name": "email",
      "displayName": "${email}",
      "validations": {
        "email": {},
+       "pattern": {
+         "pattern": "^[^@]+@([^.]+\\.)*((gmail\\.com)|(hotmail\\.com))$"
+       },
        "length": {
          "max": 255
        }
      }
    },
...
```

Now our Keycloak server is fully configured we just need to update our Onyxia deployment to let it know about it.

### Updating the Onyxia configuration

In your GitOps repo you now want to update your onyxia configuration.

```bash
git clone https://github.com/<your-github-org>/onyxia-ops
cd onyxia-ops
cd apps/onyxia
mv values-keycloak-enabled.yaml values.yaml
git commit -am "Enable keycloak"
git push
```

Here is the DIFF of the onyxia configuration:

{% embed url="<https://github.com/InseeFrLab/onyxia-ops/commit/37faa6390c9bc8c1efddfd3488dc06b38427b424>" %}

Now your users should be able to create account, log-in, and start services on their own Kubernetes namespace.

<figure><img src="/files/2AbvJ525GvINKyT3n4sI" alt=""><figcaption><p>The screen you shoud see when clicking on "login" in your Onyxia deployment</p></figcaption></figure>

Next step in the installation proccess it to enable all the S3 related features of Onyxia:

{% content-ref url="/pages/kfcoWtB9lBYzxSShcXQf" %}
[Data (S3)](/admin-doc/readme/data-s3)
{% endcontent-ref %}

[^1]: Search/replace CHANGEME


# Data (S3)

Enable S3 storage via MinIO S3

Onyxia uses [AWS Security Token Service API](https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html) to obtain S3 tokens on behalf of your users. We support any S3 storage compatible with this API. In this context, we are using [MinIO](https://min.io/), which is compatible with the Amazon S3 storage service and we demonstrate how to integrate it with Keycloak.

### Creating the 'minio' Keycloak client

Before configuring MinIO, let's create a new Keycloak client (from the previous existing "datalab" realm).

{% embed url="<https://app.tango.us/app/embed/1c5c0975-93f0-48c6-b8d9-edceb397e34c>" %}

### Deploying MinIO

Before deploying MinIO on the cluster let's set, in the MinIO configuration file, the OIDC client secret we have copied in the previous step.

```bash
git clone https://github.com/<your-github-org>/onyxia-ops
cd onyxia-ops
cd apps/minio
# In the values.yaml file replace `$KEYCLOAK_MINIO_CLIENT_SECRET` by the value
# you have copied in the previous step.
git commit -am "Set minio OIDC client secret"
git push
```

Once you've done that you can deploy MinIO!

{% embed url="<https://app.tango.us/app/embed/75b62573-7adc-4a38-b1f9-b96bb0ea50fd>" %}

### Creating the 'onyxia-minio' Keycloak client

Before configuring the onyxia region to create tokens we should go back to Keycloak and create a new client to enable onyxia-web to request token for MinIO. This client is a little bit more complex than other if you want to manage durations (here 7 days) and this client should have a claim name policy and with a value of stsonly according to our last deployment of MinIO.

{% embed url="<https://app.tango.us/app/embed/2e382be2-5d73-4cc8-8682-1b86b0e1de58>" %}

### Updating the Onyxia configuration

Now let's update our Onyxia configuration to let it know that there is now a S3 server available on the cluster.

```bash
git clone https://github.com/<your-github-org>/onyxia-ops
cd onyxia-ops
cd apps/onyxia
mv values-minio-enabled.yaml.yaml values.yaml
# OR: For deploying Onyxia in s3 Explorer standalone mode:
# mv values-s3-explorer-only.yaml values.yaml
git commit -am "Enable MinIO"
git push
```

Diff of the changes applied to the Onyxia configuration:

<figure><img src="/files/J1PmXK6zCfAliaFSvxiK" alt=""><figcaption></figcaption></figure>

Next step in the installation process is to setup Vault to provide a way to your user so store secret and also to provide something that Onyxia can use as a persistance layer for user configurations.

{% content-ref url="/pages/ICyiajR6Gdnxc42LvZsl" %}
[Vault](/admin-doc/readme/vault)
{% endcontent-ref %}


# Vault

{% hint style="info" %}
Vault is also used by Onyxia as the persistance layer for all saved configuration. If Vault is not configured, all user settings will be stored in the browser's local storage.
{% endhint %}

Onyxia-web uses vault as a storage for two kinds of secrets:\
1\. secrets or information generated by Onyxia to store different values (S3 sources configuration)\
2\. user secrets\\

**Onyxia uses the KV version 2 secret engine.**\
**Vault must be configured with JWT or OIDC authentification methods.**

As Vault needs to be initialized with a master key, it can't be directly configured with all parameters such as oidc or access policies and roles. So first step we create a vault with dev mode (do not use this in production and do your initialization with any of the recommanded configuration: Shamir, gcp, another vault).

```bash
helm repo add hashicorp https://helm.releases.hashicorp.com
 
DOMAIN=my-domain.net

cat << EOF > ./vault-values.yaml
server:
  dev:
    enabled: true
    # Set VAULT_DEV_ROOT_TOKEN_ID value
    devRootToken: "root"
  ingress:
    enabled: true
    annotations:
      kubernetes.io/ingress.class: nginx
    hosts:
      - host: "vault.lab.$DOMAIN"
    tls:
      - hosts:
          - vault.lab.$DOMAIN
EOF

helm install vault hashicorp/vault -f vault-values.yaml
```

#### Setting up JWT authentification for Vault

From Keycloak, create a client called "vault" (realm "datalab" as usually in this documentation)

1. *Root URL*: **<https://vault.lab.my-domain.net/>**
2. *Valid redirect URIs*: **<https://vault.lab.my-domain.net/\\>**\* and **<https://datalab.my-domain.net/\\>**\*
3. *Web origins*: **\***

The expected value for the audience (aud) field of the JWT token by Vault is `vault`. You need to configure this in Keycloak.

1. Create a new Client scope: `vault`
2. Add Mapper by configuration
3. Choose Audience
   * Name: Audience for Vault
   * Included Client Audience: `vault`
   * Save

* Choose Clients: `vault`
  * Add Client Scope: `vault`

We will now configure Vault to enable `JWT` support, set policies for users permissions and initialize the secret engine.

You will need the Vault `CLI`. You can either download it [here](https://www.vaultproject.io/downloads) and configure `VAULT_ADDR=https://vault.lab.my-domain.net` and `VAULT_TOKEN=root` or exec into the vault pod `kubectl exec -it vault-0 -n vault -- /bin/sh` which will have vault `CLI` installed and pre-configured.

First, we start by creating a `JWT` endpoint in Vault, and writing information about Keycloak to the configuration. We use the same realm as usually in this documentation.

```
vault auth enable jwt
```

```
vault write auth/jwt/config \
    oidc_discovery_url="https://auth.lab.my-domain.net/auth/realms/datalab" \
    default_role="onyxia-user"
```

Onyxia uses only one single role for every user in Vault. This is in this tutorial `onyxia-user`\`. **To provide an authorization mechanism a policy is used that will depend on claims inside the JWT token.**

First you need to get the identifier (mount accessor) for the JWT authentification just created. You can use :

```
vault auth list -format=json | jq -r '.["jwt/"].accessor'
```

which should provide you something like `auth_jwt_xyz`. You will need it to **write a proper policy** by replacing the `auth_jwt_xyz` content with your own value.

#### Setting up a policy

Create locally a file named `onyxia-policy.hcl`.

You can notice that this policy is written for a KV version 2 secret engine mounted to the `onyxia-kv` path. The following policy is only working for personnal access because the entity name will be the preferred username in the JWT token.

{% code title="onyxia-policy.hcl" %}

```hcl
path "onyxia-kv/user-{{identity.entity.aliases.auth_jwt_xyz.name}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/user-{{identity.entity.aliases.auth_jwt_xyz.name}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/user-{{identity.entity.aliases.auth_jwt_xyz.name}}/*" {
  capabilities = ["delete", "list", "read"]
}
```

{% endcode %}

{% hint style="info" %}
You can include access to any secret engine in the policy, which will be accessible within the services, though the Onyxia interface won’t utilize these permissions. If you have a use case where it would be beneficial for the Onyxia interface to access other secret engines, please let us know on Slack.
{% endhint %}

Allowing personal Vault tokens to access group storage in Vault is a bit more complex. We will map the group from the token into the entity’s metadata. The following policy maps the first 10 groups statically.

{% hint style="success" %}
If you have suggestions for a better authorization mechanism within Vault, please share them with us on Slack, as the current approach is not ideal.
{% endhint %}

{% code title="onyxia-policy.hcl" %}

```hcl
path "onyxia-kv/user-{{identity.entity.aliases.auth_jwt_xyz.name}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/user-{{identity.entity.aliases.auth_jwt_xyz.name}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/user-{{identity.entity.aliases.auth_jwt_xyz.name}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group0}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group0}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group0}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group1}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group1}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group1}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group2}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group2}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group2}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group3}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group3}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group3}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group4}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group4}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group4}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group5}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group5}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group5}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group6}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group6}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group6}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group7}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group7}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group7}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group8}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group8}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group8}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group9}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group9}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group9}}/*" {
  capabilities = ["delete", "list", "read"]
}

```

{% endcode %}

Once the policy file is created, we can proceed with creating the policy.

```bash
vault policy write onyxia-policy onyxia-policy.hcl
```

We can go on with the role `onyxia-user`.

```bash
vault write auth/jwt/role/onyxia-user \
    role_type="jwt" \
    bound_audiences="vault" \
    user_claim="preferred_username" \
    claim_mappings="/groups/0=group0,/groups/1=group1,/groups/2=group2,/groups/3=group3,/groups/4=group4,/groups/5=group5,/groups/6=group6,/groups/7=group7,/groups/8=group8,/groups/9=group9" \
    token_policies="onyxia-policy"
```

We need to enable the secret engine.

```
vault secrets enable -path=onyxia-kv kv-v2
```

Then, you need to allow the URL <https://datalab.my-domain.net> in Vault's CORS settings.

```
vault write sys/config/cors allowed_origins="https://datalab.my-domain.net" enabled=true
```

You can finally modify your onyxia config file (in the helm values) :tada:

{% code title="onyxia/values.yaml" %}

```yaml
onyxia:
  web:
    # ...
  api:
    # ...
    regions:
      [
        {
          "id": "paris",
          ...
          "services": {...},
          "data": {...},
          "vault": {
              "URL": "https://vault.lab.my-domain.net",
              "kvEngine": "onyxia-kv",
              "role": "onyxia-user",
              "authPath": "jwt",
              "prefix": "user-",
              "groupPrefix" : "",
              "oidcConfiguration":
                {
                  "issuerURI": "https://auth.lab.my-domain.net/auth/realms/datalab",
                  "clientID": "vault",
                }
          }

    ]
```

{% endcode %}


# S3 Explorer Standalone Deployment

Deploy a standalone S3 browser with OIDC login and STS-based temporary credentials

Onyxia S3 Explorer is a browser-based file manager for S3-compatible object storage. Files can be previewed without leaving the explorer, including images, videos, PDFs, and text or source code with syntax highlighting.

For data workflows, CSV, JSON, and Parquet files can be explored as tabular data directly in the browser. Powered by DuckDB-Wasm, the explorer queries and streams data from object storage as needed, allowing even large files to be inspected quickly without downloading them in full first.

What makes Onyxia S3 Explorer suitable for multi-user production deployments is its native integration with your organization's identity provider and your storage provider's authorization system:

* users sign in through OpenID Connect (OIDC);
* Onyxia exchanges their OIDC access token for short-lived S3 credentials through STS, so users never need to copy or manage access keys;
* the S3 provider's roles and policies remain authoritative over which buckets and objects each user can access; and
* administrators can generate built-in bookmarks from identity claims, directing each user to their personal or project buckets.

The resulting flow is: **OIDC sign-in → STS → temporary credentials → direct browser access to S3**. Onyxia handles authentication, credential acquisition, and navigation; it does not become a data proxy or replace the authorization rules of the storage provider.

This page presents two deployment paths:

* **Quick evaluation without OIDC:** deploy the explorer with almost no configuration and browse a public bucket. This lets you try the interface before setting up identity and storage integration, but it is not intended as a production architecture.
* **Production deployment with OIDC and STS:** connect Onyxia to your identity provider and let users automatically obtain temporary, policy-scoped credentials.

If you only want to see the product running, start with the quick evaluation. If you are evaluating its production architecture, skip directly to [Production Deployment: OIDC and STS](#production-deployment-oidc-and-sts).

{% hint style="warning" %}
The explorer runs entirely in the browser: Onyxia does not proxy S3 requests. The bucket you want to browse must therefore allow the origin of the Onyxia application, for example `https://onyxia.example.com`, in its CORS configuration.
{% endhint %}

## Quick Evaluation: Deploy Without OIDC

In this mode, users create their own S3 profiles and provide an endpoint, a region, and, when required, an access key ID and secret access key. Use it to evaluate the explorer, not as a model for a multi-user production deployment.

Add the Onyxia Helm repository and create a values file:

{% code title="onyxia-values.yaml" %}

```yaml
ingress:
  enabled: true
  hosts:
    - host: onyxia.example.com

web:
  env:
    HEADER_TEXT_FOCUS: "S3 Explorer"

api:
  enabled: false
```

{% endcode %}

Install Onyxia:

```bash
helm repo add onyxia https://inseefrlab.github.io/onyxia
helm repo update

helm upgrade --install onyxia onyxia/onyxia \
  --namespace onyxia \
  --create-namespace \
  --values onyxia-values.yaml
```

Open `https://onyxia.example.com`. Onyxia prompts you to create an S3 profile. Credentials are optional for buckets that allow anonymous access.

### Try It With a Public Bucket

Create a profile with the following values:

* **Profile name:** `aws_us-west-2_anonymous`, for example
* **URL of the S3 service:** `https://s3.amazonaws.com`
* **Default region:** `us-west-2`
* **Anonymous access:** enabled

After saving the profile, navigate to `s3://multimedia-commons/` and add it to your bookmarks.

{% hint style="warning" %}
Because there is no backend in this deployment mode, user-created profiles, including any access keys, are stored in the browser's local storage. Do not enter long-lived credentials on a shared or untrusted device.
{% endhint %}

## Production Deployment: OIDC and STS

In a production multi-user deployment, Onyxia can obtain temporary credentials for each user through OIDC and STS. Your storage provider must support [`AssumeRoleWithWebIdentity`](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html).

{% hint style="info" %}
Onyxia does not decide which buckets a user can access. Roles and policies are configured independently in the S3/STS provider. Onyxia only authenticates the user, requests temporary credentials from STS, and displays administrator-defined bookmarks (example `s3://user-bucket-johnd/`). A bookmark does not grant access to its target.
{% endhint %}

For a complete walkthrough that deploys Kubernetes, Keycloak, MinIO, and Onyxia from scratch, follow the installation tutorial. Its **Data (S3)** section includes the standalone S3 Explorer option.

{% content-ref url="/pages/vCDZVeJuS6Knw1KHTLIL" %}
[Install](/)
{% endcontent-ref %}

### Example: MinIO and Keycloak

This example follows the [MinIO configuration from `onyxia-ops`](https://github.com/InseeFrLab/onyxia-ops/blob/main/apps/minio/values.yaml). It gives each user read/write access to a bucket derived from their username.

#### 1. Configure the OIDC Client and Token Claims

Create a **public OIDC client** named `onyxia-minio` in Keycloak or your OIDC provider. It must use the Authorization Code flow with PKCE and must not have a client secret.

For a user named `johnd`, tokens issued to this client must contain:

* an ID token with `preferred_username: "johnd"`; and
* a JWT access token with `preferred_username: "johnd"` and a hard-coded `policy: "stsonly"` claim.

In abbreviated form:

{% tabs %}
{% tab title="ID token" %}
Payload of the ID Token. Used to construct the bookmarks.

```json
{
  "aud": "onyxia-minio",
  "preferred_username": "johnd"
}
```

{% endtab %}

{% tab title="Access token" %}
Payload of the AccessToken sent to MinIO

```json
{
  "azp": "onyxia-minio", // Client the token was issued to.
  "aud": "minio", // Client the token is intended for.
  "preferred_username": "johnd", // Username used to template the access rules.
  "policy": "stsonly" // Required claim for the rules to apply.
}
```

{% endtab %}
{% endtabs %}

Onyxia reads `preferred_username` from the **ID token** to build the bookmark. MinIO reads `policy` and `preferred_username` from the **access token** to authorize the STS request. The username must therefore have the same value in both tokens.

#### 2. Configure MinIO's OIDC Trust and Policy

The relevant parts of the example MinIO values are:

{% code title="apps/minio/values.yaml" %}

```yaml
minio:
  oidc:
    enabled: true
    configUrl: "https://auth.example.com/realms/my-realm/.well-known/openid-configuration"
    clientId: "minio"
    clientSecret: "<MINIO_OIDC_CLIENT_SECRET>"
    claimName: "policy"
    claimPrefix: ""

  policies:
    - name: stsonly
      statements:
        - resources:
            - 'arn:aws:s3:::user-${jwt:preferred_username}'
            - 'arn:aws:s3:::user-${jwt:preferred_username}/*'
          actions:
            - "s3:*"
```

{% endcode %}

MinIO uses the access token's `policy` claim to select the `stsonly` policy. It then substitutes the token's `preferred_username` claim in the resource names. For `johnd`, the policy grants S3 operations on the `user-johnd` bucket and its objects.

The `user-` prefix is a convention in this example, not an Onyxia requirement. If your user's bucket should be named exactly `johnd`, remove the prefix from both the MinIO policy resources and the Onyxia bookmark so that they continue to match.

If the bucket does not exist, Onyxia will ask the user if they want to create it.

{% hint style="info" %}
The `minio` client shown in the MinIO values is used by the MinIO Console and has a client secret. It is separate from the public `onyxia-minio` client used by Onyxia. Never place a client secret in the Onyxia web configuration.
{% endhint %}

#### 3. Configure Onyxia

The following configuration creates a `default` profile and uses the ID token's `preferred_username` claim to create the matching personal-bucket bookmark:

{% code title="onyxia-values.yaml" %}

```yaml
ingress:
  enabled: true
  hosts:
    - host: onyxia.example.com

web:
  env:
    HEADER_TEXT_FOCUS: "S3 Explorer"
    S3: |
      {
        URL: "https://minio.example.com",
        region: "us-east-1",
        pathStyleAccess: true,
        sts: {
          role: {
            profileName: "default",
            roleARN: "",
            roleSessionName: ""
          },
          oidcConfiguration: {
            issuerURI: "https://auth.example.com/realms/my-realm",
            clientID: "onyxia-minio"
          }
        },
        bookmarks: [
          {
            s3Uri: "s3://user-$1/",
            title: { en: "Personal bucket", fr: "Bucket personnel" },
            claimName: "preferred_username",
            forProfileName: "default"
          }
        ]
      }

api:
  enabled: false
```

{% endcode %}

Install the chart with the same Helm command used in the quick-evaluation example, then open `https://onyxia.example.com`.

For `johnd`, the complete flow is:

1. Onyxia authenticates the user through the public `onyxia-minio` client.
2. Onyxia resolves the ID token's `preferred_username` and displays `s3://user-johnd/` as a bookmark.
3. Onyxia sends the access token to MinIO's STS endpoint.
4. MinIO selects the `stsonly` policy and resolves its resource to `user-johnd`.
5. MinIO returns temporary credentials that allow the browser to access that bucket.

{% hint style="info" %}
The empty `roleARN` and `roleSessionName` values are specific to MinIO's claim-based OIDC mode: MinIO derives authorization from the JWT claims instead. Other STS providers, including AWS, require valid role values. See [S3 Configuration](/admin-doc/s3-configuration) for provider-independent examples and the complete configuration reference.
{% endhint %}

## Where to Put the S3 Configuration

The location of the S3 configuration depends on whether the Onyxia API is enabled:

* **Standalone deployment:** set the configuration as JSON5 in `web.env.S3`, as shown above.
* **Full Onyxia deployment:** set it in `api.regions[].data.S3`. The Helm chart passes the first region's S3 configuration to the web application automatically.

For other identity and storage providers, see:

{% content-ref url="/pages/cbRuBfK8MsCv9sJMr8ex" %}
[S3 Configuration](/admin-doc/s3-configuration)
{% endcontent-ref %}

{% content-ref url="/pages/DwA18GQM35k1Kr67zkoU" %}
[OpenID Connect Configuration](/admin-doc/openid-connect-configuration)
{% endcontent-ref %}


# Theme and branding

Customize your Onyxia instance with your assets and your colors, make it your own!

{% embed url="<https://youtu.be/NrVuVXsbloA>" %}

The full documentation of the available parameter can be found here:

{% embed url="<https://github.com/InseeFrLab/onyxia/blob/main/web/.env>" %}

## Theme Galery

Here is a galery of theme that you can try out.

{% hint style="info" %}
If you want to test theses theme in your local dev env (as shown in the video) download the ZIP file specified as `CUSTOM_RESOURCES` and extract it in **web/public/custom-resources**.
{% endhint %}

{% tabs %}
{% tab title="Ultraviolet" %}

<figure><img src="/files/fJsBSY8jlcPZ74737FgC" alt=""><figcaption><p>Light Mode</p></figcaption></figure>

<figure><img src="/files/xT4D8Co1mvEOHadgzZJT" alt=""><figcaption><p>Dark mode</p></figcaption></figure>

{% code title="values.yaml" %}

```yaml
onyxia:
  web:
    env:
      #ONYXIA_API_URL: https://datalab.sspcloud.fr/api
      CUSTOM_RESOURCES: "https://www.sspcloud.fr/ultraviolet/custom-resources.zip"
      FONT: |
        { 
          fontFamily: "Geist", 
          dirUrl: "%PUBLIC_URL%/custom-resources/fonts/Geist", 
          "400": "Geist-Regular.woff2",
          "500": "Geist-Medium.woff2",
          "600": "Geist-SemiBold.woff2",
          "700": "Geist-Bold.woff2"
        }
      PALETTE_OVERRIDE: |
        {
          focus: {
            main: "#067A76",
            light: "#0AD6CF",
            light2: "#AEE4E3"
          },
          dark: {
            main: "#2D1C3A",
            light: "#4A3957",
            greyVariant1: "#22122E",
            greyVariant2: "#493E51",
            greyVariant3: "#918A98",
            greyVariant4: "#C0B8C6"
          },
          light: {
            main: "#F7F5F4",
            light: "#FDFDFC",
            greyVariant1: "#E6E6E6",
            greyVariant2: "#C9C9C9",
            greyVariant3: "#9E9E9E",
            greyVariant4: "#747474"
          }
        }
      SOCIAL_MEDIA_IMAGE: "%PUBLIC_URL%/custom-resources/preview.png"
```

{% endcode %}
{% endtab %}

{% tab title="SSPCloud" %}

<figure><img src="/files/x9FdBS9mo6DkzIVNLP5g" alt=""><figcaption><p>Light Mode</p></figcaption></figure>

<figure><img src="/files/J6FSMn0BR3eiZZoOBIxi" alt=""><figcaption><p>Dark Mode</p></figcaption></figure>

{% code title="onyxia/values.yaml" %}

```yaml
onyxia:
  web:
    env:
      #ONYXIA_API_URL: https://datalab.sspcloud.fr/api
      CUSTOM_RESOURCES: "https://www.sspcloud.fr/onyxia-theme-sspcloud-v0.zip"
      GLOBAL_ALERT: |
        {
          severity: "success",
          message: {
            en: "If you like the platform, you can give us a ⭐️ [on GitHub](https://github.com/InseeFrLab/onyxia). Thank you very much! 😊",
            fr: "Si vous aimez la plateforme, vous pouvez nous mettre une ⭐️ [sur GitHub](https://github.com/InseeFrLab/onyxia). Merci beaucoup ! 😊",
          }
        }
      DISABLE_PERSONAL_INFOS_INJECTION_IN_GROUP: true
      TERMS_OF_SERVICES: |
        {
          en: "%PUBLIC_URL%/custom-resources/tos_en.md",
          fr: "%PUBLIC_URL%/custom-resources/tos_fr.md"
        }
      HEADER_LINKS: |
        [
          {
            label: {
              en: "Tutorials",
              fr: "Tutoriels",
              "zh-CN": "教程",
              fi: "Opastus",
              no: "Opplæring",
              it: "Tutorial",
              nl: "Zelfstudie"
            },
            icon: "https://www.sspcloud.fr/trainings.svg",
            url: "https://www.sspcloud.fr/formation"
          },
          {
            label: "AI Chat",
            icon: "SmartToy",
            url: "https://llm.lab.sspcloud.fr"
          },
          {
            "label": {
              "en": "Contact us",
              "fr": "Contactez nous"
            },
            "icon": "Support",
            "url": "https://join.slack.com/t/3innovation/shared_invite/zt-1bo6y53oy-Y~zKzR2SRg37pq5oYgiPuA"
          }
        ]
      HOMEPAGE_CALL_TO_ACTION_BUTTON_AUTHENTICATED: |
        {
          "label": {
            "fr": "Nouvel utilisateur du datalab ?",
            "en": "New user of the datalab?",
            "zh-CN": "数据实验室新用户？",
            "fi": "Uusi datalabin käyttäjä?",
            "no": "Ny bruker av datalaben?",
            "it": "Nuovo utente del datalab?",
            "nl": "Nieuwe gebruiker van het datalab?"
          },
          "startIcon": "MenuBook",
          "url": "https://docs.sspcloud.fr"
        }
      SOCIAL_MEDIA_TITLE: "SSPCloud Datalab"
      SOCIAL_MEDIA_DESCRIPTION: "Open Innovation Platform powered by Onyxia"
      SOCIAL_MEDIA_IMAGE: "%PUBLIC_URL%/custom-resources/social-preview.png"
      HEADER_TEXT_BOLD: "SSPCloud"
      HEADER_TEXT_FOCUS: "Datalab"
      FONT: |
        {
          fontFamily: "Geist",
          dirUrl: "%PUBLIC_URL%/fonts/Geist",
          "400": "Geist-Regular.woff2",
          "500": "Geist-Medium.woff2",
          "600": "Geist-SemiBold.woff2",
          "700": "Geist-Bold.woff2"
        }
      PALETTE_OVERRIDE_LIGHT: |
        {
            focus: {
                main: "#3B82F6",
                light: "#3B82F6",
            },
            light: {
                main: "#FAFAFA",
                light: "#FFFFFF",
                greyVariant1: "#EBEFF6"
            },
        }
      PALETTE_OVERRIDE_DARK: |
        {
            focus: {
              main: "#5695FB",
              light: "#5695FB",
            },
            dark: {
              main: "#0A152B",
              light: "#040B17",
            },
        }
      HOMEPAGE_MAIN_ASSET: "false"
      CUSTOM_HTML_HEAD: |
          <link rel="stylesheet" href="%PUBLIC_URL%/custom-resources/main.css"></link>
      BACKGROUND_ASSET: |
        {
          "light": "%PUBLIC_URL%/custom-resources/OnyxiaNeumorphismLightMode.svg",
          "dark": "%PUBLIC_URL%/custom-resources/OnyxiaNeumorphismDarkMode.svg"
        }
      CONTACT_FOR_ADDING_EMAIL_DOMAIN: |
        {
          "en": "If your email domain is not yet allowed [contact us](https://3innovation.slack.com/signup#/domain-signup)",
          "fr": "Si votre domaine de messagerie n'est pas encore autorisé [contactez-nous](https://3innovation.slack.com/signup#/domain-signup)"
        }
```

{% endcode %}
{% endtab %}

{% tab title="France" %}

<figure><img src="/files/tkvVTAYGpGA2Bnh32dH4" alt=""><figcaption><p>Light Mode</p></figcaption></figure>

<figure><img src="/files/AM9hItP3B1wldY8HQbzb" alt=""><figcaption><p>Dark Mode</p></figcaption></figure>

{% code title="onyxia/values.yaml" %}

```yaml
onyxia:
  web:
    env:
      #ONYXIA_API_URL: https://datalab.sspcloud.fr/api
      CUSTOM_RESOURCES: "https://www.sspcloud.fr/france/custom-resources.zip"
      FONT: |
        { 
          fontFamily: "Marianne", 
          dirUrl: "%PUBLIC_URL%/custom-resources/fonts/Marianne", 
          "400": "Marianne-Regular.woff2",
          "400-italic": "Marianne-Regular_Italic.woff2",
          "500": "Marianne-Medium.woff2",
          "700": "Marianne-Bold.woff2",
          "700-italic": "Marianne-Bold_Italic.woff2"
        }
      PALETTE_OVERRIDE: |
        {
          focus: {
            main: "#000091",
            light: "#9A9AFF",
            light2: "#E5E5F4"
          },
          dark: {
            main: "#2A2A2A",
            light: "#383838",
            greyVariant1: "#161616",
            greyVariant2: "#9C9C9C",
            greyVariant3: "#CECECE",
            greyVariant4: "#E5E5E5"
          },
          light: {
            main: "#F1F0EB",
            light: "#FDFDFC",
            greyVariant1: "#E6E6E6",
            greyVariant2: "#C9C9C9",
            greyVariant3: "#9E9E9E",
            greyVariant4: "#747474"
          }
        }
      SOCIAL_MEDIA_IMAGE: "%PUBLIC_URL%/custom-resources/preview-france.png"
      HOMEPAGE_MAIN_ASSET: "false"
```

{% endcode %}
{% endtab %}

{% tab title="Honey" %}

<figure><img src="/files/mbcEfHRxMLqnsL9Ykdt1" alt=""><figcaption><p>Light mode</p></figcaption></figure>

<figure><img src="/files/t355gluuhyU6psgAfHmS" alt=""><figcaption><p>Dark mode</p></figcaption></figure>

{% code title="onyxia/values.yaml" %}

```yaml
onyxia:
  web:
    env:
      #ONYXIA_API_URL: https://datalab.sspcloud.fr/api
      CUSTOM_RESOURCES: "https://www.sspcloud.fr/honey/custom-resources.zip"
      HEADER_LOGO: "%PUBLIC_URL%/custom-resources/dapla_honey.svg"
      HEADER_TEXT_BOLD: "Onyxia Preview"
      HEADER_TEXT_FOCUS: "v10"
      HEADER_LINKS: |
        [
          {
            "label": "AIML4OS",
            "icon": "VideoCall",
            "url": "https://insee-fr.zoom.us/webinar/register/WN_6dhMgoUvRXmkiyvYYIyKvw"
          }
        ]
      PALETTE_OVERRIDE: |
        {
          "focus": {
            "main": "#FF9100", // Light mode focus
            light: "#FAB900", // Dark mode focus
          },
          "limeGreen": {
              "main": "#00DF0A"
          },
          "dapla": {
            yellow: "#FAB900",
            darkerYellow: "#FF9100"
          }
        }
      FONT: |
        {
          fontFamily: "Geist",
          dirUrl: "%PUBLIC_URL%/custom-resources/fonts/Geist",
          "400": "Geist-Regular.woff2",
          "500": "Geist-Medium.woff2",
          "600": "Geist-SemiBold.woff2",
          "700": "Geist-Bold.woff2"
        }
      HOMEPAGE_MAIN_ASSET: "%PUBLIC_URL%/custom-resources/dapla_bee_logo.png"
      HOMEPAGE_MAIN_ASSET_SCALE_FACTOR: "0.8"
      HOMEPAGE_MAIN_ASSET_Y_OFFSET: "3rem"
      HEADER_HIDE_ONYXIA: "true"
      BACKGROUND_ASSET: |
        {
          dark: "%PUBLIC_URL%/custom-resources/dapla_background_dark.svg",
          light: "%PUBLIC_URL%/custom-resources/dapla_background_light.svg"
        }
      #HOMEPAGE_CARDS: "[]"
      ENABLED_LANGUAGES: "no,en"
      HOMEPAGE_HERO_TEXT: |
        {
          en: "Welcome to the Dapla **Lab**",
          no: "Velkommen til Dapla **Lab**",
        }
      HOMEPAGE_HERO_TEXT_AUTHENTICATED: |
        {
          en: "Welcome %USER_FIRSTNAME%!",
          no: "Velkommen %USER_FIRSTNAME%!",
        }
      TERMS_OF_SERVICES: |
        {
          en: "%PUBLIC_URL%/custom-resources/tos_en.md",
          fr: "%PUBLIC_URL%/custom-resources/tos_fr.md",
        }
      CUSTOM_HTML_HEAD: |
        <link rel="stylesheet" href="%PUBLIC_URL%/custom-resources/custom.css">
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Additional Notes

Note that your custom assets are imported into your Onyxia instance via the use of the `CUSTOM_RESOURCES` parameter, url of a ZIP archive that should contain your assets.

{% hint style="info" %}
Onyxia is configured to make the the browser cache assets so they are not re-downloaded each time the user access the app.

If you update some of your asset but keep the same URL, you can force the browser of your users to download the new version by adding a query parameter to the URL. Eample:

`HEADER_LOGO: "%PUBLIC_URL%/custom-resources/logo.svg?v=2"`
{% endhint %}

Make sure to checkout the version of this document that matches the Onyxia version that you are deploying. [See releases](https://github.com/InseeFrLab/onyxia/releases).


# Catalog of services

How Onyxia catalogs map to Helm repositories and how to customize them.

Onyxia ships with a set of **official service catalogs**.

If you don’t configure anything, these are the defaults:

<table data-view="cards"><thead><tr><th></th><th data-type="content-ref"></th><th data-hidden data-card-cover data-type="image">Cover image</th></tr></thead><tbody><tr><td>Interactive services (IDEs)</td><td><a href="https://github.com/inseefrlab/helm-charts-interactive-services">https://github.com/inseefrlab/helm-charts-interactive-services</a></td><td><a href="/files/RnZgOxptW9N9ZHNRvZop">/files/RnZgOxptW9N9ZHNRvZop</a></td></tr><tr><td>Databases</td><td><a href="https://github.com/inseefrlab/helm-charts-databases">https://github.com/inseefrlab/helm-charts-databases</a></td><td><a href="/files/6qYJZfV1teLPYrabkYQW">/files/6qYJZfV1teLPYrabkYQW</a></td></tr><tr><td>Automation</td><td><a href="https://github.com/InseeFrLab/helm-charts-automation/">https://github.com/InseeFrLab/helm-charts-automation/</a></td><td><a href="/files/816epoF5roy4kAv9KhEw">/files/816epoF5roy4kAv9KhEw</a></td></tr><tr><td>Data visualization (optional)</td><td><a href="https://github.com/InseeFrLab/helm-charts-datavisualization">https://github.com/InseeFrLab/helm-charts-datavisualization</a></td><td><a href="/files/2u5iO0sVZH4qlDYbTc3u">/files/2u5iO0sVZH4qlDYbTc3u</a></td></tr></tbody></table>

As an instance admin, you can heavily customize what users see and can do:

* Change defaults for a service (resources, images, features).
* Apply different policies per user group (example: who can request H100).
* Fork our catalogs or build your own.
* Turn any Helm-deployable software into a service.

Example: [Doom launched as an Onyxia service](https://youtu.be/7SuXRfQqdGM?si=2Y_jrQyW-fMfGn6M\&t=731).

## Mental model: Onyxia is a UI for Helm

If you already know Helm, most of this will feel familiar.

### Helm concepts (baseline)

* A **Helm repository** is a collection of Helm charts.
* A **Helm chart** is a recipe to deploy software on Kubernetes.
* Charts expose configuration via **values**.

Defaults live in `values.yaml`.

Example: [`values.yaml` (jupyter-python)](https://github.com/InseeFrLab/helm-charts-interactive-services/blob/main/charts/jupyter-python/values.yaml).

When installing a chart, you can override any default value.

Charts can also ship a `values.schema.json`.

This JSON Schema describes:

* which options exist
* the expected types / formats
* constraints (min/max, enums, patterns, …)

Example: [`values.schema.json` (jupyter-python)](https://github.com/InseeFrLab/helm-charts-interactive-services/blob/main/charts/jupyter-python/values.schema.json).

### How Onyxia uses Helm to build the UX

You configure which Helm repositories Onyxia should load as catalogs.

{% hint style="info" %}
If you don’t configure catalogs, Onyxia loads the defaults from [`catalogs.json`](https://github.com/InseeFrLab/onyxia-api/blob/main/onyxia-api/src/main/resources/catalogs.json).
{% endhint %}

On the “Service catalog” page:

* Each **Helm repo** becomes a **tab** (Interactive services, Databases, Automation, …).
* Each **chart** becomes a **service card** (Jupyter, RStudio, …).

<figure><img src="/files/PwSH0X07K8KBBOG5MMKW" alt=""><figcaption></figcaption></figure>

When a user opens a service:

* Onyxia reads the chart’s `values.schema.json`.
* It renders a form from the schema.
* It generates a final `values` object that Helm will apply.

Onyxia can also inject user-specific defaults. For example, it can prefill S3 credentials:

<figure><img src="/files/YgG4XgPCZX5JlQa3Exnh" alt=""><figcaption></figcaption></figure>

## Customizing the catalog

You have two main customization paths:

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th data-type="content-ref"></th></tr></thead><tbody><tr><td>Instance-level overrides (recommended for most setups)</td><td><a href="/spaces/x3LIftMZY501x5liXUPV/pages/aRY1V5YfezXSATQCl5bQ">/spaces/x3LIftMZY501x5liXUPV/pages/aRY1V5YfezXSATQCl5bQ</a></td></tr><tr><td>Bring your own catalogs (or a fork of ours)</td><td><a href="/spaces/x3LIftMZY501x5liXUPV/pages/mDbXa79UBD9mFNjVL7X9">/spaces/x3LIftMZY501x5liXUPV/pages/mDbXa79UBD9mFNjVL7X9</a></td></tr></tbody></table>


# values.schema.json overrides

Instance Level Customization of the Service Catalog

This is the most common customization path.

It lets you change defaults and constraints without forking catalogs.

Use it to:

* set global policies (example: default RAM, max disk)
* restrict advanced options to specific roles (example: H100 only for users with a specific role assigned)

### Mental model

Some fields in a chart’s `values.schema.json` point to a schema file.

Onyxia ships a set of “well-known” schema files in the API:

{% embed url="<https://github.com/InseeFrLab/onyxia-api/tree/main/onyxia-api/src/main/resources/schemas>" %}

When a chart uses [`x-onyxia`](/admin-doc/catalog-of-services/custom-catalogs/onyxia-extension)`.overwriteSchemaWith`, Onyxia resolves that schema path.

### How `overwriteSchemaWith` works

In the catalog charts (example: [InseeFrLab/helm-charts-interactive-services](https://github.com/inseefrlab/helm-charts-interactive-services)), look for `x-onyxia.overwriteSchemaWith` in `charts/*/values.schema.json`.

Example:

{% code title="charts/jupyter-python/values.schema.json (excerpt)" %}

```json
{
  "properties": {
    "service": {
      "properties": {
        "image": {
          "properties": {
            "custom": {
              "properties": {
                "enabled": {
                  "x-onyxia": {
                    "overwriteSchemaWith": "ide/customImage.json"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
```

{% endcode %}

This means: “for this field, use the schema at `ide/customImage.json`”.

That schema file can come from:

* the default schemas embedded in Onyxia API
* an override you provide at the instance level (see below)

### Instance-wide overrides

Let's consider the [`ide/customImage.json`](https://github.com/InseeFrLab/onyxia-api/blob/main/onyxia-api/src/main/resources/schemas/ide/customImage.json) schema for exaple. By default this will be used:

{% code title="ide/customImage.json (default)" %}

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Use a custom image instead",
  "type": "boolean",
  "default": false
}
```

{% endcode %}

It enable users to provide a custom Docker image for a given service, let's say we want to remove this option. To do that you would configure your Onyxia instance like this:

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    schemas:
      enabled: true
      files:
        - relativePath: ide/customImage.json
          content: |
            {
              "$schema": "http://json-schema.org/draft-07/schema#",
              "hidden": true,
              "const": false
            }
```

{% endcode %}

Result: the “custom image” toggle disappears from the launcher.

<details>

<summary>Other Example: change default resource sliders</summary>

This is a typical way to enforce sane defaults and limits for CPU/memory.

{% code title="apps/onyxia/values.yaml (excerpt)" %}

```yaml
onyxia:
  api:
    schemas:
      enabled: true
      files:
        - relativePath: ide/resources.json
          content: |
            {
              "$schema": "http://json-schema.org/draft-07/schema#",
              "title": "Resources",
              "description": "Your service will have at least the requested resources and never more than its limits.",
              "type": "object",
              "properties": {
                "requests": {
                  "description": "Guaranteed resources",
                  "type": "object",
                  "properties": {
                    "cpu": {
                      "title": "CPU",
                      "type": "string",
                      "default": "100m",
                      "render": "slider",
                      "sliderMin": 50,
                      "sliderMax": 10000,
                      "sliderStep": 50,
                      "sliderUnit": "m",
                      "sliderExtremity": "down",
                      "sliderExtremitySemantic": "guaranteed",
                      "sliderRangeId": "cpu"
                    },
                    "memory": {
                      "title": "Memory",
                      "type": "string",
                      "default": "2Gi",
                      "render": "slider",
                      "sliderMin": 1,
                      "sliderMax": 200,
                      "sliderStep": 1,
                      "sliderUnit": "Gi",
                      "sliderExtremity": "down",
                      "sliderExtremitySemantic": "guaranteed",
                      "sliderRangeId": "memory"
                    }
                  }
                },
                "limits": {
                  "description": "Max resources",
                  "type": "object",
                  "properties": {
                    "cpu": {
                      "title": "CPU",
                      "type": "string",
                      "default": "5000m",
                      "render": "slider",
                      "sliderMin": 50,
                      "sliderMax": 10000,
                      "sliderStep": 50,
                      "sliderUnit": "m",
                      "sliderExtremity": "up",
                      "sliderExtremitySemantic": "maximum",
                      "sliderRangeId": "cpu"
                    },
                    "memory": {
                      "title": "Memory",
                      "type": "string",
                      "default": "50Gi",
                      "render": "slider",
                      "sliderMin": 1,
                      "sliderMax": 200,
                      "sliderStep": 1,
                      "sliderUnit": "Gi",
                      "sliderExtremity": "up",
                      "sliderExtremitySemantic": "maximum",
                      "sliderRangeId": "memory"
                    }
                  }
                }
              }
            }
```

{% endcode %}

</details>

### Role-based overrides (different schema per user role)

Instance-wide overrides apply to everyone.

You can also apply schema overrides per role.

Onyxia reads roles from the decoded JWT access token.

By default, it uses the `roles` claim.

You can change that in your OIDC configuration.

See [OpenID Connect Configuration](/docs.onyxia.sh/v10/admin-doc/openid-connect-configuration).

#### Example: let `fullgpu` users choose H100

Here we override the built-in `nodeSelector-gpu.json` schema only for users with role `fullgpu`.

Other users still get the default schema.

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    schemas:
      enabled: true
      roles:
        - roleName: fullgpu
          files:
            - relativePath: nodeSelector-gpu.json
              content: |
                {
                  "$schema": "http://json-schema.org/draft-07/schema#",
                  "title": "Node Selector",
                  "type": "object",
                  "properties": {
                    "disktype": {
                      "description": "The type of disk",
                      "type": "string",
                      "enum": ["ssd", "hdd"],
                      "default": "ssd"
                    },
                    "gpu": {
                      "description": "The type of GPU",
                      "type": "string",
                      "enum": ["A2", "H100"],
                      "default": "A2"
                    }
                  },
                  "additionalProperties": false
                }
```

{% endcode %}

Result: `fullgpu` users can request GPU nodes and select `H100`.

### Next: user-specific defaults

So far you can override schemas:

* for everyone (instance-wide)
* for a subset of users (per role)

If you want per-user defaults (prefill from identity), use `x-onyxia.overwriteDefaultWith`.

Follow up with:

{% content-ref url="/spaces/x3LIftMZY501x5liXUPV/pages/a0M5hMtGR3cgjUUzN7uU" %}
[x-onyxia](/docs.onyxia.sh/v10/admin-doc/catalog-of-services/custom-catalogs/onyxia-extension)
{% endcontent-ref %}


# Custom Catalogs

Declare your own repository of charts

Use custom catalogs when you want to:

* fork the official catalogs and maintain your own variants
* publish internal charts (private org tooling)
* expose non-official charts as first-class Onyxia services

{% hint style="info" %}
If you don’t configure `onyxia.api.catalogs`, Onyxia loads the defaults from [`catalogs.json`](https://github.com/InseeFrLab/onyxia-api/blob/main/onyxia-api/src/main/resources/catalogs.json).
{% endhint %}

{% hint style="info" %}
If you only need to change defaults/constraints, avoid forking catalogs. Use [values.schema.json overrides](/docs.onyxia.sh/v10/admin-doc/catalog-of-services/override-schema-for-a-specific-instance).
{% endhint %}

### Configure catalogs

Catalogs are configured in `apps/onyxia/values.yaml` under `onyxia.api.catalogs`.

Example: you’re NASA and you want an “Aerospace services” tab.

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  web:
    # ...
  api:
    # ...
    catalogs: [
      {
        type: "helm",
        id: "aerospace",
        # The url of the Helm chart repository
        location: "https://myorg.github.io/helm-charts-aerospace/",
        # Display under the search bar as selection tab:
        # https://github.com/InseeFrLab/onyxia/assets/6702424/a7247c7d-b0be-48db-893b-20c9352fdb94
        name: { 
          en: "Aerospace services",
          fr: "Services aérospatiaux"
          # ... other languages your instance supports
        },
        # Optional. Defines the chart that should appear first
        highlightedCharts: ["jupyter-artemis", "rstudio-dragonfly"],
        # Optional. Defines the chart that should be excluded
        excludedCharts: ["a-vendor-locking-chart"],
        # Optional, If defined, displayed in the header of the catalog page:
        # https://github.com/InseeFrLab/onyxia/assets/6702424/57e32f44-b889-41b2-b0c7-727c35b07650
        # Is rendered as Markdown
        description: { 
          en: "A catalog of services for aerospace engineers",
          fr: "Un catalogue de services pour les ingénieurs aérospatiaux"
          # ...
        },
        # Can be "PROD" or "TEST". If test the catalogs will be accessible if you type the url in the search bar
        # but you won't have a tab to select it.
        status: "PROD",
        # Optional. If true the certificate verification for `${location}/index.yaml` will be skipped.
        skipTlsVerify: false,
        # Optional. certificate authority file to use for the TLS verification
        caFile: "/path/to/ca.crt",
        # Optional: Enables you to a specific group of users.
        # You can match any claim in the JWT token.  
        # If the claim's value is an array, it match if one of the value is the one you specified.
        # The match property can also be a regex.
        restrictions: [
          {
            userAttribute: {
              key: "groups",
              matches: "nasa-engineers"
            }
          }
        ]
      },
       # { ... } another catalog
    ]
```

{% endcode %}

### Next: fork or build a catalog repo

Most setups start by forking an official catalog and editing `charts/*/values.schema.json` and chart defaults.

Good starting point: [InseeFrLab/helm-charts-interactive-services](https://github.com/inseefrlab/helm-charts-interactive-services).

To go further, you’ll want the Onyxia JSON Schema extensions:

{% content-ref url="/spaces/x3LIftMZY501x5liXUPV/pages/a0M5hMtGR3cgjUUzN7uU" %}
[x-onyxia](/docs.onyxia.sh/v10/admin-doc/catalog-of-services/custom-catalogs/onyxia-extension)
{% endcontent-ref %}


# x-onyxia

Onyxia's JSON Schema extention

Onyxia defines a custom extension to the [JSON Schema spec](/admin-doc/catalog-of-services/custom-catalogs/json-schema-support). It adds Onyxia-specific properties under a reserved key: `x-onyxia`.

The main use case is per-user defaults based on identity. For example, you can inject the right Git and S3 credentials for each user.

### overwriteDefaultWith

Let's consider a sample of the `values.schema.json` of the InseeFrLab/helm-charts-interactive-services' Jupyter chart:

<pre class="language-json" data-title="values.schema.json"><code class="lang-json">"git": {
    "description": "Git user configuration",
    "type": "object",
    "properties": {
        "enabled": {
            "type": "boolean",
            "description": "Add git config inside your environment",
            "default": true
        },
        "name": {
            "type": "string",
            "description": "user name for git",
            "default": "",
<strong>            "x-onyxia": {
</strong><strong>                "overwriteDefaultWith": "{{git.name}}"
</strong><strong>            },
</strong>            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "email": {
            "type": "string",
            "description": "user email for git",
            "default": "",
<strong>            "x-onyxia": {
</strong><strong>                "overwriteDefaultWith": "{{git.email}}"
</strong><strong>            },
</strong>            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "cache": {
            "type": "string",
            "description": "duration in seconds of the credentials cache duration",
            "default": "",
<strong>            "x-onyxia": {
</strong><strong>                "overwriteDefaultWith": "{{git.credentials_cache_duration}}"
</strong><strong>            },
</strong>            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "token": {
            "type": "string",
            "description": "personal access token",
            "default": "",
<strong>            "x-onyxia": {
</strong><strong>                "overwriteDefaultWith": "{{git.token}}"
</strong><strong>            },
</strong>            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "repository": {
            "type": "string",
            "description": "Repository url",
            "default": "",
            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "branch": {
            "type": "string",
            "description": "Brach automatically checkout",
            "default": "",
            "hidden": {
                "value": "",
                "path": "git/repository"
            }
        }
    }
},
</code></pre>

And it translates into this:

{% embed url="<https://user-images.githubusercontent.com/6702424/177571819-f2e1b4ef-ecd1-479b-a5a1-658d87d7c7c0.png>" %}

Note the `"git.name"`, `"git.email"` and `"git.token"`, this enables [onyxia-web](https://github.com/InseeFrLab/onyxia-web) to pre fill the fields.

If the user took the time to fill its profile information, [onyxia-web](https://github.com/InseeFrLab/onyxia-web) knows what is the Git **username**, **email** and **personal access token** of the user.

![The onyxia user profile](/files/WA8zHt4hYRSdn3zcqJHz)

[Here](https://github.com/InseeFrLab/onyxia/blob/main/web/src/core/ports/OnyxiaApi/XOnyxia.ts) is defined the structure of the context that you can use in the `overwriteDefaultWith` field:

```typescript

export type XOnyxiaParams = {
    /**
     * This is where you can reference values from the onyxia context so that they
     * are dynamically injected by the Onyxia launcher.
     *
     * Examples:
     * "overwriteDefaultWith": "user.email" ( You can also write "{{user.email}}" it's equivalent )
     * "overwriteDefaultWith": "{{project.id}}-{{k8s.randomSubdomain}}.{{k8s.domain}}"
     * "overwriteDefaultWith": [ "a hardcoded value", "some other hardcoded value", "{{region.oauth2.clientId}}" ]
     * "overwriteDefaultWith": { "foo": "bar", "bar": "{{region.oauth2.clientId}}" }
     */
    overwriteDefaultWith?: string | Stringifyable[] | Record<string, Stringifyable>;
    overwriteListEnumWith?: string | Stringifyable[];
    hidden?: boolean;
    readonly?: boolean;
};

export type XOnyxiaContext = {
    user: {
        idep: string;
        name: string;
        email: string;
        password: string;
        ip: string;
        darkMode: boolean;
        lang: "en" | "fr" | "zh-CN" | "no" | "fi" | "nl" | "it" | "es" | "de";
        /**
         * Decoded JWT OIDC ID token of the user launching the service.
         *
         * Sample value:
         * {
         *   "sub": "9000ffa3-5fb8-45b5-88e4-e2e869ba3cfa",
         *   "name": "Joseph Garrone",
         *   "aud": ["onyxia", "minio-datanode"],
         *   "groups": [
         *       "USER_ONYXIA",
         *       "codegouv",
         *       "onyxia",
         *       "sspcloud-admin",
         *   ],
         *   "preferred_username": "jgarrone",
         *   "given_name": "Joseph",
         *   "locale": "en",
         *   "family_name": "Garrone",
         *   "email": "joseph.garrone@insee.fr",
         *   "policy": "stsonly",
         *   "typ": "ID",
         *   "azp": "onyxia",
         *   "email_verified": true,
         *   "realm_access": {
         *       "roles": ["offline_access", "uma_authorization", "default-roles-sspcloud"]
         *   }
         * }
         */
        decodedIdToken: Record<string, unknown>;
        accessToken: string;
        refreshToken: string;
        // See: https://docs.onyxia.sh/v/v10/admin-doc/catalog-of-services/customize-your-charts/declarative-user-profile
        profile: Record<string, Stringifyable> | undefined;
    };
    service: {
        oneTimePassword: string;
    };
    project: {
        id: string;
        password: string;
        basic: string;
    };
    git: {
        name: string;
        email: string;
        credentials_cache_duration: number;
        token: string | undefined;
    };
    vault:
        | {
              VAULT_ADDR: string;
              VAULT_TOKEN: string | undefined;
              VAULT_MOUNT: string;
              VAULT_TOP_DIR: string;
          }
        | undefined;
    s3:
        | {
              profileName: string;
              AWS_ACCESS_KEY_ID: string | undefined;
              AWS_SECRET_ACCESS_KEY: string | undefined;
              AWS_SESSION_TOKEN: string | undefined;
              AWS_DEFAULT_REGION: string;
              AWS_S3_ENDPOINT: string;
              port: number;
              pathStyleAccess: boolean;
              /**
               * If true the bucket's (directory) should be accessible without any credentials.
               * In this case s3.AWS_ACCESS_KEY_ID, s3.AWS_SECRET_ACCESS_KEY and s3.AWS_SESSION_TOKEN
               * are undefined.
               */
              isAnonymous: boolean;
          }
        | undefined;
    s3_array: {
        profileName: string;
        AWS_ACCESS_KEY_ID: string | undefined;
        AWS_SECRET_ACCESS_KEY: string | undefined;
        AWS_SESSION_TOKEN: string | undefined;
        AWS_DEFAULT_REGION: string;
        AWS_S3_ENDPOINT: string;
        port: number;
        pathStyleAccess: boolean;
        /**
         * If true the bucket's (directory) should be accessible without any credentials.
         * In this case s3.AWS_ACCESS_KEY_ID, s3.AWS_SECRET_ACCESS_KEY and s3.AWS_SESSION_TOKEN
         * are undefined.
         */
        isAnonymous: boolean;
    }[];
    region: {
        defaultIpProtection: boolean | undefined;
        defaultNetworkPolicy: boolean | undefined;
        allowedURIPattern: string;
        customValues: Record<string, unknown> | undefined;
        kafka:
            | {
                  url: string;
                  topicName: string;
              }
            | undefined;
        tolerations: unknown[] | undefined;
        from: unknown[] | undefined;
        nodeSelector: Record<string, unknown> | undefined;
        startupProbe: Record<string, unknown> | undefined;
        sliders: Record<
            string,
            {
                sliderMin: number;
                sliderMax: number;
                sliderStep: number;
                sliderUnit: string;
            }
        >;
        resources:
            | {
                  cpuRequest?: `${number}${string}`;
                  cpuLimit?: `${number}${string}`;
                  memoryRequest?: `${number}${string}`;
                  memoryLimit?: `${number}${string}`;
                  disk?: `${number}${string}`;
                  gpu?: `${number}`;
              }
            | undefined;
        openshiftSCC:
            | {
                  scc: string;
                  enabled: boolean;
              }
            | undefined;
    };
    k8s: {
        domain: string;
        ingressClassName: string | undefined;
        ingress: boolean | undefined;
        route: boolean | undefined;
        istio:
            | {
                  enabled: boolean;
                  gateways: string[];
              }
            | undefined;
        randomSubdomain: string;
        initScriptUrl: string;
        useCertManager: boolean;
        certManagerClusterIssuer: string | undefined;
    };
    proxyInjection:
        | {
              enabled: boolean | undefined;
              httpProxyUrl: string | undefined;
              httpsProxyUrl: string | undefined;
              noProxy: string | undefined;
          }
        | undefined;
    packageRepositoryInjection:
        | {
              cranProxyUrl: string | undefined;
              condaProxyUrl: string | undefined;
              packageManagerUrl: string | undefined;
              pypiProxyUrl: string | undefined;
          }
        | undefined;
    certificateAuthorityInjection:
        | {
              cacerts: string | undefined;
              pathToCaBundle: string | undefined;
          }
        | undefined;
};

assert<Equals<XOnyxiaContext["user"]["lang"], Language>>();

```

You can also concatenate string values using by wrapping the XOnyxia targeted values in `{{}}`.

{% code title="values.shema.json" %}

```json
"hostname": {
  "type": "string",
  "form": true,
  "title": "Hostname",
  "x-onyxia": {
    "overwriteDefaultWith": "{{project.id}}-{{k8s.randomSubdomain}}.{{k8s.domain}}"
  }
}
```

{% endcode %}

### overwriteListEnumWith

This is an option for customizing the options of the forms fields rendered as select.

<figure><img src="/files/g8XWP9Tow1N5qUWkisLb" alt="" width="375"><figcaption><p>Example of select form field in the onyxia launcher</p></figcaption></figure>

In your values shema such a field would be defined like:

{% code title="values.shema.json" %}

```json
"pullPolicy": {
    "type": "string",
    "default": "IfNotPresent",
    "listEnum": [
        "IfNotPresent",
        "Always",
        "Never"
    ]
}
```

{% endcode %}

But what if you want to dynamically generate the option? For this you can use the overwriteListEnumWith x-onyxia option.\
For example if you need to let the user select one of the groups he belongs to you can write:

<pre class="language-json" data-title="values.schema.json"><code class="lang-json">"group": {
  "type": "string",
<strong>  "default": "",
</strong><strong>  "listEnum": [""],
</strong>  "x-onyxia": {
<strong>    "overwriteDefaultWith": "{{user.decodedIdToken.groups[0]}}",
</strong><strong>    "overwriteListEnumWith": "{{user.decodedIdToken.groups}}"
</strong>  }
}
</code></pre>

### overwriteSchemaWith

See: [values.schema.json overrides](/admin-doc/catalog-of-services/override-schema-for-a-specific-instance)


# Declarative User Profile

You can define a custom user profile form that appears directly within the user interface.

<figure><img src="/files/JM7t6pGAwPiu0csampAn" alt=""><figcaption><p>Custom form defined by the Onyxia instance administrator</p></figcaption></figure>

This form is configured using a JSON Schema provided via your Onyxia `values.yaml`. Here's an example that produces the form shown above:

{% code title="onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    userProfile:
      enabled: true
      default:
        profileSchema: |
          {
            "type": "object",
            "properties": {
              "generalInfo": {
                "type": "object",
                "description": "General profile information",
                "properties": {
                  "firstName": {
                    "type": "string",
                    "title": "First name",
                    "description": "Your first name",
                    "x-onyxia": {
                      "overwriteDefaultWith": "{{user.decodedIdToken.given_name}}"
                    }
                  },
                  "familyName": {
                    "type": "string",
                    "title": "Family name",
                    "description": "Your family name",
                    "x-onyxia": {
                      "overwriteDefaultWith": "{{user.decodedIdToken.family_name}}"
                    }
                  },
                  "email": {
                    "type": "string",
                    "title": "Email",
                    "description": "Your email address",
                    "x-onyxia": {
                      "overwriteDefaultWith": "{{user.decodedIdToken.email}}"
                    }
                  }
                }
              },
              "git": {
                "type": "object",
                "description": "Git configuration",
                "properties": {
                  "username": {
                    "type": "string",
                    "title": "Git username",
                    "description": "Your username for Git operations (e.g. git commit, git push)",
                    "x-onyxia": {
                      "overwriteDefaultWith": "{{git.name}}"
                    }
                  },
                  "email": {
                    "type": "string",
                    "title": "Git email",
                    "description": "Your email for Git operations",
                    "x-onyxia": {
                      "overwriteDefaultWith": "{{git.email}}"
                    }
                  }
                }
              }
            }
          }
      roles:
        # NOTE: You can define role-specific schemas if needed.
        #- roleName: datascientist
        #  profileSchema: |
        #    ...
```

{% endcode %}

***

## Why Use a Custom User Profile?

Once defined, this form allows users to fill in personal and development-related information. These values become programmatically accessible, enabling dynamic behavior within your charts and deployments.

For example, with the schema above, and assuming the user has filled out the form as shown in the screenshot, the following values will be available in the Onyxia context:

{% code title="xOnyxiaContext.user.profile" %}

```json
{
  "generalInfo": {
    "firstName": "Joseph",
    "lastName": "Garrone",
    "email": "joseph.garrone@code.gouv.fr"
  },
  "git": {
    "username": "garronej",
    "email": "joseph.garrone.gj@gmail.com"
  }
}
```

{% endcode %}

These values can be injected into Helm charts. For instance:

```json
"x-onyxia": {
  "overwriteDefaultWith": "{{user.profile.generalInfo.lastName}}"
}
```

This will auto-fill the corresponding field with `"Garrone"`.\
\
(Here this example is not very inspired since we already have a Git configuration tab so there's no reason to define a Git configuration section in the declarative user profile but you get the idea)

{% hint style="warning" %}
Each time you update the JSON Schema you provide to define the user profile, all existing values that the user might have filled will be lost.
{% endhint %}

***

## Recap

* Define your schema in `onyxia.values.yaml`.
* Enable role-based customization if needed.
* Use the collected values in your Helm charts for a tailored, user-aware deployment experience.


# JSON Schema Support

This section describes JSON Schema support in the launcher.

Onyxia uses JSON Schema to dynamically create its service launch interface, often referred to as the "launcher." By defining parameters and configurations in JSON Schema, Onyxia can automatically generate forms and interfaces that guide users through setting up and deploying services.

The JSON Schema draft Onyxia follows is largely based on [Draft 7](https://json-schema.org/specification-links.html#draft-7), but it only implements a subset of the specification. This means that while Onyxia’s schema supports many core features of Draft 7—like data types, required fields, and basic validations—it may not include every feature or validation option found in the full Draft 7 specification. This subset approach keeps the schema manageable and efficient for the specific needs of Onyxia's interface generation and deployment configurations. In the following section, you’ll also see that Onyxia adds additional semantic layers.

## **Summary**

* **String**: Supports plain text input
* **Number / Integer**: Allows numerical input.
* **Boolean**: Renders a toggle.
* **Array**: Supported for list-like inputs, often used for specifying multiple items (e.g., environments, tags). Onyxia provide a way to add or remove item.
  * **Items**: Onyxia supports homogenous arrays, where all items are expected to be of the same type.
* **Object**: Forms the basis for grouping multiple fields together.
  * **Properties**: Each property in an object renders as an individual input element within the launcher.

## String

#### Render

In Onyxia’s JSON Schema implementation, string elements include various `render` types to adjust the input style based on each field’s function, creating a more intuitive user experience. Here are the primary `render` types supported for string fields:

1. **Dropdown Selection (`render: "list"`)**: Displays a dropdown menu for selecting from a set of predefined values, which is useful for fields like software versions or configurations.

   Example with schema validation :

   ```json
   {
     "type": "string",
     "enum": ["version1", "version2", "version3"],
     "default": "version1",
     "description": "Choose a software version"
   }
   ```

   Example without schema validation (usefull if your chart are reused in other context and you want people to specify other value):

   ```json
   {
     "type": "string",
     "render": "list",
     "listEnum": ["version1", "version2", "version3"], // this is onyxia specification
     "default": "version1",
     "description": "Choose a software version"
   }
   ```
2. **Password Field (`render: "password"`)**: Provides a masked input field to secure sensitive data, such as passwords or API keys.

   ```json
   {
     "type": "string",
     "render": "password",
     "description": "Enter your API key"
   }
   ```
3. **Multi-line Text (`render: "textArea"`)**: Creates a resizable, multi-line text box for longer text entries, such as configuration scripts or notes, enhancing readability and usability.

   ```json
   {
     "type": "string",
     "render": "textArea",
     "description": "Enter configuration details\n Thank you!"
   }
   ```
4. **Slider (`render: "slider"`)**: For numeric inputs (stored as strings), `render: "slider"` allows users to select a value within a specified range using a slider, commonly used for resource allocation (e.g., CPU, memory). This includes additional attributes like `sliderMin`, `sliderMax`, `sliderStep`, and `sliderUnit` to configure the slider's behavior.

   ```json
   {
     "type": "string",
     "render": "slider",
     "sliderMin": 50,
     "sliderMax": 40000,
     "sliderStep": 50,
     "sliderUnit": "m",
     "description": "Set the CPU limit"
   }
   ```

Onyxia also define some extention to the JSON Schema standard in order to let you pre-fill some values levraging what we know about the user.

{% content-ref url="/pages/a0M5hMtGR3cgjUUzN7uU" %}
[x-onyxia](/admin-doc/catalog-of-services/custom-catalogs/onyxia-extension)
{% endcontent-ref %}


# OpenID Connect Configuration

[The installation guide](/admin-doc/readme/user-authentication) explain how to set up a new [Keycloak](https://www.keycloak.org/) instance to enable authentication on your datalab.

However, chances are that your organization already has an existing IAM system in place. This guide covers how to integrate Onyxia with various commonly used OIDC providers, including [Keycloak](https://www.keycloak.org/), [Auth0](https://auth0.com/), and [Microsoft Entra ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id).

{% hint style="warning" %}
Onyxia use **Public** OpenID Connect client: **no client Secret**.

The technical term for a public OIDC client is **Authorization Code Flow + PKCE**.

It's the type of client that you create for Single Page Application (SPA).
{% endhint %}

## API Reference

<details>

<summary>Overview of all the available parameters</summary>

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    env:
      # Mandatory and no other authentication mode is currently supported.
      authentication.mode: "openidconnect"

      # Mandatory: The issuer URI of the OIDC provider.  
      oidc.issuer-uri: "..."

      # Mandatory: The client ID of the OIDC client representing the Onyxia Web Application.
      oidc.clientID: "..."

      # Mandatory: Defines which claim in the Access Token's JWT serves as the unique 
      # user identifier.  
      # This identifier must contain only lowercase alphanumeric characters and `-`. 
      # Specifically, it must comply with RFC 1123: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names
      #
      # - If your usernames already conform to this constraint, you can use 
      #   `"preferred_username"` for a more human-readable identifier.
      # - If usernames contain special characters, use another claim 
      #   such as `"sub"` (Ensure that the `sub` values comply with RFC 1123).  
      #
      oidc.username-claim: "..."

      # Optional: Defaults to `"groups"`. Defines which claim represents user groups.
      # See: https://docs.onyxia.sh/admin-doc/setting-up-group-projects
      oidc.groups-claim: "..."

      # Optional: Defaults to `"roles"`. Defines which claim represents user roles.
      oidc.roles-claim: "..."

      # Optional: Additional query parameters to append to the OIDC authorization
      # endpoint (the login url).   
      # Example: If using Keycloak with Google OAuth as an identity provider, you might want  
      # to preselect Google as the login option using `"kc_idp_hint=google"`.  
      # 
      # ⚠️ This string is appended as-is. Ensure it is properly URI-encoded.  
      # If adding multiple parameters, separate them with `&`.  
      #
      # Example: `"foo=foo%20value&bar=bar%20value"`
      #
      # duct-taping case: If you provide an audience as query param like
      # `"audience=onyxia"`, the audience will also be passed as an extra
      # token param because some AS might expect it.  
      oidc.extra-query-params: "..."

      # Optional: Expected audience (`aud`) value in the Access Token.  
      # If set, Onyxia-API validates the `aud` claim and rejects requests
      # where it doesn’t match (or isn’t included if `aud` is an array).  
      # This setting applies only on the server side.
      # Defining it here won’t change how the OIDC client requests tokens.  
      # Refer to your provider’s documentation below for details.
      oidc.audience: "..."

      # Optional: Specifies the OIDC scopes requested by the Onyxia client.  
      # Defaults to `"openid profile"`.  
      # This is a space-separated list. `"openid"` is always requested, 
      # regardless of this setting.
      oidc.scope: "..."
      
      # Optional: Automatically logs out users after a set period of inactivity. 
      # If you are using Keycloak do not provide this value, it's inferred automatically. 
      oidc.idleSessionLifetimeInSeconds: "..."

      # Optional: The Onyxia API fetches `<issuer-uri>/.well-known/openid-configuration` 
      # to retrieve JWKs for validating Access Tokens (used as Authorization Bearers).  
      #
      # ⚠️ In development, if you lack proper root certificates, you can disable TLS verification.  
      # However, in production, it is strongly recommended to mount the correct `cacerts` instead.
      oidc.skip-tls-verify: "true|false"
```

{% endcode %}

</details>

***

## OIDC Provider Specific Configuration Guides

{% tabs %}
{% tab title="Keycloak" %}
**Onyxia Login Theme**

Each version of Onyxia ships with [a custom Keycloak login theme](https://youtu.be/NrVuVXsbloA?si=fDCPpXUIpSlCHsYw\&t=405). You can download it from the [release page](https://github.com/InseeFrLab/onyxia/releases). Specific instructions for loading the theme in your Onyxia instance can be found [in this guide](https://docs.keycloakify.dev/deploying-your-theme).

If you are deploying Keycloak using Helm, as instructed in the installation guide, [here are the relevant lines](https://github.com/InseeFrLab/onyxia-ops/blob/35f86c848a3ddeef6bfe4a9a4f41e5d516eb66db/apps/keycloak/values.yaml#L60-L79) in the Onyxia-ops repository.

**Choosing the Unique User Identifier Claim**

Onyxia requires a unique user identifier. You must specify which claim in the Access Token should be used for this purpose.

Ideally, you can use `preferred_username` as an identifier, but this requires ensuring it complies with [RFC 1123](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names). This means it must contain only lowercase alphanumeric characters and `-`.

Since this format is restrictive, if you already have an existing user base, `preferred_username` may not be an option. In that case, you have two alternatives:

* **Define a custom claim**: Configure a Keycloak mapper to generate an RFC 1123-compliant claim in the Access Token.
* **Use `"sub"`**: This claim is guaranteed to be unique and always present, but ensure that the `sub` values comply with RFC 1123.

If you are starting fresh with no existing users, you can enforce a regex pattern in the **User Profile Attributes** to require usernames that comply with the restriction.

More details can be found in [the installation guide](https://docs.onyxia.sh/admin-doc/readme/user-authentication) (search for "pattern").

**Configuring Keycloak**

Beyond what's covered in the installation guide, if you need a more general tutorial on setting up a public Keycloak OIDC client like Onyxia, refer to the following guide. It includes a test project to validate your configuration.

{% embed url="<https://docs.oidc-spa.dev/providers-configuration/keycloak>" %}
For Onyxia, use these substitutions in the guide:\
\&#xNAN;**\<KC\_DOMAIN>**: `auth.lab.my-domain.net`\
\&#xNAN;**\<KC\_RELATIVE\_PATH>**: `/auth`\
\&#xNAN;**\<REALM\_NAME>**: `datalab`\
\&#xNAN;**\<APP\_DOMAIN>**: `datalab.my-domain.net`\
\&#xNAN;**\<BASE\_URL>**: `/`\
\&#xNAN;**\<DEV\_PORT>**: `5173`\
✅ Note that Onyxia implement an auto logout countdown that will start to display once minute befor auto logout if you configure your client as [a sensible app](https://docs.oidc-spa.dev/providers-configuration/keycloak#security-sensitive-apps-banking-admin-panels-etc)
{% endembed %}

Here is an overview of what your Onyxia `values.yaml` should look like:

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    env:
      authentication.mode: "openidconnect"
      # Example: "https://auth.lab.my-domain.net/auth/realms/datalab"
      oidc.issuer-uri: "https://<KC_DOMAIN><KC_RELATIVE_PATH>/realms/<REALM_NAME>"
      # Example: "onyxia"
      oidc.clientID: "<ONYXIA_CLIENT_ID>"
      # Examples:
      # `"preferred_username"` if a regex pattern is enforced for usernames.
      # `"my-custom-claim"`    if a custom Keycloak mapper is configured.
      # `"sub"`                always works and is unique.
      oidc.username-claim: "..."
      # NOTE: By default, Access Tokens issued by Keycloak have an `aud` claim 
      # of "account". You can change this value in your protocol mapper and 
      # update this setting accordingly.  
      oidc.audience: "account"
```

{% endcode %}
{% endtab %}

{% tab title="Microsoft Entra ID" %}
Follow this guide to configure a Microsoft Entra ID application for Onyxia.

{% embed url="<https://docs.oidc-spa.dev/providers-configuration/microsoft-entra-id>" %}
For Onyxia, use these substitutions:\
`My App - API` -> `Onyxia - API`\
`api://my-app-api` -> `api://onyxia-api`\
`My App` -> `Onyxia`\
[`https://my-app.com/`](https://my-app.com/) -> `https://datalab.my-domain.net/`
{% endembed %}

Here is what your configuration should look like:

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    env:
      authentication.mode: "openidconnect"
      oidc.issuer-uri: "https://login.microsoftonline.com/<Directory (tenant) ID (Onyxia)>/v2.0"
      oidc.clientID: "<Application (client) ID (Onyxia)>"
      # Do **not** use `"sub"` or `"upn"` as they may contain 
      # non-alphanumeric characters.
      oidc.username-claim: "oid"
      oidc.scope: "profile api://onyxia-api/access_as_user"
      oidc.audience: "<Application (client) ID (Onyxia - API)>"
      
```

{% endcode %}
{% endtab %}

{% tab title="Auth0" %}
Follow this guide to configure an Auth0 application for Onyxia.

{% embed url="<https://docs.oidc-spa.dev/providers-configuration/auth0>" %}
For Onyxia, use these substitutions:\
`"My App"` → `"Onyxia"`\
\&#xNAN;**\<APP\_DOMAIN>** → `datalab.my-domain.net`\
\&#xNAN;**\<BASE\_URL>** → `/`\
\&#xNAN;**\<DEV\_PORT>** → `5173`\
`"My App - API"` → `"Onyxia - API"`\
`https://myapp.my-company.com/api` → `https://datalab.my-domain.net/api`\
`"auth.my-company.com"` → `"auth.my-domain.net"`
{% endembed %}

**Generating an RFC 1123-Compliant Claim in the Access Token**

By default, Auth0 does not issue a claim that Onyxia can use as a unique user identifier. You must create one by defining a **custom claim** in the access token using an Auth0 **Trigger Action**.

**Steps to Create the `onyxia-username` Claim**

1️⃣ **Create a Custom Action**:

1. Go to **Auth0 Dashboard** → **Actions** → **Library**.
2. Click **Create Action**.
3. Set:
   * **Name**: `GenerateOnyxiaUsername`
   * **Trigger**: **Post Login**
   * **Runtime**: `Node 22`
4. Click **Create**.

2️⃣ **Add the Custom Code**:\
Replace the default content with:

```js
function toRFC1123(input) {
  if (!input) return "";
  let output = input.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+|-+$/g, "");
  return output.length > 63 ? output.substring(0, 63).replace(/-+$/, "") : output;
}

exports.onExecutePostLogin = async (event, api) => {
  const sub = event.user.user_id;
  if (sub) api.accessToken.setCustomClaim("onyxia-username", toRFC1123(sub));
};
```

3️⃣ **Deploy and Activate the Action**:

1. Click **Deploy**.
2. Go to **Auth0 Dashboard** → **Actions** → **Triggers** → **Post Login**.
3. Drag & drop `GenerateOnyxiaUsername` into the flow.
4. Click **Apply Changes**.

Now, your access token will include the `onyxia-username` claim.

<figure><img src="/files/hkFujVZfTbnRErUexWja" alt="" width="375"><figcaption><p>Preview of the decoded JWT of the Access Token issued by Auth0<br>with the custom action enabled when previewed with the<br>test app of the oidc-spa guide</p></figcaption></figure>

**Final Configuration**

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    env:
      authentication.mode: "openidconnect"
      oidc.issuer-uri: "https://auth.my-domain.net"
      oidc.clientID: "<Onyxia Application Client ID>"  
      oidc.username-claim: "onyxia-username"
      oidc.extra-query-params: "audience=https%3A%2F%2Fdatalab.my-domain.net%2Fapi"
      oidc.audience: "https://datalab.my-domain.net/api"
      # Optional: Auto logout after inactivity.
      oidc.idleSessionLifetimeInSeconds: "300"
```

{% endcode %}
{% endtab %}

{% tab title="Other" %}
If you're using another OIDC provider and need help configuring Onyxia, reach out [on Slack](https://join.slack.com/t/3innovation/shared_invite/zt-2skhjkavr-xO~uTRLgoNOCm6ubLpKG7Q). We’ll be happy to schedule a call and assist with the integration.

However, here are some generic instructions.

{% embed url="<https://docs.oidc-spa.dev/providers-configuration/other>" %}
Replace `https://my-app.com/` by `https://datalab.my-domain.net/`.
{% endembed %}
{% endtab %}
{% endtabs %}

## **OIDC Configuration for Services Onyxia Connects To**

Onyxia uses an OIDC client for authentication, but it also connects to other OIDC-enabled services.\
Each of these services **can** have its own OIDC client instance configuration, allowing Onyxia to authenticate using a separate client identity.

In the **region configuration**, you can specify an optional `oidcConfiguration` object for\
each service:

* **S3 (MinIO STS)** → `onyxia.api.regions[].data.S3.sts.oidcConfiguration`
* **Vault** → `onyxia.api.regions[].vault.oidcConfiguration`
* **Kubernetes API** → `onyxia.api.regions[].services.k8sPublicEndpoint.oidcConfiguration`

Each configuration follows this structure:

```ts
type OidcConfiguration = {
    issuerURI?: string;
    clientID?: string;
    extraQueryParams?: string;
    scope?: string;
    idleSessionLifetimeInSeconds?: number;
};
```

If no `oidcConfiguration` is provided for a service, Onyxia will reuse the same access\_token used for onyxia-api.

However, defining a separate OIDC client for each service is recommended to improve access control and security.

You might find it strange that Onyxia requires creating multiple OIDC clients to communicate with different resource servers (e.g. `onyxia-api`, `minio`, `vault`, or the Kubernetes API). You’ll typically end up with several clients such as `onyxia`, `onyxia-vault`, `onyxia-minio`, and `onyxia-kube`.\
At first, this can feel counterintuitive, a *client ID* seems like it should represent one application, not multiple variants of it.

Conceptually, a single client requesting tokens for multiple resource servers (each with its own audience and claims) would make more sense.\
However, Keycloak doesn’t model things that way. While Onyxia supports any OpenID Connect provider, it’s primarily designed around Keycloak’s behavior and limitations.

In Keycloak’s model, an OIDC *client* actually represents **an application talking to a specific resource server**, not just an application itself.

### Example Configuration in `values.yaml`

{% code title="" %}

```yaml
onyxia:
  api:
    env:
      authentication.mode: "openidconnect"
      oidc.issuer-uri: "https://auth.lab.my-domain.net/auth/realms/datalab"
      oidc.clientID: "onyxia"
    regions: 
      [
        {
          data: {
            S3: {
              sts: {
                oidcConfiguration: {
                  clientID: "onyxia-minio",
                }
              }
            }
          },
          vault: {
            oidcConfiguration: {
              clientID: "onyxia-vault"
            }
          },
          services: {
            k8sPublicEndpoint: {
              oidcConfiguration: {
                clientID: "onyxia-k8s"
              }
            }
          }
        }
      ]
```

{% endcode %}

***

### Ensuring Claim Consistency Across Services

When a user logs in, the OIDC provider issues an Access Token for the `onyxia` client.\
This token includes claims such as:

```json
{
  "sub": "abcd1234",
  "preferred_username": "jhondoe",
  "groups": [ "funathon", "spark-lab" ],
  "roles": [ "vip", "admin-keycloak" ]
}
```

If `oidc.username-claim: "preferred_username"` is configured in Onyxia’s main configuration,\
then all services it connects to—such as `onyxia-minio`, `onyxia-vault`, and `onyxia-k8s`—\
**must also receive Access Tokens where the `preferred_username` claim exists and holds the same value**.

To prevent issues, **all OIDC clients** (`onyxia`, `onyxia-minio`, `onyxia-vault`, `onyxia-k8s`)\
should be configured within **the same SSO realm** in your OIDC provider.\
This ensures that every issued Access Token follows the same claim structure and contains\
consistent values for the same user.

If you're unsure whether your setup meets this requirement, **check the JWT of each Access Token**\
issued for different clients and confirm that the claims are aligned.


# S3 Configuration

Use `onyxia.api.regions[].data.S3` to connect an Onyxia region to AWS S3 or an S3-compatible object store.

Onyxia Web uses this configuration to:

* expose administrator-defined S3 profiles in the file explorer;
* exchange the user's OIDC access token for temporary credentials through `AssumeRoleWithWebIdentity`;
* inject the selected profile and its credentials into services such as Jupyter and RStudio.

The browser talks directly to the S3 and STS endpoints. Onyxia API does not proxy these requests, create IAM roles, or install bucket policies. Configure the S3 provider, OIDC trust, roles, and policies separately, and allow requests from the Onyxia Web origin in the S3 provider's CORS configuration.

The [installation guide](/admin-doc/readme/data-s3) demonstrates a basic [MinIO](https://min.io/) deployment. This page documents the region configuration consumed by Onyxia Web.

## Minimal MinIO Example

This example defines a single administrator-managed profile named `default`. The bookmark resolves to a bucket named after the user's `preferred_username` claim.

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    regions:
      - {
          "id": "default",
          "data": {
            "S3": {
              "URL": "https://minio.lab.example.com",
              "sts": {
                "role": {
                  "profileName": "default",
                  "roleARN": "",
                  "roleSessionName": ""
                },
                "oidcConfiguration": {
                  "clientID": "onyxia-minio"
                }
              },
              "bookmarks": [
                {
                  "s3Uri": "s3://$1/",
                  "title": "Personal Bucket",
                  "claimName": "preferred_username",
                  "forProfileName": "default"
                }
              ]
            }
          }
        }
```

{% endcode %}

`roleARN` and `roleSessionName` must be present in the Onyxia configuration, but they can exceptionally be empty for MinIO. Onyxia omits empty values from the STS request. In MinIO's claim-based OIDC mode, when `RoleArn` is absent, MinIO determines the user's authorization from the configured policy claim in the JWT. MinIO's `AssumeRoleWithWebIdentity` endpoint also does not require a role session name.

To configure MinIO so that users automatically receive temporary credentials that gives them read/write access to a bucket that matches their username (the preferred\_username claim in the ID and Access token) see [this example](https://github.com/InseeFrLab/paris-sspcloud/blob/master/apps/onyxia-aws/values.yaml).

This is MinIO-specific. Providers such as AWS STS require a valid role ARN and role session name.

For a user whose S3 OIDC ID token contains:

```json
{
  "preferred_username": "alice"
}
```

Onyxia exposes the following profile:

```json
{
  "profileName": "default",
  "bookmarks": ["s3://alice/"]
}
```

If the `alice` bucket does not exist, the file explorer can offer to create it. Whether creation succeeds depends on the permissions granted by MinIO.

See [OIDC Configuration for Services Onyxia Connects To](/admin-doc/openid-connect-configuration#oidc-configuration-for-services-onyxia-connects-to) for the complete `oidcConfiguration` format. When this object is omitted or only partially specified, Onyxia reuses the corresponding values from its main OIDC configuration.

## Multiple Profiles From Claims

`data.S3` accepts either one S3 configuration object or an array of them. Within one S3 configuration, `sts.role` also accepts either one role or an array.

A role with a `claimName` can produce several profiles. If the claim is an array of strings, Onyxia resolves the role once for every accepted value.

The following example creates:

* one personal profile named `default`;
* one `project-*` profile per group, excluding groups whose names start with `USER_ONYXIA`;
* a public bookmark attached to the personal and project profiles.

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    regions:
      - {
          "id": "default",
          "data": {
            "S3": {
              "URL": "https://ceph.lab.sspcloud.fr",
              "sts": {
                "role": [
                  {
                    "profileName": "default",
                    "roleARN": "arn:aws:iam::123456789012:role/$1",
                    "roleSessionName": "onyxia-personal-bucket",
                    "claimName": "preferred_username"
                  },
                  {
                    "profileName": "project-$1",
                    "roleARN": "arn:aws:iam::329456783432:role/projet-$1",
                    "roleSessionName": "onyxia-project-bucket-$1",
                    "claimName": "groups",
                    "excludedClaimPattern": "^USER_ONYXIA.*"
                  }
                ],
                "oidcConfiguration": {
                  "clientID": "onyxia-ceph"
                }
              },
              "bookmarks": [
                {
                  "s3Uri": "s3://$1/",
                  "title": "Personal Bucket",
                  "claimName": "preferred_username",
                  "forProfileName": "default"
                },
                {
                  "s3Uri": "s3://project-$1/",
                  "title": "$1 Reserved Bucket",
                  "claimName": "groups",
                  "excludedClaimPattern": "^USER_ONYXIA.*",
                  "forProfileName": "project-$1"
                },
                {
                  "s3Uri": "s3://donnees-insee/diffusion/",
                  "title": {
                    "fr": "Données de diffusion",
                    "en": "Dissemination Data"
                  },
                  "forProfileName": ["default", "project-*"]
                }
              ]
            }
          }
        }
```

{% endcode %}

For this S3 OIDC ID token:

```json
{
  "preferred_username": "johnd",
  "groups": ["sspcloud", "codegouv", "USER_ONYXIA_admin"]
}
```

Onyxia resolves:

```json
[
  {
    "profileName": "default",
    "bookmarks": [
      "s3://johnd/",
      "s3://donnees-insee/diffusion/"
    ]
  },
  {
    "profileName": "project-sspcloud",
    "bookmarks": [
      "s3://project-sspcloud/",
      "s3://donnees-insee/diffusion/"
    ]
  },
  {
    "profileName": "project-codegouv",
    "bookmarks": [
      "s3://project-codegouv/",
      "s3://donnees-insee/diffusion/"
    ]
  }
]
```

No profile is generated for `USER_ONYXIA_admin` because it matches `excludedClaimPattern`.

## Configuration Reference

The following type describes the complete `data.S3` configuration accepted by Onyxia Web:

```typescript
type RegionData = {
  S3?: S3Config | S3Config[];
};

type S3Config = {
  /** S3 API endpoint. */
  URL: string;

  /**
   * Region passed to both the S3 and STS clients.
   * The clients use "us-east-1" when this is omitted.
   */
  region?: string;

  /**
   * true:  https://s3.example.com/bucket/key
   * false: https://bucket.s3.example.com/key
   * Default: true
   */
  pathStyleAccess?: boolean;

  /** When present, this entry creates administrator-defined profiles. */
  sts?: {
    /** STS endpoint. Defaults to S3Config.URL. */
    URL?: string;

    /**
     * Requested temporary-credential lifetime in seconds.
     * Default requested by Onyxia: 604800 (seven days).
     */
    durationSeconds?: number;

    /** Each resolved role creates one profile. */
    role: StsRole | StsRole[];

    /** Partial OIDC override for this S3 service. */
    oidcConfiguration?: OidcConfiguration;
  };

  /** Read-only, administrator-defined bookmarks in the S3 explorer. */
  bookmarks?: Bookmark[];
};

type StsRole = {
  profileName: string;
  roleARN: string;
  roleSessionName: string;

  /** When set, resolve this role from the named ID-token claim. */
  claimName?: string;
  includedClaimPattern?: string;
  excludedClaimPattern?: string;
};

type Bookmark = {
  s3Uri: string;
  title: LocalizedString;

  /**
   * Attach the bookmark only to these profiles.
   * Supports a string, an array, and * wildcards.
   * When omitted, attach it to every profile from this S3Config.
   */
  forProfileName?: string | string[];

  /** When set, resolve this bookmark from the named ID-token claim. */
  claimName?: string;
  includedClaimPattern?: string;
  excludedClaimPattern?: string;
};

type LocalizedString = string | Record<string, string>;

type OidcConfiguration = {
  issuerURI?: string;
  clientID?: string;
  extraQueryParams?: string;
  scope?: string;
  idleSessionLifetimeInSeconds?: number | string;
};
```

The configured `durationSeconds` is only a request. The STS provider can reject it or limit the resulting credential lifetime. In particular, Onyxia's seven-day default may be too high for some providers, so set an explicit value compatible with your STS service.

### Claim Expansion and Templates

`claimName` is read from the decoded ID token produced by the OIDC configuration used for S3. Dot notation is supported for nested claims, for example `realm_access.roles`.

The claim must be a string or an array of strings:

* a string resolves one role or bookmark;
* an array resolves one role or bookmark per accepted value;
* a missing claim resolves nothing for that entry.

The claim filters are JavaScript regular expressions. Resolution works as follows:

1. `excludedClaimPattern` is tested first. A matching value is discarded.
2. `includedClaimPattern` is then applied. If omitted, it defaults to `^(.+)$`.
3. `$1`, `$2`, and subsequent placeholders are replaced with capture groups from the included match.

Role templates are supported in:

* `roleARN`;
* `roleSessionName`;
* `profileName`.

Bookmark templates are supported in:

* `s3Uri`;
* `title`, including every localized value;
* `forProfileName`.

Without `claimName`, Onyxia creates exactly one role or bookmark and treats `$1` literally.

### Bookmark Profile Selection

`forProfileName` controls which resolved profiles receive a bookmark:

* omit it to attach the bookmark to every profile generated from the same S3 configuration;
* use a string for one selector;
* use an array for several selectors;
* use `*` within a selector as a wildcard, for example `project-*`.

The selector filters bookmarks in the UI. It does not grant S3 permissions.

### Defaults for User-Created Profiles

An S3 configuration without `sts` does not create an administrator-defined profile. Instead, it supplies the default URL, region, and path-style setting shown when a user creates a profile manually.

If `data.S3` contains several entries, Onyxia uses the first entry without `sts` for those form defaults. If every entry has `sts`, it uses the first entry.

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    regions:
      - {
          "id": "default",
          "data": {
            "S3": [
              {
                "URL": "https://minio.lab.example.com",
                "region": "us-east-1",
                "pathStyleAccess": true
              },
              {
                "URL": "https://ceph.lab.example.com",
                "region": "us-east-1",
                "pathStyleAccess": true,
                "sts": {
                  "role": {
                    "profileName": "default",
                    "roleARN": "arn:aws:iam::123456789012:role/onyxia-$1",
                    "roleSessionName": "onyxia-$1",
                    "claimName": "preferred_username"
                  },
                  "oidcConfiguration": {
                    "clientID": "onyxia-ceph"
                  }
                }
              }
            ]
          }
        }
```

{% endcode %}

Here, the MinIO entry only supplies defaults for the manual profile form. The Ceph entry creates the administrator-defined `default` profile.

## Operational Requirements

* The S3 and STS endpoints must be reachable from users' browsers.
* The S3 provider must allow the Onyxia Web origin through CORS.
* The STS provider must trust the issuer and client configured in `sts.oidcConfiguration`.
* Referenced roles and policies must already exist and grant access consistent with the displayed bookmarks.
* The OIDC access token is sent to STS as the web identity token. Claim templates, however, are resolved from the corresponding decoded ID token.
* Profile names must be unique across administrator-defined and user-created profiles. Name collisions are unsupported and can cause the conflicting profiles to be discarded.


# Setting up group projects

Enabling a group of users to share the same Kubernetes namespace to work on something together.

The user interface of onyxia enables to create projects for groups of Onyxia users.

Users will be able to dynamically switch from one project to another using a select input in the header.

<figure><img src="/files/DmSN2VjDTCCYXu4ra9j7" alt=""><figcaption></figcaption></figure>

This select doesn't appear when the user isn't in any group project.

All users of a group project share:

* The Kubernetes namespace, in "My Services" you can see everything that's running, including services launched by other person of the group.
* Project settings. If a user change a project setting, it affects every member of the group.
* Secrets
* S3 Bucket (or an S3 subpath)

As of today, new group can only be created by Onyxia instance administrator, on demand and the procedure to create group is not publicly documented yet because we're still actively working on it.\
However, if you want to enable this feature for your users, reach us, we will guide you through it!

{% embed url="<https://join.slack.com/t/3innovation/shared_invite/zt-1hnzukjcn-6biCSmVy4qvyDGwbNI~sWg>" %}


# Security considerations

Information about security considerations

#### 1. Autolaunch Feature

The autolaunch feature empowers you to create HTTP links that automatically deploy an environment. This is an invaluable tool for initiating trainings effortlessly. However, exercise caution while using it as it could pose a security risk to the user. Consider disabling this feature if it doesn't suit your requirements or if security is a primary concern.

[Disable Autolaunch](https://github.com/InseeFrLab/onyxia/blob/0ffdc6da0e5934a5aba2d412baa2bee5a5046586/web/.env#L149C1-L189)

#### 2. Group Feature

Onyxia is primarily designed to allocate resources such as a namespace and an S3 bucket to an individual user for work purposes. Additionally, it incorporates a feature that allows multiple users to share access to the same resources within a project. While this can be extremely beneficial for collaboration, be aware that it might be exploited by a malicious user within the group to leverage the privileges of another project member. Always monitor shared resources and maintain proper user access control to prevent such security breaches.


# Offline / airgap considerations

Onyxia can be installed in constrained environments such as behind a proxy, offline or airgap.\
This page aims at listing various things and configurations to have in mind when installing Onyxia in such environments.

### Catalogs

By default, Onyxia (Onyxia-API to be precise) is configured to use [Inseefrlab Opensource catalogs straight from Github](https://github.com/InseeFrLab/onyxia-api/blob/main/onyxia-api/src/main/resources/catalogs.json)\
This won't work if you don't have access to internet.\
If behind a proxy, you can [configure the proxy](https://github.com/InseeFrLab/onyxia-api/tree/main?tab=readme-ov-file#http-configuration) by using the corresponding API env variables.\
\
You can configure your own catalogs by using the `catalogs` key from the [Helm chart](https://github.com/InseeFrLab/onyxia/blob/1b404b5f043fc23e8e54bea1b7b3e163739d4404/helm-chart/values.yaml#L153) :\
A catalog is a regular Helm charts repository, see [here](/admin-doc/catalog-of-services) for more details on how to create your own catalog.\
Note that Onyxia does not currently support OCI-based repositories, you need to have an `index.yaml` based repository. See [this issue](https://github.com/InseeFrLab/onyxia-api/issues/547) to track progress on this.

### Certificates

If you are using non-public (internal) certificates, you need to either mount them (recommended) or skip tls validation (not recommended).

#### Mounting certificates (recommended)

Certificates can be mounted on the API pod :

```
api:
  extraVolumeMounts:
    - mountPath: "/usr/local/share/ca-certificates"
      name: ca-bundle
  extraVolumes:
    - name: ca-bundle
      secret:
        secretName: ca-bundle
```

#### Disabling tls validation (not recommended)

To disable tls validation for the API ⇒ OIDC provider : `oidc.skip-tls-verify`\
To disable tls validation for Helm (catalogs retrieval) : [skipTlsVerify](https://github.com/InseeFrLab/onyxia-api/blob/b47eece8103fa6bc78302390b3f0b8570de9e494/onyxia-api/src/main/resources/catalogs.json#L20)

### Images

Currently, Onyxia's images and images used by our opensource catalogs are hosted on [Dockerhub](https://hub.docker.com/u/inseefrlab).\
Make sure your cluster nodes are configured to pull from a mirror or prepull the corresponding images.\
If needed, you can override the images Onyxia uses in the `values.yaml` and the images of your services in your catalogs `values.yaml` / `values.schema.json`


# Custom Pages

You can host your own custom documentation pages directly within your Onyxia instance.\
This is ideal if you want to provide onboarding instructions or write step-by-step tutorials specifically tailored to your users.

{% embed url="<https://youtu.be/aQVu-vsf51w>" %}

## How It Works

Your documentation must consist of Markdown files. These files will be rendered as HTML within the Onyxia UI.\
The documents must be hosted within your Onyxia instance; external links are not supported. You need to include them in the `custom-resources.zip` file, provided through the `CUSTOM_RESOURCES` configuration key.\
More details are available in the [theme and branding documentation](/admin-doc/theme).

You can link to your Markdown files from any customizable section of the interface: header, sidebar, footer, and even from other Markdown files.

### Example

Assume we include the following files in `custom-resources.zip`:

```
/onboarding_en.md
/onboarding_fr.md
```

We can reference them in our configuration:

<pre class="language-yaml" data-title="onyxia/values.yaml"><code class="lang-yaml">onyxia:
  web:
    env:
<strong>      CUSTOM_RESOURCES: "https://.../custom-resources.zip"
</strong>      HEADER_TEXT_BOLD: My Organization
      HEADER_TEXT_FOCUS: Datalab
      HEADER_LINKS: |
        [
          {
            label: {
              en: "Onboarding Guide",
              fr: "Guide d'intégration"
            },
            icon: "School",
            url: {
<strong>              en: "%PUBLIC_URL%/custom-resources/onboarding_en.md",
</strong><strong>              fr: "%PUBLIC_URL%/custom-resources/onboarding_fr.md"
</strong>            }
          }
        ]
      FOOTER_LINKS: |
        [
          {
            label: {
              en: "Onboarding Guide",
              fr: "Guide d'intégration"
            },
            icon: "School",
            url: {
<strong>              en: "%PUBLIC_URL%/custom-resources/onboarding_en.md",
</strong><strong>              fr: "%PUBLIC_URL%/custom-resources/onboarding_fr.md"
</strong>            }
          }
        ]
      HOMEPAGE_BELOW_HERO_TEXT: |
        {
<strong>          en: "See our [onboarding guide](%PUBLIC_URL%/custom-resources/onboarding_en.md)",
</strong><strong>          fr: "Consultez notre [guide d'intégration](%PUBLIC_URL%/custom-resources/onboarding_fr.md)"
</strong>        }
      HOMEPAGE_CALL_TO_ACTION_BUTTON: |
        {
          label: {
            en: "Read our get started guide",
            fr: "Lire notre guide de démarrage"
          },
          startIcon: "School",
          url: {
<strong>            en: "%PUBLIC_URL%/custom-resources/onboarding_en.md",
</strong><strong>            fr: "%PUBLIC_URL%/custom-resources/onboarding_fr.md"
</strong>          }
        }
      TERMS_OF_SERVICES: "%PUBLIC_URL%/custom-resources/tos_fr.md"
</code></pre>

Example of Mardown document

{% code title="onboarding\_en.md" %}

````markdown
# This is a test document in english

This could be for example a guide specific to your Onyxia instance.  

## It's standard markdown

You can embed images, including with HTML syntax:  

<img src="%PUBLIC_URL%/custom-resources/preview.png" width="100%">  

You can render code snippets:  

```bash
echo "Hello world"
```

You can also link to pages of your instance: [Catalog](/catalog).

You can link to [another document](%PUBLIC_URL%/custom-resources/onboarding_sub_en.md).

<a href="%PUBLIC_URL%/launcher/ide/rstudio?name=rstudio&version=2.3.2&s3=region-ec97c721&resources.limits.cpu=«22700m»&autoLaunch=true">
    <img height=20 src="https://user-images.githubusercontent.com/6702424/173724486-30b6232a-c5d2-40da-a0cc-4d4a11824135.png">
</a>
````

{% endcode %}


# The Web Application

The TypeScript App that runs in the browser.

This is the documentation for [InseeFrLab/onyxia -> web/](https://github.com/InseeFrLab/onyxia/tree/main/web).

```bash
git clone https://github.com/InseeFrLab/onyxia
cd onyxia/web

yarn install

# To start the app locally
yarn dev

# If you want to test against your own Onyxia instance edit the .env.local.yaml
# file (created automatically the first time you run `yarn dev`)
```

You have a video here where we guide you through the setup of the dev environnement:

{% embed url="<https://youtu.be/NrVuVXsbloA?si=46kZVbVGEMWxhqc7>" %}


# Technical stack

Technologies at play in Onyxia-web

To find your way in Onyxia, the best approach is to start by getting a surface-level understanding of the libraries that are leveraged in the project.

{% hint style="info" %}
Modules marked by 🐔 are our own.
{% endhint %}

### tsafe 🐔

{% embed url="<https://www.tsafe.dev>" %}

We also heavily rely on [tsafe](https://github.com/garronej/tsafe). It's a collection of utilities that help write cleaner TypeScript code. It is crutial to understand at least [`assert`](https://docs.tsafe.dev/assert), [id](https://docs.tsafe.dev/id), [Equals](https://docs.tsafe.dev/equals) and [symToStr](https://docs.tsafe.dev/symtostr) to be able to contribute on the codebase.

## For working on what the end user 👁

Anything contained in the [src/ui](https://github.com/InseeFrLab/onyxia-web/tree/main/web/src/ui) directory.

### Onyxia-UI 🐔

{% embed url="<https://github.com/InseeFrLab/onyxia-ui>" %}

The UI toolkit used in the project, you can find the setup of [onyxia-UI](https://github.com/InseeFrLab/onyxia-ui) in onyxia-web here: [web/src/ui/theme/theme.tsx](https://github.com/InseeFrLab/onyxia/blob/main/web/src/ui/theme/theme.tsx).

#### [MUI](https://mui.com) integration

[Onyxia-UI](https://github.com/InseeFrLab/onyxia-ui) is fully compatible with [MUI](https://mui.com).

Onyxia-UI offers [a library of reusable components](https://inseefrlab.github.io/onyxia-ui) but you can also use [MUI](https://mui.com) components in the project, their aspect will automatically be adapted to blend in with the theme.

#### 🔡 Linking onyxia-ui in onyxia-web

To release a new version of [Onyxia-UI](#typescript). You just need to bump the [package.json's version](https://github.com/InseeFrLab/onyxia-ui/blob/470fdb4e54e2b16051ff8b7442ea4d765d76ba92/package.json#L3) and push. [The CI](https://github.com/garronej/ts-ci) will automate publish [a new version on NPM](#typescript).

If you want to test some changes made to onyxia-ui in onyxia-web before releasing a new version of onyxia-ui to NPM you can link locally onyxia-ui in onyxia-web.

```bash
cd ~/github
git clone https//github.com/InseeFrLab/onyxia
cd onyxia/web
yarn install

cd ~/github/onyxia #This is just a suggestion, clone wherever you see fit.
git clone https://github.com/InseeFrLab/onyxia-ui ui
cd ui
yarn install
yarn build
yarn link-in-web
npx tsc -w

# Open a new terminal
cd ~/github/onyxia/web
yarn start

```

Now you can make changes in `~/github/onyxia/ui/`and see the live updates.

If you want to install/update some dependencies, you must remove the node\_modules, do you updates, then link again.

### tss-react 🐔

{% embed url="<https://github.com/garronej/tss-react>" %}

The library we use for styling.

Rules of thumbs when it comes to styling:

* Every component should accept[ an optional `className`](https://github.com/InseeFrLab/onyxia-web/blob/f6e2907e43eea825d39f350207705d564360eb23/src/app/components/App/Footer.tsx#L9)prop it should always [overwrite the internal styles](https://github.com/InseeFrLab/onyxia-web/blob/f6e2907e43eea825d39f350207705d564360eb23/src/app/components/App/Footer.tsx#L55).
* A component should not size or position itself. It should always be the responsibility of the parent component to do it. In other words, you should never have `height`, `width`, `top`, `left`, `right`, `bottom` or `margin` in [the root styles](https://github.com/InseeFrLab/onyxia-web/blob/f6e2907e43eea825d39f350207705d564360eb23/src/app/components/App/Footer.tsx#L16-L23) of your components.
* You should never have a color or a dimension hardcoded elsewhere than in the theme configuration. Use `theme.spacing()` ([ex1](https://github.com/InseeFrLab/onyxia-web/blob/f6e2907e43eea825d39f350207705d564360eb23/src/app/components/pages/MyServices/MyServicesCards/MyServicesCard/MyServicesCard.tsx#L24), [ex2](https://github.com/InseeFrLab/onyxia-web/blob/f6e2907e43eea825d39f350207705d564360eb23/src/app/components/pages/MyServices/MyServicesCards/MyServicesCard/MyServicesCard.tsx#L31), [ex3](https://github.com/InseeFrLab/onyxia-web/blob/95667d66cc6ee835ede8d9d6a9bca5299d11bc1a/src/app/components/pages/MyServices/MyServicesSavedConfigs/MyServicesSavedConfig/MyServicesSavedConfig.tsx#L30)) and [`theme.colors.useCases.xxx`](https://github.com/InseeFrLab/onyxia-web/blob/08addbc60c820b8306cf8b0ccbe4793bd2f85661/src/app/components/pages/MyServices/MyServicesSavedConfigs/MyServicesSavedConfig/MyServicesSavedConfigOptions.tsx#L23-L32).

### screen-scaler 🐔

{% embed url="<https://github.com/garronej/screen-scaler>" %}

Onyxia is mostly used on desktop computer screens. It's not worth the effort to create a fully flege responsive design for the UI.\
screen-scaler enables us to design for a sigle canonical screen size. The library take charge of scaling/shrinking the image. depending on the real size of the screen.\
It also asks to rotate the screen when the app is rendered in protrait mode.

### Storybook

{% embed url="<https://storybook.js.org/>" %}

It enables us to test the graphical components in isolation.

To launch Storybook locally run the following command:

```bash
yarn storybook
```

{% embed url="<https://youtu.be/2L7rtAOlqtc>" %}
Setting up a new story
{% endembed %}

### vite-envs 🐔

We need to be able to do:

{% embed url="<https://github.com/garronej/vite-envs>" %}

```bash
docker run --env OIDC_URL="https://url-of-our-keycloak.fr/auth" InseeFrLab/onyxia-web
```

Then, somehow, access `OIDC_URL` in the code like `process.env["OIDC_URL"]`.

In theory it shouldn't be possible, onyxia-web is an SPA, it is just static JS/CSS/HTML. If we want to bundle values in the code, we should have to recompile. But this is where [`cra-envs`](https://github.com/garronej/cra-envs) comes into play.

It enables to run onyxia-web again a specific infrastructure while keeping the app docker image generic.

Checkout [the helm chart](https://github.com/InseeFrLab/onyxia/tree/main/helm-chart):

```
  web:
    replicaCount: 2
    env:
      MINIO_URL: https://minio.lab.sspcloud.fr
      VAULT_URL: https://vault.lab.sspcloud.fr
      OIDC_URL: https://auth.lab.sspcloud.fr/auth
      OIDC_REALM: sspcloud
      TITLE: SSP Cloud
```

* All the accepted environment variables are defined here: [.env](https://github.com/InseeFrLab/onyxia-web/blob/main/web/.env). They are all prefixed with `REACT_APP_` to be compatible [with create-react-app](https://create-react-app.dev/docs/adding-custom-environment-variables/#adding-development-environment-variables-in-env). Default values are defined in this file.
* Then, in the code the variable can be accessed [like this](https://github.com/InseeFrLab/onyxia-web/blob/f6e2907e43eea825d39f350207705d564360eb23/src/app/libApi/LibProvider.tsx#L32).

{% hint style="warning" %}
Please try not to access the environment variable to liberally through out the code. In principle they should only be accessed [here](https://github.com/InseeFrLab/onyxia-web/blob/main/src/app/libApi/LibProvider.tsx). We try to keep things [pure](https://en.wikipedia.org/wiki/Pure_function) as much as possible.
{% endhint %}

{% embed url="<https://youtu.be/JaX14cborxE>" %}

### powerhooks 🐔

{% embed url="<https://github.com/garronej/powerhooks>" %}

It's a collection general purpose react hooks. Let's document the few use cases **you absolutely need to understand**:

#### Avoiding useless re-render of Components

For the sake of performance we enforce that every component be wrapped into [`React.memo()`](https://reactjs.org/docs/react-api.html#reactmemo). It makes that a component only re-render if one of their prop has changed.

However if you use inline functions or [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback) as callbacks props your components will re-render every time anyway:

{% embed url="<https://stackblitz.com/edit/react-ts-fyrwng?embed=1&file=index.tsx>" %}
Playground to explain the usefulness of useConstCallback
{% endembed %}

We always use [useConstCallback](https://github.com/garronej/powerhooks#useconstcallback) for callback props. And [`useCallbackFactory`](https://github.com/garronej/powerhooks#usecallbackfactory) for callback prop in lists.

#### Measuring Components

It is very handy to be able to get the height and the width of components dynamically. It prevents from having to hardcode dimension when we don’t need to. For that we use [`useDomRect`](https://github.com/garronej/powerhooks#usedomrect)\`\`

### Keycloakify 🐔

{% embed url="<https://github.com/InseeFrLab/keycloakify>" %}

It's a build tool that enables to implement the login and register pages that users see when they are redirected to Keycloak for authentication.

If the app is being run on Keycloak the [`kcContext`](https://github.com/InseeFrLab/onyxia/blob/9ced438bf6bad76a85049d52220617070f6daa79/web/src/index.tsx#L3) isn't `undefined` and it means shat we should render the login/register pages.

If you want to test, uncomment [this line](https://github.com/InseeFrLab/onyxia/blob/9ced438bf6bad76a85049d52220617070f6daa79/web/src/keycloak-theme/login/kcContext.ts#L53) and run `yarn start`. You can also test the login pages in a local keycloak container by running `yarn keycloak`. All the instructions will be printed on the console.

The `keycloak-theme.jar` file is automatically [build](https://github.com/InseeFrLab/onyxia/blob/9ced438bf6bad76a85049d52220617070f6daa79/.github/workflows/ci.yml#L90-L93) and [uploaded as a GitHub release asset](https://github.com/InseeFrLab/onyxia/blob/9ced438bf6bad76a85049d52220617070f6daa79/.github/workflows/ci.yml#L113) by the CI.

### type-routes

{% embed url="<https://github.com/typehero/type-route>" %}

The library we use for routing. It's like [react-router](https://reactrouter.com) but type safe.

### i18nifty 🐔

{% embed url="<https://www.i18nifty.dev>" %}

For internalization and translation.

### Vite

{% embed url="<https://vitejs.dev/>" %}

## For working on 🧠 of the App

Anything contained in the [src/core](https://github.com/InseeFrLab/onyxia/tree/main/web/src/core) directory.

### clean-architecture 🐔

{% embed url="<https://github.com/garronej/clean-architecture>" %}

The framework used to implement strict separation of concern betwen the UI and the Core and high modularity of the code.

There is [a snake game (the classic nokia game) example](https://github.com/garronej/snake-clean-architecture) for helping you understand the clean architecture framework.

<figure><img src="/files/2Ubiwo6rASDgvWngpgPK" alt="" width="375"><figcaption><p>Snake game for understanding the clean-architecture framwork</p></figcaption></figure>

###

### oidc-spa 🐔

{% embed url="<https://github.com/garronej/oidc-spa>" %}

For everything related to user authentication.

### EVT 🐔

{% embed url="<https://www.evt.land>" %}

EVT is an event management library (like [RxJS ](https://rxjs.dev)is).

A lot of the things we do is powered under the hood by EVT. You don't need to know EVT to work on onyxia-web however, in order to demystify the parts of the codes that involve it, here are the key ideas to take away:

* If we need to perform particular actions when a value gets changed, we use[`StatefullEvt`](https://docs.evt.land/api/statefulevt).
* We use `Ctx`to detaches event handlers when we no longer need them. (See line 108 on [this playground](https://stackblitz.com/edit/evt-playground?embed=1\&file=index.ts\&hideExplorer=1))
* In React, we use the [useEvt](https://docs.evt.land/react-hooks) hook to work with DOM events.


# Architecture

## Main rules

* [`src/ui`](https://github.com/InseeFrLab/onyxia/tree/main/web/src/ui) contains the React application, it's the UI of the app.
* [`src/core`](https://github.com/InseeFrLab/onyxia/tree/main/web/src/core) contains the 🧠 of the app.
  * Nothing in the `src/core` directory should relate to React. A concept like react hooks for example is out of scope for the src/core directory.
  * `src/core` should never import anything from `src/ui`, even types.
  * It should be possible for example to port onyxia-web to Vue.js or React Native without changing anything to the `src/core` directory.
  * The goal of `src/core` is to expose an API that serves the UI.
  * The API exposed should be reactive. We should not expose to the UI functions that returns promises, instead, the functions we expose should update states and the UI should react to these states updates.

## Architecture

* Whenever we need to interact with the infrastructure we define a port in [`src/core/port`](https://github.com/InseeFrLab/onyxia/tree/main/web/src/core/ports). A port is only a type definition. In our case the infrastructure is: the Keycloak server, the Vault server, the Minio server and a Kubernetes API (Onyxia-API).
* In [`src/core/adapters`](https://github.com/InseeFrLab/onyxia/tree/main/web/src/core/adapters) are the implementations of the ports. For each port we should have at least two implementations, a dummy and a real one. It enabled the app to still run, be it in degraded mode, if one piece of the infrastructure is missing. Say we don’t have a Vault server we should still be able to launch containers.
* In [`src/lib/usecases`](https://github.com/InseeFrLab/onyxia/tree/main/web/src/core/usecases) we expose APIs for the UI to consume.

The following framework is the backbone of onyxia-web, if you can familiarize yourself with it it will make working with onyxia-web much easyer.

{% embed url="<https://github.com/garronej/clean-architecture>" %}

## In practice

Let's say we want to create a new page in onyxia-web where users can type in a repo name and get the current number of stars the repo has on GitHub.

{% hint style="info" %}
UPDATE: This video remain relevant but please not that the clean archi setup have been considerably improved in latest releases. [A dedicated repo](https://github.com/garronej/clean-architecture) have been created to explain it in detail.

Main take-way is that `app` have been renamed `ui` and `lib` have been renamed `core`.
{% endhint %}

{% embed url="<https://youtu.be/RDxAag3Iq0o>" %}

{% hint style="info" %}
You might wonder why some values, instead of being redux state, are returned by thunks functions.

For example, it might seem more natural to do:

```tsx
const { isUserLoggedIn } = useCoreState(state => state.userAuthentication);
```

Instead of what we actually do, which is:

```tsx
const { userAuthenticationThunks } = useThunks();
const isUserLoggedIn = userAuthenticationThunks.getIsUserLoggedIn();
```

However the rule is to never store as a redux state, values that are not susceptible to change. Redux states are values that we observe, any redux state changes should trigger a re-render of the React components that uses them. Conversely, there is no need to observe a value that will never change. We can get it once and never again, get it in a callback or wherever.

But, you may object, users do login and logout, `isUserLoggedIn` is not a constant!

Actually, from the standpoint of the web app, it is. When a user that isn't authenticated click on the login button, it is being redirected away. When he returns to the app everything is reloaded from scratch.
{% endhint %}

Now let's say we want the search to be restricted to a given GitHub organization. (Example: InseeFrLab.) The GitHub organization should be specified as an environment variable by the person in charge of deploying Onyxia. e.g.:

```yaml
  web:
    env:
      MINIO_URL: https://minio.lab.sspcloud.fr
      VAULT_URL: https://vault.lab.sspcloud.fr
      OIDC_URL: https://auth.lab.sspcloud.fr/auth
      OIDC_REALM: sspcloud
      TITLE: SSP Cloud
      ORG_NAME: InseeFrLab #<==========
      
```

If no `ORG_NAME` is provided by the administrator, the app should always show 999 stars for any repo name queried.

{% embed url="<https://youtu.be/eaU-tYFzWwA>" %}

## Another example: Recording user's GitLab token

Currently users can save their GitHub Personal access token in their Onyxia account but not yet their GitLab token. Let's see how we would implement that.

{% embed url="<https://www.youtube.com/watch?v=WVFKCR1QfVk>" %}

## How to deal with project switching

The easy action to take when the user selects another project is to simply reload the page (`windows.location.reload()`). We want to avoid doing this to enable what we call "*hot projet swiping*":

![The page is not reloaded when changing the project](https://user-images.githubusercontent.com/6702424/147413744-480235af-53cc-4b4d-a69a-7e9e73a79407.gif)

To implement this behavior you have to leverage the evtAction middleware from clean-redux. It enabled to register functions to be run when certain actions are dispatched.

{% hint style="info" %}
Unlike the other video, the following one is voiced. Find the relevant code [here](https://github.com/InseeFrLab/onyxia-web/blob/61b4d660faebefacc9e963c506b707c04d57521f/src/core/usecases/runningService.ts#L316-L332).
{% endhint %}

{% embed url="<https://youtu.be/TWDHBxceH0Q>" %}


# The REST API

The backend REST API in Java

This is the documentation for [InseeFrLab/onyxia -> api/](https://github.com/InseeFrLab/onyxia-api).

It's the part of the App that runs in the clusters. It handles the things that can't be done directly from the frontend.

{% embed url="<https://mango-dune-07a8b7110.1.azurestaticapps.net/?repo=InseeFrLab/onyxia-api>" %}


# Roadmap

Onyxia Project Core Team Future Developments Roadmap

Want to know what we are up to?

Checkout our Milestones on GitHub:

{% embed url="<https://github.com/InseeFrLab/onyxia/milestones>" %}
Onyxia project GitHub Milestones
{% endembed %}

Roadmap is also often discussed during our [monthly public community calls](https://docs.onyxia.sh/contributors-doc/community-calls), feel free to attend.

Do not hesitate to vote or comment on the issues that are the most important to you.\
We prioritize our work based on community feedback !

Or you can ask us on Slack, we're very prompt to respond !

{% embed url="<https://join.slack.com/t/3innovation/shared_invite/zt-3r26584mp-SGPr9XvTukNkJiDZfRjZiQ>" %}


# Community calls

Our community calls take place **on the last Friday of each month at 13:00 (Paris time)**.

{% file src="/files/Iyi1bDhpLL5NClCFLObS" %}

These calls are open to **everyone** — a great opportunity to:

* Get the latest project updates
* Ask questions
* Discuss the roadmap
* Showcase how you're using the project<br>

To join, simply head over to our Slack workspace and join the [*#community-meeting*](https://3innovation.slack.com/archives/C0664UVJ77W) channel.

\
List of previous community calls minutes :

{% content-ref url="/pages/9MmgFFv1m7wSo9YTv5S8" %}
[July 2026 community call](/contributors-doc/community-calls/july-2026-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/ZaLAhHTKyqOweItbwaMb" %}
[January 2026 community call](/contributors-doc/community-calls/january-2026-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/wZ4VmUWUgKzRtiPrJh72" %}
[October 2025 community call](/contributors-doc/community-calls/october-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/OFUHa9PC9iGycBbuB0jo" %}
[September 2025 community call](/contributors-doc/community-calls/september-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/5s4Ms3s8BDTVbcLXvwco" %}
[August 2025 community call](/contributors-doc/community-calls/august-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/d15LxZ2GifmvJDz3YhPc" %}
[July 2025 community call](/contributors-doc/community-calls/july-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/OlsWhRSxgVTBMAjmrUvR" %}
[June 2025 community call](/contributors-doc/community-calls/june-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/ibQ5rACInHWGVN6fJ6kp" %}
[May 2025 community call](/contributors-doc/community-calls/may-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/NoegysrkPLwdLyoPfZ6F" %}
[April 2025 community call](/contributors-doc/community-calls/april-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/kKEbuDhiM0H2KxAWUtu8" %}
[March 2025 community call](/contributors-doc/community-calls/march-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/i8xpbeNwXcQahSlmJXih" %}
[February 2025 community call](/contributors-doc/community-calls/february-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/g2ZKKekFvPhOWhamS16i" %}
[January 2025 community call](/contributors-doc/community-calls/january-2025-community-call)
{% endcontent-ref %}


# July 2026 community call

Community call 07/31/2026

## Onyxia news

* Reference custom plugin: [Onyxia-LS3](https://github.com/onyxia-datalab/onyxia-LS3). For customizing Onyxia in depth and cater to the specific need of a specific organization.
* Onyxia S3 Explorer : fully revamped. Let us know how it feels !
* Work on AI integration. Current testing version : <https://onyxialpha.kub.sspcloud.fr/account/ai> . Dev branch `ia-integration` (beware : work in progress) : <https://github.com/InseeFrLab/onyxia/tree/ia-integration>

## Community discussions

* Welcome to Bryan Devos from belgium. Currently in early stage of Onyxia installation.


# January 2026 community call

### January 2026 community call

#### Welcome back Marc

* Marc, our primary designer (2020-2022) came back home last month

#### Onyxia communication

* <https://community.ima-dt.org/france-corporate-innovation-award-2026/content/liste-nomines> we won the european sovereignty category.
* Cloud native Day Paris february 3 : we have a booth, come to talk to us :)

#### Onyxia news

* still working on file explorer (supporting profile file semantic, bookmark, enhance UX)
* support of postgres CNPG
* Planned support of Iceberg Rest API. Onyxia will be able to use this king of lakehouse as it perfectly fit cloud native env.
* Ingress-nginx project will be retired in March 2026 (soon !). If you are currently using it (SSPCloud is currently using it), what is your plan ? Onyxia has no major dependency to ingress-nginx but admins may want to take this deadline as an opportunity to migrate to gateway API. Onyxia support for gateway API is planned / in-progress but will probably not be ready by the march 2026 deadline (work is needed on onyxia-api but most importantly on charts). Context : <https://kubernetes.io/blog/2026/01/29/ingress-nginx-statement/>
* Seaweedfs (<https://github.com/seaweedfs/seaweedfs>) now supports policy variables (<https://github.com/seaweedfs/seaweedfs/issues/8037>) allowing to define policies such as `user johndoe has access to bucket user-johndoe` just like in minIO / AWS. You may find this project as a good replacment for minIO following their licensing changes
* DPOP ! onyxia is an SPA, we need to protect our access tokens. oidc-spa support DPOP : <https://oauth.net/2/dpop/>


# October 2025 community call

Community call 30/25/2025

## Onyxia news

* working progress on data explorer <https://onyxialpha.kub.sspcloud.fr/s3?profile=1efe8fb1>
* feature released on data catolog <https://github.com/InseeFrLab/onyxia/issues/1021> on sspcloud , example on data.gouv.fr <https://datalab.sspcloud.fr/data-collection?source=https%3A%2F%2Fwww.data.gouv.fr%2Fapi%2F1%2Forganizations%2F534fff81a3a7292c64a77e5c%2Fcatalog.jsonld%3Fformat%3Dparquet>
* More work in progress in the go migration / rewrite, now working on the "main" API (services, my-lab …) and moving onto a monorepo for all backend modules (onboarding, services …) : <https://github.com/onyxia-datalab/onyxia-backend> Feel free to join the team :)


# September 2025 community call

Community call 09/25/2025

## Onyxia news

* [CVE-2025-58366](https://github.com/InseeFrLab/onyxia/security/advisories/GHSA-m773-6vm8-8x6q) Private helm repository credentials leak : only affected if you were using private helm catalogs (specifying credentials in the `catalogs` json). Patched on Onyxia 10.28. Take a look at our "vulnerability disclosure" documentation page and feel free to register to our security mailing to be notified when a vulnerability is discovered : <https://docs.onyxia.sh/vulnerability-disclosure>
* working on data catolog <https://github.com/InseeFrLab/onyxia/issues/1021>
* Reminder : the onboarding module, rewritten in go, is up for testing and use. It has been in use in production at SSPCloud for a month now. To test it, set `onboarding.enabled=true` in your chart values <https://github.com/InseeFrLab/onyxia/blob/eeb00ae9d7047849b06dd5244eb1c4a4806db4ae/helm-chart/values.yaml#L317> no additional change is needed, we aimed for fully compatibility with the existing onboarding behaviour. Feedback welcome ! Onboarding code is hosted on the `onyxia-datalab` org on github : <https://github.com/onyxia-datalab/onyxia-onboarding>
* More work in progress in the go migration / rewrite, now working on the "main" API (services, my-lab ...) and moving onto a monorepo for all backend modules (onboarding, services ...) : <https://github.com/onyxia-datalab/onyxia-backend> Feel free to join the team :)


# August 2025 community call

Community call 08/28/2025

## Project news

* Release v10.27
  * Onboarding as a separate go module (test it, `onboarding.enabled=true` in your values : <https://github.com/InseeFrLab/onyxia/blob/ba06172cba57b6893d0646f6521a1895532d844a/helm-chart/values.yaml#L316>)
* State of go :
  * Onboarding is functional and at (almost, mainly missing events support) parity with current API
  * Work is now on `services` API
  * Code is available on a monorepo : <https://github.com/onyxia-datalab/onyxia-backend>
  * Feedback and contributions welcome ! #dev-rewrite-to-go on slack to discuss
* Bitnamigate happening today :scream: : <https://github.com/bitnami/charts/issues/35164>
  * Bitnami dropping / reducing docker & helm charts availability and support
  * Inseefrlab (defaults for Onyxia) catalogs have been updated this week to use `bitnamilegacy` (mainly for databases catalog)
  * Future : we will try to remove catalog dependencies to bitnami wherever and whenever it's feasible


# July 2025 community call

Community call 07/31/2025\
\
Project news

* Release v10.25
  * API v4.8.0 : cache for packages retrieval (recommended update !)
  * S3 bookmarks + dynamic
* New onboarding module in Go available for testing : <https://github.com/onyxia-datalab/onyxia-onboarding> , chart with new module as an option WIP (will be merged soon (tm) to the regular Helm chart) : <https://github.com/InseeFrLab/helm-charts-dev/tree/main/charts/onyxia>
* Poster session at kubecon North America in Atlanta from 10 to 13 Nov


# June 2025 community call

Community call 05/26/2025\
\
Project news :

* Release v10.23
  * [Declarative user profile](/admin-doc/catalog-of-services/custom-catalogs/declarative-user-profile)
  * [S3 bookmarks](/admin-doc/s3-configuration)
* Work in progress\
  [overwriteDefaultWith for object and array #992](https://github.com/InseeFrLab/onyxia/issues/992)\
  [FR : Hidden Profile Fields as Hints #995](https://github.com/InseeFrLab/onyxia/issues/995)


# May 2025 community call

Community call 05/29/2025

Project's news :

* Release v10.18 (including API 4.6.0) :
  * Basic auth for helm repo
  * Support for multiple S3 configurations (first step, still need more work especially on services / catalogs)
  * Ability to host and embed markdown files : [Issue](https://github.com/InseeFrLab/onyxia/pull/976) [Example](https://datalab.sspcloud.fr/document?source=%257B%2522en%2522%253A%2522%252Fcustom-resources%252Ftos_en.md%2522%252C%2522fr%2522%253A%2522%252Fcustom-resources%252Ftos_fr.md%2522%257D)
* WIP : User profile : <https://github.com/InseeFrLab/onyxia/pull/980> . Feedback / usecases welcome
* WIP : bookmarks for file explorer : <https://github.com/InseeFrLab/onyxia/issues/968>

<figure><img src="/files/Zn00nbLEdknC6pmyCjSH" alt=""><figcaption></figcaption></figure>


# April 2025 community call

Community call 04/24/2025

Project's news :

* New schedule for community calls ! Last thursday of the month at 16:30 Paris time. ICS calendar available : <https://docs.onyxia.sh/contributors-doc/community-calls>
* Various improvements to My files
* WIP : Multiple STS configuration support. Still needs work especially on User interface and catalogs configuration / injection. Need to figure out what to do with the dropdown menu that currently allows switching between S3 configurations. Almost all the services (python, R ...) are ready to support multiple STS configurations but duckdb is not, an issue is currently open on their side.
* Work has started on "User profile" feature (<https://github.com/InseeFrLab/onyxia/discussions/954>). Not testable yet but feedback welcome on usecase, would you use it ?
* Onyxia support for charts without values.schema.json by fallbacking into the the new YAML editor.
  * Possibility to see all defaults in the text editor, including the ones defined in the values.yaml.
* Customization: Possibility to define different color palettes for dark and light mode. Possibility to add custom CSS for light and dark mode.

Community discussions :

* Data(S3) configuration should allow configurations that don't contain prefix/ prefixGroup or bucketNamePrefix/ bucketNamePrefixGroup. Basically keeping S3 configuration / STS / injection but disabling user bucket / working directory path … In this setup, users will then run things like `mc ls s3` and have access to mulitple buckets not tied to their username.
* Document Group Projects : documentation is lackluster on how to configure / enable groups feature and what the feature is about (what the group gives access to, how it's supposed to be used …)


# March 2025 community call

Community call 03/28/2025

Project's news :

* New repo ! Awesome Onyxia : <https://github.com/onyxia-datalab/awesome-onyxia> Listing of resources related to the Onyxia ecosystem. Feel free to contribute :)
* Onboarding module in go : still WIP, not much this month. Contributions welcome, please join #dev-rewrite-to-go on Slack
* Feature request : Customizable User Profile. <https://github.com/InseeFrLab/onyxia/discussions/954> . Work currently in progress to implement this using json schemas. Admin of the instance would specify a json schema describing the user profile. UI would render it and let user customize their profile (e.g git configuration). All the data would be then available for injection in the service launcher form.
* Debate : overwriteSchemaWith / patchSchemaWith (@Gaspard) : <https://github.com/InseeFrLab/onyxia-api/pull/573> Currently Onyxia is relying on overwriteSchemaWith that only allow to replace a schema part while discarding the existing one. Patchschemawith would allow to keep the existing definition (e.g when using Onyxia's opensource catalogs) and upstream changes while patching only what's necessary. Would also reduce duplication.
* New customization features:
  * Different palette for the dark and light mode
  * Gradiant background color
* Desktop App:
  * It would be an electron wrapper around Onyxia Web
  * The main goal would be to be able to bypass CORS issues (that are a blocker for accessing public S3 Bucket through the Onyxia UI)
* New button for accessing Keycloak user profile (when applicable). Do you want an option to disable it?

Community discussions :

* SSB : plugin showcase :\
  <https://github.com/statisticsnorway/onyxia/tree/ssb-assets/web/public/custom-resources>


# February 2025 community call

Community call 02/28/2025

News :

* Rewrite of the onboarding as a go module : Going well, first version has been released yesterday : <https://github.com/onyxia-datalab/onyxia-onboarding> . Current work is on the Helm chart with a standalone version already published and integration of this module as a optional dependency in the main Onyxia chart should arrive soon. Slack channel for discussion on the rewrite effort : #dev-rewrite-to-go
* Onyxia-web : Work on improving support for OIDC providers other than Keycloak. In particular Entra ID (Microsoft) and Auth0. Thoughough documentation will be added to the docs.onyxia.dev website shortly. <https://docs.oidc-spa.dev/>

Community contributions :

* Trygve : Great work on the new Go-based onboarding API so far! We just need to make sure the test coverage for the go rewrite is maintained at a good level to avoid reproducing the same mistakes as the current Java Onyxia-API (which has a ridiculous low level of test)
* Trygve : they developped a custom web plugin to display the estimated cost of running the service based on resources (cpu / mem) beside the resources slider. Really interesting but may be hard to opensource properly due to variety in setups and Charts
* Discussion on how to get billing usage : prometheus, opencost. Also hard to opensource / bundle to Onyxia / make it generic\
  ![image](https://hackmd.io/_uploads/Syp-qVki1g.png)
* Trygve : 200 users daily :heart:
* NTTS (eurostat conference) 11-13 march 2025. Come and say hi :relaxed:
* CSTB (<https://www.cstb.fr/>) : approx 1000 employees including datascientists. Currently running Jupyterhub and other tools. Interested in Onyxia for unifying tools among all projects and embrace opensource ecosystems. Welcome :relaxed:


# January 2025 community call

Community call 01/31/25

* CVE\
  A 9.4 vulnerability has been found (thanks team norway !) in Onyxia-API at the end of December.\
  Read more here : <https://nvd.nist.gov/vuln/detail/CVE-2024-56333>\
  We created a new section on the docs for everything related to security including a mailing list : <https://docs.onyxia.sh/vulnerability-disclosure>
* API rewrite Java => Go\
  We started the process of rewriting the Onyxia-API that is currently written in Java to Golang as go is a lot more integrated with cloud native technologies. It would greatly improve performance, maintainability (letting us get rid of the Helm wrapper we built) and security.\
  We take this opportunity to also split the API into separated modules, starting with the Onboarding.\
  See discussion here : <https://github.com/InseeFrLab/onyxia/discussions/925>\
  Feel free to join the effort by joining the #dev-rewrite-to-go channel on Slack\
  Also we created the <https://github.com/onyxia-datalab> org
* YAML editor\
  New feature ! Making progress towards more support for advanced users such as developpers. Letting them directly modify values instead of using the UI form. Also support for charts that don't have a `values.schema.json`.
* Parquet\
  Improvements on how parquet are displayed in the Data explorer, also improvements on file format detection

Mercator :

* Updated their fork from v9 => v10. Quite some work was needed but happy with the new features.

API rewrite from Java to Go is not just a rewrite in another language, it's also an opportunity to change the architecture. Discussions on key points such as if / how we should support things other than helm packages are currently taking place so anyone interested in this are more than welcome to join the discussion #dev-rewrite-to-go and <https://github.com/InseeFrLab/onyxia/discussions/925>


# Getting started with Onyxia

Using Onyxia (as a data scientist)

{% hint style="success" %}
See also [https://docs.sspcloud.fr](https://docs.sspcloud.fr/)

It's the Onyxia user guide dedicated to our staff.
{% endhint %}

There are 3 main components accessible on the onyxia web interface :

* catalogs and services launched by the users (Kubernetes access)
* a file browser (S3 access)
* secret browser (Vault access)

## Start a service

Following is a documentation Onyxia when configured with the default service catalogs :

{% embed url="<https://github.com/inseefrlab/helm-charts-interactive-services>" %}

This collection of charts help users to launch many IDE with various binary stacks (python , R) with or without GPU support. Docker images are built [here](https://github.com/inseefrlab/images-datascience) and help us to give a homogeneous stack.

{% embed url="<https://github.com/inseefrlab/helm-charts-databases>" %}

This collection of charts help users to launch many databases system. Most of them are based on [bitnami/charts](https://guthub.com/bitnami/charts).

{% embed url="<https://github.com/InseeFrLab/helm-charts-automation>" %}

This collection of charts help users to start automation tools for their datascience activity.

{% embed url="<https://github.com/InseeFrLab/helm-charts-datavisualization>" %}

This collection of charts helps users to launch tools to visualize and share data insights.

{% hint style="info" %}
The Onyxia user experience may be very different from one catalog of service to another.

The catalog defines what options are available though Onyxia.
{% endhint %}

Users can edit various parameters. Onyxia do some assertion based on the charts values schema and the configuration on the instance. For example some identity token can be injected by default (because Onyxia connect users to many APIs).

<figure><img src="/files/ceu3JtiCLD1fsIkO9v3j" alt=""><figcaption></figcaption></figure>

After launching a service, notes are shown to the user. He can retrieve those notes on the README button. Charts administrator should explain how to connect to the services (url , account) and what happens on deletion.

<figure><img src="/files/6cJLzozNR5bchVMli0Uy" alt=""><figcaption></figcaption></figure>

Now you want to learn how to setup your devloppement environement for day to day usage:

{% content-ref url="/pages/cdkxJw8KXKGlY30oMRVS" %}
[Setting up your dev environment in Onyxia](/user-doc/setting-up-your-dev-environment-in-onyxia)
{% endcontent-ref %}

## File browser

Users can manage their files on S3. There is no support for rename in S3 so don't be surprise. Onyxia is educational. Any action on the S3 browser in the UI is written in a console with a cli.

<figure><img src="/files/NUs6qnJmOJeCPNOFGRKH" alt=""><figcaption><p>s3 browser</p></figcaption></figure>

User can do the following S3 actions :

* download files
* upload files
* delete files

Of course, in our default catalags there are all the necessary tools to connect to S3.

Our advice is to never download file to your container but directly ingest in memory the data.

{% embed url="<https://youtu.be/Fg4drnvgd20>" %}
Connecting to an external S3
{% endembed %}

## Secret browser

Users can mange their secrets on Vault. There is also a cli console.

<figure><img src="/files/cV7SXm3ywD5xh6lu7Fi9" alt=""><figcaption></figcaption></figure>

Onyxia use only a key value v2 secret engine in Vault. Users can store some secrets there and inject them in their services if configured by the helm chart.

<figure><img src="/files/6thJIozbiRKEWa6MSgbF" alt=""><figcaption></figcaption></figure>

Of course, in our default catalags there are all the necessary tools to connect to Vault.


# Datascience Trainings and Tutorials

The Onyxia team maintain a catalog of training and tutorials with several practical exercices that can be performed on an Onyxia instance!

{% hint style="info" %}
By default the when you open the trainings will be open on <https://datalab.sspcloud.fr> (our onyxia instance) but if you don't have a Datalab acount you can edit the urls of the practical exercises so you can run them on the instance you have access to.
{% endhint %}

{% embed url="<https://www.sspcloud.fr/formation>" %}


# Setting up your dev environment in Onyxia

In this video, we guide you through setting up your development environment in Onyxia. We demonstrate how to automatically clone your Git repository, install any missing dependencies, and open a port for your development server.

You can also find initialization scripts of interactive services [here](https://github.com/InseeFrLab/sspcloud-init-scripts).

{% hint style="info" %}
I forgot to show in the video that you can setup your GitHub/GitLab username and token in My Account -> External services.

This will enable Onyxia to clone private repos!

<img src="/files/S45FXYRh9NBbUsKvyPKF" alt="" data-size="original">
{% endhint %}

{% embed url="<https://www.youtube.com/watch?v=_6rKPeQj650>" %}


# Community resources

You can find extra information on how to use Onyxia as a datascientist by checking out the community website of the french statistician workforce. It's in french though.

{% embed url="<https://docs.sspcloud.fr/>" %}

Want to share something you've done with Onyxia? You can click on "edit this page on GitHub" and submit a pull request!


# Install

Convinced by Onyxia? Let's see how you can get your own instance today!

{% hint style="info" %}

## Oneliner

If you are already familiar with Kubernetes and Helm, here's how you can get an Onyxia instance up and running in just a matter of seconds.

```bash
helm repo add onyxia https://inseefrlab.github.io/onyxia

cat << EOF > ./onyxia-values.yaml
ingress:
  enabled: true
  hosts:
    - host: onyxia.my-domain.net
EOF

helm install onyxia onyxia/onyxia -f onyxia-values.yaml

# Navigate to https://onyxia.my-domain.net
```

With this minimal configuration, you'll have an Onyxia instance operating in a degraded mode, which lacks features such as authentication, S3 explorer, secret management, etc. However, you will still retain the capability to launch services from the catalog.
{% endhint %}

Whether you are a Kubernetes veteran or a beginner with cloud technologies, this guide aims to guide you through the instantiation and configuration of an Onyxia instance with it's full range of features enabled. Let's dive right in! 🤿

First let's make sure we have a suitable deployment environement to work with!&#x20;

{% content-ref url="/pages/LvC5vcZc9pkCe267bM3D" %}
[Kubernetes](/docs.onyxia.sh/v10/admin-doc/readme/kubernetes)
{% endcontent-ref %}


# Kubernetes

Provision a Kubernetes cluster

First you'll need a Kubernetes cluster. If you have one already you can skip and directly go to [the Onyxia installation section](/docs.onyxia.sh/v10/admin-doc/readme/gitops).

{% tabs %}
{% tab title="Provisioning a cluster on AWS, GCP or Azure" %}
[Hashicorp](https://www.hashicorp.com/) maintains great tutorials for [terraforming](https://www.terraform.io/) Kubernetes clusters on [AWS](https://aws.amazon.com/what-is-aws/), [GCP](https://cloud.google.com/) or [Azure](https://acloudguru.com/videos/acg-fundamentals/what-is-microsoft-azure).

Pick one of the three and follow the guide.

You can stop after the [configure kubectl section](https://learn.hashicorp.com/tutorials/terraform/eks#configure-kubectl).

{% embed url="<https://developer.hashicorp.com/terraform/tutorials/kubernetes/eks>" %}

{% embed url="<https://developer.hashicorp.com/terraform/tutorials/kubernetes/gke?in=terraform%2Fkubernetes>" %}

{% embed url="<https://developer.hashicorp.com/terraform/tutorials/kubernetes/aks?in=terraform%2Fkubernetes>" %}

**Ingress controller**

Let's install ingress-ngnix on our newly created cluster:

{% hint style="warning" %}
The following command is [for AWS](https://kubernetes.github.io/ingress-nginx/deploy/#aws).

For GCP use [this command](https://kubernetes.github.io/ingress-nginx/deploy/#gce-gke).

For Azure use [this command](https://kubernetes.github.io/ingress-nginx/deploy/#azure).
{% endhint %}

```bash
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.2.0/deploy/static/provider/aws/deploy.yaml
```

**DNS**

Let's assume you own the domain name **my-domain.net**, for the rest of the guide you should replace **my-domain.net** by a domain you actually own.

Now you need to get the external address of your cluster, run the command

```bash
kubectl get services -n ingress-nginx
```

and write down the `External IP` assigned to the `LoadBalancer`.

Depending on the cloud provider you are using it can be an IPv4, an IPv6 or a domain. On AWS for example, it will be a domain like **xxx.elb.eu-west-1.amazonaws.com**.

If you see `<pending>`, wait a few seconds and try again.

Once you have the address, create the following DNS records:

```dns-zone-file
datalab.my-domain.net CNAME xxx.elb.eu-west-1.amazonaws.com. 
*.lab.my-domain.net   CNAME xxx.elb.eu-west-1.amazonaws.com. 
```

If the address you got was an IPv4 (`x.x.x.x`), create a `A` record instead of a CNAME.

If the address you got was ans IPv6 (`y:y:y:y:y:y:y:y`), create a `AAAA` record.

**<https://datalab.my-domain.net>** will be the URL for your instance of Onyxia. The URL of the services created by Onyxia are going to look like: **https\://\<something>.lab.my-domain.net**

{% hint style="info" %}
You can customise "**datalab**" and "**lab**" to your liking, for example you could chose **onyxia.my-domain.net** and **\*.kub.my-domain.net**.
{% endhint %}

**SSL**

In this section we will obtain a TLS certificate issued by [LetsEncrypt](https://letsencrypt.org/) using the [certbot](https://certbot.eff.org/) commend line tool then get our ingress controller to use it.

If you are already familiar with `certbot` you're probably used to run it on a remote host via SSH. In this case you are expected to run it on your own machine, we'll use the DNS chalenge instead of the HTTP chalenge.

```bash
brew install certbot #On Mac, lookup how to install certbot for your OS

#Because we need a wildcard certificate we have to complete the DNS callange.  
sudo certbot certonly --manual --preferred-challenges dns

# When asked for the domains you wish to optains a certificate for enter:
#   datalab.my-domain.net *.lab.my-domain.net
```

{% hint style="info" %}
The obtained certificate needs to be renewed every three month.

To avoid the burden of having to remember to re-run the `certbot` command periodically you can setup [cert-manager](https://cert-manager.io/) and configure a [DNS01 challenge provider](https://cert-manager.io/docs/configuration/acme/dns01/) on your cluster but that's out of scope for Onyxia.

You may need to delegate your DNS Servers to one of the supported [DNS service provider](https://cert-manager.io/docs/configuration/acme/dns01/#supported-dns01-providers).
{% endhint %}

Now we want to create a Kubernetes secret containing our newly obtained certificate:

```bash
DOMAIN=my-domain.net
sudo kubectl create secret tls onyxia-tls \
    -n ingress-nginx \
    --key /etc/letsencrypt/live/datalab.$DOMAIN/privkey.pem \
    --cert /etc/letsencrypt/live/datalab.$DOMAIN/fullchain.pem
```

Lastly, we want to tell our ingress controller to use this TLS certificate, to do so run:

```bash
kubectl edit deployment ingress-nginx-controller -n ingress-nginx
```

This command will open your configured text editor, go to containers -> args and add:

```
      - --default-ssl-certificate=ingress-nginx/onyxia-tls
      - --watch-ingress-without-class
```

<figure><img src="/files/37IXE3fdFzoMK74lbsYZ" alt=""><figcaption></figcaption></figure>

Save and quit. Done :tada:\
We installed the ingress-nginx in our cluster, (but note that any other ingress controller could have been used as well). The configuration was adjusted to handle all ingress objects, even those lacking a specified class, and to employ our SSL certificate for our wildcard certificate. This strategy facilitated an effortless SSL termination, managed by the reverse proxy for both **\*.lab.my-domain.net** and **datalab.my-domain.net**, thus removing any additional SSL configuration concerns.
{% endtab %}

{% tab title="Test on your machine" %}
If you are on a Mac or Window computer you can install [Docker desktop](https://www.docker.com/products/docker-desktop/) then enable Kubernetes.

<figure><img src="/files/963SPSYgl9OctTv2c3bl" alt=""><figcaption><p>Enabling Kubernetes in the Docker desktop App</p></figcaption></figure>

{% hint style="warning" %}
WARNING: If you are folowing this installating guide on an Apple Sillicon Mac, be aware that many of the services that comes by default with Onyxia like Jupyter RStudio and VSCode won't run because we do not yet compile our datacience stack for the ARM64 architecture.\
If you would like to see this change please [sumit an issue about it](https://github.com/InseeFrLab/helm-charts-interactive-services/issues).
{% endhint %}

{% hint style="info" %}
Docker desktop isn't available on Linux, you can use [Kind](https://kind.sigs.k8s.io/) instead.
{% endhint %}

**Port Forwarding**

You'll need to [forward the TCP ports 80 and 443 to your local machine](https://user-images.githubusercontent.com/6702424/174459930-23fb577c-11a2-49ef-a082-873f4139aca1.png). It's done from the administration panel of your domestic internet Box. If you're on a corporate network you'll have to [test onyxia on a remote Kubernetes cluster](#provisioning-a-cluster-on-aws-gcp-or-azure).

**DNS**

Let's assume you own the domain name **my-domain.net,** for the rest of the guide you should replace **my-domain.net** by a domain you actually own.

Get [your internet box routable IP](http://monip.org/) and create the following DNS records:

```dns-zone-file
datalab.my-domain.net A <YOUR_IP>
*.lab.my-domain.net   A <YOUR_IP>
```

{% hint style="success" %}
If you have DDNS domain you can create `CNAME` instead example:

```
datalab.my-domain.net CNAME jhon-doe-home.ddns.net.
*.lab.my-domain.net   CNAME jhon-doe-home.ddnc.net.
```

{% endhint %}

***<https://datalab.my-domain.net>*** will be the URL for your instance of Onyxia.

The URL of the services created by Onyxia are going to look like: ***<https://xxx.lab.my-domain.net>***

{% hint style="info" %}
You can customise "**datalab**" and "**lab**" to your liking, for example you could chose **onyxia.my-domain.net** and **\*.kub.my-domain.net**.
{% endhint %}

**SSL**

In this section we will obtain a TLS certificate issued by [LetsEncrypt](https://letsencrypt.org/) using the [certbot](https://certbot.eff.org/) commend line tool.

```bash
brew install certbot #On Mac, lookup how to install certbot for your OS

# Because we need a wildcard certificate we have to complete the DNS callange.  
sudo certbot certonly --manual --preferred-challenges dns

# When asked for the domains you wish to optains a certificate for enter:
#   datalab.my-domain.net *.lab.my-domain.net
```

{% hint style="info" %}
The obtained certificate needs to be renewed every three month.

To avoid the burden of having to remember to re-run the `certbot` command periodically you can setup [cert-manager](https://cert-manager.io/) and configure a [DNS01 challenge provider](https://cert-manager.io/docs/configuration/acme/dns01/) on your cluster but that's out of scope for Onyxia.

You may need to delegate your DNS Servers to one of the supported [DNS service provider](https://cert-manager.io/docs/configuration/acme/dns01/#supported-dns01-providers).
{% endhint %}

Now we want to create a Kubernetes secret containing our newly obtained certificate:

```bash
# First let's make sure we connect to our local Kube cluser
kubectl config use-context docker-desktop

kubectl create namespace ingress-nginx
DOMAIN=my-domain.net
sudo kubectl create secret tls onyxia-tls \
    -n ingress-nginx \
    --key /etc/letsencrypt/live/datalab.$DOMAIN/privkey.pem \
    --cert /etc/letsencrypt/live/datalab.$DOMAIN/fullchain.pem
```

**Ingress controller**

We will install ingress-nginx in our cluster, although any other ingress controller would be suitable as well. The configuration will be set up to handle all ingress objects, including those without a specified class, and to utilize our SSL certificate for our wildcard certificate. This approach ensures a straightforward SSL termination managed by the reverse proxy for both **\*.lab.my-domain.net** and **datalab.my-domain.net**, eliminating any further concerns regarding SSL setup.

```bash
cat << EOF > ./ingress-nginx-values.yaml
controller:
  extraArgs:
    default-ssl-certificate: "ingress-nginx/onyxia-tls"
  watchIngressWithoutClass: true
EOF

helm install ingress-nginx ingress-nginx \
    --repo https://kubernetes.github.io/ingress-nginx \
    --version 4.9.1 \
    --namespace ingress-nginx \
    -f ./ingress-nginx-values.yaml
```

{% endtab %}
{% endtabs %}

Now that we have a Kubernetes cluster ready to use let's levrage ArgoCD and GitOps practices to deploy and monitor the core services of our Onyxia Datalab.

{% content-ref url="/pages/l0ZKsb6EVc5JlIZJBecZ" %}
[GitOps](/docs.onyxia.sh/v10/admin-doc/readme/gitops)
{% endcontent-ref %}


# GitOps

Let's install ArgoCD to manage and monitor our Onyxia Datalab deployment!

{% hint style="info" %}
At this stage of this installation process we assumes that:

* You have a Kubernetes cluster and `kubectl` configured
* **datalab.my-domain.net** and **\*.lab.my-domain.net**'s DNS are pointing to your cluster's external address. **my-domain.net** being a domain that you own.
* Your ingress-nginx is set up with a default TLS certificate that covers both **datalab.my-domain.net** and **\*.lab.my-domain.net**, processing all ingress objects, [even those that do not have a class specified](#user-content-fn-1)[^1].
  {% endhint %}

We can proceed with manually installing various services via Helm to set up the datalab. However, it's more convenient and reproducible to maintain a Git repository that outlines the required services that we need for our datalab, allowing [ArgoCD](https://argo-cd.readthedocs.io/en/stable/) to handle the deployment for us.

To clarify, using ArgoCD is merely an approach that we recommend, but it is by no means a requirement. Feel free to manually helm install the different services using the `values.yaml` from [InseeFrLab/onyxia-ops](https://github.com/InseeFrLab/onyxia-ops)!

Let's install ArgoCD on the cluster.

```bash
DOMAIN=my-domain.net

cat << EOF > ./argocd-values.yaml
server:
  extraArgs:
    - --insecure
  ingress:
    #ingressClassName: nginx
    enabled: true
    hostname: argocd.lab.$DOMAIN
    extraTls:
      - hosts:
          - argocd.lab.$DOMAIN
EOF

helm install argocd argo-cd \
  --repo https://argoproj.github.io/argo-helm \
  --version 6.0.9 \
  -f ./argocd-values.yaml
```

Now you have to get the password that have been automatically generated to protect ArgoCD's admin console.\
Allow some time for ArgoCD to start, you can follow the progress by running `kubectl get pods` and making sure that all pod are ready 1/1. After that running this command will print the password:

```bash
kubectl get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d
```

You can now login to **<https://argocd.lab.my-domain.net>** using:

* username: **admin**
* password: **\<the output of the previous command (without the `%` at the end)>**

<figure><img src="/files/aOyVMzDzL3eiN4e9LYOY" alt=""><figcaption></figcaption></figure>

Now that we have an ArgoCD we want to connect it to a Git repository that will describe what services we want to be running on our cluster.

Let's fork the onyxia-ops GitHub repo and use it to deploy an Onyxia instance!

{% hint style="info" %}
Note that in this guide, we use GitHub, but feel free to fork the [InseeFrLab/onyxia-ops](https://github.com/InseeFrLab/onyxia-ops) repository on GitLab or any other forge. You'll need to slightly adapt the instructions, but you should be able to follow along!
{% endhint %}

{% embed url="<https://app.tango.us/app/embed/55af08f3-43b0-4b5d-84b7-dfb75f6983c9>" %}

At this point you should have a very bare bone Onyxia instance that you can use to launch services.

What's great, is that now, if you want to update the configuration of your Onyxia instance you only have to commit the change to your GitOps repo, ArgoCD will takes charge of restarting the service for you with the new configuration.\
To put that to the test try to modify your Onyxia configuration by setting up a global alert that will be shown as a banner to all users!

{% code title="apps/onyxia/values.yaml" %}

```diff
 onyxia:
   ingress:
     enabled: true
     hosts:
       - host: datalab.demo-domain.ovh
   web:
     env:
+      GLOBAL_ALERT: |
+       {
+         severity: "success",
+         message: {
+           en: "A **big** announcement! [Check it out](https://example.com)!",
+           fr: "Une annonce **importante**! [Regardez](https://example.com)!"
+         }
+       }
   api:
     regions: [...]
```

{% endcode %}

After a few seconds, if you reload **<https://datalab.my-domain.net>** you should see the message!\\

<figure><img src="/files/XPFW8px8SO1yryTFYLGa" alt="" width="354"><figcaption></figcaption></figure>

Next step is to see how to enable your user to authenticate themselvs to your datalab!

{% content-ref url="/pages/1rEWljYFN5WjJKGt73DO" %}
[User authentication](/docs.onyxia.sh/v10/admin-doc/readme/user-authentication)
{% endcontent-ref %}

[^1]: This simplifies the process but is not a requirement of Onyxia. Should your ingress controller filter ingress objects based on a specific class name, be mindful of the various `ingressClassName: nginx` entries commented out in the chart configurations. To adapt to this setup, simply edit/uncomment those lines.


# User authentication

Using Keycloak to enable user authentication

Let's setup Keycloak to enable users to create account and login to our Onyxia instance.

Note that in this installation guide we make you use Keycloak but you can use any OIDC compliant provider like Entra ID or Auth0. See the following gide for specific instructions for different provider and detailed authentication related configuration options.

{% content-ref url="/pages/DwA18GQM35k1Kr67zkoU" %}
[OpenID Connect Configuration](/docs.onyxia.sh/v10/admin-doc/openid-connect-configuration)
{% endcontent-ref %}

### Deploying Keycloak

We're going to install Keycloak just like we installed Onyxia.

Before anything open [`apps/keycloak/values.yaml`](https://github.com/InseeFrLab/onyxia-ops/blob/main/apps/keycloak/values.yaml) in your onyxia-ops repo and [change the passwords](#user-content-fn-1)[^1]. Also write down the [`keycloak.auth.adminPassword`](https://github.com/InseeFrLab/onyxia-ops/blob/bad75636d72c20c48f1b34ec08593df83ee6c9a6/apps/keycloak/values.yaml#L11), you'll need it to connect to the Keycloak console.

{% embed url="<https://app.tango.us/app/embed/dbb21e90-db2c-41f4-b2ab-5f8b9f4d33c0>" %}

{% hint style="info" %}
Try to remember, when you [update Onyxia in `apps/onyxia/Chart.yaml`](https://github.com/InseeFrLab/onyxia-ops/blob/bad75636d72c20c48f1b34ec08593df83ee6c9a6/apps/onyxia/Chart.yaml#L6) to also update [the Onyxia theme in `apps/keycloak/values.yaml`](https://github.com/InseeFrLab/onyxia-ops/blob/bad75636d72c20c48f1b34ec08593df83ee6c9a6/apps/keycloak/values.yaml#L69).
{% endhint %}

### Configuring Keycloak

You can now login to the **administration console** of **<https://auth.lab.my-domain.net/auth/>** and login using username: keycloak and password: \<the one you've wrote down earlier>.

1. Create a realm called "datalab" (or something else), go to **Realm settings**
   1. On the tab General
      1. *User Profile Enabled*: **On**
   2. On the tab **login**
      1. *User registration*: **On**
      2. *Forgot password*: **On**
      3. *Remember me*: **On**
   3. On the tab **email,** we give an example with [AWS SES](https://aws.amazon.com/ses/), if you don't have a SMTP server at hand you can skip this by going to **Authentication** (on the left panel) -> Tab **Required Actions** -> Uncheck "set as default action" **Verify Email**. Be aware that with email verification disable, anyone will be able to sign up to your service.
      1. *From*: **<noreply@lab.my-domain.net>**
      2. *Host*: **email-smtp.us-east-2.amazonaws.com**
      3. *Port*: **465**
      4. *Authentication*: **enabled**
      5. *Username*: **\*\*\*\*\*\*\*\*\*\*\*\*\*\***
      6. *Password*: **\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\***
      7. When clicking "save" you'll be asked for a test email, you have to provide one that correspond **to a pre-existing user** or you will get a silent error and the credentials won't be saved.
   4. On the tab **Themes**
      1. *Login theme*: **onyxia-web** (you can also select the login theme on a per client basis)
      2. *Email theme*: **onyxia-web**
   5. On the tab **Localization**
      1. *Internationalization*: **Enabled**
      2. *Supported locales*: \<Select the languages you wish to support>
   6. On the tab **Session**.
      * Users **without** "Remember Me" will need to log in **every 2 weeks**:
        * Set **Session idle timeout**: `14 days`.
        * Set **Session max idle timeout**: `14 days`.
      * Users **who checked "Remember Me"** should stay logged in for **1 year**:
        * Set **Session idle timeout (Remember Me)**: `365 days`.
        * Set **Session max idle timeout (Remember Me)**: `365 days`.
2. Create a client with client ID "onyxia"
   1. *Root URL*: **<https://datalab.my-domain.net/>**
   2. *Valid redirect URIs*: **<https://datalab.my-domain.net/>**
   3. Login theme: **onyxia-web**
3. In **Authentication** (on the left panel) -> Tab **Required Actions** enable and set as default action **Therms and Conditions.**

Now you want to ensure that the username chosen by your users complies with Onyxia requirement (only alphanumerical characters) and define a list of email domain allowed to register to your service.

Go to **Realm Settings** (on the left panel) -> Tab **User Profile** -> **JSON Editor**.

Now you can edit the file as suggested in the following DIFF snippet. Be mindful that in this example we only allow emails @gmail.com and @hotmail.com to register you want to edit that.

```diff
{
  "attributes": [
    {
      "name": "username",
      "displayName": "${username}",
      "validations": {
        "length": {
          "min": 3,
          "max": 255
        },
+       "pattern": {
+         "error-message": "${lowerCaseAlphanumericalCharsOnly}",
+         "pattern": "^[a-z0-9]*$"
+       },
        "username-prohibited-characters": {}
      }
    },
    {
      "name": "email",
      "displayName": "${email}",
      "validations": {
        "email": {},
+       "pattern": {
+         "pattern": "^[^@]+@([^.]+\\.)*((gmail\\.com)|(hotmail\\.com))$"
+       },
        "length": {
          "max": 255
        }
      }
    },
...
```

Now our Keycloak server is fully configured we just need to update our Onyxia deployment to let it know about it.

### Updating the Onyxia configuration

In your GitOps repo you now want to update your onyxia configuration.

```bash
git clone https://github.com/<your-github-org>/onyxia-ops
cd onyxia-ops
cd apps/onyxia
mv values-keycloak-enabled.yaml values.yaml
git commit -am "Enable keycloak"
git push
```

Here is the DIFF of the onyxia configuration:

{% embed url="<https://github.com/InseeFrLab/onyxia-ops/commit/37faa6390c9bc8c1efddfd3488dc06b38427b424>" %}

Now your users should be able to create account, log-in, and start services on their own Kubernetes namespace.

<figure><img src="/files/2AbvJ525GvINKyT3n4sI" alt=""><figcaption><p>The screen you shoud see when clicking on "login" in your Onyxia deployment</p></figcaption></figure>

Next step in the installation proccess it to enable all the S3 related features of Onyxia:

{% content-ref url="/pages/kfcoWtB9lBYzxSShcXQf" %}
[Data (S3)](/docs.onyxia.sh/v10/admin-doc/readme/data-s3)
{% endcontent-ref %}

[^1]: Search/replace CHANGEME


# Data (S3)

Enable S3 storage via MinIO S3

Onyxia uses [AWS Security Token Service API](https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html) to obtain S3 tokens on behalf of your users. We support any S3 storage compatible with this API. In this context, we are using [MinIO](https://min.io/), which is compatible with the Amazon S3 storage service and we demonstrate how to integrate it with Keycloak.

### Creating the 'minio' Keycloak client

Before configuring MinIO, let's create a new Keycloak client (from the previous existing "datalab" realm).

{% embed url="<https://app.tango.us/app/embed/1c5c0975-93f0-48c6-b8d9-edceb397e34c>" %}

### Deploying MinIO

Before deploying MinIO on the cluster let's set, in the MinIO configuration file, the OIDC client secret we have copied in the previous step. &#x20;

```bash
git clone https://github.com/<your-github-org>/onyxia-ops
cd onyxia-ops
cd apps/minio
# In the values.yaml file replace `$KEYCLOAK_MINIO_CLIENT_SECRET` by the value
# you have copied in the previous step.
git commit -am "Set minio OIDC client secret"
git push
```

Once you've done that you can deploy MinIO! &#x20;

{% embed url="<https://app.tango.us/app/embed/75b62573-7adc-4a38-b1f9-b96bb0ea50fd>" %}

### Creating the 'onyxia-minio' Keycloak client

Before configuring the onyxia region to create tokens we should go back to Keycloak and create a new client to enable onyxia-web to request token for MinIO. This client is a little bit more complex than other if you want to manage durations (here 7 days) and this client should have a claim name policy and with a value of stsonly according to our last deployment of MinIO.

{% embed url="<https://app.tango.us/app/embed/2e382be2-5d73-4cc8-8682-1b86b0e1de58>" %}

### Updating the Onyxia configuration

Now let's update our Onyxia configuration to let it know that there is now a S3 server available on the cluster. &#x20;

```bash
git clone https://github.com/<your-github-org>/onyxia-ops
cd onyxia-ops
cd apps/onyxia
mv values-minio-enabled.yaml.yaml values.yaml
git commit -am "Enable MinIO"
git push
```

Diff of the changes applied to the Onyxia configuration: &#x20;

{% embed url="<https://github.com/InseeFrLab/onyxia-ops/commit/e8e5d57743d9954f60213346e33b55d2de41f707>" %}

Congratulation, all the S3 related features of Onyxia are now enabled in your instance! Now if you navigate to your Onyxia instance you should have `My Files` in the left menu. &#x20;

<figure><img src="/files/J1PmXK6zCfAliaFSvxiK" alt=""><figcaption></figcaption></figure>

Next step in the installation process is to setup Vault to provide a way to your user so store secret and also to provide something that Onyxia can use as a persistance layer for user configurations.

{% content-ref url="/pages/CbrCbAREDFVdcCBpTHdp" %}
[Broken mention](broken://pages/CbrCbAREDFVdcCBpTHdp)
{% endcontent-ref %}


# Vault

{% hint style="info" %}
Vault is also used by Onyxia as the persistance layer for all saved configuration. If Vault is not configured, all user settings will be stored in the browser's local storage.
{% endhint %}

Onyxia-web uses vault as a storage for two kinds of secrets:\
1\. secrets or information generated by Onyxia to store different values (S3 sources configuration)\
2\. user secrets\\

**Onyxia uses the KV version 2 secret engine.**\
**Vault must be configured with JWT or OIDC authentification methods.**

As Vault needs to be initialized with a master key, it can't be directly configured with all parameters such as oidc or access policies and roles. So first step we create a vault with dev mode (do not use this in production and do your initialization with any of the recommanded configuration: Shamir, gcp, another vault).

```bash
helm repo add hashicorp https://helm.releases.hashicorp.com
 
DOMAIN=my-domain.net

cat << EOF > ./vault-values.yaml
server:
  dev:
    enabled: true
    # Set VAULT_DEV_ROOT_TOKEN_ID value
    devRootToken: "root"
  ingress:
    enabled: true
    annotations:
      kubernetes.io/ingress.class: nginx
    hosts:
      - host: "vault.lab.$DOMAIN"
    tls:
      - hosts:
          - vault.lab.$DOMAIN
EOF

helm install vault hashicorp/vault -f vault-values.yaml
```

#### Setting up JWT authentification for Vault

From Keycloak, create a client called "vault" (realm "datalab" as usually in this documentation)

1. *Root URL*: **<https://vault.lab.my-domain.net/>**
2. *Valid redirect URIs*: **<https://vault.lab.my-domain.net/\\>**\* and **<https://datalab.my-domain.net/\\>**\*
3. *Web origins*: **\***

The expected value for the audience (aud) field of the JWT token by Vault is `vault`. You need to configure this in Keycloak.

1. Create a new Client scope: `vault`
2. Add Mapper by configuration
3. Choose Audience
   * Name: Audience for Vault
   * Included Client Audience: `vault`
   * Save

* Choose Clients: `vault`
  * Add Client Scope: `vault`

We will now configure Vault to enable `JWT` support, set policies for users permissions and initialize the secret engine.

You will need the Vault `CLI`. You can either download it [here](https://www.vaultproject.io/downloads) and configure `VAULT_ADDR=https://vault.lab.my-domain.net` and `VAULT_TOKEN=root` or exec into the vault pod `kubectl exec -it vault-0 -n vault -- /bin/sh` which will have vault `CLI` installed and pre-configured.

First, we start by creating a `JWT` endpoint in Vault, and writing information about Keycloak to the configuration. We use the same realm as usually in this documentation.

```
vault auth enable jwt
```

```
vault write auth/jwt/config \
    oidc_discovery_url="https://auth.lab.my-domain.net/auth/realms/datalab" \
    default_role="onyxia-user"
```

Onyxia uses only one single role for every user in Vault. This is in this tutorial `onyxia-user`\`. **To provide an authorization mechanism a policy is used that will depend on claims inside the JWT token.**

First you need to get the identifier (mount accessor) for the JWT authentification just created. You can use :

```
vault auth list -format=json | jq -r '.["jwt/"].accessor'
```

which should provide you something like `auth_jwt_xyz`. You will need it to **write a proper policy** by replacing the `auth_jwt_xyz` content with your own value.

#### Setting up a policy

Create locally a file named `onyxia-policy.hcl`.

You can notice that this policy is written for a KV version 2 secret engine mounted to the `onyxia-kv` path. The following policy is only working for personnal access because the entity name will be the preferred username in the JWT token.

{% code title="onyxia-policy.hcl" %}

```hcl
path "onyxia-kv/user-{{identity.entity.aliases.auth_jwt_xyz.name}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/user-{{identity.entity.aliases.auth_jwt_xyz.name}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/user-{{identity.entity.aliases.auth_jwt_xyz.name}}/*" {
  capabilities = ["delete", "list", "read"]
}
```

{% endcode %}

{% hint style="info" %}
You can include access to any secret engine in the policy, which will be accessible within the services, though the Onyxia interface won’t utilize these permissions. If you have a use case where it would be beneficial for the Onyxia interface to access other secret engines, please let us know on Slack.
{% endhint %}

Allowing personal Vault tokens to access group storage in Vault is a bit more complex. We will map the group from the token into the entity’s metadata. The following policy maps the first 10 groups statically.

{% hint style="success" %}
If you have suggestions for a better authorization mechanism within Vault, please share them with us on Slack, as the current approach is not ideal.
{% endhint %}

{% code title="onyxia-policy.hcl" %}

```hcl
path "onyxia-kv/user-{{identity.entity.aliases.auth_jwt_xyz.name}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/user-{{identity.entity.aliases.auth_jwt_xyz.name}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/user-{{identity.entity.aliases.auth_jwt_xyz.name}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group0}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group0}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group0}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group1}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group1}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group1}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group2}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group2}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group2}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group3}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group3}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group3}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group4}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group4}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group4}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group5}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group5}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group5}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group6}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group6}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group6}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group7}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group7}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group7}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group8}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group8}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group8}}/*" {
  capabilities = ["delete", "list", "read"]
}

path "onyxia-kv/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group9}}/*" {
  capabilities = ["create","update","read","delete","list"]
}

path "onyxia-kv/data/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group9}}/*" {
  capabilities = ["create","update","read"]
}

path "onyxia-kv/metadata/projet-{{identity.entity.aliases.auth_jwt_xyz.metadata.group9}}/*" {
  capabilities = ["delete", "list", "read"]
}

```

{% endcode %}

Once the policy file is created, we can proceed with creating the policy.

```bash
vault policy write onyxia-policy onyxia-policy.hcl
```

We can go on with the role `onyxia-user`.

```bash
vault write auth/jwt/role/onyxia-user \
    role_type="jwt" \
    bound_audiences="vault" \
    user_claim="preferred_username" \
    claim_mappings="/groups/0=group0,/groups/1=group1,/groups/2=group2,/groups/3=group3,/groups/4=group4,/groups/5=group5,/groups/6=group6,/groups/7=group7,/groups/8=group8,/groups/9=group9" \
    token_policies="onyxia-policy"
```

We need to enable the secret engine.

```
vault secrets enable -path=onyxia-kv kv-v2
```

Then, you need to allow the URL <https://datalab.my-domain.net> in Vault's CORS settings.

```
vault write sys/config/cors allowed_origins="https://datalab.my-domain.net" enabled=true
```

You can finally modify your onyxia config file (in the helm values) :tada:

{% code title="onyxia/values.yaml" %}

```yaml
onyxia:
  web:
    # ...
  api:
    # ...
    regions:
      [
        {
          "id": "paris",
          ...
          "services": {...},
          "data": {...},
          "vault": {
              "URL": "https://vault.lab.my-domain.net",
              "kvEngine": "onyxia-kv",
              "role": "onyxia-user",
              "authPath": "jwt",
              "prefix": "user-",
              "groupPrefix" : "",
              "oidcConfiguration":
                {
                  "issuerURI": "https://auth.lab.my-domain.net/auth/realms/datalab",
                  "clientID": "vault",
                }
          }

    ]
```

{% endcode %}


# Theme and branding

Customize your Onyxia instance with your assets and your colors, make it your own!

{% embed url="<https://youtu.be/NrVuVXsbloA>" %}

The full documentation of the available parameter can be found here:

{% embed url="<https://github.com/InseeFrLab/onyxia/blob/main/web/.env>" %}

## Theme Galery

Here is a galery of theme that you can try out.&#x20;

{% hint style="info" %}
If you want to test theses theme in your local dev env (as shown in the video) download the ZIP file specified as `CUSTOM_RESOURCES` and extract it in **web/public/custom-resources**.
{% endhint %}

{% tabs %}
{% tab title="Ultraviolet" %}

<figure><img src="/files/fJsBSY8jlcPZ74737FgC" alt=""><figcaption><p>Light Mode</p></figcaption></figure>

<figure><img src="/files/xT4D8Co1mvEOHadgzZJT" alt=""><figcaption><p>Dark mode</p></figcaption></figure>

{% code title="values.yaml" %}

```yaml
onyxia:
  web:
    env:
      #ONYXIA_API_URL: https://datalab.sspcloud.fr/api
      CUSTOM_RESOURCES: "https://www.sspcloud.fr/ultraviolet/custom-resources.zip"
      FONT: |
        { 
          fontFamily: "Geist", 
          dirUrl: "%PUBLIC_URL%/custom-resources/fonts/Geist", 
          "400": "Geist-Regular.woff2",
          "500": "Geist-Medium.woff2",
          "600": "Geist-SemiBold.woff2",
          "700": "Geist-Bold.woff2"
        }
      PALETTE_OVERRIDE: |
        {
          focus: {
            main: "#067A76",
            light: "#0AD6CF",
            light2: "#AEE4E3"
          },
          dark: {
            main: "#2D1C3A",
            light: "#4A3957",
            greyVariant1: "#22122E",
            greyVariant2: "#493E51",
            greyVariant3: "#918A98",
            greyVariant4: "#C0B8C6"
          },
          light: {
            main: "#F7F5F4",
            light: "#FDFDFC",
            greyVariant1: "#E6E6E6",
            greyVariant2: "#C9C9C9",
            greyVariant3: "#9E9E9E",
            greyVariant4: "#747474"
          }
        }
      SOCIAL_MEDIA_IMAGE: "%PUBLIC_URL%/custom-resources/preview.png"
```

{% endcode %}
{% endtab %}

{% tab title="SSPCloud" %}

<figure><img src="/files/x9FdBS9mo6DkzIVNLP5g" alt=""><figcaption><p>Light Mode</p></figcaption></figure>

<figure><img src="/files/J6FSMn0BR3eiZZoOBIxi" alt=""><figcaption><p>Dark Mode</p></figcaption></figure>

{% code title="onyxia/values.yaml" %}

```yaml
onyxia:
  web:
    env:
      #ONYXIA_API_URL: https://datalab.sspcloud.fr/api
      CUSTOM_RESOURCES: "https://www.sspcloud.fr/onyxia-theme-sspcloud.zip"
      GLOBAL_ALERT: |
        {
          severity: "success",
          message: {
            en: "If you like the platform, you can give us a ⭐️ [on GitHub](https://github.com/InseeFrLab/onyxia). Thank you very much! 😊",
            fr: "Si vous aimez la plateforme, vous pouvez nous mettre une ⭐️ [sur GitHub](https://github.com/InseeFrLab/onyxia). Merci beaucoup ! 😊",
          }
        }
      DISABLE_PERSONAL_INFOS_INJECTION_IN_GROUP: true
      TERMS_OF_SERVICES: |
        {
          en: "%PUBLIC_URL%/custom-resources/tos_en.md",
          fr: "%PUBLIC_URL%/custom-resources/tos_fr.md"
        }
      HEADER_LINKS: |
        [
          {
            label: {
              en: "Tutorials",
              fr: "Tutoriels",
              "zh-CN": "教程",
              fi: "Opastus",
              no: "Opplæring",
              it: "Tutorial",
              nl: "Zelfstudie"
            },
            icon: "https://www.sspcloud.fr/trainings.svg",
            url: "https://www.sspcloud.fr/formation"
          },
          {
            label: "AI Chat",
            icon: "SmartToy",
            url: "https://llm.lab.sspcloud.fr"
          },
          {
            "label": {
              "en": "Contact us",
              "fr": "Contactez nous"
            },
            "icon": "Support",
            "url": "https://join.slack.com/t/3innovation/shared_invite/zt-1bo6y53oy-Y~zKzR2SRg37pq5oYgiPuA"
          }
        ]
      HOMEPAGE_CALL_TO_ACTION_BUTTON_AUTHENTICATED: |
        {
          "label": {
            "fr": "Nouvel utilisateur du datalab ?",
            "en": "New user of the datalab?",
            "zh-CN": "数据实验室新用户？",
            "fi": "Uusi datalabin käyttäjä?",
            "no": "Ny bruker av datalaben?",
            "it": "Nuovo utente del datalab?",
            "nl": "Nieuwe gebruiker van het datalab?"
          },
          "startIcon": "MenuBook",
          "url": "https://docs.sspcloud.fr"
        }
      SOCIAL_MEDIA_TITLE: "SSPCloud Datalab"
      SOCIAL_MEDIA_DESCRIPTION: "Open Innovation Platform powered by Onyxia"
      SOCIAL_MEDIA_IMAGE: "%PUBLIC_URL%/custom-resources/social-preview.png"
      HEADER_TEXT_BOLD: "SSPCloud"
      HEADER_TEXT_FOCUS: "Datalab"
      FONT: |
        {
          fontFamily: "Geist",
          dirUrl: "%PUBLIC_URL%/fonts/Geist",
          "400": "Geist-Regular.woff2",
          "500": "Geist-Medium.woff2",
          "600": "Geist-SemiBold.woff2",
          "700": "Geist-Bold.woff2"
        }
      PALETTE_OVERRIDE_LIGHT: |
        {
            focus: {
                main: "#3B82F6",
                light: "#3B82F6",
            },
            light: {
                main: "#FAFAFA",
                light: "#FFFFFF",
                greyVariant1: "#EBEFF6"
            },
        }
      PALETTE_OVERRIDE_DARK: |
        {
            focus: {
              main: "#5695FB",
              light: "#5695FB",
            },
            dark: {
              main: "#0A152B",
              light: "#040B17",
            },
        }
      HOMEPAGE_MAIN_ASSET: "false"
      CUSTOM_HTML_HEAD: |
          <link rel="stylesheet" href="%PUBLIC_URL%/custom-resources/main.css"></link>
      BACKGROUND_ASSET: |
        {
          "light": "%PUBLIC_URL%/custom-resources/OnyxiaNeumorphismLightMode.svg",
          "dark": "%PUBLIC_URL%/custom-resources/OnyxiaNeumorphismDarkMode.svg"
        }
      CONTACT_FOR_ADDING_EMAIL_DOMAIN: |
        {
          "en": "If your email domain is not yet allowed [contact us](https://3innovation.slack.com/signup#/domain-signup)",
          "fr": "Si votre domaine de messagerie n'est pas encore autorisé [contactez-nous](https://3innovation.slack.com/signup#/domain-signup)"
        }
```

{% endcode %}
{% endtab %}

{% tab title="France" %}

<figure><img src="/files/tkvVTAYGpGA2Bnh32dH4" alt=""><figcaption><p>Light Mode</p></figcaption></figure>

<figure><img src="/files/AM9hItP3B1wldY8HQbzb" alt=""><figcaption><p>Dark Mode</p></figcaption></figure>

{% code title="onyxia/values.yaml" %}

```yaml
onyxia:
  web:
    env:
      #ONYXIA_API_URL: https://datalab.sspcloud.fr/api
      CUSTOM_RESOURCES: "https://www.sspcloud.fr/france/custom-resources.zip"
      FONT: |
        { 
          fontFamily: "Marianne", 
          dirUrl: "%PUBLIC_URL%/custom-resources/fonts/Marianne", 
          "400": "Marianne-Regular.woff2",
          "400-italic": "Marianne-Regular_Italic.woff2",
          "500": "Marianne-Medium.woff2",
          "700": "Marianne-Bold.woff2",
          "700-italic": "Marianne-Bold_Italic.woff2"
        }
      PALETTE_OVERRIDE: |
        {
          focus: {
            main: "#000091",
            light: "#9A9AFF",
            light2: "#E5E5F4"
          },
          dark: {
            main: "#2A2A2A",
            light: "#383838",
            greyVariant1: "#161616",
            greyVariant2: "#9C9C9C",
            greyVariant3: "#CECECE",
            greyVariant4: "#E5E5E5"
          },
          light: {
            main: "#F1F0EB",
            light: "#FDFDFC",
            greyVariant1: "#E6E6E6",
            greyVariant2: "#C9C9C9",
            greyVariant3: "#9E9E9E",
            greyVariant4: "#747474"
          }
        }
      SOCIAL_MEDIA_IMAGE: "%PUBLIC_URL%/custom-resources/preview-france.png"
      HOMEPAGE_MAIN_ASSET: "false"
```

{% endcode %}
{% endtab %}

{% tab title="Honey" %}

<figure><img src="/files/mbcEfHRxMLqnsL9Ykdt1" alt=""><figcaption><p>Light mode</p></figcaption></figure>

<figure><img src="/files/t355gluuhyU6psgAfHmS" alt=""><figcaption><p>Dark mode</p></figcaption></figure>

{% code title="onyxia/values.yaml" %}

```yaml
onyxia:
  web:
    env:
      #ONYXIA_API_URL: https://datalab.sspcloud.fr/api
      CUSTOM_RESOURCES: "https://www.sspcloud.fr/honey/custom-resources.zip"
      HEADER_LOGO: "%PUBLIC_URL%/custom-resources/dapla_honey.svg"
      HEADER_TEXT_BOLD: "Onyxia Preview"
      HEADER_TEXT_FOCUS: "v10"
      HEADER_LINKS: |
        [
          {
            "label": "AIML4OS",
            "icon": "VideoCall",
            "url": "https://insee-fr.zoom.us/webinar/register/WN_6dhMgoUvRXmkiyvYYIyKvw"
          }
        ]
      PALETTE_OVERRIDE: |
        {
          "focus": {
            "main": "#FF9100", // Light mode focus
            light: "#FAB900", // Dark mode focus
          },
          "limeGreen": {
              "main": "#00DF0A"
          },
          "dapla": {
            yellow: "#FAB900",
            darkerYellow: "#FF9100"
          }
        }
      FONT: |
        {
          fontFamily: "Geist",
          dirUrl: "%PUBLIC_URL%/custom-resources/fonts/Geist",
          "400": "Geist-Regular.woff2",
          "500": "Geist-Medium.woff2",
          "600": "Geist-SemiBold.woff2",
          "700": "Geist-Bold.woff2"
        }
      HOMEPAGE_MAIN_ASSET: "%PUBLIC_URL%/custom-resources/dapla_bee_logo.png"
      HOMEPAGE_MAIN_ASSET_SCALE_FACTOR: "0.8"
      HOMEPAGE_MAIN_ASSET_Y_OFFSET: "3rem"
      HEADER_HIDE_ONYXIA: "true"
      BACKGROUND_ASSET: |
        {
          dark: "%PUBLIC_URL%/custom-resources/dapla_background_dark.svg",
          light: "%PUBLIC_URL%/custom-resources/dapla_background_light.svg"
        }
      #HOMEPAGE_CARDS: "[]"
      ENABLED_LANGUAGES: "no,en"
      HOMEPAGE_HERO_TEXT: |
        {
          en: "Welcome to the Dapla **Lab**",
          no: "Velkommen til Dapla **Lab**",
        }
      HOMEPAGE_HERO_TEXT_AUTHENTICATED: |
        {
          en: "Welcome %USER_FIRSTNAME%!",
          no: "Velkommen %USER_FIRSTNAME%!",
        }
      TERMS_OF_SERVICES: |
        {
          en: "%PUBLIC_URL%/custom-resources/tos_en.md",
          fr: "%PUBLIC_URL%/custom-resources/tos_fr.md",
        }
      CUSTOM_HTML_HEAD: |
        <link rel="stylesheet" href="%PUBLIC_URL%/custom-resources/custom.css">
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Additional Notes

Note that your custom assets are imported into your Onyxia instance via the use of the `CUSTOM_RESOURCES` parameter, url of a ZIP archive that should contain your assets.&#x20;

{% hint style="info" %}
Onyxia is configured to make the the browser cache assets so they are not re-downloaded each time the user access the app.

If you update some of your asset but keep the same URL, you can force the browser of your users to download the new version by adding a query parameter to the URL. Eample:

`HEADER_LOGO: "%PUBLIC_URL%/custom-resources/logo.svg?v=2"`
{% endhint %}

Make sure to checkout the version of this document that matches the Onyxia version that you are deploying. [See releases](https://github.com/InseeFrLab/onyxia/releases).


# Catalog of services

How Onyxia catalogs map to Helm repositories and how to customize them.

Onyxia ships with a set of **official service catalogs**.

If you don’t configure anything, these are the defaults:

<table data-view="cards"><thead><tr><th></th><th data-type="content-ref"></th><th data-hidden data-card-cover data-type="image">Cover image</th></tr></thead><tbody><tr><td>Interactive services (IDEs)</td><td><a href="https://github.com/inseefrlab/helm-charts-interactive-services">https://github.com/inseefrlab/helm-charts-interactive-services</a></td><td><a href="/files/RnZgOxptW9N9ZHNRvZop">/files/RnZgOxptW9N9ZHNRvZop</a></td></tr><tr><td>Databases</td><td><a href="https://github.com/inseefrlab/helm-charts-databases">https://github.com/inseefrlab/helm-charts-databases</a></td><td><a href="/files/6qYJZfV1teLPYrabkYQW">/files/6qYJZfV1teLPYrabkYQW</a></td></tr><tr><td>Automation</td><td><a href="https://github.com/InseeFrLab/helm-charts-automation/">https://github.com/InseeFrLab/helm-charts-automation/</a></td><td><a href="/files/816epoF5roy4kAv9KhEw">/files/816epoF5roy4kAv9KhEw</a></td></tr><tr><td>Data visualization (optional)</td><td><a href="https://github.com/InseeFrLab/helm-charts-datavisualization">https://github.com/InseeFrLab/helm-charts-datavisualization</a></td><td><a href="/files/2u5iO0sVZH4qlDYbTc3u">/files/2u5iO0sVZH4qlDYbTc3u</a></td></tr></tbody></table>

As an instance admin, you can heavily customize what users see and can do:

* Change defaults for a service (resources, images, features).
* Apply different policies per user group (example: who can request H100).
* Fork our catalogs or build your own.
* Turn any Helm-deployable software into a service.

Example: [Doom launched as an Onyxia service](https://youtu.be/7SuXRfQqdGM?si=2Y_jrQyW-fMfGn6M\&t=731).

## Mental model: Onyxia is a UI for Helm

If you already know Helm, most of this will feel familiar.

### Helm concepts (baseline)

* A **Helm repository** is a collection of Helm charts.
* A **Helm chart** is a recipe to deploy software on Kubernetes.
* Charts expose configuration via **values**.

Defaults live in `values.yaml`.

Example: [`values.yaml` (jupyter-python)](https://github.com/InseeFrLab/helm-charts-interactive-services/blob/main/charts/jupyter-python/values.yaml).

When installing a chart, you can override any default value.

Charts can also ship a `values.schema.json`.

This JSON Schema describes:

* which options exist
* the expected types / formats
* constraints (min/max, enums, patterns, …)

Example: [`values.schema.json` (jupyter-python)](https://github.com/InseeFrLab/helm-charts-interactive-services/blob/main/charts/jupyter-python/values.schema.json).

### How Onyxia uses Helm to build the UX

You configure which Helm repositories Onyxia should load as catalogs.

{% hint style="info" %}
If you don’t configure catalogs, Onyxia loads the defaults from [`catalogs.json`](https://github.com/InseeFrLab/onyxia-api/blob/main/onyxia-api/src/main/resources/catalogs.json).
{% endhint %}

On the “Service catalog” page:

* Each **Helm repo** becomes a **tab** (Interactive services, Databases, Automation, …).
* Each **chart** becomes a **service card** (Jupyter, RStudio, …).

<figure><img src="/files/PwSH0X07K8KBBOG5MMKW" alt=""><figcaption></figcaption></figure>

When a user opens a service:

* Onyxia reads the chart’s `values.schema.json`.
* It renders a form from the schema.
* It generates a final `values` object that Helm will apply.

Onyxia can also inject user-specific defaults. For example, it can prefill S3 credentials:

<figure><img src="/files/YgG4XgPCZX5JlQa3Exnh" alt=""><figcaption></figcaption></figure>

## Customizing the catalog

You have two main customization paths:

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th data-type="content-ref"></th></tr></thead><tbody><tr><td>Instance-level overrides (recommended for most setups)</td><td><a href="/spaces/x3LIftMZY501x5liXUPV/pages/aRY1V5YfezXSATQCl5bQ">/spaces/x3LIftMZY501x5liXUPV/pages/aRY1V5YfezXSATQCl5bQ</a></td></tr><tr><td>Bring your own catalogs (or a fork of ours)</td><td><a href="/spaces/x3LIftMZY501x5liXUPV/pages/mDbXa79UBD9mFNjVL7X9">/spaces/x3LIftMZY501x5liXUPV/pages/mDbXa79UBD9mFNjVL7X9</a></td></tr></tbody></table>


# values.schema.json overrides

Instance Level Customization of the Service Catalog

This is the most common customization path.

It lets you change defaults and constraints without forking catalogs.

Use it to:

* set global policies (example: default RAM, max disk)
* restrict advanced options to specific roles (example: H100 only for users with a specific role assigned)

### Mental model

Some fields in a chart’s `values.schema.json` point to a schema file.

Onyxia ships a set of “well-known” schema files in the API:

{% embed url="<https://github.com/InseeFrLab/onyxia-api/tree/main/onyxia-api/src/main/resources/schemas>" %}

When a chart uses [`x-onyxia`](/docs.onyxia.sh/v10/admin-doc/catalog-of-services/custom-catalogs/onyxia-extension)`.overwriteSchemaWith`, Onyxia resolves that schema path.

### How `overwriteSchemaWith` works

In the catalog charts (example: [InseeFrLab/helm-charts-interactive-services](https://github.com/inseefrlab/helm-charts-interactive-services)), look for `x-onyxia.overwriteSchemaWith` in `charts/*/values.schema.json`.

Example:

{% code title="charts/jupyter-python/values.schema.json (excerpt)" %}

```json
{
  "properties": {
    "service": {
      "properties": {
        "image": {
          "properties": {
            "custom": {
              "properties": {
                "enabled": {
                  "x-onyxia": {
                    "overwriteSchemaWith": "ide/customImage.json"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
```

{% endcode %}

This means: “for this field, use the schema at `ide/customImage.json`”.

That schema file can come from:

* the default schemas embedded in Onyxia API
* an override you provide at the instance level (see below)

### Instance-wide overrides

Let's consider the [`ide/customImage.json`](https://github.com/InseeFrLab/onyxia-api/blob/main/onyxia-api/src/main/resources/schemas/ide/customImage.json) schema for exaple. By default this will be used:

{% code title="ide/customImage.json (default)" %}

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Use a custom image instead",
  "type": "boolean",
  "default": false
}
```

{% endcode %}

It enable users to provide a custom Docker image for a given service, let's say we want to remove this option. To do that you would configure your Onyxia instance like this: &#x20;

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    schemas:
      enabled: true
      files:
        - relativePath: ide/customImage.json
          content: |
            {
              "$schema": "http://json-schema.org/draft-07/schema#",
              "hidden": true,
              "const": false
            }
```

{% endcode %}

Result: the “custom image” toggle disappears from the launcher.

<details>

<summary>Other Example: change default resource sliders</summary>

This is a typical way to enforce sane defaults and limits for CPU/memory.

{% code title="apps/onyxia/values.yaml (excerpt)" %}

```yaml
onyxia:
  api:
    schemas:
      enabled: true
      files:
        - relativePath: ide/resources.json
          content: |
            {
              "$schema": "http://json-schema.org/draft-07/schema#",
              "title": "Resources",
              "description": "Your service will have at least the requested resources and never more than its limits.",
              "type": "object",
              "properties": {
                "requests": {
                  "description": "Guaranteed resources",
                  "type": "object",
                  "properties": {
                    "cpu": {
                      "title": "CPU",
                      "type": "string",
                      "default": "100m",
                      "render": "slider",
                      "sliderMin": 50,
                      "sliderMax": 10000,
                      "sliderStep": 50,
                      "sliderUnit": "m",
                      "sliderExtremity": "down",
                      "sliderExtremitySemantic": "guaranteed",
                      "sliderRangeId": "cpu"
                    },
                    "memory": {
                      "title": "Memory",
                      "type": "string",
                      "default": "2Gi",
                      "render": "slider",
                      "sliderMin": 1,
                      "sliderMax": 200,
                      "sliderStep": 1,
                      "sliderUnit": "Gi",
                      "sliderExtremity": "down",
                      "sliderExtremitySemantic": "guaranteed",
                      "sliderRangeId": "memory"
                    }
                  }
                },
                "limits": {
                  "description": "Max resources",
                  "type": "object",
                  "properties": {
                    "cpu": {
                      "title": "CPU",
                      "type": "string",
                      "default": "5000m",
                      "render": "slider",
                      "sliderMin": 50,
                      "sliderMax": 10000,
                      "sliderStep": 50,
                      "sliderUnit": "m",
                      "sliderExtremity": "up",
                      "sliderExtremitySemantic": "maximum",
                      "sliderRangeId": "cpu"
                    },
                    "memory": {
                      "title": "Memory",
                      "type": "string",
                      "default": "50Gi",
                      "render": "slider",
                      "sliderMin": 1,
                      "sliderMax": 200,
                      "sliderStep": 1,
                      "sliderUnit": "Gi",
                      "sliderExtremity": "up",
                      "sliderExtremitySemantic": "maximum",
                      "sliderRangeId": "memory"
                    }
                  }
                }
              }
            }
```

{% endcode %}

</details>

### Role-based overrides (different schema per user role)

Instance-wide overrides apply to everyone.

You can also apply schema overrides per role.

Onyxia reads roles from the decoded JWT access token.

By default, it uses the `roles` claim.

You can change that in your OIDC configuration.

See [OpenID Connect Configuration](/docs.onyxia.sh/v10/admin-doc/openid-connect-configuration).

#### Example: let `fullgpu` users choose H100

Here we override the built-in `nodeSelector-gpu.json` schema only for users with role `fullgpu`.

Other users still get the default schema.

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    schemas:
      enabled: true
      roles:
        - roleName: fullgpu
          files:
            - relativePath: nodeSelector-gpu.json
              content: |
                {
                  "$schema": "http://json-schema.org/draft-07/schema#",
                  "title": "Node Selector",
                  "type": "object",
                  "properties": {
                    "disktype": {
                      "description": "The type of disk",
                      "type": "string",
                      "enum": ["ssd", "hdd"],
                      "default": "ssd"
                    },
                    "gpu": {
                      "description": "The type of GPU",
                      "type": "string",
                      "enum": ["A2", "H100"],
                      "default": "A2"
                    }
                  },
                  "additionalProperties": false
                }
```

{% endcode %}

Result: `fullgpu` users can request GPU nodes and select `H100`.

### Next: user-specific defaults

So far you can override schemas:

* for everyone (instance-wide)
* for a subset of users (per role)

If you want per-user defaults (prefill from identity), use `x-onyxia.overwriteDefaultWith`.

Follow up with:

{% content-ref url="/spaces/x3LIftMZY501x5liXUPV/pages/a0M5hMtGR3cgjUUzN7uU" %}
[x-onyxia](/docs.onyxia.sh/v10/admin-doc/catalog-of-services/custom-catalogs/onyxia-extension)
{% endcontent-ref %}


# Custom Catalogs

Declare your own repository of charts

Use custom catalogs when you want to:

* fork the official catalogs and maintain your own variants
* publish internal charts (private org tooling)
* expose non-official charts as first-class Onyxia services

{% hint style="info" %}
If you don’t configure `onyxia.api.catalogs`, Onyxia loads the defaults from [`catalogs.json`](https://github.com/InseeFrLab/onyxia-api/blob/main/onyxia-api/src/main/resources/catalogs.json).
{% endhint %}

{% hint style="info" %}
If you only need to change defaults/constraints, avoid forking catalogs. Use [values.schema.json overrides](/docs.onyxia.sh/v10/admin-doc/catalog-of-services/override-schema-for-a-specific-instance).
{% endhint %}

### Configure catalogs

Catalogs are configured in `apps/onyxia/values.yaml` under `onyxia.api.catalogs`.

Example: you’re NASA and you want an “Aerospace services” tab.

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  web:
    # ...
  api:
    # ...
    catalogs: [
      {
        type: "helm",
        id: "aerospace",
        # The url of the Helm chart repository
        location: "https://myorg.github.io/helm-charts-aerospace/",
        # Display under the search bar as selection tab:
        # https://github.com/InseeFrLab/onyxia/assets/6702424/a7247c7d-b0be-48db-893b-20c9352fdb94
        name: { 
          en: "Aerospace services",
          fr: "Services aérospatiaux"
          # ... other languages your instance supports
        },
        # Optional. Defines the chart that should appear first
        highlightedCharts: ["jupyter-artemis", "rstudio-dragonfly"],
        # Optional. Defines the chart that should be excluded
        excludedCharts: ["a-vendor-locking-chart"],
        # Optional, If defined, displayed in the header of the catalog page:
        # https://github.com/InseeFrLab/onyxia/assets/6702424/57e32f44-b889-41b2-b0c7-727c35b07650
        # Is rendered as Markdown
        description: { 
          en: "A catalog of services for aerospace engineers",
          fr: "Un catalogue de services pour les ingénieurs aérospatiaux"
          # ...
        },
        # Can be "PROD" or "TEST". If test the catalogs will be accessible if you type the url in the search bar
        # but you won't have a tab to select it.
        status: "PROD",
        # Optional. If true the certificate verification for `${location}/index.yaml` will be skipped.
        skipTlsVerify: false,
        # Optional. certificate authority file to use for the TLS verification
        caFile: "/path/to/ca.crt",
        # Optional: Enables you to a specific group of users.
        # You can match any claim in the JWT token.  
        # If the claim's value is an array, it match if one of the value is the one you specified.
        # The match property can also be a regex.
        restrictions: [
          {
            userAttribute: {
              key: "groups",
              matches: "nasa-engineers"
            }
          }
        ]
      },
       # { ... } another catalog
    ]
```

{% endcode %}

### Next: fork or build a catalog repo

Most setups start by forking an official catalog and editing `charts/*/values.schema.json` and chart defaults.

Good starting point: [InseeFrLab/helm-charts-interactive-services](https://github.com/inseefrlab/helm-charts-interactive-services).

To go further, you’ll want the Onyxia JSON Schema extensions:

{% content-ref url="/spaces/x3LIftMZY501x5liXUPV/pages/a0M5hMtGR3cgjUUzN7uU" %}
[x-onyxia](/docs.onyxia.sh/v10/admin-doc/catalog-of-services/custom-catalogs/onyxia-extension)
{% endcontent-ref %}


# x-onyxia

Onyxia's JSON Schema extention

Onyxia defines a custom extension to the [JSON Schema spec](/docs.onyxia.sh/v10/admin-doc/catalog-of-services/custom-catalogs/json-schema-support). It adds Onyxia-specific properties under a reserved key: `x-onyxia`.

The main use case is per-user defaults based on identity. For example, you can inject the right Git and S3 credentials for each user.

### overwriteDefaultWith

Let's consider a sample of the `values.schema.json` of the InseeFrLab/helm-charts-interactive-services' Jupyter chart:

<pre class="language-json" data-title="values.schema.json"><code class="lang-json">"git": {
    "description": "Git user configuration",
    "type": "object",
    "properties": {
        "enabled": {
            "type": "boolean",
            "description": "Add git config inside your environment",
            "default": true
        },
        "name": {
            "type": "string",
            "description": "user name for git",
            "default": "",
<strong>            "x-onyxia": {
</strong><strong>                "overwriteDefaultWith": "{{git.name}}"
</strong><strong>            },
</strong>            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "email": {
            "type": "string",
            "description": "user email for git",
            "default": "",
<strong>            "x-onyxia": {
</strong><strong>                "overwriteDefaultWith": "{{git.email}}"
</strong><strong>            },
</strong>            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "cache": {
            "type": "string",
            "description": "duration in seconds of the credentials cache duration",
            "default": "",
<strong>            "x-onyxia": {
</strong><strong>                "overwriteDefaultWith": "{{git.credentials_cache_duration}}"
</strong><strong>            },
</strong>            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "token": {
            "type": "string",
            "description": "personal access token",
            "default": "",
<strong>            "x-onyxia": {
</strong><strong>                "overwriteDefaultWith": "{{git.token}}"
</strong><strong>            },
</strong>            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "repository": {
            "type": "string",
            "description": "Repository url",
            "default": "",
            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "branch": {
            "type": "string",
            "description": "Brach automatically checkout",
            "default": "",
            "hidden": {
                "value": "",
                "path": "git/repository"
            }
        }
    }
},
</code></pre>

And it translates into this:

{% embed url="<https://user-images.githubusercontent.com/6702424/177571819-f2e1b4ef-ecd1-479b-a5a1-658d87d7c7c0.png>" %}

Note the `"git.name"`, `"git.email"` and `"git.token"`, this enables [onyxia-web](https://github.com/InseeFrLab/onyxia-web) to pre fill the fields.

If the user took the time to fill its profile information, [onyxia-web](https://github.com/InseeFrLab/onyxia-web) knows what is the Git **username**, **email** and **personal access token** of the user.

![The onyxia user profile](/files/WA8zHt4hYRSdn3zcqJHz)

[Here](https://github.com/InseeFrLab/onyxia/blob/main/web/src/core/ports/OnyxiaApi/XOnyxia.ts) is defined the structure of the context that you can use in the `overwriteDefaultWith` field:

```typescript
export type XOnyxiaParams = {
    /**
     * This is where you can reference values from the onyxia context so that they
     * are dynamically injected by the Onyxia launcher.
     *
     * Examples:
     * "overwriteDefaultWith": "user.email" ( You can also write "{{user.email}}" it's equivalent )
     * "overwriteDefaultWith": "{{project.id}}-{{k8s.randomSubdomain}}.{{k8s.domain}}"
     * "overwriteDefaultWith": [ "a hardcoded value", "some other hardcoded value", "{{region.oauth2.clientId}}" ]
     * "overwriteDefaultWith": { "foo": "bar", "bar": "{{region.oauth2.clientId}}" }
     *
     */
    overwriteDefaultWith?:
        | string
        | number
        | boolean
        | unknown[]
        | Record<string, unknown>;
    overwriteListEnumWith?: unknown[] | string;
    hidden?: boolean;
    readonly?: boolean;
    useRegionSliderConfig?: string;
};

export type XOnyxiaContext = {
    user: {
        idep: string;
        name: string;
        email: string;
        password: string;
        ip: string;
        darkMode: boolean;
        lang: "en" | "fr" | "zh-CN" | "no" | "fi" | "nl" | "it" | "es" | "de";
        /**
         * Decoded JWT OIDC ID token of the user launching the service.
         *
         * Sample value:
         * {
         *   "sub": "9000ffa3-5fb8-45b5-88e4-e2e869ba3cfa",
         *   "name": "Joseph Garrone",
         *   "aud": ["onyxia", "minio-datanode"],
         *   "groups": [
         *       "USER_ONYXIA",
         *       "codegouv",
         *       "onyxia",
         *       "sspcloud-admin",
         *   ],
         *   "preferred_username": "jgarrone",
         *   "given_name": "Joseph",
         *   "locale": "en",
         *   "family_name": "Garrone",
         *   "email": "joseph.garrone@insee.fr",
         *   "policy": "stsonly",
         *   "typ": "ID",
         *   "azp": "onyxia",
         *   "email_verified": true,
         *   "realm_access": {
         *       "roles": ["offline_access", "uma_authorization", "default-roles-sspcloud"]
         *   }
         * }
         */
        decodedIdToken: Record<string, unknown>;
        accessToken: string;
        refreshToken: string;
        // See: https://docs.onyxia.sh/v/v10/admin-doc/catalog-of-services/customize-your-charts/declarative-user-profile
        profile: Record<string, Stringifyable> | undefined;
    };
    service: {
        oneTimePassword: string;
    };
    project: {
        id: string;
        password: string;
        basic: string;
    };
    git: {
        name: string;
        email: string;
        credentials_cache_duration: number;
        token: string | undefined;
    };
    vault: {
        VAULT_ADDR: string;
        VAULT_TOKEN: string;
        VAULT_MOUNT: string;
        VAULT_TOP_DIR: string;
    };
    s3: {
        AWS_ACCESS_KEY_ID: string;
        AWS_SECRET_ACCESS_KEY: string;
        AWS_SESSION_TOKEN: string;
        AWS_DEFAULT_REGION: string;
        AWS_S3_ENDPOINT: string;
        AWS_BUCKET_NAME: string;
        port: number;
        pathStyleAccess: boolean;
        /**
         * The user is assumed to have read/write access on every
         * object starting with this prefix on the bucket
         **/
        objectNamePrefix: string;
        /**
         * Only for making it easier for charts editors.
         * <AWS_BUCKET_NAME>/<objectNamePrefix>
         * */
        workingDirectoryPath: string;
        /**
         * If true the bucket's (directory) should be accessible without any credentials.
         * In this case s3.AWS_ACCESS_KEY_ID, s3.AWS_SECRET_ACCESS_KEY and s3.AWS_SESSION_TOKEN
         * will be empty strings.
         */
        isAnonymous: boolean;
    };
    region: {
        defaultIpProtection: boolean | undefined;
        defaultNetworkPolicy: boolean | undefined;
        allowedURIPattern: string;
        customValues: Record<string, unknown> | undefined;
        kafka:
            | {
                  url: string;
                  topicName: string;
              }
            | undefined;
        tolerations: unknown[] | undefined;
        from: unknown[] | undefined;
        nodeSelector: Record<string, unknown> | undefined;
        startupProbe: Record<string, unknown> | undefined;
        sliders: Record<
            string,
            {
                sliderMin: number;
                sliderMax: number;
                sliderStep: number;
                sliderUnit: string;
            }
        >;
        resources:
            | {
                  cpuRequest?: `${number}${string}`;
                  cpuLimit?: `${number}${string}`;
                  memoryRequest?: `${number}${string}`;
                  memoryLimit?: `${number}${string}`;
                  disk?: `${number}${string}`;
                  gpu?: `${number}`;
              }
            | undefined;
    };
    k8s: {
        domain: string;
        ingressClassName: string | undefined;
        ingress: boolean | undefined;
        route: boolean | undefined;
        istio:
            | {
                  enabled: boolean;
                  gateways: string[];
              }
            | undefined;
        randomSubdomain: string;
        initScriptUrl: string;
        useCertManager: boolean;
        certManagerClusterIssuer: string | undefined;
    };
    proxyInjection:
        | {
              enabled: string | undefined;
              httpProxyUrl: string | undefined;
              httpsProxyUrl: string | undefined;
              noProxy: string | undefined;
          }
        | undefined;
    packageRepositoryInjection:
        | {
              cranProxyUrl: string | undefined;
              condaProxyUrl: string | undefined;
              packageManagerUrl: string | undefined;
              pypiProxyUrl: string | undefined;
          }
        | undefined;
    certificateAuthorityInjection:
        | {
              cacerts: string | undefined;
              pathToCaBundle: string | undefined;
          }
        | undefined;
};
```

You can also concatenate string values using by wrapping the XOnyxia targeted values in `{{}}`.

{% code title="values.shema.json" %}

```json
"hostname": {
  "type": "string",
  "form": true,
  "title": "Hostname",
  "x-onyxia": {
    "overwriteDefaultWith": "{{project.id}}-{{k8s.randomSubdomain}}.{{k8s.domain}}"
  }
}
```

{% endcode %}

### overwriteListEnumWith

This is an option for customizing the options of the forms fields rendered as select.

<figure><img src="/files/g8XWP9Tow1N5qUWkisLb" alt="" width="375"><figcaption><p>Example of select form field in the onyxia launcher</p></figcaption></figure>

In your values shema such a field would be defined like:

{% code title="values.shema.json" %}

```json
"pullPolicy": {
    "type": "string",
    "default": "IfNotPresent",
    "listEnum": [
        "IfNotPresent",
        "Always",
        "Never"
    ]
}
```

{% endcode %}

But what if you want to dynamically generate the option? For this you can use the overwriteListEnumWith x-onyxia option.\
For example if you need to let the user select one of the groups he belongs to you can write:

<pre class="language-json" data-title="values.schema.json"><code class="lang-json">"group": {
  "type": "string",
<strong>  "default": "",
</strong><strong>  "listEnum": [""],
</strong>  "x-onyxia": {
<strong>    "overwriteDefaultWith": "{{user.decodedIdToken.groups[0]}}",
</strong><strong>    "overwriteListEnumWith": "{{user.decodedIdToken.groups}}"
</strong>  }
}
</code></pre>

### overwriteSchemaWith

See: [values.schema.json overrides](/docs.onyxia.sh/v10/admin-doc/catalog-of-services/override-schema-for-a-specific-instance)


# Declarative User Profile

You can define a custom user profile form that appears directly within the user interface.

<figure><img src="/files/JM7t6pGAwPiu0csampAn" alt=""><figcaption><p>Custom form defined by the Onyxia instance administrator</p></figcaption></figure>

This form is configured using a JSON Schema provided via your Onyxia `values.yaml`. Here's an example that produces the form shown above:

{% code title="onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    userProfile:
      enabled: true
      default:
        profileSchema: |
          {
            "type": "object",
            "properties": {
              "generalInfo": {
                "type": "object",
                "description": "General profile information",
                "properties": {
                  "firstName": {
                    "type": "string",
                    "title": "First name",
                    "description": "Your first name",
                    "x-onyxia": {
                      "overwriteDefaultWith": "{{user.decodedIdToken.given_name}}"
                    }
                  },
                  "familyName": {
                    "type": "string",
                    "title": "Family name",
                    "description": "Your family name",
                    "x-onyxia": {
                      "overwriteDefaultWith": "{{user.decodedIdToken.family_name}}"
                    }
                  },
                  "email": {
                    "type": "string",
                    "title": "Email",
                    "description": "Your email address",
                    "x-onyxia": {
                      "overwriteDefaultWith": "{{user.decodedIdToken.email}}"
                    }
                  }
                }
              },
              "git": {
                "type": "object",
                "description": "Git configuration",
                "properties": {
                  "username": {
                    "type": "string",
                    "title": "Git username",
                    "description": "Your username for Git operations (e.g. git commit, git push)",
                    "x-onyxia": {
                      "overwriteDefaultWith": "{{git.name}}"
                    }
                  },
                  "email": {
                    "type": "string",
                    "title": "Git email",
                    "description": "Your email for Git operations",
                    "x-onyxia": {
                      "overwriteDefaultWith": "{{git.email}}"
                    }
                  }
                }
              }
            }
          }
      roles:
        # NOTE: You can define role-specific schemas if needed.
        #- roleName: datascientist
        #  profileSchema: |
        #    ...
```

{% endcode %}

***

## Why Use a Custom User Profile?

Once defined, this form allows users to fill in personal and development-related information. These values become programmatically accessible, enabling dynamic behavior within your charts and deployments.

For example, with the schema above, and assuming the user has filled out the form as shown in the screenshot, the following values will be available in the Onyxia context:

{% code title="xOnyxiaContext.user.profile" %}

```json
{
  "generalInfo": {
    "firstName": "Joseph",
    "lastName": "Garrone",
    "email": "joseph.garrone@code.gouv.fr"
  },
  "git": {
    "username": "garronej",
    "email": "joseph.garrone.gj@gmail.com"
  }
}
```

{% endcode %}

These values can be injected into Helm charts. For instance:

```json
"x-onyxia": {
  "overwriteDefaultWith": "{{user.profile.generalInfo.lastName}}"
}
```

This will auto-fill the corresponding field with `"Garrone"`.  \
\
(Here this example is not very inspired since we already have a Git configuration tab so there's no reason to define a Git configuration section in the declarative user profile but you get the idea)

{% hint style="warning" %}
Each time you update the JSON Schema you provide to define the user profile, all existing values that the user might have filled will be lost. &#x20;
{% endhint %}

***

## Recap

* Define your schema in `onyxia.values.yaml`.
* Enable role-based customization if needed.
* Use the collected values in your Helm charts for a tailored, user-aware deployment experience.


# JSON Schema Support

This section describes JSON Schema support in the launcher.

Onyxia uses JSON Schema to dynamically create its service launch interface, often referred to as the "launcher." By defining parameters and configurations in JSON Schema, Onyxia can automatically generate forms and interfaces that guide users through setting up and deploying services.

The JSON Schema draft Onyxia follows is largely based on [Draft 7](https://json-schema.org/specification-links.html#draft-7), but it only implements a subset of the specification. This means that while Onyxia’s schema supports many core features of Draft 7—like data types, required fields, and basic validations—it may not include every feature or validation option found in the full Draft 7 specification. This subset approach keeps the schema manageable and efficient for the specific needs of Onyxia's interface generation and deployment configurations. In the following section, you’ll also see that Onyxia adds additional semantic layers.

## **Summary**

* **String**: Supports plain text input
* **Number / Integer**: Allows numerical input.
* **Boolean**: Renders a toggle.
* **Array**: Supported for list-like inputs, often used for specifying multiple items (e.g., environments, tags). Onyxia provide a way to add or remove item.
  * **Items**: Onyxia supports homogenous arrays, where all items are expected to be of the same type.
* **Object**: Forms the basis for grouping multiple fields together.
  * **Properties**: Each property in an object renders as an individual input element within the launcher.

## String

#### Render

In Onyxia’s JSON Schema implementation, string elements include various `render` types to adjust the input style based on each field’s function, creating a more intuitive user experience. Here are the primary `render` types supported for string fields:

1. **Dropdown Selection (`render: "list"`)**: Displays a dropdown menu for selecting from a set of predefined values, which is useful for fields like software versions or configurations.

   Example with schema validation :

   ```json
   {
     "type": "string",
     "enum": ["version1", "version2", "version3"],
     "default": "version1",
     "description": "Choose a software version"
   }
   ```

   Example without schema validation (usefull if your chart are reused in other context and you want people to specify other value):

   ```json
   {
     "type": "string",
     "render": "list",
     "listEnum": ["version1", "version2", "version3"], // this is onyxia specification
     "default": "version1",
     "description": "Choose a software version"
   }
   ```
2. **Password Field (`render: "password"`)**: Provides a masked input field to secure sensitive data, such as passwords or API keys.

   ```json
   {
     "type": "string",
     "render": "password",
     "description": "Enter your API key"
   }
   ```
3. **Multi-line Text (`render: "textArea"`)**: Creates a resizable, multi-line text box for longer text entries, such as configuration scripts or notes, enhancing readability and usability.

   ```json
   {
     "type": "string",
     "render": "textArea",
     "description": "Enter configuration details\n Thank you!"
   }
   ```
4. **Slider (`render: "slider"`)**: For numeric inputs (stored as strings), `render: "slider"` allows users to select a value within a specified range using a slider, commonly used for resource allocation (e.g., CPU, memory). This includes additional attributes like `sliderMin`, `sliderMax`, `sliderStep`, and `sliderUnit` to configure the slider's behavior.

   ```json
   {
     "type": "string",
     "render": "slider",
     "sliderMin": 50,
     "sliderMax": 40000,
     "sliderStep": 50,
     "sliderUnit": "m",
     "description": "Set the CPU limit"
   }
   ```

Onyxia also define some extention to the JSON Schema standard in order to let you pre-fill some values levraging what we know about the user. &#x20;

{% content-ref url="/pages/a0M5hMtGR3cgjUUzN7uU" %}
[x-onyxia](/docs.onyxia.sh/v10/admin-doc/catalog-of-services/custom-catalogs/onyxia-extension)
{% endcontent-ref %}


# OpenID Connect Configuration

[The installation guide](/docs.onyxia.sh/v10/admin-doc/readme/user-authentication) explain how to set up a new [Keycloak](https://www.keycloak.org/) instance to enable authentication on your datalab.

However, chances are that your organization already has an existing IAM system in place. This guide covers how to integrate Onyxia with various commonly used OIDC providers, including [Keycloak](https://www.keycloak.org/), [Auth0](https://auth0.com/), and [Microsoft Entra ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id).

{% hint style="warning" %}
Onyxia use **Public** OpenID Connect client: **no client Secret**.

The technical term for a public OIDC client is **Authorization Code Flow + PKCE**.

It's the type of client that you create for Single Page Application (SPA).
{% endhint %}

## API Reference

<details>

<summary>Overview of all the available parameters</summary>

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    env:
      # Mandatory and no other authentication mode is currently supported.
      authentication.mode: "openidconnect"

      # Mandatory: The issuer URI of the OIDC provider.  
      oidc.issuer-uri: "..."

      # Mandatory: The client ID of the OIDC client representing the Onyxia Web Application.
      oidc.clientID: "..."

      # Mandatory: Defines which claim in the Access Token's JWT serves as the unique 
      # user identifier.  
      # This identifier must contain only lowercase alphanumeric characters and `-`. 
      # Specifically, it must comply with RFC 1123: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names
      #
      # - If your usernames already conform to this constraint, you can use 
      #   `"preferred_username"` for a more human-readable identifier.
      # - If usernames contain special characters, use another claim 
      #   such as `"sub"` (Ensure that the `sub` values comply with RFC 1123).  
      #
      oidc.username-claim: "..."

      # Optional: Defaults to `"groups"`. Defines which claim represents user groups.
      # See: https://docs.onyxia.sh/admin-doc/setting-up-group-projects
      oidc.groups-claim: "..."

      # Optional: Defaults to `"roles"`. Defines which claim represents user roles.
      oidc.roles-claim: "..."

      # Optional: Additional query parameters to append to the OIDC authorization
      # endpoint (the login url).   
      # Example: If using Keycloak with Google OAuth as an identity provider, you might want  
      # to preselect Google as the login option using `"kc_idp_hint=google"`.  
      # 
      # ⚠️ This string is appended as-is. Ensure it is properly URI-encoded.  
      # If adding multiple parameters, separate them with `&`.  
      #
      # Example: `"foo=foo%20value&bar=bar%20value"`
      #
      # duct-taping case: If you provide an audience as query param like
      # `"audience=onyxia"`, the audience will also be passed as an extra
      # token param because some AS might expect it.  
      oidc.extra-query-params: "..."

      # Optional: Expected audience (`aud`) value in the Access Token.  
      # If set, Onyxia-API validates the `aud` claim and rejects requests
      # where it doesn’t match (or isn’t included if `aud` is an array).  
      # This setting applies only on the server side.
      # Defining it here won’t change how the OIDC client requests tokens.  
      # Refer to your provider’s documentation below for details.
      oidc.audience: "..."

      # Optional: Specifies the OIDC scopes requested by the Onyxia client.  
      # Defaults to `"openid profile"`.  
      # This is a space-separated list. `"openid"` is always requested, 
      # regardless of this setting.
      oidc.scope: "..."
      
      # Optional: Automatically logs out users after a set period of inactivity. 
      # If you are using Keycloak do not provide this value, it's inferred automatically. 
      oidc.idleSessionLifetimeInSeconds: "..."

      # Optional: The Onyxia API fetches `<issuer-uri>/.well-known/openid-configuration` 
      # to retrieve JWKs for validating Access Tokens (used as Authorization Bearers).  
      #
      # ⚠️ In development, if you lack proper root certificates, you can disable TLS verification.  
      # However, in production, it is strongly recommended to mount the correct `cacerts` instead.
      oidc.skip-tls-verify: "true|false"
```

{% endcode %}

</details>

***

## OIDC Provider Specific Configuration Guides

{% tabs %}
{% tab title="Keycloak" %}
**Onyxia Login Theme**

Each version of Onyxia ships with [a custom Keycloak login theme](https://youtu.be/NrVuVXsbloA?si=fDCPpXUIpSlCHsYw\&t=405). You can download it from the [release page](https://github.com/InseeFrLab/onyxia/releases). Specific instructions for loading the theme in your Onyxia instance can be found [in this guide](https://docs.keycloakify.dev/deploying-your-theme).

If you are deploying Keycloak using Helm, as instructed in the installation guide, [here are the relevant lines](https://github.com/InseeFrLab/onyxia-ops/blob/35f86c848a3ddeef6bfe4a9a4f41e5d516eb66db/apps/keycloak/values.yaml#L60-L79) in the Onyxia-ops repository.

**Choosing the Unique User Identifier Claim**

Onyxia requires a unique user identifier. You must specify which claim in the Access Token should be used for this purpose.

Ideally, you can use `preferred_username` as an identifier, but this requires ensuring it complies with [RFC 1123](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names). This means it must contain only lowercase alphanumeric characters and `-`.

Since this format is restrictive, if you already have an existing user base, `preferred_username` may not be an option. In that case, you have two alternatives:

* **Define a custom claim**: Configure a Keycloak mapper to generate an RFC 1123-compliant claim in the Access Token.
* **Use `"sub"`**: This claim is guaranteed to be unique and always present, but ensure that the `sub` values comply with RFC 1123.

If you are starting fresh with no existing users, you can enforce a regex pattern in the **User Profile Attributes** to require usernames that comply with the restriction.

More details can be found in [the installation guide](https://docs.onyxia.sh/admin-doc/readme/user-authentication) (search for "pattern").

**Configuring Keycloak**

Beyond what's covered in the installation guide, if you need a more general tutorial on setting up a public Keycloak OIDC client like Onyxia, refer to the following guide. It includes a test project to validate your configuration.

{% embed url="<https://docs.oidc-spa.dev/providers-configuration/keycloak>" %}
For Onyxia, use these substitutions in the guide:\
**\<KC\_DOMAIN>**: `auth.lab.my-domain.net`\
**\<KC\_RELATIVE\_PATH>**: `/auth`\
**\<REALM\_NAME>**: `datalab`\
**\<APP\_DOMAIN>**: `datalab.my-domain.net`\
**\<BASE\_URL>**: `/`\
**\<DEV\_PORT>**: `5173`\
✅ Note that Onyxia implement an auto logout countdown that will start to display once minute befor auto logout if you configure your client as [a sensible app](https://docs.oidc-spa.dev/providers-configuration/keycloak#security-sensitive-apps-banking-admin-panels-etc)
{% endembed %}

Here is an overview of what your Onyxia `values.yaml` should look like:

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    env:
      authentication.mode: "openidconnect"
      # Example: "https://auth.lab.my-domain.net/auth/realms/datalab"
      oidc.issuer-uri: "https://<KC_DOMAIN><KC_RELATIVE_PATH>/realms/<REALM_NAME>"
      # Example: "onyxia"
      oidc.clientID: "<ONYXIA_CLIENT_ID>"
      # Examples:
      # `"preferred_username"` if a regex pattern is enforced for usernames.
      # `"my-custom-claim"`    if a custom Keycloak mapper is configured.
      # `"sub"`                always works and is unique.
      oidc.username-claim: "..."
      # NOTE: By default, Access Tokens issued by Keycloak have an `aud` claim 
      # of "account". You can change this value in your protocol mapper and 
      # update this setting accordingly.  
      oidc.audience: "account"
```

{% endcode %}
{% endtab %}

{% tab title="Microsoft Entra ID" %}
Follow this guide to configure a Microsoft Entra ID application for Onyxia.

{% embed url="<https://docs.oidc-spa.dev/providers-configuration/microsoft-entra-id>" %}
For Onyxia, use these substitutions:\
`My App - API` -> `Onyxia - API`\
`api://my-app-api` -> `api://onyxia-api`\
`My App` -> `Onyxia`\
[`https://my-app.com/`](https://my-app.com/) -> `https://datalab.my-domain.net/`
{% endembed %}

Here is what your configuration should look like:

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    env:
      authentication.mode: "openidconnect"
      oidc.issuer-uri: "https://login.microsoftonline.com/<Directory (tenant) ID (Onyxia)>/v2.0"
      oidc.clientID: "<Application (client) ID (Onyxia)>"
      # Do **not** use `"sub"` or `"upn"` as they may contain 
      # non-alphanumeric characters.
      oidc.username-claim: "oid"
      oidc.scope: "profile api://onyxia-api/access_as_user"
      oidc.audience: "<Application (client) ID (Onyxia - API)>"
      
```

{% endcode %}
{% endtab %}

{% tab title="Auth0" %}
Follow this guide to configure an Auth0 application for Onyxia.

{% embed url="<https://docs.oidc-spa.dev/providers-configuration/auth0>" %}
For Onyxia, use these substitutions:\
`"My App"` → `"Onyxia"`\
**\<APP\_DOMAIN>** → `datalab.my-domain.net`\
**\<BASE\_URL>** → `/`\
**\<DEV\_PORT>** → `5173`\
`"My App - API"` → `"Onyxia - API"`\
`https://myapp.my-company.com/api` → `https://datalab.my-domain.net/api`\
`"auth.my-company.com"` → `"auth.my-domain.net"`
{% endembed %}

**Generating an RFC 1123-Compliant Claim in the Access Token**

By default, Auth0 does not issue a claim that Onyxia can use as a unique user identifier. You must create one by defining a **custom claim** in the access token using an Auth0 **Trigger Action**.

**Steps to Create the `onyxia-username` Claim**

1️⃣ **Create a Custom Action**:

1. Go to **Auth0 Dashboard** → **Actions** → **Library**.
2. Click **Create Action**.
3. Set:
   * **Name**: `GenerateOnyxiaUsername`
   * **Trigger**: **Post Login**
   * **Runtime**: `Node 22`
4. Click **Create**.

2️⃣ **Add the Custom Code**:\
Replace the default content with:

```js
function toRFC1123(input) {
  if (!input) return "";
  let output = input.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+|-+$/g, "");
  return output.length > 63 ? output.substring(0, 63).replace(/-+$/, "") : output;
}

exports.onExecutePostLogin = async (event, api) => {
  const sub = event.user.user_id;
  if (sub) api.accessToken.setCustomClaim("onyxia-username", toRFC1123(sub));
};
```

3️⃣ **Deploy and Activate the Action**:

1. Click **Deploy**.
2. Go to **Auth0 Dashboard** → **Actions** → **Triggers** → **Post Login**.
3. Drag & drop `GenerateOnyxiaUsername` into the flow.
4. Click **Apply Changes**.

Now, your access token will include the `onyxia-username` claim.

<figure><img src="/files/hkFujVZfTbnRErUexWja" alt="" width="375"><figcaption><p>Preview of the decoded JWT of the Access Token issued by Auth0<br>with the custom action enabled when previewed with the<br>test app of the oidc-spa guide</p></figcaption></figure>

**Final Configuration**

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    env:
      authentication.mode: "openidconnect"
      oidc.issuer-uri: "https://auth.my-domain.net"
      oidc.clientID: "<Onyxia Application Client ID>"  
      oidc.username-claim: "onyxia-username"
      oidc.extra-query-params: "audience=https%3A%2F%2Fdatalab.my-domain.net%2Fapi"
      oidc.audience: "https://datalab.my-domain.net/api"
      # Optional: Auto logout after inactivity.
      oidc.idleSessionLifetimeInSeconds: "300"
```

{% endcode %}
{% endtab %}

{% tab title="Other" %}
If you're using another OIDC provider and need help configuring Onyxia, reach out [on Slack](https://join.slack.com/t/3innovation/shared_invite/zt-2skhjkavr-xO~uTRLgoNOCm6ubLpKG7Q). We’ll be happy to schedule a call and assist with the integration.

However, here are some generic instructions.&#x20;

{% embed url="<https://docs.oidc-spa.dev/providers-configuration/other>" %}
Replace `https://my-app.com/` by `https://datalab.my-domain.net/`.
{% endembed %}
{% endtab %}
{% endtabs %}

## **OIDC Configuration for Services Onyxia Connects To**

Onyxia uses an OIDC client for authentication, but it also connects to other OIDC-enabled services.\
Each of these services **can** have its own OIDC client instance configuration, allowing Onyxia to authenticate using a separate client identity.

In the **region configuration**, you can specify an optional `oidcConfiguration` object for\
each service:

* **S3 (MinIO STS)** → `onyxia.api.regions[].data.S3.sts.oidcConfiguration`
* **Vault** → `onyxia.api.regions[].vault.oidcConfiguration`
* **Kubernetes API** → `onyxia.api.regions[].services.k8sPublicEndpoint.oidcConfiguration`

Each configuration follows this structure:

```ts
type OidcConfiguration = {
    issuerURI?: string;
    clientID?: string;
    extraQueryParams?: string;
    scope?: string;
    idleSessionLifetimeInSeconds?: number;
};
```

If no `oidcConfiguration` is provided for a service, Onyxia will reuse the same access\_token used for onyxia-api.

However, defining a separate OIDC client for each service is recommended to improve access control and security.

You might find it strange that Onyxia requires creating multiple OIDC clients to communicate with different resource servers (e.g. `onyxia-api`, `minio`, `vault`, or the Kubernetes API). You’ll typically end up with several clients such as `onyxia`, `onyxia-vault`, `onyxia-minio`, and `onyxia-kube`.\
At first, this can feel counterintuitive, a *client ID* seems like it should represent one application, not multiple variants of it.

Conceptually, a single client requesting tokens for multiple resource servers (each with its own audience and claims) would make more sense.\
However, Keycloak doesn’t model things that way. While Onyxia supports any OpenID Connect provider, it’s primarily designed around Keycloak’s behavior and limitations.

In Keycloak’s model, an OIDC *client* actually represents **an application talking to a specific resource server**, not just an application itself.

### Example Configuration in `values.yaml`

{% code title="" %}

```yaml
onyxia:
  api:
    env:
      authentication.mode: "openidconnect"
      oidc.issuer-uri: "https://auth.lab.my-domain.net/auth/realms/datalab"
      oidc.clientID: "onyxia"
    regions: 
      [
        {
          data: {
            S3: {
              sts: {
                oidcConfiguration: {
                  clientID: "onyxia-minio",
                }
              }
            }
          },
          vault: {
            oidcConfiguration: {
              clientID: "onyxia-vault"
            }
          },
          services: {
            k8sPublicEndpoint: {
              oidcConfiguration: {
                clientID: "onyxia-k8s"
              }
            }
          }
        }
      ]
```

{% endcode %}

***

### Ensuring Claim Consistency Across Services

When a user logs in, the OIDC provider issues an Access Token for the `onyxia` client.\
This token includes claims such as:

```json
{
  "sub": "abcd1234",
  "preferred_username": "jhondoe",
  "groups": [ "funathon", "spark-lab" ],
  "roles": [ "vip", "admin-keycloak" ]
}
```

If `oidc.username-claim: "preferred_username"` is configured in Onyxia’s main configuration,\
then all services it connects to—such as `onyxia-minio`, `onyxia-vault`, and `onyxia-k8s`—\
**must also receive Access Tokens where the `preferred_username` claim exists and holds the same value**.

To prevent issues, **all OIDC clients** (`onyxia`, `onyxia-minio`, `onyxia-vault`, `onyxia-k8s`)\
should be configured within **the same SSO realm** in your OIDC provider.\
This ensures that every issued Access Token follows the same claim structure and contains\
consistent values for the same user.

If you're unsure whether your setup meets this requirement, **check the JWT of each Access Token**\
issued for different clients and confirm that the claims are aligned.


# Custom Pages

You can host your own custom documentation pages directly within your Onyxia instance.\
This is ideal if you want to provide onboarding instructions or write step-by-step tutorials specifically tailored to your users.

{% embed url="<https://youtu.be/aQVu-vsf51w>" %}

## How It Works

Your documentation must consist of Markdown files. These files will be rendered as HTML within the Onyxia UI.\
The documents must be hosted within your Onyxia instance; external links are not supported. You need to include them in the `custom-resources.zip` file, provided through the `CUSTOM_RESOURCES` configuration key.\
More details are available in the [theme and branding documentation](/docs.onyxia.sh/v10/admin-doc/theme).

You can link to your Markdown files from any customizable section of the interface: header, sidebar, footer, and even from other Markdown files.

### Example

Assume we include the following files in `custom-resources.zip`:

```
/onboarding_en.md
/onboarding_fr.md
```

We can reference them in our configuration:

<pre class="language-yaml" data-title="onyxia/values.yaml"><code class="lang-yaml">onyxia:
  web:
    env:
<strong>      CUSTOM_RESOURCES: "https://.../custom-resources.zip"
</strong>      HEADER_TEXT_BOLD: My Organization
      HEADER_TEXT_FOCUS: Datalab
      HEADER_LINKS: |
        [
          {
            label: {
              en: "Onboarding Guide",
              fr: "Guide d'intégration"
            },
            icon: "School",
            url: {
<strong>              en: "%PUBLIC_URL%/custom-resources/onboarding_en.md",
</strong><strong>              fr: "%PUBLIC_URL%/custom-resources/onboarding_fr.md"
</strong>            }
          }
        ]
      FOOTER_LINKS: |
        [
          {
            label: {
              en: "Onboarding Guide",
              fr: "Guide d'intégration"
            },
            icon: "School",
            url: {
<strong>              en: "%PUBLIC_URL%/custom-resources/onboarding_en.md",
</strong><strong>              fr: "%PUBLIC_URL%/custom-resources/onboarding_fr.md"
</strong>            }
          }
        ]
      HOMEPAGE_BELOW_HERO_TEXT: |
        {
<strong>          en: "See our [onboarding guide](%PUBLIC_URL%/custom-resources/onboarding_en.md)",
</strong><strong>          fr: "Consultez notre [guide d'intégration](%PUBLIC_URL%/custom-resources/onboarding_fr.md)"
</strong>        }
      HOMEPAGE_CALL_TO_ACTION_BUTTON: |
        {
          label: {
            en: "Read our get started guide",
            fr: "Lire notre guide de démarrage"
          },
          startIcon: "School",
          url: {
<strong>            en: "%PUBLIC_URL%/custom-resources/onboarding_en.md",
</strong><strong>            fr: "%PUBLIC_URL%/custom-resources/onboarding_fr.md"
</strong>          }
        }
      TERMS_OF_SERVICES: "%PUBLIC_URL%/custom-resources/tos_fr.md"
</code></pre>

Example of Mardown document

{% code title="onboarding\_en.md" %}

````markdown
# This is a test document in english

This could be for example a guide specific to your Onyxia instance.  

## It's standard markdown

You can embed images, including with HTML syntax:  

<img src="%PUBLIC_URL%/custom-resources/preview.png" width="100%">  

You can render code snippets:  

```bash
echo "Hello world"
```

You can also link to pages of your instance: [Catalog](/catalog).

You can link to [another document](%PUBLIC_URL%/custom-resources/onboarding_sub_en.md).

<a href="%PUBLIC_URL%/launcher/ide/rstudio?name=rstudio&version=2.3.2&s3=region-ec97c721&resources.limits.cpu=«22700m»&autoLaunch=true">
    <img height=20 src="https://user-images.githubusercontent.com/6702424/173724486-30b6232a-c5d2-40da-a0cc-4d4a11824135.png">
</a>
````

{% endcode %}


# S3 Configuration

Configuration parameters for integrating your Onyxia service with S3.

[The installation guide](/docs.onyxia.sh/v10/admin-doc/readme/data-s3) provides instructions on how to set up [Minio](https://min.io/) with a basic configuration. However, you may want more control or need to connect to a different S3-compatible system.

Below are all the available configuration options.

{% code title="apps/onyxia/values.yaml" %}

```yaml
onyxia:
  api:
    regions: [
      {
        # ...
        data: {
          S3 : { ... } # ...See expected format below
        }
      }
    ]
```

{% endcode %}

````typescript
type S3 = {
  /**
   * The URL of the S3 server.
   * Examples: "https://minio.lab.sspcloud.fr" or "https://s3.amazonaws.com".
   */
  URL: string;

  /**
   * The AWS S3 region. This parameter is optional if you are configuring
   * integration with a MinIO server.
   * Example: "us-east-1"
   */
  region?: string;

  /**
   * This parameter informs Onyxia how to format file download URLs for the configured 
   * S3 server.
   * Default: true
   *
   * Example:
   * Assume "https://minio.lab.sspcloud.fr" as the value for region.data.S3.URL.
   * For a file "a/b/c/foo.parquet" in the bucket "user-bob":
   *
   * With pathStyleAccess set to true, the download link will be:
   *   https://minio.lab.sspcloud.fr/user-bob/a/b/c/foo.parquet
   *
   * With pathStyleAccess set to false (virtual-hosted style), the link will be:
   *   https://user-bob.minio.lab.sspcloud.fr/a/b/c/foo.parquet
   *
   * For MinIO, pathStyleAccess is typically set to true.
   * For Amazon Web Services S3, is has to be set to false.
   */
  pathStyleAccess?: boolean;

  /**
   * Defines where users are permitted to read/write S3 files,
   * specifying the allocated storage space in terms of bucket and object name prefixes.
   *
   * Mandatory unless data.S3.sts is not defined then it's optional.
   *
   * Example:
   * For a user "bob" in the "exploration" group, using the configuration:
   *
   * Shared bucket mode, all the users share a single bucket:
   *   "workingDirectory": {
   *       "bucketMode": "shared",
   *       "bucketName": "onyxia",
   *       "prefix": "user-",
   *       "prefixGroup": "project-"
   *   }
   *
   * In this configuration Onyxia will assumes that Bob has read/write access to 
   * objects starting with "user-bob/" and "project-exploration/" in the "onyxia" 
   * bucket.
   *
   * Multi bucket mode:
   *   "workingDirectory": {
   *       "bucketMode": "multi",
   *       "bucketNamePrefix": "user-",
   *       "bucketNamePrefixGroup": "project-",
   *   }
   *
   * In this configuration Onyxia will assumes that Bob has read/wite access to the 
   * entire "user-bob" and "project-exploration" buckets.
   *
   * If STS is enabled and a bucket doesn't exist, Onyxia will try to create it.
   */
  workingDirectory?:
    | {
        bucketMode: "shared";
        bucketName: string;
        prefix: string;
        prefixGroup: string;
      }
    | {
        bucketMode: "multi";
        bucketNamePrefix: string;
        bucketNamePrefixGroup: string;
      };
  /**
   * Defines a list of S3 directory bookmarks to display in the user's file explorer 
   * interface.
   * 
   * Bookmarks can be:
   * - Static: shown to all users.
   * - Dynamic: shown only if specific conditions based on the user's identity token 
   *   are met.
   *
   * Each bookmark must define:
   * - `fullPath`: The absolute S3 path to the bookmarked folder.
   * - `title`: The display title, supporting dynamic content via template variables.
   * - `description` (optional): A short description of the bookmark.
   * - `tags` (optional): An array of LocalizedString tags for UI categorization.
   *
   * For static bookmarks:
   * - Do not specify any `claimName`.
   * - The bookmark is shown to all users.
   *
   * For dynamic bookmarks:
   * - Set `claimName` to the name of a claim (e.g., `"groups"`) from the user's 
   *   **ID token**.
   * - The ID token is the one issued by the **OIDC configuration associated 
   *   with the S3 client** (i.e., from `sts.oidcConfiguration`).
   * - `includedClaimPattern` is a regular expression that must match at least one 
   *    value in the specified claim for the bookmark to be shown.
   * - `excludedClaimPattern` is a regular expression that, if matched by any value 
   *    in the claim, causes the bookmark to be ignored.
   * - If a `claimValue` matches both, exclusion takes precedence 
   *   (i.e., the bookmark is not shown).
   *
   * Template placeholders:
   * - `$1`, `$2`, ...: inserts corresponding capture groups from 
   *   `includedClaimPattern` (useful for custom rendering in `fullPath`, `title`, 
   *   `description`, or `tags`).
   *
   * 🔁 Example (static):
   * ```json
   * {
   *   "bookmarkedDirectories": [
   *     {
   *       "fullPath": "data/public",
   *       "title": {
   *         "fr": "Données publiques",
   *         "en": "Public Data"
   *       },
   *       "description": {
   *         "fr": "Dossier partagé contenant des jeux de données publics.",
   *         "en": "Shared folder containing public datasets."
   *       },
   *       "tags": [
   *         {
   *           "fr": "lecture seule",
   *           "en": "read-only"
   *         }
   *       ]
   *     }
   *   ]
   * }
   * ```
   *
   * 🔁 Example (dynamic):
   * ```json
   * {
   *   "bookmarkedDirectories": [
   *     {
   *       "fullPath": "group-$1/",
   *       "claimName": "groups",
   *       "includedClaimPattern": "^group-(.*)$",
   *       "excludedClaimPattern": "^group-secret$",
   *       "title": "Group: $1",
   *       "description": "Files accessible to group $1",
   *       "tags": ["group", "$1"]
   *     }
   *   ]
   * }
   * ```
   */
  bookmarkedDirectories?: ({
    fullPath: string;
    title: LocalizedString;
    description?: LocalizedString;
    tags?: LocalizedString[];
  } & (
    | {}
    | {
        claimName: string;
        includedClaimPattern: string;
        excludedClaimPattern: string;
      }
  ))[];
  
  /**
   * Configuration for Onyxia to dynamically request S3 tokens on behalf of users.
   * Enabling S3 allows users to avoid manual configuration of a service account via the Onyxia interface.
   */
  sts?: {
    /**
     * The STS endpoint URL of your S3 server.
     * For integration with MinIO, this property is optional as it defaults to region.data.S3.URL.
     * For Amazon Web Services S3, set this to "https://sts.amazonaws.com".
     */
    URL?: string;

    /**
     * The duration for which temporary credentials are valid.
     * AWS: Maximum of 43200 seconds (12 hours).
     * MinIO: Maximum of 604800 seconds (7 days).
     * Without this parameter, Onyxia requests 7-day validity, subject to the S3 server's policy limits.
     */
    durationSeconds?: number;

    /**
     * Optional parameter to specify RoleARN and RoleSessionName for the STS request.
     *
     * Example:
     *   "role": {
     *     "roleARN": "arn:aws:iam::123456789012:role/onyxia",
     *     "roleSessionName": "onyxia"
     *   }
     */
    role?: {
      roleARN: string;
      roleSessionName: string;
    };

    /**
     * See: https://docs.onyxia.sh/admin-doc/openid-connect-configuration#oidc-configuration-for-services-onyxia-connects-to
     */
    oidcConfiguration?: OidcConfiguration;
  };
};
````


# Setting up group projects

Enabling a group of users to share the same Kubernetes namespace to work on something together.

The user interface of onyxia enables to create projects for groups of Onyxia users. &#x20;

Users will be able to dynamically switch from one project to another using a select input in the header.

<figure><img src="/files/DmSN2VjDTCCYXu4ra9j7" alt=""><figcaption></figcaption></figure>

This select doesn't appear when the user isn't in any group project. &#x20;

All users of a group project share:

* The Kubernetes namespace, in "My Services" you can see everything that's running, including services launched by other person of the group. &#x20;
* Project settings. If a user change a project setting, it affects every member of the group.
* Secrets
* S3 Bucket (or an S3 subpath)

As of today, new group can only be created by Onyxia instance administrator, on demand and the procedure to create group is not publicly documented yet because we're still actively working on it.  \
However, if you want to enable this feature for your users, reach us, we will guide you through it! &#x20;

{% embed url="<https://join.slack.com/t/3innovation/shared_invite/zt-1hnzukjcn-6biCSmVy4qvyDGwbNI~sWg>" %}


# Security considerations

Information about security considerations

#### 1. Autolaunch Feature

The autolaunch feature empowers you to create HTTP links that automatically deploy an environment. This is an invaluable tool for initiating trainings effortlessly. However, exercise caution while using it as it could pose a security risk to the user. Consider disabling this feature if it doesn't suit your requirements or if security is a primary concern. &#x20;

[Disable Autolaunch](https://github.com/InseeFrLab/onyxia/blob/0ffdc6da0e5934a5aba2d412baa2bee5a5046586/web/.env#L149C1-L189)

#### 2. Group Feature

Onyxia is primarily designed to allocate resources such as a namespace and an S3 bucket to an individual user for work purposes. Additionally, it incorporates a feature that allows multiple users to share access to the same resources within a project. While this can be extremely beneficial for collaboration, be aware that it might be exploited by a malicious user within the group to leverage the privileges of another project member. Always monitor shared resources and maintain proper user access control to prevent such security breaches.


# Offline / airgap considerations

Onyxia can be installed in constrained environments such as behind a proxy, offline or airgap.  \
This page aims at listing various things and configurations to have in mind when installing Onyxia in such environments. &#x20;

### Catalogs &#x20;

By default, Onyxia (Onyxia-API to be precise) is configured to use [Inseefrlab Opensource catalogs straight from Github](https://github.com/InseeFrLab/onyxia-api/blob/main/onyxia-api/src/main/resources/catalogs.json)\
This won't work if you don't have access to internet.  \
If behind a proxy, you can [configure the proxy](https://github.com/InseeFrLab/onyxia-api/tree/main?tab=readme-ov-file#http-configuration) by using the corresponding API env variables.  \
\
You can configure your own catalogs by using the `catalogs` key from the [Helm chart](https://github.com/InseeFrLab/onyxia/blob/1b404b5f043fc23e8e54bea1b7b3e163739d4404/helm-chart/values.yaml#L153) : \
A catalog is a regular Helm charts repository, see [here](/docs.onyxia.sh/v10/admin-doc/catalog-of-services) for more details on how to create your own catalog.  \
Note that Onyxia does not currently support OCI-based repositories, you need to have an `index.yaml` based repository. See [this issue](https://github.com/InseeFrLab/onyxia-api/issues/547) to track progress on this.

### Certificates

If you are using non-public (internal) certificates, you need to either mount them (recommended) or skip tls validation (not recommended). &#x20;

#### Mounting certificates (recommended)&#x20;

Certificates can be mounted on the API pod :

```
api:
  extraVolumeMounts:
    - mountPath: "/usr/local/share/ca-certificates"
      name: ca-bundle
  extraVolumes:
    - name: ca-bundle
      secret:
        secretName: ca-bundle
```

#### Disabling tls validation (not recommended)

To disable tls validation for the API ⇒ OIDC provider : `oidc.skip-tls-verify`\
To disable tls validation for Helm (catalogs retrieval) : [skipTlsVerify](https://github.com/InseeFrLab/onyxia-api/blob/b47eece8103fa6bc78302390b3f0b8570de9e494/onyxia-api/src/main/resources/catalogs.json#L20)

### Images

Currently, Onyxia's images and images used by our opensource catalogs are hosted on [Dockerhub](https://hub.docker.com/u/inseefrlab).  \
Make sure your cluster nodes are configured to pull from a mirror or prepull the corresponding images.  \
If needed, you can override the images Onyxia uses in the `values.yaml` and the images of your services in your catalogs `values.yaml` / `values.schema.json`


# The Web Application

The TypeScript App that runs in the browser.

This is the documentation for [InseeFrLab/onyxia -> web/](https://github.com/InseeFrLab/onyxia/tree/main/web). &#x20;

```bash
git clone https://github.com/InseeFrLab/onyxia
cd onyxia/web

yarn install

# To start the app locally
yarn dev

# If you want to test against your own Onyxia instance edit the .env.local.yaml
# file (created automatically the first time you run `yarn dev`)
```

You have a video here where we guide you through the setup of the dev environnement: &#x20;

{% embed url="<https://youtu.be/NrVuVXsbloA?si=46kZVbVGEMWxhqc7>" %}


# Technical stack

Technologies at play in Onyxia-web

To find your way in Onyxia, the best approach is to start by getting a surface-level understanding of the libraries that are leveraged in the project.

{% hint style="info" %}
Modules marked by 🐔 are our own.
{% endhint %}

### tsafe 🐔

{% embed url="<https://www.tsafe.dev>" %}

We also heavily rely on [tsafe](https://github.com/garronej/tsafe). It's a collection of utilities that help write cleaner TypeScript code. It is crutial to understand at least [`assert`](https://docs.tsafe.dev/assert), [id](https://docs.tsafe.dev/id), [Equals](https://docs.tsafe.dev/equals) and [symToStr](https://docs.tsafe.dev/symtostr) to be able to contribute on the codebase.

## For working on what the end user 👁

Anything contained in the [src/ui](https://github.com/InseeFrLab/onyxia-web/tree/main/web/src/ui) directory.

### Onyxia-UI 🐔

{% embed url="<https://github.com/InseeFrLab/onyxia-ui>" %}

The UI toolkit used in the project, you can find the setup of [onyxia-UI](https://github.com/InseeFrLab/onyxia-ui) in onyxia-web here: [web/src/ui/theme/theme.tsx](https://github.com/InseeFrLab/onyxia/blob/main/web/src/ui/theme/theme.tsx).

#### [MUI](https://mui.com) integration

[Onyxia-UI](https://github.com/InseeFrLab/onyxia-ui) is fully compatible with [MUI](https://mui.com).

Onyxia-UI offers [a library of reusable components](https://inseefrlab.github.io/onyxia-ui) but you can also use [MUI](https://mui.com) components in the project, their aspect will automatically be adapted to blend in with the theme.

#### 🔡 Linking onyxia-ui in onyxia-web

To release a new version of [Onyxia-UI](#typescript). You just need to bump the [package.json's version](https://github.com/InseeFrLab/onyxia-ui/blob/470fdb4e54e2b16051ff8b7442ea4d765d76ba92/package.json#L3) and push. [The CI](https://github.com/garronej/ts-ci) will automate publish [a new version on NPM](#typescript).

If you want to test some changes made to onyxia-ui in onyxia-web before releasing a new version of onyxia-ui to NPM you can link locally onyxia-ui in onyxia-web.

```bash
cd ~/github
git clone https//github.com/InseeFrLab/onyxia
cd onyxia/web
yarn install

cd ~/github/onyxia #This is just a suggestion, clone wherever you see fit.
git clone https://github.com/InseeFrLab/onyxia-ui ui
cd ui
yarn install
yarn build
yarn link-in-web
npx tsc -w

# Open a new terminal
cd ~/github/onyxia/web
yarn start

```

Now you can make changes in `~/github/onyxia/ui/`and see the live updates. &#x20;

If you want to install/update some dependencies, you must remove the node\_modules, do you updates, then link again. &#x20;

### tss-react 🐔

{% embed url="<https://github.com/garronej/tss-react>" %}

The library we use for styling.

Rules of thumbs when it comes to styling:

* Every component should accept[ an optional `className`](https://github.com/InseeFrLab/onyxia-web/blob/f6e2907e43eea825d39f350207705d564360eb23/src/app/components/App/Footer.tsx#L9)prop it should always [overwrite the internal styles](https://github.com/InseeFrLab/onyxia-web/blob/f6e2907e43eea825d39f350207705d564360eb23/src/app/components/App/Footer.tsx#L55).
* A component should not size or position itself. It should always be the responsibility of the parent component to do it. In other words, you should never have `height`, `width`, `top`, `left`, `right`, `bottom` or `margin` in [the root styles](https://github.com/InseeFrLab/onyxia-web/blob/f6e2907e43eea825d39f350207705d564360eb23/src/app/components/App/Footer.tsx#L16-L23) of your components.
* You should never have a color or a dimension hardcoded elsewhere than in the theme configuration. Use `theme.spacing()` ([ex1](https://github.com/InseeFrLab/onyxia-web/blob/f6e2907e43eea825d39f350207705d564360eb23/src/app/components/pages/MyServices/MyServicesCards/MyServicesCard/MyServicesCard.tsx#L24), [ex2](https://github.com/InseeFrLab/onyxia-web/blob/f6e2907e43eea825d39f350207705d564360eb23/src/app/components/pages/MyServices/MyServicesCards/MyServicesCard/MyServicesCard.tsx#L31), [ex3](https://github.com/InseeFrLab/onyxia-web/blob/95667d66cc6ee835ede8d9d6a9bca5299d11bc1a/src/app/components/pages/MyServices/MyServicesSavedConfigs/MyServicesSavedConfig/MyServicesSavedConfig.tsx#L30)) and [`theme.colors.useCases.xxx`](https://github.com/InseeFrLab/onyxia-web/blob/08addbc60c820b8306cf8b0ccbe4793bd2f85661/src/app/components/pages/MyServices/MyServicesSavedConfigs/MyServicesSavedConfig/MyServicesSavedConfigOptions.tsx#L23-L32).

### screen-scaler 🐔

{% embed url="<https://github.com/garronej/screen-scaler>" %}

Onyxia is mostly used on desktop computer screens. It's not worth the effort to create a fully flege responsive design for the UI.  \
screen-scaler enables us to design for a sigle canonical screen size. The library take charge of scaling/shrinking the image. depending on the real size of the screen.  \
It also asks to rotate the screen when the app is rendered in protrait mode. &#x20;

### Storybook

{% embed url="<https://storybook.js.org/>" %}

It enables us to test the graphical components in isolation.

To launch Storybook locally run the following command:

```bash
yarn storybook
```

{% embed url="<https://youtu.be/2L7rtAOlqtc>" %}
Setting up a new story
{% endembed %}

### vite-envs 🐔

We need to be able to do:

{% embed url="<https://github.com/garronej/vite-envs>" %}

```bash
docker run --env OIDC_URL="https://url-of-our-keycloak.fr/auth" InseeFrLab/onyxia-web
```

Then, somehow, access `OIDC_URL` in the code like `process.env["OIDC_URL"]`.

In theory it shouldn't be possible, onyxia-web is an SPA, it is just static JS/CSS/HTML. If we want to bundle values in the code, we should have to recompile. But this is where [`cra-envs`](https://github.com/garronej/cra-envs) comes into play.

It enables to run onyxia-web again a specific infrastructure while keeping the app docker image generic.

Checkout [the helm chart](https://github.com/InseeFrLab/onyxia/tree/main/helm-chart):

```
  web:
    replicaCount: 2
    env:
      MINIO_URL: https://minio.lab.sspcloud.fr
      VAULT_URL: https://vault.lab.sspcloud.fr
      OIDC_URL: https://auth.lab.sspcloud.fr/auth
      OIDC_REALM: sspcloud
      TITLE: SSP Cloud
```

* All the accepted environment variables are defined here: [.env](https://github.com/InseeFrLab/onyxia-web/blob/main/web/.env). They are all prefixed with `REACT_APP_` to be compatible [with create-react-app](https://create-react-app.dev/docs/adding-custom-environment-variables/#adding-development-environment-variables-in-env). Default values are defined in this file.
* Then, in the code the variable can be accessed [like this](https://github.com/InseeFrLab/onyxia-web/blob/f6e2907e43eea825d39f350207705d564360eb23/src/app/libApi/LibProvider.tsx#L32).

{% hint style="warning" %}
Please try not to access the environment variable to liberally through out the code. In principle they should only be accessed [here](https://github.com/InseeFrLab/onyxia-web/blob/main/src/app/libApi/LibProvider.tsx). We try to keep things [pure](https://en.wikipedia.org/wiki/Pure_function) as much as possible.
{% endhint %}

{% embed url="<https://youtu.be/JaX14cborxE>" %}

### powerhooks 🐔

{% embed url="<https://github.com/garronej/powerhooks>" %}

It's a collection general purpose react hooks. Let's document the few use cases **you absolutely need to understand**:

#### Avoiding useless re-render of Components

For the sake of performance we enforce that every component be wrapped into [`React.memo()`](https://reactjs.org/docs/react-api.html#reactmemo). It makes that a component only re-render if one of their prop has changed.

However if you use inline functions or [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback) as callbacks props your components will re-render every time anyway:

{% embed url="<https://stackblitz.com/edit/react-ts-fyrwng?embed=1&file=index.tsx>" %}
Playground to explain the usefulness of useConstCallback
{% endembed %}

We always use [useConstCallback](https://github.com/garronej/powerhooks#useconstcallback) for callback props. And [`useCallbackFactory`](https://github.com/garronej/powerhooks#usecallbackfactory) for callback prop in lists.

#### Measuring Components

It is very handy to be able to get the height and the width of components dynamically. It prevents from having to hardcode dimension when we don’t need to. For that we use [`useDomRect`](https://github.com/garronej/powerhooks#usedomrect)\`\`

### Keycloakify 🐔

{% embed url="<https://github.com/InseeFrLab/keycloakify>" %}

It's a build tool that enables to implement the login and register pages that users see when they are redirected to Keycloak for authentication.

If the app is being run on Keycloak the [`kcContext`](https://github.com/InseeFrLab/onyxia/blob/9ced438bf6bad76a85049d52220617070f6daa79/web/src/index.tsx#L3) isn't `undefined` and it means shat we should render the login/register pages.

If you want to test, uncomment [this line](https://github.com/InseeFrLab/onyxia/blob/9ced438bf6bad76a85049d52220617070f6daa79/web/src/keycloak-theme/login/kcContext.ts#L53) and run `yarn start`. You can also test the login pages in a local keycloak container by running `yarn keycloak`. All the instructions will be printed on the console.

The `keycloak-theme.jar` file is automatically [build](https://github.com/InseeFrLab/onyxia/blob/9ced438bf6bad76a85049d52220617070f6daa79/.github/workflows/ci.yml#L90-L93) and [uploaded as a GitHub release asset](https://github.com/InseeFrLab/onyxia/blob/9ced438bf6bad76a85049d52220617070f6daa79/.github/workflows/ci.yml#L113) by the CI.&#x20;

### type-routes

{% embed url="<https://github.com/typehero/type-route>" %}

The library we use for routing. It's like [react-router](https://reactrouter.com) but type safe.

### i18nifty 🐔

{% embed url="<https://www.i18nifty.dev>" %}

For internalization and translation.

### Vite

{% embed url="<https://vitejs.dev/>" %}

## For working on 🧠 of the App

Anything contained in the [src/core](https://github.com/InseeFrLab/onyxia/tree/main/web/src/core) directory.

### clean-architecture 🐔

{% embed url="<https://github.com/garronej/clean-architecture>" %}

The framework used to implement strict separation of concern betwen the UI and the Core and high modularity of the code. &#x20;

There is [a snake game (the classic nokia game) example](https://github.com/garronej/snake-clean-architecture) for helping you understand the clean architecture framework. &#x20;

<figure><img src="/files/2Ubiwo6rASDgvWngpgPK" alt="" width="375"><figcaption><p>Snake game for understanding the clean-architecture framwork</p></figcaption></figure>

###

### oidc-spa 🐔

{% embed url="<https://github.com/garronej/oidc-spa>" %}

For everything related to user authentication.&#x20;

### EVT 🐔

{% embed url="<https://www.evt.land>" %}

EVT is an event management library (like [RxJS ](https://rxjs.dev)is).

A lot of the things we do is powered under the hood by EVT. You don't need to know EVT to work on onyxia-web however, in order to demystify the parts of the codes that involve it, here are the key ideas to take away:

* If we need to perform particular actions when a value gets changed, we use[`StatefullEvt`](https://docs.evt.land/api/statefulevt).
* We use `Ctx`to detaches event handlers when we no longer need them. (See line 108 on [this playground](https://stackblitz.com/edit/evt-playground?embed=1\&file=index.ts\&hideExplorer=1))
* In React, we use the [useEvt](https://docs.evt.land/react-hooks) hook to work with DOM events.


# Architecture

## Main rules

* [`src/ui`](https://github.com/InseeFrLab/onyxia/tree/main/web/src/ui) contains the React application, it's the UI of the app.
* [`src/core`](https://github.com/InseeFrLab/onyxia/tree/main/web/src/core) contains the 🧠 of the app.
  * Nothing in the `src/core` directory should relate to React. A concept like react hooks for example is out of scope for the src/core directory.
  * `src/core` should never import anything from `src/ui`, even types.
  * It should be possible for example to port onyxia-web to Vue.js or React Native without changing anything to the `src/core` directory.
  * The goal of `src/core` is to expose an API that serves the UI.
  * The API exposed should be reactive. We should not expose to the UI functions that returns promises, instead, the functions we expose should update states and the UI should react to these states updates.

## Architecture

* Whenever we need to interact with the infrastructure we define a port in [`src/core/port`](https://github.com/InseeFrLab/onyxia/tree/main/web/src/core/ports). A port is only a type definition. In our case the infrastructure is: the Keycloak server, the Vault server, the Minio server and a Kubernetes API (Onyxia-API).
* In [`src/core/adapters`](https://github.com/InseeFrLab/onyxia/tree/main/web/src/core/adapters) are the implementations of the ports. For each port we should have at least two implementations, a dummy and a real one. It enabled the app to still run, be it in degraded mode, if one piece of the infrastructure is missing. Say we don’t have a Vault server we should still be able to launch containers.
* In [`src/lib/usecases`](https://github.com/InseeFrLab/onyxia/tree/main/web/src/core/usecases) we expose APIs for the UI to consume. &#x20;

The following framework is the backbone of onyxia-web, if you can familiarize yourself with it it will make working with onyxia-web much easyer.

{% embed url="<https://github.com/garronej/clean-architecture>" %}

## In practice

Let's say we want to create a new page in onyxia-web where users can type in a repo name and get the current number of stars the repo has on GitHub.

{% hint style="info" %}
UPDATE: This video remain relevant but please not that the clean archi setup have been considerably improved in latest releases. [A dedicated repo](https://github.com/garronej/clean-architecture) have been created to explain it in detail.

Main take-way is that `app` have been renamed `ui` and `lib` have been renamed `core`.
{% endhint %}

{% embed url="<https://youtu.be/RDxAag3Iq0o>" %}

{% hint style="info" %}
You might wonder why some values, instead of being redux state, are returned by thunks functions.

For example, it might seem more natural to do:

```tsx
const { isUserLoggedIn } = useCoreState(state => state.userAuthentication);
```

Instead of what we actually do, which is:

```tsx
const { userAuthenticationThunks } = useThunks();
const isUserLoggedIn = userAuthenticationThunks.getIsUserLoggedIn();
```

However the rule is to never store as a redux state, values that are not susceptible to change. Redux states are values that we observe, any redux state changes should trigger a re-render of the React components that uses them. Conversely, there is no need to observe a value that will never change. We can get it once and never again, get it in a callback or wherever.

But, you may object, users do login and logout, `isUserLoggedIn` is not a constant!

Actually, from the standpoint of the web app, it is. When a user that isn't authenticated click on the login button, it is being redirected away. When he returns to the app everything is reloaded from scratch.
{% endhint %}

Now let's say we want the search to be restricted to a given GitHub organization. (Example: InseeFrLab.) The GitHub organization should be specified as an environment variable by the person in charge of deploying Onyxia. e.g.:

```yaml
  web:
    env:
      MINIO_URL: https://minio.lab.sspcloud.fr
      VAULT_URL: https://vault.lab.sspcloud.fr
      OIDC_URL: https://auth.lab.sspcloud.fr/auth
      OIDC_REALM: sspcloud
      TITLE: SSP Cloud
      ORG_NAME: InseeFrLab #<==========
      
```

If no `ORG_NAME` is provided by the administrator, the app should always show 999 stars for any repo name queried.

{% embed url="<https://youtu.be/eaU-tYFzWwA>" %}

## Another example: Recording user's GitLab token

Currently users can save their GitHub Personal access token in their Onyxia account but not yet their GitLab token. Let's see how we would implement that.

{% embed url="<https://www.youtube.com/watch?v=WVFKCR1QfVk>" %}

## How to deal with project switching

The easy action to take when the user selects another project is to simply reload the page (`windows.location.reload()`). We want to avoid doing this to enable what we call "*hot projet swiping*":

![The page is not reloaded when changing the project](https://user-images.githubusercontent.com/6702424/147413744-480235af-53cc-4b4d-a69a-7e9e73a79407.gif)

To implement this behavior you have to leverage the evtAction middleware from clean-redux. It enabled to register functions to be run when certain actions are dispatched.

{% hint style="info" %}
Unlike the other video, the following one is voiced. Find the relevant code [here](https://github.com/InseeFrLab/onyxia-web/blob/61b4d660faebefacc9e963c506b707c04d57521f/src/core/usecases/runningService.ts#L316-L332).
{% endhint %}

{% embed url="<https://youtu.be/TWDHBxceH0Q>" %}


# The REST API

The backend REST API in Java

This is the documentation for [InseeFrLab/onyxia -> api/](https://github.com/InseeFrLab/onyxia-api). &#x20;

It's the part of the App that runs in the clusters. It handles the things that can't be done directly from the frontend. &#x20;

{% embed url="<https://mango-dune-07a8b7110.1.azurestaticapps.net/?repo=InseeFrLab/onyxia-api>" %}


# Roadmap

Onyxia Project Core Team Future Developments Roadmap

Want to know what we are up to? &#x20;

Checkout our Milestones on GitHub: &#x20;

{% embed url="<https://github.com/InseeFrLab/onyxia/milestones>" %}
Onyxia project GitHub Milestones
{% endembed %}

Roadmap is also often discussed during our [monthly public community calls](https://docs.onyxia.sh/contributors-doc/community-calls), feel free to attend.

Do not hesitate to vote or comment on the issues that are the most important to you.  \
We prioritize our work based on community feedback ! &#x20;

Or you can ask us on Slack, we're very prompt to respond ! &#x20;

{% embed url="<https://join.slack.com/t/3innovation/shared_invite/zt-3r26584mp-SGPr9XvTukNkJiDZfRjZiQ>" %}


# Community calls

Our community calls take place **on the last Friday of each month at 13:00 (Paris time)**.

{% file src="/files/Iyi1bDhpLL5NClCFLObS" %}

These calls are open to **everyone** — a great opportunity to:

* Get the latest project updates &#x20;
* Ask questions
* Discuss the roadmap
* Showcase how you're using the project<br>

To join, simply head over to our Slack workspace and join the [*#community-meeting*](https://3innovation.slack.com/archives/C0664UVJ77W) channel.

\
List of previous community calls minutes :&#x20;

{% content-ref url="/pages/RL8Lfkkch4ESpYHEvwk2" %}
[July 2026 community call](/docs.onyxia.sh/v10/contributors-doc/community-calls/july-2026-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/ZaLAhHTKyqOweItbwaMb" %}
[January 2026 community call](/docs.onyxia.sh/v10/contributors-doc/community-calls/january-2026-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/wZ4VmUWUgKzRtiPrJh72" %}
[October 2025 community call](/docs.onyxia.sh/v10/contributors-doc/community-calls/october-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/OFUHa9PC9iGycBbuB0jo" %}
[September 2025 community call](/docs.onyxia.sh/v10/contributors-doc/community-calls/september-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/5s4Ms3s8BDTVbcLXvwco" %}
[August 2025 community call](/docs.onyxia.sh/v10/contributors-doc/community-calls/august-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/d15LxZ2GifmvJDz3YhPc" %}
[July 2025 community call](/docs.onyxia.sh/v10/contributors-doc/community-calls/july-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/OlsWhRSxgVTBMAjmrUvR" %}
[June 2025 community call](/docs.onyxia.sh/v10/contributors-doc/community-calls/june-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/ibQ5rACInHWGVN6fJ6kp" %}
[May 2025 community call](/docs.onyxia.sh/v10/contributors-doc/community-calls/may-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/NoegysrkPLwdLyoPfZ6F" %}
[April 2025 community call](/docs.onyxia.sh/v10/contributors-doc/community-calls/april-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/kKEbuDhiM0H2KxAWUtu8" %}
[March 2025 community call](/docs.onyxia.sh/v10/contributors-doc/community-calls/march-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/i8xpbeNwXcQahSlmJXih" %}
[February 2025 community call](/docs.onyxia.sh/v10/contributors-doc/community-calls/february-2025-community-call)
{% endcontent-ref %}

{% content-ref url="/pages/g2ZKKekFvPhOWhamS16i" %}
[January 2025 community call](/docs.onyxia.sh/v10/contributors-doc/community-calls/january-2025-community-call)
{% endcontent-ref %}


# July 2026 community call

Community call 07/31/2026

## Onyxia news

* Reference custom plugin: [Onyxia-LS3](https://github.com/onyxia-datalab/onyxia-LS3). For customizing Onyxia in depth and cater to the specific need of a specific organization.
* Onyxia S3 Explorer : fully revamped. Let us know how it feels !
* Work on AI integration. Current testing version : <https://onyxialpha.kub.sspcloud.fr/account/ai> . Dev branch `ia-integration` (beware : work in progress) : <https://github.com/InseeFrLab/onyxia/tree/ia-integration>

## Community discussions

* Welcome to Bryan Devos from belgium. Currently in early stage of Onyxia installation.


# January 2026 community call

### January 2026 community call

#### Welcome back Marc

* Marc, our primary designer (2020-2022) came back home last month

#### Onyxia communication

* <https://community.ima-dt.org/france-corporate-innovation-award-2026/content/liste-nomines> we won the european sovereignty category.
* Cloud native Day Paris february 3 : we have a booth, come to talk to us :)

#### Onyxia news

* still working on file explorer (supporting profile file semantic, bookmark, enhance UX)
* support of postgres CNPG
* Planned support of Iceberg Rest API. Onyxia will be able to use this king of lakehouse as it perfectly fit cloud native env.
* Ingress-nginx project will be retired in March 2026 (soon !). If you are currently using it (SSPCloud is currently using it), what is your plan ? Onyxia has no major dependency to ingress-nginx but admins may want to take this deadline as an opportunity to migrate to gateway API. Onyxia support for gateway API is planned / in-progress but will probably not be ready by the march 2026 deadline (work is needed on onyxia-api but most importantly on charts). Context : <https://kubernetes.io/blog/2026/01/29/ingress-nginx-statement/>
* Seaweedfs (<https://github.com/seaweedfs/seaweedfs>) now supports policy variables (<https://github.com/seaweedfs/seaweedfs/issues/8037>) allowing to define policies such as `user johndoe has access to bucket user-johndoe` just like in minIO / AWS. You may find this project as a good replacment for minIO following their licensing changes
* DPOP ! onyxia is an SPA, we need to protect our access tokens. oidc-spa support DPOP : <https://oauth.net/2/dpop/>


# October 2025 community call

Community call 30/25/2025

## Onyxia news

* working progress on data explorer <https://onyxialpha.kub.sspcloud.fr/s3?profile=1efe8fb1>
* feature released on data catolog <https://github.com/InseeFrLab/onyxia/issues/1021> on sspcloud , example on data.gouv.fr <https://datalab.sspcloud.fr/data-collection?source=https%3A%2F%2Fwww.data.gouv.fr%2Fapi%2F1%2Forganizations%2F534fff81a3a7292c64a77e5c%2Fcatalog.jsonld%3Fformat%3Dparquet>
* More work in progress in the go migration / rewrite, now working on the "main" API (services, my-lab …) and moving onto a monorepo for all backend modules (onboarding, services …) : <https://github.com/onyxia-datalab/onyxia-backend> Feel free to join the team :)


# September 2025 community call

Community call 09/25/2025

## Onyxia news

* [CVE-2025-58366](https://github.com/InseeFrLab/onyxia/security/advisories/GHSA-m773-6vm8-8x6q) Private helm repository credentials leak : only affected if you were using private helm catalogs (specifying credentials in the `catalogs` json). Patched on Onyxia 10.28. Take a look at our "vulnerability disclosure" documentation page and feel free to register to our security mailing to be notified when a vulnerability is discovered : <https://docs.onyxia.sh/vulnerability-disclosure>
* working on data catolog <https://github.com/InseeFrLab/onyxia/issues/1021>
* Reminder : the onboarding module, rewritten in go, is up for testing and use. It has been in use in production at SSPCloud for a month now. To test it, set `onboarding.enabled=true` in your chart values <https://github.com/InseeFrLab/onyxia/blob/eeb00ae9d7047849b06dd5244eb1c4a4806db4ae/helm-chart/values.yaml#L317> no additional change is needed, we aimed for fully compatibility with the existing onboarding behaviour. Feedback welcome ! Onboarding code is hosted on the `onyxia-datalab` org on github : <https://github.com/onyxia-datalab/onyxia-onboarding>
* More work in progress in the go migration / rewrite, now working on the "main" API (services, my-lab ...) and moving onto a monorepo for all backend modules (onboarding, services ...) : <https://github.com/onyxia-datalab/onyxia-backend> Feel free to join the team :)


# August 2025 community call

Community call 08/28/2025

## Project news

* Release v10.27
  * Onboarding as a separate go module (test it, `onboarding.enabled=true` in your values : <https://github.com/InseeFrLab/onyxia/blob/ba06172cba57b6893d0646f6521a1895532d844a/helm-chart/values.yaml#L316>)
* State of go :
  * Onboarding is functional and at (almost, mainly missing events support) parity with current API
  * Work is now on `services` API
  * Code is available on a monorepo : <https://github.com/onyxia-datalab/onyxia-backend>
  * Feedback and contributions welcome ! #dev-rewrite-to-go on slack to discuss
* Bitnamigate happening today :scream: : <https://github.com/bitnami/charts/issues/35164>
  * Bitnami dropping / reducing docker & helm charts availability and support
  * Inseefrlab (defaults for Onyxia) catalogs have been updated this week to use `bitnamilegacy` (mainly for databases catalog)
  * Future : we will try to remove catalog dependencies to bitnami wherever and whenever it's feasible


# July 2025 community call

Community call 07/31/2025\
\
Project news

* Release v10.25
  * API v4.8.0 : cache for packages retrieval (recommended update !)
  * S3 bookmarks + dynamic
* New onboarding module in Go available for testing : <https://github.com/onyxia-datalab/onyxia-onboarding> , chart with new module as an option WIP (will be merged soon (tm) to the regular Helm chart) : <https://github.com/InseeFrLab/helm-charts-dev/tree/main/charts/onyxia>
* Poster session at kubecon North America in Atlanta from 10 to 13 Nov


# June 2025 community call

Community call 05/26/2025\
\
Project news :

* Release v10.23
  * [Declarative user profile](/docs.onyxia.sh/v10/admin-doc/catalog-of-services/custom-catalogs/declarative-user-profile)&#x20;
  * [S3 bookmarks](/docs.onyxia.sh/v10/admin-doc/s3-configuration)
* Work in progress\
  [overwriteDefaultWith for object and array #992](https://github.com/InseeFrLab/onyxia/issues/992)\
  [FR : Hidden Profile Fields as Hints #995](https://github.com/InseeFrLab/onyxia/issues/995)


# May 2025 community call

Community call 05/29/2025

Project's news :

* Release v10.18 (including API 4.6.0) :
  * Basic auth for helm repo
  * Support for multiple S3 configurations (first step, still need more work especially on services / catalogs)
  * Ability to host and embed markdown files : [Issue](https://github.com/InseeFrLab/onyxia/pull/976) [Example](https://datalab.sspcloud.fr/document?source=%257B%2522en%2522%253A%2522%252Fcustom-resources%252Ftos_en.md%2522%252C%2522fr%2522%253A%2522%252Fcustom-resources%252Ftos_fr.md%2522%257D)
* WIP : User profile : <https://github.com/InseeFrLab/onyxia/pull/980> . Feedback / usecases welcome
* WIP : bookmarks for file explorer : <https://github.com/InseeFrLab/onyxia/issues/968>

<figure><img src="/files/Zn00nbLEdknC6pmyCjSH" alt=""><figcaption></figcaption></figure>


# April 2025 community call

Community call 04/24/2025

Project's news :

* New schedule for community calls ! Last thursday of the month at 16:30 Paris time. ICS calendar available : <https://docs.onyxia.sh/contributors-doc/community-calls>
* Various improvements to My files
* WIP : Multiple STS configuration support. Still needs work especially on User interface and catalogs configuration / injection. Need to figure out what to do with the dropdown menu that currently allows switching between S3 configurations. Almost all the services (python, R ...) are ready to support multiple STS configurations but duckdb is not, an issue is currently open on their side.
* Work has started on "User profile" feature (<https://github.com/InseeFrLab/onyxia/discussions/954>). Not testable yet but feedback welcome on usecase, would you use it ?
* Onyxia support for charts without values.schema.json by fallbacking into the the new YAML editor.
  * Possibility to see all defaults in the text editor, including the ones defined in the values.yaml.
* Customization: Possibility to define different color palettes for dark and light mode. Possibility to add custom CSS for light and dark mode.

Community discussions :

* Data(S3) configuration should allow configurations that don't contain prefix/ prefixGroup or bucketNamePrefix/ bucketNamePrefixGroup. Basically keeping S3 configuration / STS / injection but disabling user bucket / working directory path … In this setup, users will then run things like `mc ls s3` and have access to mulitple buckets not tied to their username.
* Document Group Projects : documentation is lackluster on how to configure / enable groups feature and what the feature is about (what the group gives access to, how it's supposed to be used …)


# March 2025 community call

Community call 03/28/2025

Project's news :

* New repo ! Awesome Onyxia : <https://github.com/onyxia-datalab/awesome-onyxia> Listing of resources related to the Onyxia ecosystem. Feel free to contribute :)
* Onboarding module in go : still WIP, not much this month. Contributions welcome, please join #dev-rewrite-to-go on Slack
* Feature request : Customizable User Profile. <https://github.com/InseeFrLab/onyxia/discussions/954> . Work currently in progress to implement this using json schemas. Admin of the instance would specify a json schema describing the user profile. UI would render it and let user customize their profile (e.g git configuration). All the data would be then available for injection in the service launcher form.
* Debate : overwriteSchemaWith / patchSchemaWith (@Gaspard) : <https://github.com/InseeFrLab/onyxia-api/pull/573> Currently Onyxia is relying on overwriteSchemaWith that only allow to replace a schema part while discarding the existing one. Patchschemawith would allow to keep the existing definition (e.g when using Onyxia's opensource catalogs) and upstream changes while patching only what's necessary. Would also reduce duplication.
* New customization features:
  * Different palette for the dark and light mode
  * Gradiant background color
* Desktop App:
  * It would be an electron wrapper around Onyxia Web
  * The main goal would be to be able to bypass CORS issues (that are a blocker for accessing public S3 Bucket through the Onyxia UI)
* New button for accessing Keycloak user profile (when applicable). Do you want an option to disable it?

Community discussions :

* SSB : plugin showcase :\
  <https://github.com/statisticsnorway/onyxia/tree/ssb-assets/web/public/custom-resources>


# February 2025 community call

Community call 02/28/2025

News :

* Rewrite of the onboarding as a go module : Going well, first version has been released yesterday : <https://github.com/onyxia-datalab/onyxia-onboarding> . Current work is on the Helm chart with a standalone version already published and integration of this module as a optional dependency in the main Onyxia chart should arrive soon. Slack channel for discussion on the rewrite effort : #dev-rewrite-to-go
* Onyxia-web : Work on improving support for OIDC providers other than Keycloak. In particular Entra ID (Microsoft) and Auth0. Thoughough documentation will be added to the docs.onyxia.dev website shortly. <https://docs.oidc-spa.dev/>

Community contributions :

* Trygve : Great work on the new Go-based onboarding API so far! We just need to make sure the test coverage for the go rewrite is maintained at a good level to avoid reproducing the same mistakes as the current Java Onyxia-API (which has a ridiculous low level of test)
* Trygve : they developped a custom web plugin to display the estimated cost of running the service based on resources (cpu / mem) beside the resources slider. Really interesting but may be hard to opensource properly due to variety in setups and Charts
* Discussion on how to get billing usage : prometheus, opencost. Also hard to opensource / bundle to Onyxia / make it generic\
  ![image](https://hackmd.io/_uploads/Syp-qVki1g.png)
* Trygve : 200 users daily :heart:
* NTTS (eurostat conference) 11-13 march 2025. Come and say hi :relaxed:
* CSTB (<https://www.cstb.fr/>) : approx 1000 employees including datascientists. Currently running Jupyterhub and other tools. Interested in Onyxia for unifying tools among all projects and embrace opensource ecosystems. Welcome :relaxed:


# January 2025 community call

Community call 01/31/25

* CVE\
  A 9.4 vulnerability has been found (thanks team norway !) in Onyxia-API at the end of December.\
  Read more here : <https://nvd.nist.gov/vuln/detail/CVE-2024-56333>\
  We created a new section on the docs for everything related to security including a mailing list : <https://docs.onyxia.sh/vulnerability-disclosure>
* API rewrite Java => Go\
  We started the process of rewriting the Onyxia-API that is currently written in Java to Golang as go is a lot more integrated with cloud native technologies. It would greatly improve performance, maintainability (letting us get rid of the Helm wrapper we built) and security.\
  We take this opportunity to also split the API into separated modules, starting with the Onboarding.\
  See discussion here : <https://github.com/InseeFrLab/onyxia/discussions/925>\
  Feel free to join the effort by joining the #dev-rewrite-to-go channel on Slack\
  Also we created the <https://github.com/onyxia-datalab> org
* YAML editor\
  New feature ! Making progress towards more support for advanced users such as developpers. Letting them directly modify values instead of using the UI form. Also support for charts that don't have a `values.schema.json`.
* Parquet\
  Improvements on how parquet are displayed in the Data explorer, also improvements on file format detection

Mercator :

* Updated their fork from v9 => v10. Quite some work was needed but happy with the new features.

API rewrite from Java to Go is not just a rewrite in another language, it's also an opportunity to change the architecture. Discussions on key points such as if / how we should support things other than helm packages are currently taking place so anyone interested in this are more than welcome to join the discussion #dev-rewrite-to-go and <https://github.com/InseeFrLab/onyxia/discussions/925>


# Getting started with Onyxia

Using Onyxia (as a data scientist)

{% hint style="success" %}
See also [https://docs.sspcloud.fr](https://docs.sspcloud.fr/)

It's the Onyxia user guide dedicated to our staff. &#x20;
{% endhint %}

There are 3 main components accessible on the onyxia web interface :

* catalogs and services launched by the users (Kubernetes access)
* a file browser (S3 access)
* secret browser (Vault access)

## Start a service

Following is a documentation Onyxia when configured with the default service catalogs :&#x20;

{% embed url="<https://github.com/inseefrlab/helm-charts-interactive-services>" %}

This collection of charts help users to launch many IDE with various binary stacks (python , R) with or without GPU support. Docker images are built [here](https://github.com/inseefrlab/images-datascience) and help us to give a homogeneous stack.

{% embed url="<https://github.com/inseefrlab/helm-charts-databases>" %}

This collection of charts help users to launch many databases system. Most of them are based on [bitnami/charts](https://guthub.com/bitnami/charts).

{% embed url="<https://github.com/InseeFrLab/helm-charts-automation>" %}

This collection of charts help users to start automation tools for their datascience activity.&#x20;

{% embed url="<https://github.com/InseeFrLab/helm-charts-datavisualization>" %}

This collection of charts helps users to launch tools to visualize and share data insights.

{% hint style="info" %}
The Onyxia user experience may be very different from one catalog of service to another. &#x20;

The catalog defines what options are available though Onyxia. &#x20;
{% endhint %}

Users can edit various parameters. Onyxia do some assertion based on the charts values schema and the configuration on the instance. For example some identity token can be injected by default (because Onyxia connect users to many APIs).

<figure><img src="/files/ceu3JtiCLD1fsIkO9v3j" alt=""><figcaption></figcaption></figure>

After launching a service, notes are shown to the user. He can retrieve those notes on the README button. Charts administrator should explain how to connect to the services (url , account) and what happens on deletion.

<figure><img src="/files/6cJLzozNR5bchVMli0Uy" alt=""><figcaption></figcaption></figure>

Now you want to learn how to setup your devloppement environement for day to day usage: &#x20;

{% content-ref url="/pages/cdkxJw8KXKGlY30oMRVS" %}
[Setting up your dev environment in Onyxia](/docs.onyxia.sh/v10/user-doc/setting-up-your-dev-environment-in-onyxia)
{% endcontent-ref %}

## File browser

Users can manage their files on S3. There is no support for rename in S3 so don't be surprise. Onyxia is educational. Any action on the S3 browser in the UI is written in a console with a cli.

<figure><img src="/files/NUs6qnJmOJeCPNOFGRKH" alt=""><figcaption><p>s3 browser</p></figcaption></figure>

User can do the following S3 actions :&#x20;

* download files
* upload files
* delete files

Of course, in our default catalags there are all the necessary tools to connect to S3.

Our advice is to never download file to your container but directly ingest in memory the data.

{% embed url="<https://youtu.be/Fg4drnvgd20>" %}
Connecting to an external S3
{% endembed %}

## Secret browser

Users can mange their secrets on Vault. There is also a cli console.

<figure><img src="/files/cV7SXm3ywD5xh6lu7Fi9" alt=""><figcaption></figcaption></figure>

Onyxia use only a key value v2 secret engine in Vault. Users can store some secrets there and inject them in their services if configured by the helm chart.

<figure><img src="/files/6thJIozbiRKEWa6MSgbF" alt=""><figcaption></figcaption></figure>

Of course, in our default catalags there are all the necessary tools to connect to Vault.


# Datascience Trainings and Tutorials

The Onyxia team maintain a catalog of training and tutorials with several practical exercices that can be performed on an Onyxia instance! &#x20;

{% hint style="info" %}
By default the when you open the trainings will be open on <https://datalab.sspcloud.fr> (our onyxia instance) but if you don't have a Datalab acount you can edit the urls of the practical exercises so you can run them on the instance you have access to. &#x20;
{% endhint %}

{% embed url="<https://www.sspcloud.fr/formation>" %}


# Setting up your dev environment in Onyxia

In this video, we guide you through setting up your development environment in Onyxia. We demonstrate how to automatically clone your Git repository, install any missing dependencies, and open a port for your development server.

You can also find initialization scripts of interactive services [here](https://github.com/InseeFrLab/sspcloud-init-scripts).

{% hint style="info" %}
I forgot to show in the video that you can setup your GitHub/GitLab username and token in My Account -> External services.

This will enable Onyxia to clone private repos! &#x20;

![](/files/kpCg0WEZMPE6OaVD6JAT)
{% endhint %}

{% embed url="<https://www.youtube.com/watch?v=_6rKPeQj650>" %}


# Community resources

You can find extra information on how to use Onyxia as a datascientist by checking out the community website of the french statistician workforce. It's in french though. &#x20;

{% embed url="<https://docs.sspcloud.fr/>" %}

Want to share something you've done with Onyxia? You can click on "edit this page on GitHub" and submit a pull request! &#x20;


# Install

Convinced by Onyxia? Let's see how you can get your own instance today!

{% hint style="info" %}

## Oneliner

If you are already familiar with Kubernetes and Helm, here's how you can get an Onyxia instance up and running in just a matter of seconds.

```bash
helm repo add onyxia https://inseefrlab.github.io/onyxia

cat << EOF > ./onyxia-values.yaml
ingress:
  enabled: true
  hosts:
    - host: onyxia.my-domain.net
EOF

helm install onyxia onyxia/onyxia -f onyxia-values.yaml

# Navigate to https://onyxia.my-domain.net
```

With this minimal configuration, you'll have an Onyxia instance operating in a degraded mode, which lacks features such as authentication, S3 explorer, secret management, etc. However, you will still retain the capability to launch services from the catalog.
{% endhint %}

Whether you are a Kubernetes veteran or a beginner with cloud technologies, this guide aims to guide you through the instantiation and configuration of an Onyxia instance with it's full range of features enabled. Let's dive right in! 🤿

First let's make sure we have a suitable deployment environement to work with!&#x20;

{% content-ref url="/pages/LvC5vcZc9pkCe267bM3D" %}
[Kubernetes](/docs.onyxia.sh/v9/admin-doc/readme/kubernetes)
{% endcontent-ref %}


# Kubernetes

Provision a Kubernetes cluster

First you'll need a Kubernetes cluster. If you have one already you can skip and directly go to [the Onyxia instalation section](/docs.onyxia.sh/v9/admin-doc/readme/gitops).

{% tabs %}
{% tab title="Provisioning a cluster on AWS, GCP or Azure" %}
[Hashicorp](https://www.hashicorp.com/) maintains great tutorials for [terraforming](https://www.terraform.io/) Kubernetes clusters on [AWS](https://aws.amazon.com/what-is-aws/), [GCP](https://cloud.google.com/) or [Azure](https://acloudguru.com/videos/acg-fundamentals/what-is-microsoft-azure).

Pick one of the three and follow the guide.

You can stop after the [configure kubectl section](https://learn.hashicorp.com/tutorials/terraform/eks#configure-kubectl).

{% embed url="<https://developer.hashicorp.com/terraform/tutorials/kubernetes/eks>" %}

{% embed url="<https://developer.hashicorp.com/terraform/tutorials/kubernetes/gke?in=terraform%2Fkubernetes>" %}

{% embed url="<https://developer.hashicorp.com/terraform/tutorials/kubernetes/aks?in=terraform%2Fkubernetes>" %}

**Ingress controller**

Let's install ingress-ngnix on our newly created cluster:

{% hint style="warning" %}
The following command is [for AWS](https://kubernetes.github.io/ingress-nginx/deploy/#aws).

For GCP use [this command](https://kubernetes.github.io/ingress-nginx/deploy/#gce-gke).

For Azure use [this command](https://kubernetes.github.io/ingress-nginx/deploy/#azure).
{% endhint %}

```bash
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.2.0/deploy/static/provider/aws/deploy.yaml
```

**DNS**

Let's assume you own the domain name **my-domain.net**, for the rest of the guide you should replace **my-domain.net** by a domain you actually own.

Now you need to get the external address of your cluster, run the command

```bash
kubectl get services -n ingress-nginx
```

and write down the `External IP` assigned to the `LoadBalancer`.

Depending on the cloud provider you are using it can be an IPv4, an IPv6 or a domain. On AWS for example, it will be a domain like **xxx.elb.eu-west-1.amazonaws.com**.

If you see `<pending>`, wait a few seconds and try again.

Once you have the address, create the following DNS records:

```dns-zone-file
datalab.my-domain.net CNAME xxx.elb.eu-west-1.amazonaws.com. 
*.lab.my-domain.net   CNAME xxx.elb.eu-west-1.amazonaws.com. 
```

If the address you got was an IPv4 (`x.x.x.x`), create a `A` record instead of a CNAME.

If the address you got was ans IPv6 (`y:y:y:y:y:y:y:y`), create a `AAAA` record.

**<https://datalab.my-domain.net>** will be the URL for your instance of Onyxia. The URL of the services created by Onyxia are going to look like: **https\://\<something>.lab.my-domain.net**

{% hint style="info" %}
You can customise "**datalab**" and "**lab**" to your liking, for example you could chose **onyxia.my-domain.net** and **\*.kub.my-domain.net**.
{% endhint %}

**SSL**

In this section we will obtain a TLS certificate issued by [LetsEncrypt](https://letsencrypt.org/) using the [certbot](https://certbot.eff.org/) commend line tool then get our ingress controller to use it.

If you are already familiar with `certbot` you're probably used to run it on a remote host via SSH. In this case you are expected to run it on your own machine, we'll use the DNS chalenge instead of the HTTP chalenge.

```bash
brew install certbot #On Mac, lookup how to install certbot for your OS

#Because we need a wildcard certificate we have to complete the DNS callange.  
sudo certbot certonly --manual --preferred-challenges dns

# When asked for the domains you wish to optains a certificate for enter:
#   datalab.my-domain.net *.lab.my-domain.net
```

{% hint style="info" %}
The obtained certificate needs to be renewed every three month.

To avoid the burden of having to remember to re-run the `certbot` command periodically you can setup [cert-manager](https://cert-manager.io/) and configure a [DNS01 challenge provider](https://cert-manager.io/docs/configuration/acme/dns01/) on your cluster but that's out of scope for Onyxia.

You may need to delegate your DNS Servers to one of the supported [DNS service provider](https://cert-manager.io/docs/configuration/acme/dns01/#supported-dns01-providers).
{% endhint %}

Now we want to create a Kubernetes secret containing our newly obtained certificate:

```bash
DOMAIN=my-domain.net
sudo kubectl create secret tls onyxia-tls \
    -n ingress-nginx \
    --key /etc/letsencrypt/live/datalab.$DOMAIN/privkey.pem \
    --cert /etc/letsencrypt/live/datalab.$DOMAIN/fullchain.pem
```

Lastly, we want to tell our ingress controller to use this TLS certificate, to do so run:

```bash
kubectl edit deployment ingress-nginx-controller -n ingress-nginx
```

This command will open your configured text editor, go to containers -> args and add:&#x20;

```
      - --default-ssl-certificate=ingress-nginx/onyxia-tls
      - --watch-ingress-without-class
```

<figure><img src="/files/37IXE3fdFzoMK74lbsYZ" alt=""><figcaption></figcaption></figure>

Save and quit. Done :tada:\
We installed the ingress-nginx in our cluster, (but note that any other ingress controller could have been used as well). The configuration was adjusted to handle all ingress objects, even those lacking a specified class, and to employ our SSL certificate for our wildcard certificate. This strategy facilitated an effortless SSL termination, managed by the reverse proxy for both **\*.lab.my-domain.net** and **datalab.my-domain.net**, thus removing any additional SSL configuration concerns.
{% endtab %}

{% tab title="Test on your machine" %}
If you are on a Mac or Window computer you can install [Docker desktop](https://www.docker.com/products/docker-desktop/) then enable Kubernetes.

<figure><img src="/files/963SPSYgl9OctTv2c3bl" alt=""><figcaption><p>Enabling Kubernetes in the Docker desktop App</p></figcaption></figure>

{% hint style="warning" %}
WARNING: If you are folowing this installating guide on an Apple Sillicon Mac, be aware that many of the services that comes by default with Onyxia like Jupyter RStudio and VSCode won't run because we do not yet compile our datacience stack for the ARM64 architecture.  \
If you would like to see this change please [sumit an issue about it](https://github.com/InseeFrLab/helm-charts-interactive-services/issues).
{% endhint %}

{% hint style="info" %}
Docker desktop isn't available on Linux, you can use [Kind](https://kind.sigs.k8s.io/) instead.
{% endhint %}

**Port Forwarding**

You'll need to [forward the TCP ports 80 and 443 to your local machine](https://user-images.githubusercontent.com/6702424/174459930-23fb577c-11a2-49ef-a082-873f4139aca1.png). It's done from the administration panel of your domestic internet Box. If you're on a corporate network you'll have to [test onyxia on a remote Kubernetes cluster](#provisioning-a-cluster-on-aws-gcp-or-azure).

**DNS**

Let's assume you own the domain name **my-domain.net,** for the rest of the guide you should replace **my-domain.net** by a domain you actually own.

Get [your internet box routable IP](http://monip.org/) and create the following DNS records:

```dns-zone-file
datalab.my-domain.net A <YOUR_IP>
*.lab.my-domain.net   A <YOUR_IP>
```

{% hint style="success" %}
If you have DDNS domain you can create `CNAME` instead example:

```
datalab.my-domain.net CNAME jhon-doe-home.ddns.net.
*.lab.my-domain.net   CNAME jhon-doe-home.ddnc.net.
```

{% endhint %}

***<https://datalab.my-domain.net>*** will be the URL for your instance of Onyxia.

The URL of the services created by Onyxia are going to look like: ***<https://xxx.lab.my-domain.net>***

{% hint style="info" %}
You can customise "**datalab**" and "**lab**" to your liking, for example you could chose **onyxia.my-domain.net** and **\*.kub.my-domain.net**.
{% endhint %}

**SSL**

In this section we will obtain a TLS certificate issued by [LetsEncrypt](https://letsencrypt.org/) using the [certbot](https://certbot.eff.org/) commend line tool.

```bash
brew install certbot #On Mac, lookup how to install certbot for your OS

# Because we need a wildcard certificate we have to complete the DNS callange.  
sudo certbot certonly --manual --preferred-challenges dns

# When asked for the domains you wish to optains a certificate for enter:
#   datalab.my-domain.net *.lab.my-domain.net
```

{% hint style="info" %}
The obtained certificate needs to be renewed every three month.

To avoid the burden of having to remember to re-run the `certbot` command periodically you can setup [cert-manager](https://cert-manager.io/) and configure a [DNS01 challenge provider](https://cert-manager.io/docs/configuration/acme/dns01/) on your cluster but that's out of scope for Onyxia.

You may need to delegate your DNS Servers to one of the supported [DNS service provider](https://cert-manager.io/docs/configuration/acme/dns01/#supported-dns01-providers).
{% endhint %}

Now we want to create a Kubernetes secret containing our newly obtained certificate:

```bash
# First let's make sure we connect to our local Kube cluser
kubectl config use-context docker-desktop

kubectl create namespace ingress-nginx
DOMAIN=my-domain.net
sudo kubectl create secret tls onyxia-tls \
    -n ingress-nginx \
    --key /etc/letsencrypt/live/datalab.$DOMAIN/privkey.pem \
    --cert /etc/letsencrypt/live/datalab.$DOMAIN/fullchain.pem
```

**Ingress controller**

We will install ingress-nginx in our cluster, although any other ingress controller would be suitable as well. The configuration will be set up to handle all ingress objects, including those without a specified class, and to utilize our SSL certificate for our wildcard certificate. This approach ensures a straightforward SSL termination managed by the reverse proxy for both **\*.lab.my-domain.net** and **datalab.my-domain.net**, eliminating any further concerns regarding SSL setup.

```bash
cat << EOF > ./ingress-nginx-values.yaml
controller:
  extraArgs:
    default-ssl-certificate: "ingress-nginx/onyxia-tls"
  watchIngressWithoutClass: true
EOF

helm install ingress-nginx ingress-nginx \
    --repo https://kubernetes.github.io/ingress-nginx \
    --version 4.9.1 \
    --namespace ingress-nginx \
    -f ./ingress-nginx-values.yaml
```

{% endtab %}
{% endtabs %}

Now that we have a Kubernetes cluster  ready to use let's levrage ArgoCD and GitOps practices to deploy and monitor the core services of our Onyxia Datalab. &#x20;

{% content-ref url="/pages/l0ZKsb6EVc5JlIZJBecZ" %}
[GitOps](/docs.onyxia.sh/v9/admin-doc/readme/gitops)
{% endcontent-ref %}


# GitOps

Let's install ArgoCD to manage and monitor our Onyxia Datalab deployment!

{% hint style="info" %}
At this stage of this installation process we assumes that:

* You have a Kubernetes cluster and `kubectl` configured
* **datalab.my-domain.net** and **\*.lab.my-domain.net**'s DNS are pointing to your cluster's external address. **my-domain.net** being a domain that you own.
* Your ingress-nginx is set up with a default TLS certificate that covers both **datalab.my-domain.net** and **\*.lab.my-domain.net**, processing all ingress objects, [even those that do not have a class specified](#user-content-fn-1)[^1].&#x20;
  {% endhint %}

We can proceed with manually installing various services via Helm to set up the datalab. However, it's more convenient and reproducible to maintain a Git repository that outlines the required services that we need for our datalab, allowing [ArgoCD](https://argo-cd.readthedocs.io/en/stable/) to handle the deployment for us.

To clarify, using ArgoCD is merely an approach that we recommend, but it is by no means a requirement. Feel free to manually helm install the different services using the `values.yaml` from [InseeFrLab/onyxia-ops](https://github.com/InseeFrLab/onyxia-ops)!

Let's install ArgoCD on the our cluster.

```bash
DOMAIN=my-domain.net

cat << EOF > ./argocd-values.yaml
server:
  extraArgs:
    - --insecure
  ingress:
    #ingressClassName: nginx
    enabled: true
    hostname: argocd.lab.$DOMAIN
    extraTls:
      - hosts:
          - argocd.lab.$DOMAIN
EOF

helm install argocd argo-cd \
  --repo https://argoproj.github.io/argo-helm \
  --version 6.0.9 \
  -f ./argocd-values.yaml
```

Now you have to get the password that have been automatically generated to protect ArgoCD's admin console.  \
Allow some time for ArgoCD to strart, you can follow the progress by running `kubectl get pods` and making sure that all pod are ready 1/1. After that running this command will print the password:

```bash
kubectl get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d
```

You can now login to **<https://argocd.lab.my-domain.net>** using:

* username: **admin**
* password: **\<the output of the previous command (without the `%` at the end)>**

<figure><img src="/files/aOyVMzDzL3eiN4e9LYOY" alt=""><figcaption></figcaption></figure>

Now that we have an ArgoCD we want to connect it to a Git repository that will describe what services we want to be running on our cluster.

Let's fork the onyxia-ops GitHub repo and use it to deploy an Onyxia instance!

{% hint style="info" %}
Note that in this guide, we use GitHub, but feel free to fork the [InseeFrLab/onyxia-ops](https://github.com/InseeFrLab/onyxia-ops) repository on GitLab or any other forge. You'll need to slightly adapt the instructions, but you should be able to follow along!&#x20;
{% endhint %}

{% embed url="<https://app.tango.us/app/embed/55af08f3-43b0-4b5d-84b7-dfb75f6983c9>" %}

At this point you should have a very bare bone Onyxia instance that you can use to launch services.

What's great, is that now, if you want to update the configuration of your Onyxia instance you only have to commit the change to your GitOps repo, ArgoCD will takes charge of restarting the service for you with the new configuration.\
To put that to the test try to modify your Onyxia configuration by setting up a global alert that will be shown as a banner to all users!

{% code title="apps/onyxia/values.yaml" %}

```diff
 onyxia:
   ingress:
     enabled: true
     hosts:
       - host: datalab.demo-domain.ovh
   web:
     env:
+      GLOBAL_ALERT: |
+       {
+         severity: "success",
+         message: {
+           en: "A **big** announcement! [Check it out](https://example.com)!",
+           fr: "Une annonce **importante**! [Regardez](https://example.com)!"
+         }
+       }
   api:
     regions: [...]
```

{% endcode %}

After a few seconds, if you reload **<https://datalab.my-domain.net>** you should see the message!<br>

<figure><img src="/files/XPFW8px8SO1yryTFYLGa" alt="" width="354"><figcaption></figcaption></figure>

Next step is to see how to enable your user to authenticate themselvs to your datalab!

{% content-ref url="/pages/1rEWljYFN5WjJKGt73DO" %}
[User authentication](/docs.onyxia.sh/v9/admin-doc/readme/user-authentication)
{% endcontent-ref %}

[^1]: This simplifies the process but is not a requirement of Onyxia. Should your ingress controller filter ingress objects based on a specific class name, be mindful of the various `ingressClassName: nginx` entries commented out in the chart configurations. To adapt to this setup, simply edit/uncomment those lines.


# User authentication

Using Keycloak to enable user authentication

Let's setup Keycloak to enable users to create account and login to our Onyxia.

{% hint style="success" %}
Note that in this instalation guide we make you use Keycloak but you can use any identity server that is Open ID Connect compliant.
{% endhint %}

### Deploying Keycloak

We're going to install Keycloak just like we installed Onyxia. &#x20;

Before anything open [`apps/keycloak/values.yaml`](https://github.com/InseeFrLab/onyxia-ops/blob/main/apps/keycloak/values.yaml) in your onyxia-ops repo and [change the passwords](#user-content-fn-1)[^1]. Also write down the [`keycloak.auth.adminPassword`](https://github.com/InseeFrLab/onyxia-ops/blob/bad75636d72c20c48f1b34ec08593df83ee6c9a6/apps/keycloak/values.yaml#L11), you'll need it to connect to the Keycloak console. &#x20;

{% embed url="<https://app.tango.us/app/embed/dbb21e90-db2c-41f4-b2ab-5f8b9f4d33c0>" %}

{% hint style="info" %}
Try to remember, when you [update Onyxia in `apps/onyxia/Chart.yaml`](https://github.com/InseeFrLab/onyxia-ops/blob/bad75636d72c20c48f1b34ec08593df83ee6c9a6/apps/onyxia/Chart.yaml#L6) to also update [the Onyxia theme in `apps/keycloak/values.yaml`](https://github.com/InseeFrLab/onyxia-ops/blob/bad75636d72c20c48f1b34ec08593df83ee6c9a6/apps/keycloak/values.yaml#L69).
{% endhint %}

### Configuring Keycloak

You can now login to the **administration console** of **<https://auth.lab.my-domain.net/auth/>** and login using username: keycloak and password: \<the one you've wrote down earlier>.

1. Create a realm called "datalab" (or something else), go to **Realm settings**
   1. On the tab General
      1. *User Profile Enabled*: **On**
   2. On the tab **login**
      1. *User registration*: **On**
      2. *Forgot password*: **On**
      3. *Remember me*: **On**
   3. On the tab **email,** we give an example with [AWS SES](https://aws.amazon.com/ses/), if you don't have a SMTP server at hand you can skip this by going to **Authentication** (on the left panel) -> Tab **Required Actions** -> Uncheck "set as default action" **Verify Email**. Be aware that with email verification disable, anyone will be able to sign up to your service.
      1. *From*: **<noreply@lab.my-domain.net>**
      2. *Host*: **email-smtp.us-east-2.amazonaws.com**
      3. *Port*: **465**
      4. *Authentication*: **enabled**
      5. *Username*: **\*\*\*\*\*\*\*\*\*\*\*\*\*\***
      6. *Password*: **\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\***
      7. When clicking "save" you'll be asked for a test email, you have to provide one that correspond **to a pre-existing user** or you will get a silent error and the credentials won't be saved.
   4. On the tab **Themes**
      1. *Login theme*: **onyxia-web** (you can also select the login theme on a per client basis)
      2. *Email theme*: **onyxia-web**
   5. On the tab **Localization**
      1. *Internationalization*: **Enabled**
      2. *Supported locales*: \<Select the languages you wish to support>
   6. On the tab **Session**.
      1. SSO Session Idle: [14 days](#user-content-fn-2)[^2]
      2. SSO Session Max: [14 days](#user-content-fn-3)[^3]
      3. SSO Session Idle Remember Me: [14 days](#user-content-fn-4)[^4]
      4. SSO Session Max Remember Me: 14 days
2. Create a client with client ID "onyxia"
   1. *Root URL*: **<https://datalab.my-domain.net/>**
   2. *Valid redirect URIs*: **<https://datalab.my-domain.net/\\>**\*
   3. *Web origins*: **\***
   4. Login theme: **onyxia-web**
3. In **Authentication** (on the left panel) -> Tab **Required Actions** enable and set as default action **Therms and Conditions.**

Now you want to ensure that the username chosen by your users complies with Onyxia requirement (only alphanumerical characters) and define a list of email domain allowed to register to your service.

Go to **Realm Settings** (on the left panel) -> Tab **User Profile** (this tab shows up only if User Profile is enabled in the General tab and you can enable user profile only if you have started Keycloak with `-Dkeycloak.profile=preview)` -> **JSON Editor**.

Now you can edit the file as suggested in the following DIFF snippet. Be mindful that in this example we only allow emails @gmail.com and @hotmail.com to register you want to edit that.

```diff
{
  "attributes": [
    {
      "name": "username",
      "displayName": "${username}",
      "validations": {
        "length": {
          "min": 3,
          "max": 255
        },
+       "pattern": {
+         "error-message": "${lowerCaseAlphanumericalCharsOnly}",
+         "pattern": "^[a-z0-9]*$"
+       },
        "username-prohibited-characters": {}
      }
    },
    {
      "name": "email",
      "displayName": "${email}",
      "validations": {
        "email": {},
+       "pattern": {
+         "pattern": "^[^@]+@([^.]+\\.)*((gmail\\.com)|(hotmail\\.com))$"
+       },
        "length": {
          "max": 255
        }
      }
    },
...
```

Now our Keycloak server is fully configured we just need to update our Onyxia deployment to let it know about it.

### Updating the Onyxia configuration

In your GitOps repo you now want to update your onyxia configuration. &#x20;

```bash
git clone https://github.com/<your-github-org>/onyxia-ops
cd onyxia-ops
cd apps/onyxia
mv values-keycloak-enabled.yaml values.yaml
git commit -am "Enable keycloak"
git push
```

Here is the DIFF of the onyxia configuration: &#x20;

{% embed url="<https://github.com/InseeFrLab/onyxia-ops/commit/37faa6390c9bc8c1efddfd3488dc06b38427b424>" %}

Now your users should be able to create account, log-in, and start services on their own Kubernetes namespace.

<figure><img src="/files/2AbvJ525GvINKyT3n4sI" alt=""><figcaption><p>The screen you shoud see when clicking on "login" in your Onyxia deployment</p></figcaption></figure>

Next step in the installation proccess it to enable all the S3 related features of Onyxia: &#x20;

{% content-ref url="/pages/kfcoWtB9lBYzxSShcXQf" %}
[Data (S3)](/docs.onyxia.sh/v9/admin-doc/readme/data-s3)
{% endcontent-ref %}

[^1]: Search/replace CHANGEME

[^2]: Here, the approach depends on the security policy you wish to implement. If you prefer requiring users to log in again each time they navigate to your Onyxia instance, consider setting a shorter duration, such as 30 minutes. Be aware that setting it to 30 minutes means users will be automatically logged out if they remain inactive within the app for this period.  \
    [https://github.com/InseeFrLab/onyxia/assets/6702424/343f74e1-1f08-43e3-8a1d-ce92f8dedc2c<br>](<https://github.com/InseeFrLab/onyxia/assets/6702424/343f74e1-1f08-43e3-8a1d-ce92f8dedc2c&#xA;>)

[^3]: You'll likely want to set this value high. It determines the maximum duration for a continuously active user session. If a user is logged in and actively using the app, there's no need to disconnect them. &#x20;

[^4]: Modify this setting if you wish to apply a different policy for users logging in with the "remember me" option selected. In "remember me" mode, users can close their browser completely and will not need to log in again on their next visit, provided the session has not expired.  \
    <https://github.com/InseeFrLab/onyxia/assets/6702424/93b139cf-b0e7-4e4b-9811-bf9a9deaf144>


# Data (S3)

Enable S3 storage via MinIO S3

Onyxia uses [AWS Security Token Service API](https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html) to obtain S3 tokens on behalf of your users. We support any S3 storage compatible with this API. In this context, we are using [MinIO](https://min.io/), which is compatible with the Amazon S3 storage service and we demonstrate how to integrate it with Keycloak.

### Creating the 'minio' Keycloak client

Before configuring MinIO, let's create a new Keycloak client (from the previous existing "datalab" realm).

{% embed url="<https://app.tango.us/app/embed/1c5c0975-93f0-48c6-b8d9-edceb397e34c>" %}

### Deploying MinIO

Before deploying MinIO on the cluster let's set, in the MinIO configuration file, the OIDC client secret we have copied in the previous step. &#x20;

```bash
git clone https://github.com/<your-github-org>/onyxia-ops
cd onyxia-ops
cd apps/minio
# In the values.yaml file replace `$KEYCLOAK_MINIO_CLIENT_SECRET` by the value
# you have copied in the previous step.
git commit -am "Set minio OIDC client secret"
git push
```

Once you've done that you can deploy MinIO! &#x20;

{% embed url="<https://app.tango.us/app/embed/75b62573-7adc-4a38-b1f9-b96bb0ea50fd>" %}

### Creating the 'onyxia-minio' Keycloak client

Before configuring the onyxia region to create tokens we should go back to Keycloak and create a new client to enable onyxia-web to request token for MinIO. This client is a little bit more complex than other if you want to manage durations (here 7 days) and this client should have a claim name policy and with a value of stsonly according to our last deployment of MinIO.

{% embed url="<https://app.tango.us/app/embed/2e382be2-5d73-4cc8-8682-1b86b0e1de58>" %}

### Updating the Onyxia configuration

Now let's update our Onyxia configuration to let it know that there is now a S3 server available on the cluster. &#x20;

```bash
git clone https://github.com/<your-github-org>/onyxia-ops
cd onyxia-ops
cd apps/onyxia
mv values-minio-enabled.yaml.yaml values.yaml
git commit -am "Enable MinIO"
git push
```

Diff of the changes applied to the Onyxia configuration: &#x20;

{% embed url="<https://github.com/InseeFrLab/onyxia-ops/commit/e8e5d57743d9954f60213346e33b55d2de41f707>" %}

Congratulation, all the S3 related features of Onyxia are now enabled in your instance! Now if you navigate to your Onyxia instance you should have `My Files` in the left menu. &#x20;

<figure><img src="/files/J1PmXK6zCfAliaFSvxiK" alt=""><figcaption></figcaption></figure>

Next step in the installation process is to setup Vault to provide a way to your user so store secret and also to provide something that Onyxia can use as a persistance layer for user configurations.

{% content-ref url="/pages/CbrCbAREDFVdcCBpTHdp" %}
[Vault](/docs.onyxia.sh/v9/admin-doc/readme/...rest)
{% endcontent-ref %}


# Vault

Let's use hashicorp Vault for storing the user secrets.

{% hint style="info" %}
Vault is also used by Onyxia as the persistance layer for all saved configuration. If you don't have a vault all user settings are stored in the local storage.
{% endhint %}

Onyxia-web use vault as a storage for two kinds of secrets :\
1\. secrets or information generate by Onyxia to store differents values (ui preferences for example)\
2\. user secrets\
\
Vault must be configured with JWT or OIDC authentification methods.

As vault needs to be initialized with a master key, it can't be directly configured with all parameters such as oidc or access policies and roles. So first step we create a vault with dev mode (do not use this in production and do your initialization with any of the recommanded configuration : shamir, gcp, another vault)

```bash
helm repo add hashicorp https://helm.releases.hashicorp.com
 
DOMAIN=my-domain.net

cat << EOF > ./vault-values.yaml
server:
  dev:
    enabled: true
    # Set VAULT_DEV_ROOT_TOKEN_ID value
    devRootToken: "root"
  ingress:
    enabled: true
    annotations:
      kubernetes.io/ingress.class: nginx
    hosts:
      - host: "vault.lab.$DOMAIN"
    tls:
      - hosts:
          - vault.lab.$DOMAIN
EOF

helm install vault hashicorp/vault -f vault-values.yaml
```

Create a client called "vault"

1. *Root URL*: **<https://vault.lab.my-domain.net/>**
2. *Valid redirect URIs*: **<https://vault.lab.my-domain.net/\\>**\*
3. *Web origins*: **\***

TODO; [Refer to the legacy documentation.](https://github.com/InseeFrLab/legacy-onyxia-entrypoint/tree/main/step-by-step#deploy-vault)


# Theme and branding

Customize your Onyxia instance with your assets and your colors, make it your own!

{% embed url="<https://youtu.be/NrVuVXsbloA>" %}

The full documentation of the available parameter can be found here:

{% embed url="<https://github.com/InseeFrLab/onyxia/blob/main/web/.env>" %}

Note that your custom assets are imported into your Onyxia instance via the use of the `CUSTOM_RESOURCES` parameter, url of a ZIP archive that should contain your assets. An example is given at the top of the [`.env`](https://github.com/InseeFrLab/onyxia/blob/main/web/.env) file.

{% hint style="info" %}
Onyxia is configured to make the the browser cache assets so they are not re-downloaded each time the user access the app.

If you update some of your asset but keep the same URL, you can force the browser of your users to download the new version by adding a query parameter to the URL. Eample:

`HEADER_LOGO: "%PUBLIC_URL%/custom-resources/logo.svg?v=2"`
{% endhint %}

Make sure to checkout the version of this document that matches the Onyxia version that you are deploying. [See releases](https://github.com/InseeFrLab/onyxia/releases).

## Default looks

Here are two base look that you can use a starting point of your configuration.

### France

👉 [Theme preview](https://datalab.sspcloud.fr/?FONT=%7B%20%0A%20%20fontFamily%3A%20%22Marianne%22%2C%20%0A%20%20dirUrl%3A%20%22%25PUBLIC_URL%25%2Ffonts%2FMarianne%22%2C%20%0A%20%20%22400%22%3A%20%22Marianne-Regular.woff2%22%2C%0A%20%20%22400-italic%22%3A%20%22Marianne-Regular_Italic.woff2%22%2C%0A%20%20%22500%22%3A%20%22Marianne-Medium.woff2%22%2C%0A%20%20%22700%22%3A%20%22Marianne-Bold.woff2%22%2C%0A%20%20%22700-italic%22%3A%20%22Marianne-Bold_Italic.woff2%22%0A%7D%0A\&PALETTE_OVERRIDE=%7B%0A%20%20focus%3A%20%7B%0A%20%20%20%20main%3A%20%22%23000091%22%2C%0A%20%20%20%20light%3A%20%22%239A9AFF%22%2C%0A%20%20%20%20light2%3A%20%22%23E5E5F4%22%0A%20%20%7D%2C%0A%20%20dark%3A%20%7B%0A%20%20%20%20main%3A%20%22%232A2A2A%22%2C%0A%20%20%20%20light%3A%20%22%23383838%22%2C%0A%20%20%20%20greyVariant1%3A%20%22%23161616%22%2C%0A%20%20%20%20greyVariant2%3A%20%22%239C9C9C%22%2C%0A%20%20%20%20greyVariant3%3A%20%22%23CECECE%22%2C%0A%20%20%20%20greyVariant4%3A%20%22%23E5E5E5%22%0A%20%20%7D%2C%0A%20%20light%3A%20%7B%0A%20%20%20%20main%3A%20%22%23F1F0EB%22%2C%0A%20%20%20%20light%3A%20%22%23FDFDFC%22%2C%0A%20%20%20%20greyVariant1%3A%20%22%23E6E6E6%22%2C%0A%20%20%20%20greyVariant2%3A%20%22%23C9C9C9%22%2C%0A%20%20%20%20greyVariant3%3A%20%22%239E9E9E%22%2C%0A%20%20%20%20greyVariant4%3A%20%22%23747474%22%0A%20%20%7D%0A%7D%0A)

```yaml
  web:
    env:
      FONT: |
        { 
          fontFamily: "Marianne", 
          dirUrl: "%PUBLIC_URL%/fonts/Marianne", 
          "400": "Marianne-Regular.woff2",
          "400-italic": "Marianne-Regular_Italic.woff2",
          "500": "Marianne-Medium.woff2",
          "700": "Marianne-Bold.woff2",
          "700-italic": "Marianne-Bold_Italic.woff2"
        }
      PALETTE_OVERRIDE: |
        {
          focus: {
            main: "#000091",
            light: "#9A9AFF",
            light2: "#E5E5F4"
          },
          dark: {
            main: "#2A2A2A",
            light: "#383838",
            greyVariant1: "#161616",
            greyVariant2: "#9C9C9C",
            greyVariant3: "#CECECE",
            greyVariant4: "#E5E5E5"
          },
          light: {
            main: "#F1F0EB",
            light: "#FDFDFC",
            greyVariant1: "#E6E6E6",
            greyVariant2: "#C9C9C9",
            greyVariant3: "#9E9E9E",
            greyVariant4: "#747474"
          }
        }
      SOCIAL_MEDIA_IMAGE: "%PUBLIC_URL%/preview-france.png"
      HOMEPAGE_MAIN_ASSET: "false"
```

### Ultraviolet

👉 [Theme preview](https://datalab.sspcloud.fr/?FONT=%7B%20%0A%20%20fontFamily%3A%20%22Geist%22%2C%20%0A%20%20dirUrl%3A%20%22%25PUBLIC_URL%25%2Ffonts%2FGeist%22%2C%20%0A%20%20%22400%22%3A%20%22Geist-Regular.woff2%22%2C%0A%20%20%22500%22%3A%20%22Geist-Medium.woff2%22%2C%0A%20%20%22600%22%3A%20%22Geist-SemiBold.woff2%22%2C%0A%20%20%22700%22%3A%20%22Geist-Bold.woff2%22%0A%7D%0A\&PALETTE_OVERRIDE=%7B%0A%20%20focus%3A%20%7B%0A%20%20%20%20main%3A%20%22%23067A76%22%2C%0A%20%20%20%20light%3A%20%22%230AD6CF%22%2C%0A%20%20%20%20light2%3A%20%22%23AEE4E3%22%0A%20%20%7D%2C%0A%20%20dark%3A%20%7B%0A%20%20%20%20main%3A%20%22%232D1C3A%22%2C%0A%20%20%20%20light%3A%20%22%234A3957%22%2C%0A%20%20%20%20greyVariant1%3A%20%22%2322122E%22%2C%0A%20%20%20%20greyVariant2%3A%20%22%23493E51%22%2C%0A%20%20%20%20greyVariant3%3A%20%22%23918A98%22%2C%0A%20%20%20%20greyVariant4%3A%20%22%23C0B8C6%22%0A%20%20%7D%2C%0A%20%20light%3A%20%7B%0A%20%20%20%20main%3A%20%22%23F7F5F4%22%2C%0A%20%20%20%20light%3A%20%22%23FDFDFC%22%2C%0A%20%20%20%20greyVariant1%3A%20%22%23E6E6E6%22%2C%0A%20%20%20%20greyVariant2%3A%20%22%23C9C9C9%22%2C%0A%20%20%20%20greyVariant3%3A%20%22%239E9E9E%22%2C%0A%20%20%20%20greyVariant4%3A%20%22%23747474%22%0A%20%20%7D%0A%7D%0A)

```yaml
web:
    env:
      FONT: |
        { 
          fontFamily: "Geist", 
          dirUrl: "%PUBLIC_URL%/fonts/Geist", 
          "400": "Geist-Regular.woff2",
          "500": "Geist-Medium.woff2",
          "600": "Geist-SemiBold.woff2",
          "700": "Geist-Bold.woff2"
        }
      PALETTE_OVERRIDE: |
        {
          focus: {
            main: "#067A76",
            light: "#0AD6CF",
            light2: "#AEE4E3"
          },
          dark: {
            main: "#2D1C3A",
            light: "#4A3957",
            greyVariant1: "#22122E",
            greyVariant2: "#493E51",
            greyVariant3: "#918A98",
            greyVariant4: "#C0B8C6"
          },
          light: {
            main: "#F7F5F4",
            light: "#FDFDFC",
            greyVariant1: "#E6E6E6",
            greyVariant2: "#C9C9C9",
            greyVariant3: "#9E9E9E",
            greyVariant4: "#747474"
          }
        }
      SOCIAL_MEDIA_IMAGE: "%PUBLIC_URL%/preview-ultraviolet.png"
```


# Catalog of services

Unserstand how Onyxia catalogs work and potentially create your own!

Every Onyxia instance may or may not have it's own catalog. There are four default catalogs :

{% embed url="<https://github.com/inseefrlab/helm-charts-interactive-services>" %}

This collection of charts helps users to launch many IDE with various binary stacks (python , R) with or without GPU support. Docker images are built [here](https://github.com/inseefrlab/images-datascience) and help us to give a homogeneous stack.

{% embed url="<https://github.com/inseefrlab/helm-charts-databases>" %}

This collection of charts helps users to launch many databases system. Most of them are based on [bitnami/charts](https://github.com/bitnami/charts).

{% embed url="<https://github.com/InseeFrLab/helm-charts-automation/>" %}

This collection of charts helps users to start automation tools for their datascience activity.

{% embed url="<https://github.com/InseeFrLab/helm-charts-datavisualization>" %}

This collection of charts helps users to launch tools to visualize and share data insights.

You can always find the source of the catalog by clicking on the "contribute to the... " link.<br>

<figure><img src="/files/rFXiw4CAMUygKOhNxFLH" alt=""><figcaption><p><a href="https://datalab.sspcloud.fr/catalog">https://datalab.sspcloud.fr/catalog</a></p></figcaption></figure>

If you take [this other instance](https://onyxia-sill.lab.sspcloud.fr), it has only one catalog, [helm-charts-sill](https://github.com/etalab/helm-charts-sill).

![https://sill-demo.etalab.gouv.fr/catalog](/files/jNpg9ZcSEm0e4clLnfao)

## Using your own catalogs (helm charts repositories)

If you do not specify catalogs in your `onyxia/values.yaml,` these are the ones that are used by default: [See file](https://github.com/InseeFrLab/onyxia-api/blob/main/onyxia-api/src/main/resources/catalogs.json).

To configure your onyxia instance to use your own custom helm repositories as onyxia catalogs you need to use the onyxia configuration `onyxia.api.catalogs`.  \
Let's say we're NASA and we want to have an "*Areospace services"* catalog on our onyxia instance. Our onyxia configuration would look a bit like this: &#x20;

{% code title="onyxia/values.yaml" %}

```yaml
onyxia:
  web:
    # ...
  api:
    # ...
    catalogs: [
      {
        type: "helm",
        id: "aerospace",
        # The url of the Helm chart repository
        location: "https://myorg.github.io/helm-charts-aerospace/",
        # Display under the search bar as selection tab:
        # https://github.com/InseeFrLab/onyxia/assets/6702424/a7247c7d-b0be-48db-893b-20c9352fdb94
        name: { 
          en: "Aerospace services",
          fr: "Services aérospatiaux"
          # ... other languages your instance supports
        },
        # Optional. Defines the chart that should appear first
        highlightedCharts: ["jupyter-artemis", "rstudio-dragonfly"],
        # Optional. Defines the chart that should be excluded
        excludedCharts: ["a-vendor-locking-chart"],
        # Optional, If defined, displayed in the header of the catalog page:
        # https://github.com/InseeFrLab/onyxia/assets/6702424/57e32f44-b889-41b2-b0c7-727c35b07650
        # Is rendered as Markdown
        description: { 
          en: "A catalog of services for aerospace engineers",
          fr: "Un catalogue de services pour les ingénieurs aérospatiaux"
          # ...
        },
        # Can be "PROD" or "TEST". If test the catalogs will be accessible if you type the url in the search bar
        # but you won't have a tab to select it.
        status": "PROD",
        # Optional. If true the certificate verification for `${location}/index.yaml` will be skipped.
        skipTlsVerify: false,
        # Optional. certificate authority file to use for the TLS verification
        caFile: "/path/to/ca.crt",
        # Optional: Enables you to a specific group of users.
        # You can match any claim in the JWT token.  
        # If the claim's value is an array, it match if one of the value is the one you specified.
        # The match property can also be a regex.
        restrictions: [
          {
            userAttribute: {
              key: "groups",
              matches: "nasa-engineers"
            }
          }
        ]
      },
       # { ... } another catalog
    ]
```

{% endcode %}

## Customizing your helm charts for Onyxia

In Onyxia we use the `values.schema.json` file to know what options should be displayed to the user at [the service configuration step](https://user-images.githubusercontent.com/6702424/177571819-f2e1b4ef-ecd1-479b-a5a1-658d87d7c7c0.png) and what default value Onyxia should inject.

![https://helm.sh/docs/topics/charts/#the-chart-file-structure](/files/JxB9FxYDIbxW0n7Jmjnk)

### \[x-onyxia] overwriteDefaultWith

Let's consider a sample of the `values.schema.json` of the InseeFrLab/helm-charts-interactive-services' Jupyter chart:

<pre class="language-javascript" data-title="values.schema.json"><code class="lang-javascript">"git": {
    "description": "Git user configuration",
    "type": "object",
    "properties": {
        "enabled": {
            "type": "boolean",
            "description": "Add git config inside your environment",
            "default": true
        },
        "name": {
            "type": "string",
            "description": "user name for git",
            "default": "",
<strong>            "x-onyxia": {
</strong><strong>                "overwriteDefaultWith": "git.name"
</strong><strong>            },
</strong>            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "email": {
            "type": "string",
            "description": "user email for git",
            "default": "",
<strong>            "x-onyxia": {
</strong><strong>                "overwriteDefaultWith": "git.email"
</strong><strong>            },
</strong>            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "cache": {
            "type": "string",
            "description": "duration in seconds of the credentials cache duration",
            "default": "",
<strong>            "x-onyxia": {
</strong><strong>                "overwriteDefaultWith": "git.credentials_cache_duration"
</strong><strong>            },
</strong>            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "token": {
            "type": "string",
            "description": "personal access token",
            "default": "",
<strong>            "x-onyxia": {
</strong><strong>                "overwriteDefaultWith": "git.token"
</strong><strong>            },
</strong>            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "repository": {
            "type": "string",
            "description": "Repository url",
            "default": "",
            "hidden": {
                "value": false,
                "path": "git/enabled"
            }
        },
        "branch": {
            "type": "string",
            "description": "Brach automatically checkout",
            "default": "",
            "hidden": {
                "value": "",
                "path": "git/repository"
            }
        }
    }
},
</code></pre>

And it translates into this:

{% embed url="<https://user-images.githubusercontent.com/6702424/177571819-f2e1b4ef-ecd1-479b-a5a1-658d87d7c7c0.png>" %}

Note the `"git.name"`, `"git.email"` and `"git.token"`, this enables [onyxia-web](https://github.com/InseeFrLab/onyxia-web) to pre fill the fields.

If the user took the time to fill its profile information, [onyxia-web](https://github.com/InseeFrLab/onyxia-web) knows what is the Git **username**, **email** and **personal access token** of the user.

![The onyxia user profile](/files/WA8zHt4hYRSdn3zcqJHz)

[Here](https://github.com/InseeFrLab/onyxia/blob/main/web/src/core/ports/OnyxiaApi/XOnyxia.ts) is defined the structure of the context that you can use in the `overwriteDefaultWith` field:

```typescript
export type XOnyxiaParams = {
    /**
     * This is where you can reference values from the onyxia context so that they
     * are dynamically injected by the Onyxia launcher.
     *
     * Examples:
     * "overwriteDefaultWith": "user.email" ( You can also write "{{user.email}}" it's equivalent )
     * "overwriteDefaultWith": "{{project.id}}-{{k8s.randomSubdomain}}.{{k8s.domain}}"
     * "overwriteDefaultWith": [ "a hardcoded value", "some other hardcoded value", "{{region.oauth2.clientId}}" ]
     * "overwriteDefaultWith": { "foo": "bar", "bar": "{{region.oauth2.clientId}}" }
     *
     */
    overwriteDefaultWith?:
        | string
        | number
        | boolean
        | unknown[]
        | Record<string, unknown>;
    overwriteListEnumWith?: unknown[] | string;
    hidden?: boolean;
    readonly?: boolean;
    useRegionSliderConfig?: string;
};

export type XOnyxiaContext = {
    user: {
        idep: string;
        name: string;
        email: string;
        password: string;
        ip: string;
        darkMode: boolean;
        lang: "en" | "fr" | "zh-CN" | "no" | "fi" | "nl" | "it" | "es" | "de";
        /**
         * Decoded JWT OIDC ID token of the user launching the service.
         *
         * Sample value:
         * {
         *   "sub": "9000ffa3-5fb8-45b5-88e4-e2e869ba3cfa",
         *   "name": "Joseph Garrone",
         *   "aud": ["onyxia", "minio-datanode"],
         *   "groups": [
         *       "USER_ONYXIA",
         *       "codegouv",
         *       "onyxia",
         *       "sspcloud-admin",
         *   ],
         *   "preferred_username": "jgarrone",
         *   "given_name": "Joseph",
         *   "locale": "en",
         *   "family_name": "Garrone",
         *   "email": "joseph.garrone@insee.fr",
         *   "policy": "stsonly",
         *   "typ": "ID",
         *   "azp": "onyxia",
         *   "email_verified": true,
         *   "realm_access": {
         *       "roles": ["offline_access", "uma_authorization", "default-roles-sspcloud"]
         *   }
         * }
         */
        decodedIdToken: Record<string, unknown>;
        accessToken: string;
        refreshToken: string;
    };
    service: {
        oneTimePassword: string;
    };
    project: {
        id: string;
        password: string;
        basic: string;
    };
    git: {
        name: string;
        email: string;
        credentials_cache_duration: number;
        token: string | undefined;
    };
    vault: {
        VAULT_ADDR: string;
        VAULT_TOKEN: string;
        VAULT_MOUNT: string;
        VAULT_TOP_DIR: string;
    };
    s3: {
        AWS_ACCESS_KEY_ID: string;
        AWS_SECRET_ACCESS_KEY: string;
        AWS_SESSION_TOKEN: string;
        AWS_DEFAULT_REGION: string;
        AWS_S3_ENDPOINT: string;
        AWS_BUCKET_NAME: string;
        port: number;
        pathStyleAccess: boolean;
        /**
         * The user is assumed to have read/write access on every
         * object starting with this prefix on the bucket
         **/
        objectNamePrefix: string;
        /**
         * Only for making it easier for charts editors.
         * <AWS_BUCKET_NAME>/<objectNamePrefix>
         * */
        workingDirectoryPath: string;
        /**
         * If true the bucket's (directory) should be accessible without any credentials.
         * In this case s3.AWS_ACCESS_KEY_ID, s3.AWS_SECRET_ACCESS_KEY and s3.AWS_SESSION_TOKEN
         * will be empty strings.
         */
        isAnonymous: boolean;
    };
    region: {
        defaultIpProtection: boolean | undefined;
        defaultNetworkPolicy: boolean | undefined;
        allowedURIPattern: string;
        customValues: Record<string, unknown> | undefined;
        kafka:
            | {
                  url: string;
                  topicName: string;
              }
            | undefined;
        tolerations: unknown[] | undefined;
        from: unknown[] | undefined;
        nodeSelector: Record<string, unknown> | undefined;
        startupProbe: Record<string, unknown> | undefined;
        sliders: Record<
            string,
            {
                sliderMin: number;
                sliderMax: number;
                sliderStep: number;
                sliderUnit: string;
            }
        >;
        resources:
            | {
                  cpuRequest?: `${number}${string}`;
                  cpuLimit?: `${number}${string}`;
                  memoryRequest?: `${number}${string}`;
                  memoryLimit?: `${number}${string}`;
                  disk?: `${number}${string}`;
                  gpu?: `${number}`;
              }
            | undefined;
    };
    k8s: {
        domain: string;
        ingressClassName: string | undefined;
        ingress: boolean | undefined;
        route: boolean | undefined;
        istio:
            | {
                  enabled: boolean;
                  gateways: string[];
              }
            | undefined;
        randomSubdomain: string;
        initScriptUrl: string;
        useCertManager: boolean;
        certManagerClusterIssuer: string | undefined;
    };
    proxyInjection:
        | {
              enabled: string | undefined;
              httpProxyUrl: string | undefined;
              httpsProxyUrl: string | undefined;
              noProxy: string | undefined;
          }
        | undefined;
    packageRepositoryInjection:
        | {
              cranProxyUrl: string | undefined;
              condaProxyUrl: string | undefined;
              packageManagerUrl: string | undefined;
              pypiProxyUrl: string | undefined;
          }
        | undefined;
    certificateAuthorityInjection:
        | {
              cacerts: string | undefined;
              pathToCaBundle: string | undefined;
          }
        | undefined;
};
```

You can also concatenate string values using by wrapping the XOnyxia targeted values in `{{}}`.

{% code title="values.shema.json" %}

```json
"hostname": {
  "type": "string",
  "form": true,
  "title": "Hostname",
  "x-onyxia": {
    "overwriteDefaultWith": "{{project.id}}-{{k8s.randomSubdomain}}.{{k8s.domain}}"
  }
}
```

{% endcode %}

### \[x-onyxia] overwriteListEnumWith

This is an option for customizing the options of the forms fields rendered as select.

<figure><img src="/files/g8XWP9Tow1N5qUWkisLb" alt="" width="375"><figcaption><p>Example of select form field in the onyxia launcher</p></figcaption></figure>

In your values shema such a field would be defined like:

{% code title="values.shema.json" %}

```json
"pullPolicy": {
    "type": "string",
    "default": "IfNotPresent",
    "listEnum": [
        "IfNotPresent",
        "Always",
        "Never"
    ]
}
```

{% endcode %}

But what if you want to dynamically generate the option? For this you can use the overwriteListEnumWith x-onyxia option.  \
For example if you need to let the user select one of the groups he belongs to you can write: &#x20;

<pre class="language-json" data-title="values.schema.json"><code class="lang-json">"group": {
  "type": "string",
<strong>  "default": "",
</strong><strong>  "listEnum": [""],
</strong>  "x-onyxia": {
<strong>    "overwriteDefaultWith": "user.decodedIdToken.groups[0]",
</strong><strong>    "overwriteListEnumWith": "user.decodedIdToken.groups"
</strong>  }
}
</code></pre>

### \[x-onyxia] overwriteSchemaWith

Certain elements of a Helm chart should be customized for each instance of Onyxia, such as resource requests and limits, node selectors and tolerations. For this purpose, chart developers can use `x-onyxia.overwriteSchemaWith` to allow administrators to override specific parts of the schema. Our default charts use this specification.

{% code title="values.shema.json" %}

```json
"nodeSelector": {
    "type": "object",
    "description": "NodeSelector",
    "default": {},
    "x-onyxia": {
        "overwriteSchemaWith": "nodeSelector.json"
    }
}
```

{% endcode %}

You can see [here](https://github.com/InseeFrLab/onyxia-api/tree/main/onyxia-api/src/main/resources/schemas) the list of default schemas included in the Onyxia API. We also provide examples demonstrating how you [can customize your services using our interactive services charts with the provided schemas](https://github.com/InseeFrLab/helm-charts-interactive-services/).

The following node selector schema provided by Onyxia API is a generic definition, which may not provide the best experience for a specific Kubernetes cluster in Onyxia.

{% code title="nodeSelector.json" %}

```json
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Node Selector",
    "type": "object",
    "description": "Node selector constraints for the pod",
    "additionalProperties": {
      "type": "string",
      "description": "Key-value pairs to select nodes"
    }
}
```

{% endcode %}

As an administrator of Onyxia, you can provide your own schemas to refine and restrict the initial schemas provided in the Helm chart.

#### node selectors

You can provide this schema to allow your users to choose between SSD or HDD disk types, and A2 or H100 NVIDIA GPUs. Any other values or labels are disallowed, and Onyxia will reject starting a service that does not comply with the provided schema.

{% code title="nodeSelector.json" %}

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Node Selector",
  "type": "object",
  "properties": {
    "disktype": {
      "description": "The type of disk",
      "type": "string",
      "enum": ["ssd", "hdd"]
    },
    "gpu": {
      "description": "The type of GPU",
      "type": "string",
      "enum": ["A2", "H100"]
    }
  },
  "additionalProperties": false //any other label is disallowed
}
```

{% endcode %}

#### rolebindings for IDE pods

This is the default role for IDE pods in our charts. It is very permissive, and you may want to restrict it to view-only access.

{% code title="ide/role.json" %}

```json
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Role",
    "type": "object",
    "properties": {
        "enabled": {
            "type": "boolean",
            "description": "allow your service to access your namespace ressources",
            "default": true
        },
        "role": {
            "type": "string",
            "description": "bind your service account to this kubernetes default role",
            "default": "view",
            "enum": [
                "view",
                "edit",
                "admin"
            ]
        }
    }
}

```

{% endcode %}

Here is the refined version

{% code title="ide/role.json" %}

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Role",
  "type": "object",
  "properties": {
    "enabled": {
      "type": "boolean",
      "const": true,
      "description": "This value must always be true, allowing your service to access your namespace resources."
    },
    "role": {
      "type": "string",
      "const": "view",
      "description": "This value must always be 'view', binding your service account to this Kubernetes default role.",
    }
  }
}

```

{% endcode %}

#### resources for IDE

You may want to modify the slide bar for resources

{% code title="ide/resources.json" %}

```json
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Resources",
    "description": "Your service will have at least the requested resources and never more than its limits. No limit for a resource and you can consume everything left on the host machine.",
    "type": "object",
    "properties": {
        "requests": {
            "description": "Guaranteed resources",
            "type": "object",
            "properties": {
                "cpu": {
                    "description": "The amount of cpu guaranteed",
                    "title": "CPU",
                    "type": "string",
                    "default": "100m",
                    "render": "slider",
                    "sliderMin": 50,
                    "sliderMax": 40000,
                    "sliderStep": 50,
                    "sliderUnit": "m",
                    "sliderExtremity": "down",
                    "sliderExtremitySemantic": "guaranteed",
                    "sliderRangeId": "cpu"
                },
                "memory": {
                    "description": "The amount of memory guaranteed",
                    "title": "memory",
                    "type": "string",
                    "default": "2Gi",
                    "render": "slider",
                    "sliderMin": 1,
                    "sliderMax": 200,
                    "sliderStep": 1,
                    "sliderUnit": "Gi",
                    "sliderExtremity": "down",
                    "sliderExtremitySemantic": "guaranteed",
                    "sliderRangeId": "memory"
                }
            }
        },
        "limits": {
            "description": "max resources",
            "type": "object",
            "properties": {
                "cpu": {
                    "description": "The maximum amount of cpu",
                    "title": "CPU",
                    "type": "string",
                    "default": "30000m",
                    "render": "slider",
                    "sliderMin": 50,
                    "sliderMax": 40000,
                    "sliderStep": 50,
                    "sliderUnit": "m",
                    "sliderExtremity": "up",
                    "sliderExtremitySemantic": "Maximum",
                    "sliderRangeId": "cpu"
                },
                "memory": {
                    "description": "The maximum amount of memory",
                    "title": "Memory",
                    "type": "string",
                    "default": "50Gi",
                    "render": "slider",
                    "sliderMin": 1,
                    "sliderMax": 200,
                    "sliderStep": 1,
                    "sliderUnit": "Gi",
                    "sliderExtremity": "up",
                    "sliderExtremitySemantic": "Maximum",
                    "sliderRangeId": "memory"
                }
            }
        }
    }
}

```

{% endcode %}

<pre class="language-json" data-title="ide/resources.json"><code class="lang-json"><strong>{
</strong>    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Resources",
    "description": "Your service will have at least the requested resources and never more than its limits. No limit for a resource and you can consume everything left on the host machine.",
    "type": "object",
    "properties": {
        "requests": {
            "description": "Guaranteed resources",
            "type": "object",
            "properties": {
                "cpu": {
                    "description": "The amount of cpu guaranteed",
                    "title": "CPU",
                    "type": "string",
                    "default": "100m",
                    "render": "slider",
                    "sliderMin": 50,
                    "sliderMax": 10000,
                    "sliderStep": 50,
                    "sliderUnit": "m",
                    "sliderExtremity": "down",
                    "sliderExtremitySemantic": "guaranteed",
                    "sliderRangeId": "cpu"
                },
                "memory": {
                    "description": "The amount of memory guaranteed",
                    "title": "memory",
                    "type": "string",
                    "default": "2Gi",
                    "render": "slider",
                    "sliderMin": 1,
                    "sliderMax": 200,
                    "sliderStep": 1,
                    "sliderUnit": "Gi",
                    "sliderExtremity": "down",
                    "sliderExtremitySemantic": "guaranteed",
                    "sliderRangeId": "memory"
                }
            }
        },
        "limits": {
            "description": "max resources",
            "type": "object",
            "properties": {
                "cpu": {
                    "description": "The maximum amount of cpu",
                    "title": "CPU",
                    "type": "string",
                    "default": "5000m",
                    "render": "slider",
                    "sliderMin": 50,
                    "sliderMax": 10000,
                    "sliderStep": 50,
                    "sliderUnit": "m",
                    "sliderExtremity": "up",
                    "sliderExtremitySemantic": "Maximum",
                    "sliderRangeId": "cpu"
                },
                "memory": {
                    "description": "The maximum amount of memory",
                    "title": "Memory",
                    "type": "string",
                    "default": "50Gi",
                    "render": "slider",
                    "sliderMin": 1,
                    "sliderMax": 200,
                    "sliderStep": 1,
                    "sliderUnit": "Gi",
                    "sliderExtremity": "up",
                    "sliderExtremitySemantic": "Maximum",
                    "sliderRangeId": "memory"
                }
            }
        }
    }
}
</code></pre>

#### How to overwrite a schema or create a new one ?

You can directly create file in the values of onyxia helm charts

{% code title="onyxia-values.yaml" %}

```yaml
onyxia:
  web:
    # ...
  api:
    # ...
    schemas:
      enabled: true
      files:
        - relativePath: ide/resources.json
          content: |
            {
                "$schema": "http://json-schema.org/draft-07/schema#",
                "title": "Resources",
                "description": "Your service will have at least the requested resources and never more than its limits. No limit for a resource and you can consume everything left on the host machine.",
                "type": "object",
                "properties": {
                    "requests": {
                        "description": "Guaranteed resources",
                        "type": "object",
                        "properties": {
                            "cpu": {
                                "description": "The amount of cpu guaranteed",
                                "title": "CPU",
                                "type": "string",
                                "default": "100m",
                                "render": "slider",
                                "sliderMin": 50,
                                "sliderMax": 10000,
                                "sliderStep": 50,
                                "sliderUnit": "m",
                                "sliderExtremity": "down",
                                "sliderExtremitySemantic": "guaranteed",
                                "sliderRangeId": "cpu"
                            },
                            "memory": {
                                "description": "The amount of memory guaranteed",
                                "title": "memory",
                                "type": "string",
                                "default": "2Gi",
                                "render": "slider",
                                "sliderMin": 1,
                                "sliderMax": 200,
                                "sliderStep": 1,
                                "sliderUnit": "Gi",
                                "sliderExtremity": "down",
                                "sliderExtremitySemantic": "guaranteed",
                                "sliderRangeId": "memory"
                            }
                        }
                    },
                    "limits": {
                        "description": "max resources",
                        "type": "object",
                        "properties": {
                            "cpu": {
                                "description": "The maximum amount of cpu",
                                "title": "CPU",
                                "type": "string",
                                "default": "5000m",
                                "render": "slider",
                                "sliderMin": 50,
                                "sliderMax": 10000,
                                "sliderStep": 50,
                                "sliderUnit": "m",
                                "sliderExtremity": "up",
                                "sliderExtremitySemantic": "Maximum",
                                "sliderRangeId": "cpu"
                            },
                            "memory": {
                                "description": "The maximum amount of memory",
                                "title": "Memory",
                                "type": "string",
                                "default": "50Gi",
                                "render": "slider",
                                "sliderMin": 1,
                                "sliderMax": 200,
                                "sliderStep": 1,
                                "sliderUnit": "Gi",
                                "sliderExtremity": "up",
                                "sliderExtremitySemantic": "Maximum",
                                "sliderRangeId": "memory"
                            }
                        }
                    }
                }
            }
        - relativePath: nodeSelector.json
          content: |
            {
              "$schema": "http://json-schema.org/draft-07/schema#",
              "title": "Node Selector",
              "type": "object",
              "properties": {
                "disktype": {
                  "description": "The type of disk",
                  "type": "string",
                  "enum": ["ssd", "hdd"]
                },
                "gpu": {
                  "description": "The type of GPU",
                  "type": "string",
                  "enum": ["A2", "H100"]
                }
              },
              "additionalProperties": false
            }
        - relativePath: ide/role.json
          content: |
            {
              "$schema": "http://json-schema.org/draft-07/schema#",
              "title": "Role",
              "type": "object",
              "properties": {
                  "enabled": {
                      "type": "boolean",
                      "description": "allow your service to access your namespace ressources",
                      "default": true
                  },
                  "role": {
                      "type": "string",
                      "description": "bind your service account to this kubernetes default role",
                      "default": "view",
                      "hidden": {
                          "value": false,
                          "path": "kubernetes/enabled"
                      },
                      "enum": [
                          "view"
                      ]
                  }
              }
            }
```

{% endcode %}


# Setting up group projects

Enabling a group of users to share the same Kubernetes namespace to work on something together.

The user interface of onyxia enables to create projects for groups of Onyxia users. &#x20;

Users will be able to dynamically switch from one project to another using a select input in the header.

<figure><img src="/files/DmSN2VjDTCCYXu4ra9j7" alt=""><figcaption></figcaption></figure>

This select doesn't appear when the user isn't in any group project. &#x20;

All users of a group project share:

* The Kubernetes namespace, in "My Services" you can see everything that's running, including services launched by other person of the group. &#x20;
* Project settings. If a user change a project setting, it affects every member of the group.
* Secrets
* S3 Bucket (or an S3 subpath)

As of today, new group can only be created by Onyxia instance administrator, on demand and the procedure to create group is not publicly documented yet because we're still actively working on it.  \
However, if you want to enable this feature for your users, reach us, we will guide you through it! &#x20;

{% embed url="<https://join.slack.com/t/3innovation/shared_invite/zt-1hnzukjcn-6biCSmVy4qvyDGwbNI~sWg>" %}


# Security consideration

Information about security considerations

#### 1. Autolaunch Feature

The autolaunch feature empowers you to create HTTP links that automatically deploy an environment. This is an invaluable tool for initiating trainings effortlessly. However, exercise caution while using it as it could pose a security risk to the user. Consider disabling this feature if it doesn't suit your requirements or if security is a primary concern. &#x20;

[Disable Autolaunch](https://github.com/InseeFrLab/onyxia/blob/0ffdc6da0e5934a5aba2d412baa2bee5a5046586/web/.env#L149C1-L189)

#### 2. Group Feature

Onyxia is primarily designed to allocate resources such as a namespace and an S3 bucket to an individual user for work purposes. Additionally, it incorporates a feature that allows multiple users to share access to the same resources within a project. While this can be extremely beneficial for collaboration, be aware that it might be exploited by a malicious user within the group to leverage the privileges of another project member. Always monitor shared resources and maintain proper user access control to prevent such security breaches.


# Migration guides


# v8 -> v9

{% hint style="warning" %}
tl;dr: **Breaking change**, `defaultConfiguration` in region configuration is not allowed anymore and has been replaced by JSONSchemas override using the new `api.schemas` key from v9 helm chart.
{% endhint %}

Onyxia v9 allows administrators to define custom JSON schemas, allowing them to override the default schemas provided by the chart. Prior to this change, Onyxia relied on providing default values for specific keys in the region configuration : `defaultConfiguration`.&#x20;

Chart owners can now define which properties can be overridden using a JSON Schema.

Here is an example of a Chart that supports JSONSchemas (taken from the default IDE catalog, see [this link](https://github.com/InseeFrLab/helm-charts-interactive-services/blob/3f32bcd4fc16ee194782616d0d4634197ec75acb/charts/vscode-python/values.schema.json#L603C5-L612C6)) : &#x20;

{% code title="values.schema.json" %}

```json
"nodeSelector": {
      "type": "object",
      "description": "NodeSelector",
      "default": {},
      "x-onyxia": {
          "hidden": false,
          "overwriteDefaultWith": "region.nodeSelector",
          "overwriteSchemaWith": "nodeSelector.json"
      }
    }

```

{% endcode %}

The `overwriteDefaultWith` attribute was the old method for overriding, instructing Onyxia to use the "defaultConfiguration" from the Region. This method is no longer supported in v9, though it can still be used for catalog compatibility with v8.

In v9, `overwriteDefaultWith` has been replaced by `overwriteSchemaWith`, which offers more flexibility due to the capabilities of JSON Schemas. Default schemas are bundled with Onyxia-API and will be used if no override is provided. You can find these default schemas here: [Onyxia-API Schemas](https://github.com/InseeFrLab/onyxia-api/tree/main/onyxia-api/src/main/resources/schemas).

To override a schema, use the new `schemas` key from the v9 Helm chart and provide the list of schemas you want to override.\
For more details, refer to the documentation: [Onyxia v9 Catalog](https://docs.onyxia.sh/v/v9/admin-doc/catalog-of-services#x-onyxia-overwriteschemawith).

{% hint style="warning" %}
&#x20;Onyxia v9 will fail to start with error message :&#x20;

`FATAL : Setting defaultConfiguration in region is no longer supported and has been replaced by JSONSchema support. See migration guide at https://docs.onyxia.sh/admin-doc/migration-guides/v8-greater-than-v9`

&#x20;if you don't remove the `defaultConfiguration` from the region configuration.
{% endhint %}


# v7 -> v8

{% hint style="info" %}
You can now have comments, trailing comas and single quotes in your region and catalog parameters! See [the PR](https://github.com/InseeFrLab/onyxia-api/pull/344).
{% endhint %}

In this release, the Onyxia S3 integration has been completely revamped!&#x20;

{% embed url="<https://github.com/InseeFrLab/onyxia-api/blob/main/docs/region-configuration.md#s3>" %}
The new S3 region parameter specification
{% endembed %}

This is the DIFF you have to apply to your Onyxia configuration assuming you have a typical MinIO integration configured:   &#x20;

{% code title="onyxia-values.yaml" %}

```diff
 ...
 api:
   ...
   regions:
     [
       {
         ...
         "data": {
           "S3": {
-            "type": "minio",
             "URL": "https://minio.lab.my-domain.net",
             "region": "us-east-1",
-            "bucketClaim": "preferred_username",
-            "defaultDurationSeconds": 86400,
-            "oidcConfiguration": {
-              "clientID": "onyxia-minio"
-            },
+            "sts": {
+              "durationSeconds": 86400,
+              "oidcConfiguration": {
+                "clientID": "onyxia-minio"
+              }
+            },
-            "bucketPrefix": "user-",
-            "groupBucketPrefix": "projet-",
+            "workingDirectory": {
+              "bucketMode": "multi",
+              "bucketNamePrefix": "user-",
+              "bucketNamePrefixGroup": "projet-"
+            }
           },
           ...

```

{% endcode %}

```bash
helm upgrade onyxia inseefrlab/onyxia -f onyxia-values.yaml
```


# v6 -> v7

In this major version a lot of the parameters of the webapp have been updated/refined.\
Here is the changes you need to apply to your values.json to migrate smoothly.

## The `THEME_ID` parameter has been removed.

Onyxia is now fully customizable instead of just letting you pick within a handful of predefined themes.

### If you where using the `france` theme:

{% code title="values.yaml" %}

```diff
 onyxia:
   web:
     env:
-      THEME_ID: france
+      FONT: |
+        { 
+          fontFamily: "Marianne", 
+          dirUrl: "%PUBLIC_URL%/fonts/Marianne", 
+          "400": "Marianne-Regular.woff2",
+          "400-italic": "Marianne-Regular_Italic.woff2",
+          "500": "Marianne-Medium.woff2",
+          "700": "Marianne-Bold.woff2",
+          "700-italic": "Marianne-Bold_Italic.woff2"
+        }
+      PALETTE_OVERRIDE: |
+        {
+          focus: {
+            main: "#000091",
+            light: "#9A9AFF",
+            light2: "#E5E5F4"
+          },
+          dark: {
+            main: "#2A2A2A",
+            light: "#383838",
+            greyVariant1: "#161616",
+            greyVariant2: "#9C9C9C",
+            greyVariant3: "#CECECE",
+            greyVariant4: "#E5E5E5"
+          },
+          light: {
+            main: "#F1F0EB",
+            light: "#FDFDFC",
+            greyVariant1: "#E6E6E6",
+            greyVariant2: "#C9C9C9",
+            greyVariant3: "#9E9E9E",
+            greyVariant4: "#747474"
+          }
+        }
+      SOCIAL_MEDIA_IMAGE: "%PUBLIC_URL%/preview-france.png"
```

{% endcode %}

### If you where using the `ultraviolet` theme:

{% code title="values.yaml" %}

```diff
 onyxia:
   web:
     env:
-      THEME_ID: ultraviolet
+      PALETTE_OVERRIDE: |
+        {
+          focus: {
+            main: "#067A76",
+            light: "#0AD6CF",
+            light2: "#AEE4E3"
+          },
+          dark: {
+            main: "#2D1C3A",
+            light: "#4A3957",
+            greyVariant1: "#22122E",
+            greyVariant2: "#493E51",
+            greyVariant3: "#918A98",
+            greyVariant4: "#C0B8C6"
+          },
+          light: {
+            main: "#F7F5F4",
+            light: "#FDFDFC",
+            greyVariant1: "#E6E6E6",
+            greyVariant2: "#C9C9C9",
+            greyVariant3: "#9E9E9E",
+            greyVariant4: "#747474"
+          }
+        }
+      SOCIAL_MEDIA_IMAGE: "%PUBLIC_URL%/preview-ultraviolet.png"
```

{% endcode %}

### If you where using the `verdant` theme:

{% code title="values.yaml" %}

```diff
 onyxia:
   web:
     env:
-      THEME_ID: verdant
+      PALETTE_OVERRIDE: |
+        focus: {
+            main: "#1F8D49",
+            light: "#4EFB8D",
+            light2: "#DFFEE6"
+        },
+        light: {
+            main: "#F4F6FF",
+            light: "#F6F6F6",
+            greyVariant1: "#E6E6E6",
+            greyVariant2: "#C9C9C9",
+            greyVariant3: "#9E9E9E",
+            greyVariant4: "#747474"
+        }
```

{% endcode %}

## Header parameters

{% code title="values.yaml" %}

```diff
 onyxia:
   web:
     env:
-      HEADER_ORGANIZATION: SSP Cloud
+      HEADER_TEXT_BOLD: SSP Cloud
-      HEADER_USECASE_DESCRIPTION: Datalab
+      HEADER_TEXT_FOCUS: Datalab
-      DESCRIPTION: Shared platform for statistical data processing and data science services
+      SOCIAL_MEDIA_DESCRIPTION: Shared platform for statistical data processing and data science services
+      SOCIAL_MEDIA_TITLE: Datalab - SSP Cloud
```

{% endcode %}

## Links in the header and the left bar

In addition to the parameter `EXTRA_LEFTBAR_ITEMS` having being renamed to `LEFTBAR_LINKS` the `iconId` property has been renamed `icon` and you can now use any icon from [the Material Design library](https://mui.com/material-ui/material-icons) or even provide your own icons.\
Please refer to [the new documentation of the `HEADER_LINKS` parameter](https://github.com/InseeFrLab/onyxia/blob/v7.0.0/web/.env).

{% code title="values.yaml" %}

```diff
 onyxia:
   web:
     env:
-      EXTRA_LEFTBAR_ITEMS: |
+      LEFTBAR_LINKS: |
```

{% endcode %}

### Assets must now be bundled

You must now bundle your assets such as the terms of services inside your onyxia instance. The newer version of Onyxia won't fetch resource from arbitrary URLs.  \
See `CUSTOM_RESOURCES` in [the .env file](https://github.com/InseeFrLab/onyxia/blob/main/web/.env).

### Keycloak Theme

If you are using the Onyxia Keycloak theme and your instance is public you might want to fill up the `ONYXIA_` prefixed environement variable in your Keycloak envs.  \
See [install doc](/docs.onyxia.sh/v9#enabling-user-authentication).


# v5 -> v6

The only breaking change in this release is the split of Onyxia service account into two separate service accounts : one for the API (which usually requires high permission to deploy services) and one for the WEB pod (qui usually should not have any permissions tied to it).\
Due to this change, the global `serviceAccount` values key was duplicated in both `web.serviceAccount` and `api.serviceAccount`.\
\
See:

{% embed url="<https://github.com/InseeFrLab/onyxia/blob/v6.0.1/helm-chart/values.yaml#L77>" %}

&#x20;and&#x20;

{% embed url="<https://github.com/InseeFrLab/onyxia/blob/v6.0.1/helm-chart/values.yaml#L160>" %}

Example of change :

{% code title="onyxia-values.yaml" %}

```diff
onyxia:
-  serviceAccount:
-    create: true
-    clusterAdmin: true
   api:
+    serviceAccount:
+      create: true
+      clusterAdmin: true
   web:
+    serviceAccount:
+      create: true 
```

{% endcode %}


# v4 -> v5

The primary breaking change in this release pertains to Keycloak configuration. With this update, you're no longer limited to using Keycloak; any OIDC-compliant identity provider is now supported.\
To accommodate this new feature, you'll need to make some adjustments to the configuration of your Onyxia instance.

{% hint style="info" %}
You don't need to specify the `issuerURI` in multiple locations as we have done here.\
If you're using just one identity server (You have only one Keycloak server for example), you can set the `issuerURI` solely in `api->env->oidc.issuer-uri`.
{% endhint %}

{% code title="onyxia-values.yaml" %}

```diff
onyxia:
   web:
     env:
-      KEYCLOAK_URL: https://auth.lab.sspcloud.fr/auth
-      KEYCLOAK_REALM: sspcloud
   api:
     env:
-      keycloak.resource: onyxia
-      keycloak.realm: sspcloud
-      keycloak.auth-server-url: https://auth.lab.sspcloud.fr/auth
-      keycloak.ssl-required: external
-      keycloak.public-client: "true"
-      keycloak.enable-basic-auth: "true"
-      keycloak.bearer-only: "true"
+      oidc.issuer-uri: "https://auth.lab.sspcloud.fr/auth/realms/sspcloud"
+      oidc.clientID: "onyxia"
+      oidc.audience: "onyxia"
       authentication.mode: "openidconnect"
     regions: 
       [
         {
           "id": "paris",
           "services": {
-              "authenticationMode": "admin",
+              "authenticationMode": "serviceAccount",
               "k8sPublicEndpoint": {
                 "URL": "https://apiserver.kub.sspcloud.fr",
-                "keycloakParams": {
-                  "URL": "https://auth.lab.sspcloud.fr/auth",
-                  "realm": "sspcloud",
-                  "clientId": "onyxia"
-                },
+                "oidcConfiguration": {
+                  "issuerURI": "https://auth.lab.sspcloud.fr/auth/realms/sspcloud",
+                  "clientID": "onyxia-k8s-apiserver",
+                }
               }
             },
           "data": {
             "S3": {
-              "keycloakParams": {
-                "URL": "https://auth.lab.sspcloud.fr/auth",
-                "realm": "sspcloud",
-                "clientId": "onyxia-minio",
-              }
+              "oidcConfiguration": {
+                "issuerURI": "https://auth.lab.sspcloud.fr/auth/realms/sspcloud",
+                "clientID": "onyxia-minio",
+              }
             }
          },
          "vault": {
              "URL": "https://vault.lab.sspcloud.fr",
-             "keycloakParams": {
-               "URL": "https://auth.lab.sspcloud.fr/auth",
-               "realm": "sspcloud",
-               "clientId": "onyxia-vault",
-             }
+             "oidcConfiguration": {
+               "issuerURI": "https://auth.lab.sspcloud.fr/auth/realms/sspcloud",
+               "clientID": "onyxia-vault"
+             }
         }
       }
     ]
```

{% endcode %}


# Migrating to the new helm repo

Previously, the Helm chart of Onyxia was hosted on the inseefrlab/helm-charts repo and has now been moved to inseefrlab/onyxia.  \
\
As a result you would now install Onyxia like this: &#x20;

```diff
-helm repo add inseefrlab https://inseefrlab.github.io/helm-charts
+helm repo add onyxia https://inseefrlab.github.io/onyxia

-helm install onyxia inseefrlab/helm-charts
+helm install onyxia onyxia/onyxia
```

In the following we assume the current version of Onyxia is 4.1.4 but you are encorging to use the latest version instead. [See releases](https://github.com/InseeFrLab/onyxia/releases).

If you use ArgoCD for deploying onyxia: &#x20;

<pre class="language-diff" data-title="apps/onyxia/Chart.yaml"><code class="lang-diff"><strong> apiVersion: v2
</strong> name: onyxia
 version: 1.0.0
 dependencies:
   - name: onyxia
-    version: 4.1.0
+    version: 4.1.4
-    repository: https://inseefrlab.github.io/helm-charts/
+    repository: https://inseefrlab.github.io/onyxia/
</code></pre>

You no longer need to manually manage the version of [onyxia-web](https://hub.docker.com/r/inseefrlab/onyxia-web) and [onyxia-api](https://hub.docker.com/r/inseefrlab/onyxia-api), now, if you want to update Onyxia, you just update the chart version number. &#x20;

```diff
helm repo add onyxia https://inseefrlab.github.io/onyxia

DOMAIN=my-domain.net

cat << EOF > ./onyxia-values.yaml
# ...
web:
  image:
-   tag: 2.29.4
api:
  image:
-   tag: v0.32   
# ...
EOF

helm install onyxia onyxia/onyxia -f onyxia-values.yaml
```

For the Keycloak theme, the version is now synchronized with the Onyxia version. &#x20;

```diff
helm repo add codecentric https://codecentric.github.io/helm-charts

cat << EOF > ./keycloak-values.yaml
# ... See https://docs.onyxia.sh/#enabling-user-authentication
extraInitContainers: |
  - name: realm-ext-provider
    image: curlimages/curl
    imagePullPolicy: IfNotPresent
    command:
      - sh
    args:
      - -c
      - |
-       curl -L -f -S -o /extensions/onyxia.jar https://github.com/InseeFrLab/onyxia/releases/download/v2.29.4/keycloak-theme.jar
+       curl -L -f -S -o /extensions/onyxia.jar https://github.com/InseeFrLab/onyxia/releases/download/v4.1.4/keycloak-theme.jar
    volumeMounts:
      - name: extensions
        mountPath: /extensions
extraVolumeMounts: |
  - name: extensions
    mountPath: /opt/jboss/keycloak/standalone/deployments
extraVolumes: |
  - name: extensions
    emptyDir: {}
# ...
EOF

helm install keycloak codecentric/keycloak -f keycloak-values.yaml
```

Also note that, the theme will now appear as "onyxia" in the dropdown. Previously it was "onyxia-web"<br>

<figure><img src="/files/1J3obaLhPhj24Gnpqb2X" alt=""><figcaption></figcaption></figure>




---

[Next Page](/llms-full.txt/1)

