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

By Hugo LassiègeOct 15, 20254 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 :

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(`*.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(`*.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

Host('*.monapp.com')

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

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

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

Il suffit de restart l'application, et désormais elle répond sur tout les sous domaines de 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 :

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 ^^)


Share this:

Written by Hugo Lassiège

Software engineer, ex-freelance, ex-cofounder, ex-CTO. I love building things, sharing knowledge and helping others.

Copyright © 2025
 Eventuallycoding
  Powered by Bloggrify