Personal hosting setup

July 10th, 2024...

DockerSelf-hostingOracleNGINXCloudflare

Introduction

As a prolific builder, I've created numerous projects that often remain unseen. A couple of years ago, I decided it was time to showcase my work to the public by hosting some of these projects online. However, with limited funds available, I needed a cost-effective solution that wouldn't require investing in an expensive dedicated server or a high-powered VPS. The setup I am going to describe below helped me run various projects, including Node.js and Rust APIs, simple frontends, and even some modded Minecraft servers for a couple of my friends.

Fortunately, Oracle Cloud was offering an appealing free tier plan at the time, featuring their new ARM-based CPUs. This package included 4 vCPU cores, 24 GB of RAM, and 200 GB of storage space—more than sufficient for my low-traffic projects. This generous allocation allowed me to launch my portfolio without incurring any costs, making it an ideal starting point for sharing my creations with the world.

Discovering nginx-proxy

Given that I was already quite familiar with Docker and docker-compose, containerizing my projects was straightforward. However, the real challenge lay in managing a Nginx reverse proxy and allocating subdomains to each project. Manually configuring this setup can be time-consuming and error-prone, so I decided to find an automated solution.

My search led me to nginx-proxy, an ingenious tool that automates Nginx proxy configuration for Docker containers using docker-gen. This project, initiated by Jason Wilder in 2014, was born out of the need for a more efficient approach to container management. Wilder detailed his rationale for developing nginx-proxy and docker-gen in a blog post published that same year, highlighting the common pain points it aims to address. Another cool tool is acme-companion. This is a companion container that runs alongside nginx-proxy and automates ACME SSL certificate generation for running containers.

Setup and workflow

The configuration turned out to be quite straightforward and without any difficulties, owing to the comprehensive instructions provided in the acme-companions readme on GitHub. There were only 2 initial setup steps. First, the nginx-proxy container needed to be started with the following command:

$ docker run --detach \
    --name nginx-proxy \
    --publish 80:80 \
    --publish 443:443 \
    --volume certs:/etc/nginx/certs \
    --volume html:/usr/share/nginx/html \
    --volume /var/run/docker.sock:/tmp/docker.sock:ro \
    nginxproxy/nginx-proxy

Next, the acme-companion container was started with the following command:

$ docker run --detach \
    --name nginx-proxy-acme \
    --volumes-from nginx-proxy \
    --volume /var/run/docker.sock:/var/run/docker.sock:ro \
    --volume acme:/etc/acme.sh \
    --env "DEFAULT_EMAIL=mail@yourdomain.tld" \
    nginxproxy/acme-companion

Yes, it's quite good. Here is a slightly refined version for clarity:

The email address is used to notify users about expiring certificates. The acme-companion container automatically generates SSL certificates for any container that has both the VIRTUAL_HOST and LETSENCRYPT_HOST environment variables set. These variables should be set to the domain name you want the project to be accessed by. For instance, if you want to use api.yourdomain.tld for your API, you should set the VIRTUAL_HOST and LETSENCRYPT_HOST environment variables to api.yourdomain.tld.

For easier management of all the run commands, I have created a private repo where I host all the setup scripts for my projects. A sample script for running one of my Rust projects, compiled to Wasm:

Docker run script

Furthermore, in addition to the two environment variables previously mentioned, I have set the VIRTUAL_PORT environment variable to the port number that the project exposes in the Docker container, in this instance, port 80.

Conclusion

This setup has been a game-changer for me. It allows me to host multiple projects on a single VPS without manually configuring Nginx for each one. The automated SSL certificate generation is a huge time-saver, eliminating the need to worry about renewing certificates manually. I highly recommend this setup to anyone looking to simplify their hosting workflow and focus more on building great projects instead of managing server configurations. While this setup may not be suitable for production-grade applications or multi-server environments, it has been a fantastic solution for personal projects and small-scale applications.