Utiliser Coolify et Nuxt en multi tenant avec des sous-domaines dynamiques

By Hugo5 min read

Lorsqu'on construit une application, c'est assez courant de créer des "instances" de votre site par client.

Par exemple, mettons que vous souhaitiez avoir : clientA.monapp.com et clientB.monapp.com

Eh bien je vous propose de voir comment on peut mettre ça en place avec Coolify.

Coolify

Commencons par le commencement. C'est quoi Coolify ?

Coolify c'est un peu votre PAAS personnel. C'est une base de lancement pour déployer vos applications, mais aussi bénéficier de bases de données managées (postgresql, redis, mysql etc..) ou des services managées (listmonk, mautic, un PDS bluesky etc...)

C'est un logiciel opensource qui remplace les PAAS du marché, que ce soit Heroku, Clever Cloud etc...

Bon évidemment, si vous l'hébergez vous-même, ça veut aussi dire que vous l'opérez vous-même, donc il faut le monitorer, le mettre à jour etc...
Cela dit, il existe une version cloud payante mais c'est pas le sujet de ce billet.

Bref, Coolify c'est ce que j'utilise pour hakanai.io et bientôt pour un autre produit sur lequel je travaille et qui devrait sortir fin du mois.

Une application multi tenant ?

Une application multi tenant, c'est globalement une application unique qui permet de servir plusieurs clients.

Par exemple Notion est une application multitenant. Vous avez une seule application qui supporte tous les utilisateurs.
A l'inverse de Jira, qui est une application multi instance, c'est-à-dire que chaque client installe une instance du logiciel.
Les bases de données ne se voient pas.

Donc gardez en tête que, dans mon cas, j'ai une application web, hébergé sur Coolify, qui doit fonctionner pour tout un tas de clients différents.

Et pourtant, je souhaite leur donner l'illusion d'être sur des instances différentes, en attribuant à chacun un sous domaine.

Donc par exemple client A => clientA.monapp.com et client B => clientB.monapp.com

Pourtant dans les faits, chaque client va atterrir sur la même application.

C'est-a-dire que par exemple :

  • clientA.monapp.com => 192.168.0.1
  • clientB.monapp.com => 192.168.0.1

La configuration Coolify

Pour pouvoir router l'ensemble du trafic correspondant à clientA, clientB, clientC sur tout les sous domaines de monapp.com, on va donc utiliser les wildcard domains

C'est à dire que votre objectif c'est de répondre sur *.monapp.com

On pourrait croire qu'il suffit de rentrer dans "Domains" le wildcard suivant : https://*.monapp.com

Or c'est pas exactement le cas, sinon j'aurais pas fait d'article à ce sujet ^^

La subtilité, c'est qu'il va falloir éditer les Container labels qui définissent la conf de Traefik.

En principe ça ressemble à quelque chose comme ça :

text
traefik.enable=true
traefik.http.middlewares.gzip.compress=true
traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
traefik.http.routers.http-0-ow8g70707sockck8sgo8kc55g.entryPoints=http
traefik.http.routers.http-0-ow8g70707sockck8sgo8kc55g.middlewares=redirect-to-https
traefik.http.routers.http-0-ow8g70707sockck8sgo8kc55g.rule=Host(`toto.monapp.com`) && PathPrefix(`/`)
traefik.http.routers.http-0-ow8g70707sockck8sgo8kc55g.service=http-0-ow8g70707sockck8sgo8kc55g
traefik.http.routers.https-0-ow8g70707sockck8sgo8kc55g.entryPoints=https
traefik.http.routers.https-0-ow8g70707sockck8sgo8kc55g.middlewares=gzip
traefik.http.routers.https-0-ow8g70707sockck8sgo8kc55g.rule=Host(`toto.monapp.com`) && PathPrefix(`/`)
traefik.http.routers.https-0-ow8g70707sockck8sgo8kc55g.service=https-0-ow8g70707sockck8sgo8kc55g
traefik.http.routers.https-0-ow8g70707sockck8sgo8kc55g.tls.certresolver=letsencrypt
traefik.http.routers.https-0-ow8g70707sockck8sgo8kc55g.tls=true
traefik.http.services.http-0-ow8g70707sockck8sgo8kc55g.loadbalancer.server.port=3000
traefik.http.services.https-0-ow8g70707sockck8sgo8kc55g.loadbalancer.server.port=3000
caddy_0.encode=zstd gzip
caddy_0.handle_path.0_reverse_proxy={{upstreams 3000}}
caddy_0.handle_path=/*
caddy_0.header=-Server

Il y a deux règles qui nous intéressent et qui définissent sur quoi va réagir Traefik

text
Host('toto.monapp.com')

Cette règle va accepter tout le trafic qui arriverait sur toto.monapp.com mais pas sur clientA.monapp.com

Pour cela, on va éditer la règle pour utiliser une expression régulière :

text
HostRegexp(`^.+\.monapp\.com$`)

Il suffit de restart l'application, et désormais elle répond sur tout les sous domaines de monapp.com

Gérer les conflits avec les autres sous domaines statiques

Si vous êtes comme moi, vous avez peut-être des sous domaines "réservées", par exemple api.monapp.com, qui est une autre application distincte de celle-ci.

Avec notre configuration actuelle, ce container va prendre tout le trafic qui arriverait sur api.monapp.com. En effet Traefik applique des règles de priorité et va prendre la plus haute. Or ces règles dépendent du nombre de caractères des rules.

text
HostRegexp(`^.+\.monapp\.com$`)

est plus long que

text
Host(`api.monapp.com`)

Donc pour éviter ça, on va retourner sur le container de api et définir une priorité à la main :

text
traefik.http.routers.https-0-c44skk4wwc0kg0s08k8w88ow.priority=100

De cette façon, le container de api va prendre la priorité et va prendre tout le trafic qui arriverait sur api.monapp.com.

La gestion des tenants sur nuxt

À partir de là, vous devez quand même faire la différentiation entre les sous domaines uniquement en vous basant sur le nom du sous domaine.

Pour ça, vous allez lire la request.

Dans une application Nuxt, vous pouvez déporter cette logique dans un composable comme celui-ci :

typescript
export const useTenant = () => {
  // Server side
  if (import.meta.server) {
    const headers = useRequestHeaders()
    const host = headers.host || ''

    const parts = host.split('.')

    if (parts.length <= 2) {
      return null
    }

    return parts[0]
  }

  // Client side
  if (import.meta.client) {
    const host = window.location.host
    const parts = host.split('.')

    if (parts.length <= 2) {
      return null
    }

    return parts[0]
  }

  return null
}

À partir de là, il suffira d'appeler useTenant pour obtenir le nom du sous domaine, qui correspond donc à votre identifiant d'instance.

Et, c'est tout :)

(c'est marrant parce que ca s'explique en moins de 150 lignes, mais j'ai galéré dessus pendant 1 jour parce que je lis mal la doc ^^)

EDIT : en fait c'est loin d'être terminé, parce que désormais il faut s'occuper du SSL. Et donc la suite est ici : la configuration du SSL

Written by Hugo

Ingénieur logiciel/Indie Hacker avec plus de 20 ans d'expérience. Je partage sur les technologies, l'entreprenariat et les startups, entre autre...

Stay in the loop

Get new articles delivered directly to your inbox. No spam, unsubscribe anytime.

0 Comments

No comments yet. Be the first to comment!

Copyright © 2026EventuallycodingPowered by Writizzy