Self-Hosting - Table of Contents

Introduction

This is a big guide that I'll be aiming at people that want to host their own instance of Tabledown, but we obviously have countless ways of deploying webapps nowadays so I'll be making a guide with some assumptions:

Create A VPS On Any Cloud Platform

Any cloud platform will have at least of service they'll provide which is a VPS/Virtual Machine, and that's what we need first. I like to rent Debian Linux servers as they tend to be more stable but suit yourself.

Once your VPS is purchased you might want to open some TCP routes first, this varies a lot with cloud provider but in general you want to find the network security rules and open a couple ports:

Protocol Type Port Direction Remote CIDR
TCP IPv4 80 (HTTP) IN * (Any IP)
TCP IPv4 443 (HTTPS) IN * (Any IP)

Having that set then any IP can access our ports for HTTP and HTTPS.

Now you'll want to note down your public IP YOUR_VPS_IP, also set up your SSH keys to connect to your VPS when needed;

SSH in to confirm you already have access to your new machine (in the example below I'm using root as the user but this can vary with cloud provider):

ssh root@YOUR_VPS_IP

Point Your Domain To The VPS

You can skip this step if you don't want to have a domain for your server nor SSL certificates, but in case you do, you'll need to go to your chosen domain registrar, but the domain you want (I'll use mydomain.com for reference) and in your registrar domain settings you need to find where to manage DNS / Nameservers. Then when you'll set your domain DNS to point to your VPS public IP.

Add these records:

Type Name Value TTL
A @ YOUR_VPS_IP 300
A api YOUR_VPS_IP 300
A www YOUR_VPS_IP 300

The @ covers mydomain.com and www covers www.mydomain.com. The api subdomain covers api.mydomain.com we'll use for the backend.

Having that done we need to wait for the DNS to propagate, which can take a few minutes up to a few hours.

Prepare The VPS

Assuming you are using a Debian-based for your VPS we first need to update the system then install a few packages:

apt update && apt upgrade -y
apt install -y nginx certbot python3-certbot-nginx docker.io docker-compose git ufw

With this we have most of what we'll use to host the server:

Now we set up the firewalls for SSH and NGinx:

ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable

And finally we start Nginx and Docker services:

systemctl enable nginx docker
systemctl start nginx docker

Deploy Your App

Now it's time to clone Tabledown into the VPS, I'll use the main Tabledown repo as an example but you can use your own fork too obviously, also I'll be cloning Tabledown into the folder /opt/tabledown but this is 100% preference, you can clone it wherever you want in your VPS.

Also note that I'll be using the HTTP version of the repo but feel free to use the SSH version and set up SSH keys for your Git remote origin in your VPS.

git clone https://codeberg.org/grepehu/tabledown.git /opt/tabledown
cd /opt/tabledown

docker compose up -d

Environment Variables

Now with Tabledown cloned we just need to set up the environment variables accordingly. In the main project we have the .env.example you can just copy and paste into a new file .env and then replace the variables with their proper values:

Sheets

We touched on this subject a little bit in the previous sections but I'd just like to reiterate that the VITE_SHEETS_URL is a variable that points to an address where the application expects to find a sheets.json this address can be anywhere even inside the application itself, the purpose of this is to allow the sheets templates to be updated constantly without the need to rebuild the application every time.

Also you can grab the current file we use in production from https://codeberg.org/grepehu/tabledown-sheets/src/branch/master/sheets.json, where you can download this file, add it to src/public/ folder in your project and set the VITE_SHEETS_URL to /sheets.json making your application fetch inside itself.

Docker management

Since Docker is already installed in the system and we have our .env setup, we can now spin up our containers with (inside the tabledown folder, where we have the docker-compose-yml file):

docker compose up -d

This will spin up a server in the background, for any future updates what I like to do personally is pull recent changes, turn off the Docker containers, clean anything from the Docker system (this is optional, but I like to do it to avoid the cache and logs that mount up really quickly on Docker) and the finally spin up the container again:

git pull
docker compose down
docker system prune -a
docker compose up -d

Configure Nginx

To handle our reverse-proxy on Nginx, we'll need to create two config files:

/etc/nginx/sites-available/mydomain.com

server {
    listen 80;
    server_name mydomain.com www.mydomain.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

/etc/nginx/sites-available/api.mydomain.com

server {
    listen 80;
    server_name api.mydomain.com;

    location / {
        proxy_pass http://127.0.0.1:3000;

        # SSE required
        proxy_buffering off;
        proxy_cache off;
        proxy_http_version 1.1;
        proxy_set_header Connection '';

        # Timeouts long enough for SSE
        proxy_read_timeout 24h;
        proxy_send_timeout 24h;
        keepalive_timeout 24h;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Note that specially for the backend we have some special lines because of the real-time SSE connections we use on the app, like big timeouts and buffering off.

Having this files written, we need to enable them and reload Nginx:

ln -s /etc/nginx/sites-available/tabledown.net /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/api.tabledown.net /etc/nginx/sites-enabled/

You can remove the old Nginx placeholder if you want:

rm /etc/nginx/sites-enabled/default

Then we check if there are any errors with our config files:

nginx -t

And finally reload Nginx:

systemctl reload nginx

SSL With Let's Encrypt

Once DNS has propagated to your VPS IP we cam activate the SSL, we can check if the DNS did propagate with:

dig mydomain.com

If successful we can finally use Certbot to add SSL certificates to our domain with:

certbot --nginx -d mydomain.com -d www.mydomain.com
certbot --nginx -d api.mydomain.com

Certbot will automatically modify your Nginx configs to add HTTPS and set up a 443 server block. It also installs a cron job to auto-renew certs.

We can always simulate if the SSL renewal works running a dry test:

certbot renew --dry-run

Once the SSL is done, you might want to add to the frontend for Nginx a way to redirect www. to remove it, so in the 443 SSL block add this below the domain names:

    if ($host = www.mydomain.com) {
        return 301 https://mydomain.com$request_uri; # Redirect www to remove it
    }

The above piece is optional but important because Tabledown uses local storage to save user data and browsers save local storage based on the website domain, so users accessing www.mydomain.com will have different data in storage than mydomain.com.

Once everything's done, we reload Nginx:

nginx -t && systemctl reload nginx

Done

Now you'll probably be able to access your app at mydomain.com and it'll be communicating properly with the backend at api.mydomain.com.

Just a note, the docker-compose configuration for this app is set to not save any data from the containers runs, so on every reset you'll lose all the rooms that were there, which isn't that bad considering Tabledown deletes rooms every 24 hours anyways, but if you want to change this behaviour you can modify the docker-compose.yml as you see fit.