Using Coolify and Nuxt for multi-tenant with dynamic subdomains

By Hugo LassiègeOct 15, 20253 min read

When building an application, it's pretty common to create "instances" of your site per client.

For example, let's say you want to have: clientA.myapp.com and clientB.myapp.com

Well, let me show you how to set this up with Coolify.

Coolify

Let's start from the beginning. What is Coolify?

Coolify is basically your personal PAAS. It's a launchpad for deploying your applications, but also for benefiting from managed databases (postgresql, redis, mysql, etc.) or managed services (listmonk, mautic, a Bluesky PDS, etc.)

It's an open source software that replaces market PAAS solutions, whether it's Heroku, Clever Cloud, etc.

Obviously, if you host it yourself, it also means you operate it yourself, so you need to monitor it, update it, etc. That said, there's a paid cloud version but that's not the topic of this post.

Anyway, Coolify is what I use for hakanai.io and soon for another product I'm working on that should be released by the end of the month.

A multi-tenant application?

A multi-tenant application is basically a single application that serves multiple clients.

For example, Notion is a multi-tenant application. You have a single application that supports all users. In contrast to Jira, which is a multi-instance application, meaning each client installs an instance of the software. The databases don't see each other.

So keep in mind that, in my case, I have a web application hosted on Coolify that needs to work for a bunch of different clients.

And yet, I want to give them the illusion of being on different instances by assigning each one a subdomain.

So for example client A => clientA.myapp.com and client B => clientB.myapp.com

Yet in reality, each client will land on the same application.

Meaning for example:

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

Coolify configuration

To route all the traffic for clientA, clientB, clientC across all subdomains of myapp.com, we'll use wildcard domains

This means your goal is to respond on *.myapp.com

You might think it's enough to just enter the following wildcard in "Domains": https://*.myapp.com

But that's not exactly the case, otherwise I wouldn't have written this article ^^

The subtlety is that you'll need to edit the Container labels that define the Traefik configuration.

It usually looks something like this:

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

There are two rules that interest us and that define what Traefik will respond to:

Host('*.myapp.com')

This rule will accept all traffic that would arrive on *.myapp.com but not on clientA.myapp.com

To fix this, we'll edit the rule to use a regular expression:

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

Just restart the application, and now it responds on all subdomains of myapp.com

Managing tenants in Nuxt

From there, you still need to differentiate between subdomains based only on the subdomain name.

For this, you'll read the request.

In a Nuxt application, you can move this logic into a composable like this one:

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
}

From there, you just need to call useTenant to get the subdomain name, which corresponds to your instance identifier.

And that's it :)

(it's funny because it can be explained in less than 150 lines, but I struggled with it for a day because I don't read the docs properly ^^)

EDIT: Actually, it's far from over, because now we have to deal with SSL. So here's the next step: SSL configuration


Share this:

Written by Hugo Lassiège

Software Engineer with more than 20 years of experience. I love to share about technologies and startups

Copyright © 2025
 Eventuallymaking
  Powered by Bloggrify