DEV Community

Cover image for How to make a multi tenant website
Manda Putra
Manda Putra

Posted on • Edited on

How to make a multi tenant website

Today I'll tell how do I make wildcard subdomains and how do I handle the data between multiple user account. I thought it was hard, but turns out it is not (or maybe it is easy because I doing it wrong? I don't know).

Every time I search about how to make a multi tenant website, there are 0 articles on how to build them. I always wonder how do they do their architecture etc.

The key to this type of website architecture are just wildcard subdomain and reading the host on your code. As simple as that.

The website requirement are :

  1. It's blog platform
  2. Every user will have their own subdomain (zeke.bloggy.net, eren.bloggy.net, etc)
  3. Every subdomain had their own data

So there are some steps.

There are some tips later on using it with cloud

Buy a Domain and VPS

First you must own a domain name and VPS (Virtual Private Server). I do think that this could be done in with serverless too, I just didn't tried that.

Point your domain to the VPS

On your domain registrar DNS Management There are 4 record that you should add. You also need your VPS IP address to point to.

| domain     | record type | value                              | host                               | ttl   |
|------------|-------------|------------------------------------|------------------------------------|-------|
| bloggy.com | A Record    | 192.0.0.1                          | bloggy.com                         | 14400 |
| bloggy.com | CNAME       | bloggy.com                         | www                                | 14400 |
| bloggy.com | A Record    | 192.0.0.1                          | *                                  | 14400 |
| bloggy.com | TXT Record  | radom-value-provided-by-lestecrypt | radom-value-provided-by-lestecrypt | 14400 |
Enter fullscreen mode Exit fullscreen mode

The IP address there are your VPS IP address the domain are your domain (till the end of this article we'll use bloggy.com)

The TXT Record will be given by LetsEncrypt when we need https, I'll on the next step.

Setup nginx to point to your domain

Install Nginx.

Apache, Nginx, or other web server thingy have their own syntax of configuration I used Nginx here. Use your own preferences

The configuration I use for bloggy.com are the same as article here try to follow that article guide (I want to keep this posts as short as possible)

The difference is when I want to register the wildcard subdomain. The certbot command I used are different.

It is not :

sudo certbot --nginx -d bloggy.com -d www.bloggy.com
Enter fullscreen mode Exit fullscreen mode

but this :

sudo certbot --server https://acme-v02.api.letsencrypt.org/directory -d *.example.com --manual --preferred-challenges dns-01 certonly
Enter fullscreen mode Exit fullscreen mode

On this part you'll need to make the TXT Record.

I don't know why the -d *.example.com won't work, seems like it is a domain registrar problem. I obtain this trick from this article

For now, you will have a working wildcard subdomain and domain. Whether you visit bloggy.com, zeke.bloggy.com, eren.bloggy.com you will see Nginx welcome page (If you use nginx)

Handle the subdomain on your code

I use node.js (Express.js) for this, different app may have different syntax. But the rule are simple, read the hostname to get the account name and fetch the data;

The app had 2 routes home and blog Here the simplest code possible

const express = require('express')
const app = express()
const port = 3000

const dataBlog = [
  { account: 'zeke', blog: ['so zeke thing']  },
  { account: 'eren', blog: ['so eren thing 2']  }
]

// Home route list all blog and account
app.get('/', (req, res) => {
  res.send(dataBlog)
})

// Blog route only list the blog based on host account name
// You will see that we dont use params here, we use hostname
app.get('/blog', (req, res) => {
  const host = req.headers.host // or 'x-forwarded-host'
  const accountName = host.toString().split('.')[0] // get the account name on domain.
  if (accountName) {
    const accountData = dataBlog.filter(acc => acc.account === accountName)
    res.send(accountData)
  } else {
    res.code(404).send('Not Found')
  }  
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})
Enter fullscreen mode Exit fullscreen mode

Proxy your web app with nginx

Node.js aren't like PHP it has its own web servers, but to contact with real world it needs proxy. So we just proxy it to our nginx.

On bloggy.com nginx configuration

# so the home route will be on bloggy.com
proxy_pass http://localhost:3000;
Enter fullscreen mode Exit fullscreen mode

On the wildcard *.bloggy.com nginx configuration

# so the blog route will point to *.bloggy.com
proxy_pass http://localhost:3000/blog;
Enter fullscreen mode Exit fullscreen mode

That's it! Every time you visit bloggy.com/blog it will respond 'not found' but if you visit zeke.bloggy.com it will fetch the data for 'zeke'.

On Cloud

So later example are for VPS, however if you want to load balance your apps with VPS it going to be little hard (in my experience).

Better use cloud service like GKE, DO, or AWS. With K8S (Kubernetess). It is more easy. You can achieve it using ingress.

The step by step to take that are on here It is the same.

And you can just point your domain name to the ingress IP.

Here are some config

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: $BUDDY_PROJECT_NAME
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    cert-manager.io/cluster-issuer: 'letsencrypt-clusterissuer'
spec:
  tls:
  - hosts:
    - "bloggy.com"
    secretName: domain-$BUDDY_PROJECT_NAME
  rules:
  - host: 'bloggy.com'
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: $BUDDY_PROJECT_NAME
            port:
              number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rewrite-$BUDDY_PROJECT_NAME
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    cert-manager.io/cluster-issuer: 'letsencrypt-clusterissuer'
    nginx.ingress.kubernetes.io/rewrite-target: /blog/$1
spec:
  tls:
  - hosts:
    - "*.bloggy.com"
    secretName: wildcard-certificate-tls
  rules:
  - host: '*.bloggy.com'
    http:
      paths:
      - backend:
          service:
            name: $BUDDY_PROJECT_NAME
            port:
              number: 80
        path: "/(.*)"
        pathType: Prefix
---
Enter fullscreen mode Exit fullscreen mode

Closing

I hope this will help you to make your multi tenant website. Have a great day. I welcome every feedback you had! :D

Top comments (1)

Collapse
 
qhkm profile image
qhkm

This is useful! Thanks very much!