busta.win: a self-hosted blog built with React Remix using the Edge

I moved my blog away from WordPress, building a self-hosted one using top modern technologies like React Remix, server-side rendering, and the edge computing.

I want something small and joyful to code and blog with total creative freedom, putting my grain of sand to bring back an indie Internet that empowers people to do good.

With this post, I want to introduce you to the main pieces this website is built upon (like the server) and how to put them together to run it. This is the high-level architecture of the website.

By setting things up the way I do, you have something fairly easy, efficient, secure, and modern, which then you can use as the basis of your own website.

And you know this works because you are reading from it.

Specifically, I focus on setting up the project and CloudFlare as a way to learn by example, leaving the low-level architecture, or how does my website internally work for another post.

I assume you Docker a bit.

What I want for my personal website

The goals of this project are:

  1. Give myself something basic that I can easily understand (e.g., posting in Markdown files).
  2. Have something highly customizable that does not meddle in my way, so I have full creative control.
  3. Allow complex UIs only when needed (e.g., some blog posts require complex graphs or tables)
  4. Something I can self-host, because there is no cloud, it is just someone else’s computer and I want to control my data—it is fun and cheap too.
  5. Lightweight to users, because people have unstable connections and the Internet has become a bloated mess.
  6. I find coding and blogging fun.

If some resonate with you, then keep reading.

The high level architecture

Considering these goals, I decided to build the blog with the following architecture, which I detail and explain how to set up in the following sections:

2. Docker Compose in a lightweight PC[rectangle]Server[rectangle]1. CloudFlare Edge[rectangle]CDN CacheReact RemixPosts in Markdown filesbusta.win domainZero TrustCloudflaredExpress ServerHTTP request from userSecure tunnel
This website's arquitecture seen by the data flow from the user requesting https://busta.win until the posts are read from a file.
  1. A free CloudFlare App that has:
    1. The busta.win domain.
    2. A CDN Cache that serves the blog to users. As the blog is mainly static files, the majority of times users hit only this cache, reducing access to our server drastically.
    3. Zero Trust, a service that creates a secure tunnel between the server and the Internet without having to configure routers or firewalls.
  2. A lightweight computer (a Raspberry Pi 4 in my case) running a Docker Compose with two services:
    1. Cloudflared, which connects the self-hosted website to the Zero Trust.
    2. An Express server serving the web. When the user accesses the website, Express calls React Remix to render the UI and the posts from the Markdown files.

Let us see in the following sections how to set up these pieces.

The cloudFlare App

In an ideal world, we would not need Cloudflare, as we would serve the website "the old way," connecting the server directly to the Internet by opening ports in the router.

However, this is insecure, and in my case, my lightweight computer is not powerful enough to handle traffic spikes. Moreover, we have to set up HTTPS and many other details better to avoid in the beginning.

We can use CloudFlare for free to handle all of the above, which is more simple and secure than doing it ourselves. As a drawback, they become a man-in-the-middle, so we lose some control of our data. Cloudflare becomes our proxy.

We set up CloudFlare by adding a website:

Adding a site
Adding a site. Checkout the 'Add a site' button at the top right of the screen.

Cloudflare has a guide on how to add a site.

The Domain

The domain is registered with CloudFlare, so we avoid touching any DNS settings. Of course, we can use any domain registrar and point the domain to Cloudflare.

Zero Trust

Once we have a website, we can link the website to our self-hosted server using Zero Trust. Zero Trust is a service to control access to our self-hosted server, so we mainly have to do two steps:

  1. Create a policy that allows anyone to access our website.
  2. Create the tunnel connecting the website to our self-hosted server.

Create a policy that allows anyone to access our website

  1. Enter into Zero Trust and complete the onboarding by choosing a free plan.
  2. Once you are in the Zero Trust dashboard, go to AccessApplications and click on Add an application.
    Adding an application
    Adding an application.
  3. Select Self Hosted and click Next. I left the defaults, configuring only the domain to be busta.win.
    Configuring the domain
    Configuring the domain.
  4. Create a policy that allows everyone to access the website.
    Creating a public policy
    Creating a public policy.
    Setting the policy for everyone
    Setting the policy for everyone.

Create the tunnel connecting the website to our self-hosted server

  1. In the Zero Trust panel, go to NetworksTunnelsCreate a Tunnel.
    Adding a tunnel
    Adding a tunnel
  2. Select cloudflared, which is the technology we use in the Server.
  3. Add the domain of the website, so Cloudflare knows what to use the tunnel for (i.e., providing for busta.win in my case). The busta.win:3210 piece will make sense later.
    Linking the tunnel to the busta.win domain
    Linking the tunnel to the busta.win domain
  4. The next page tells you how to install Cloudflared. The most important part is to copy the token, which we use later to link the cloudflared service in our self-hosted system with what we have just set up.

Running the self-hosted website

We can use busta.win as an example of how to run a website with cloudfared via Zero Tunnel.

With Docker, the trick is in the docker-compose of the project, which I detail after:

services:
  bustawin:
    build:
      context: .
      target: start
    volumes:
      - ./posts:/app/posts

  tunnel:
    image: cloudflare/cloudflared
    command: tunnel run
    env_file:
      - .tunnel.env
A simplified version of the docker-compose file of the project, used to run the self-hosted website for production.

This file runs two services:

  1. The self-hosted website itself (called bustawin).
    1. This service runs our Dockerfile, which runs the server at a docker’s internal network port of 3210.
  2. The cloudflared tunnel.
    1. The service has an env_file: .tunnel.env, which expects us to create a file called .tunnel.env with the token we got from Cloudflare.
    2. In the Cloudflare website we wrote busta.win:3210. This instructs cloudflared to link the public hostname of busta.win to the docker internal network port of 3210.

So, to recap, in Cloudflare we got a token that we add in our project. This links the project to Cloudflare’s Zero Trust. Then, in Cloudflare we told to set the busta.win domain to the Docker’s port of 3210, which is where our server is running—everything is linked now ✨.

So, to run the website:

  1. Add a .tunnel.env file with the Zero Trust token in the root of this project.
  2. Start Docker and run docker compose up.

Extras

This is just an introduction, so there are more things you can deep dive into:

Improving the cache

By going to your website in Cloudflare’s dashbaordCachingConfiguration you can turn on more caching features. For example, Always Online keeps delivering the cached version of our site even if our server is down, which is a real possibility for self-hosted home servers.

Analytics

Cloudflare provides basic Analytics that are enough to start with. Go to your website’s dashboard → Analytics & Logs. When ready to upgrade, there are more powerful, self-hosted, and privacy-preserving alternatives.

Security

Exposing anything on the Internet requires to follow good security practices.

Conclusions

We have seen how to set up a self-hosted website using Cloudflare’s free services by using this site—busta.win—as an example.

I hope you can use this basis to build your own website 🙂.

In a future post, I will talk about the low-level architecture of the website—how React Remix works and how I structured the code.


What do you think about it? Do you have any questions or feedback? If you have any website, how do you solve this? If you don’t have a website, am I enticing you?

Let’s have a conversation on Mastodon or Reddit.