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:
- You want to host an instance using a VPS/Virtual Machine in some cloud platform;
- You know basic linux commands and folders, SSH and DNS stuff. I won't be asking anything crazy, but knowing how to move folders in a linux terminal, knowing how to buy a domain and redirect it to your IP and how to connect to your VPS through SSH is a must, in case you don't know any of that, any of these topics can be easily found online and learned in like half an hour;
- You'll be buying a domain to have SSL protection. This is not necessary if your server is only ever going to be available to your and close friends, but I assume most people want to have a secure connection;
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:
- Nginx: The HTTP server itself that will do the reverse proxy to our app;
- Certbot: To automate the SSL certificates renewal;
- UFW: To set up firewalls;
- Docker: To run our app in containers;
- Git: Well .... To do Git stuff;
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:
- ENV: This determines the environment you running,
devfor development where some things like CORS will be ignored andprodfor the proper production enviroment; - ALLOWED_ORIGIN: List of allowed domain origins (including http/https) that can send requests to your backend, separated by comma/
- VITE_ENV: Much like
ENVexcept that this only exists in the client-side; - VITE_PUBLIC_URL: This is the domain where your client-side will be deployed to, in the example we're using it would be
https://mydomain.com, the client-side uses this to access resources from the absolute path in the domain, making the assets load in the HTML preload to make links much better; - VITE_BACKEND_URL: This is the backend address we'll be using, in this example it would be
https://api.mydomain.com, this client-side uses this address to send requests to the backend; - VITE_MORE_URL: This is the address of the landing page with more information, usually
https://tabledown.neocities.org; - VITE_SHEETS_URL: This is the URL we use to fetch sheets templates when finding them, leaving this blank won't break the rest of the application but will make finding pre-made templates useless. You can set this variables to
/sheets.jsonand add a file with pre-made templates to the foldersrc/public/before building, then your application will fetch local sheets if you prefer; - VERSION: This can be left empty, this is only useful for me to tag git versions of the project, it doesn't do anything for deployment;
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.