6 min read

How I Host my Critical Apps for under $85CAD/month - Part 1 - Setting the basics

Now that we've discussed why I've chosen the matrix ecosystem, it's time to elaborate on how I host everything.
picture of python code on a laptop screen
Photo by Chris Ried / Unsplash

In my last post, I discussed secure communication platforms, and why I've chosen the matrix ecosystem.

My Evaluation of Secure Communication Platforms
As I start to ramble on tech, and given the recent US elections, I can think of no better place than to start with secure messaging applications.

Now it's time to get into the weeds on how I host these services. First up in part 1 will be the architecture of my setup. It's a little more involved to fit my needs as a self-described "power-hoster".

What you need to follow along

This is my attempt at providing a guide on how I host my applications, but we won't be starting from square one. The basics of Linux are required here, along with the basics of docker-compose. If you need a starting point for those, I'd recommend looking at https://linuxhandbook.com/courses/

This also not the only way to set up a host like this. However, everyone's needs are different, and this is more meant to be inspiration on how to set something like this up yourself.

Hosting Goals

Like any project, starting with the specifics of what you want to host will help dictate what you need to support them. I want to host some "critical" apps on a reliable virtual private server (VPS) so that regardless if I completely drop the ball on my home server, the blast radius does not affect services people rely on. Fucking up prod means I was in there and am ready to fix it.

These services will be a few key features, and will be articles in their own right.

These will need to be supported by a reverse proxy, which we'll set up below, but first we need to plan out where we're going to host this.

The reverse proxy will proxy the hosted apps above, but also act as a tunnel to my publicly hosted services running on my local server. This blog being one!

Cloud Provider

There are several VPS providers out there. I wanted to talk about why I selected Digital Ocean. My first consideration is data centre location. Residing in Southern Ontario, having a Toronto-ish location is important. My second consideration is product availability. Micromanaging a Postgres cluster is not something I can easily do, so having a managed database was important to me. With cost being another obvious factor, Digital ocean is my pick. The setup will involve those products, but can easily be adapted for any VPS provider that offers a VPS and managed Redis/cache and Postgres service.

Cloud Services

We're going to rely on 4 services from Digital Ocean.

  • Droplet (Ubuntu 2vCPU 4GB RAM)
  • Managed Caching Server (1GB RAM, 10GB Disk)
  • Managed PostgreSQL Server (1GB RAM, 10GB Disk)
  • Volume (100GB)

These services cost a little under $80/month to run, and should be plenty for my (relatively) small community. My largest usage is on the droplet, running 60% RAM usage on average, which is a perfectly healthy amount.

Secure Settings

We'll want to start with securing the boxes above from remote access

VPS Firewall

It's generally a bad idea to let everything be open to all the internet. Let's start in the created Droplet → Networking → Firewalls → [FIREWALL NAME]

Here is where we'll make some edits.

SSH (Port 22) should be set to only be connectable from your current IP. This will prevent anyone but you from remote access. I'll note here is a good spot to note that if your local IP changes randomly because ISP reasons, you'll need to update this spot each time. If ssh isn't connecting, this is the first thing you set.

Next up is allowing all traffic on ports 80 and 443 for TCP traffic. This will enable us to serve a reverse proxy.

For making calls on matrix, we'll need a TURN/STUN server. My choice of Coturn uses UDP ports 49160-49200

Database Settings

These settings will be applied to both Postgres and the Caching server. We want to limit connections to our VPS we created earlier and our current IP only. This way no one can gain access to the database remotely either.

Starting the VPS

We should now be able to connect via SSH to the machine. First, we'll need a way to send our docker-compose.yml file from our editor of choice. Personally, I use GitHub for this. I'm able to push and record changes to config, and it's backed up to an external location. That being said, do NOT put secrets in a file that gets uploaded to GitHub. I use .env files to store secrets, and this file is specifically in the .gitignore file. By no means is this the only way, just the way I chose.

Next up, we'll install the basics.

Setting up Traefik

Now that we have our server configured, we should set something to listen on at least the HTTP/HTTPS ports. We're going to set up Traefik as our reverse proxy of choice. We also want to protect our server from exploitative users. Here my choice is CrowdSec to provide filtering and prevent any known bad actors from accessing anything. Let's set up our docker compose file!

There's a lot going on there, so let's talk about some sections and talk about what they do.

Traefik Commands

In the treafik service, we see

command:
      # - "--log.level=DEBUG"
      - "--providers.docker=true"
      - "--providers.file.filename=/etc/traefik/dynamic.yml"
      - '--entrypoints.web.address=:80'
      - '--entrypoints.web.http.redirections.entryPoint.to=websecure'
      - '--entrypoints.web.http.redirections.entryPoint.scheme=https'
      - '--entrypoints.websecure.address=:443'
      - "--providers.docker.exposedbydefault=false"
      - '--certificatesresolvers.letsencrypt.acme.httpchallenge=true'
      - '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=websecure'
      - '--certificatesresolvers.letsencrypt.acme.email=hello@domain.tld'
      - '--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json'
      - '--certificatesresolvers.letsencrypt.acme.tlschallenge=true'
      - '--experimental.plugins.crowdsec-bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin'
      - '--experimental.plugins.crowdsec-bouncer.version=v1.2.1' 

After enabling the docker provider, we set the dynamic configuration. This file is optional if all you're doing is hosting all the apps located on this docker compose file. Personally, I also use this server as a gateway for my local host, similar to Cloudflare Tunnels. I am able to connect my local server to tailscale/headscale. Tl;dr you don't need to include this file. We won't be going over how to use this file for several parts, so feel free to omit.

Next, we set our web (HTTP) and websecure (HTTPS) entry points, and set web to redirect to websecure automatically.

Following that, we have our Let's Encrypt configuration to enable SSL/HTTPS.

Finally, we have configuration to enable crowdsec in traefik, we'll see how this gets used in Part 2.

Traefik Files

Let's take a look at the path mappings.

volumes:
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./logsTraefik:/var/log/traefik"
      - "./traefik/config/:/etc/traefik/"
      - "./traefik/logs:/logs/"

Starting with the optional second line for the config file mentioned above. Since we're not discussing this till later parts, can be omitted.

The rest of the mappings allow let's encrypt to function, as well as the docker labels to enable services, and finally log files.

Crowdsec

  crowdsec:
    image: crowdsecurity/crowdsec
    container_name: crowdsec
    environment:
      PGID: "1000"
      COLLECTIONS: "crowdsecurity/traefik crowdsecurity/http-cve"
    volumes:
      - /var/log/crowdsec:/var/log/crowdsec:ro
      - /opt/crowdsec-db:/var/lib/crowdsec/data
      - /var/log/auth.log:/var/log/auth.log:ro
      - /opt/crowdsec:/etc/crowdsec
    restart: "unless-stopped"
    labels:
      - traefik.enable=false

This is a simple docker container to control our access. This will be mostly dormant until we enable services in the next part1!

Stay Tuned

This is a solid point to leave the post here. If you want to get notified of part 2, and all other posts, subscribe below!