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

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 :

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.

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

est plus long que

Host(`api.monapp.com`)

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

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 :

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


Share this:

Written by Hugo Lassiège

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

Related Articles

You might also be interested in these articles

Utiliser Coolify pour déployer des applications avec wildcard SSL sur des sous-domaines dynamiques

Utiliser Coolify pour déployer des applications avec wildcard SSL sur des sous-domaines dynamiques

Configurez des certificats SSL wildcard avec Coolify et Traefik via DNS challenge. Guide complet pour gérer des sous-domaines dynamiques et custom domains en HTTPS.

nuxtcoolifytraefik
Comment builder sur Github actions ou Gitlab CI et déployer sur Coolify

Comment builder sur Github actions ou Gitlab CI et déployer sur Coolify

Comment séparer un build gourmand sur GitHub Actions ou GitLab CI en utilisant Coolify.

nuxtcoolify
Implémenter un captcha sans tracking avec Atcha et Nuxt

Implémenter un captcha sans tracking avec Atcha et Nuxt

Implémentation d'Altcha avec Nuxt pour protéger un formulaire sans tracking. Retour sur ses limitations face au spam

nuxt
Copyright © 2026
 Eventuallycoding
  •
Powered by Bloggrify