Self Hosting The Experience

Recently, I attempted to launch a website from the ground up with AstroJs. Opting to rent an AWS Lightsail instance with the initial goal…

Self Hosting The Experience

Recently, I attempted to launch a website from the ground up with AstroJs. Opting to rent an AWS Lightsail instance with the initial goal of saving costs, I inadvertently ventured into a daunting journey. I had to set up everything from scratch, and the process nearly exhausted me.

What has gone wrong?

What i wanted to do

In my prior post, I leased a LightSail instance for $10, primarily for VPN hosting. However, I felt it would be wasteful to use the instance solely for VPN purposes when there’s ample computing resources available. Consequently, I decided to use the remaining resources to host my own website on the VPS. This approach not only maximizes cost efficiency but also allows me to make use of available resources.

The Techstack

I opted for AstroJS due to its Server-Side Rendering (SSR) capability, catching my attention with its support for React code. It provides a gentle approach to incorporating more sophisticated elements, should I require them.

However, for server-side rendering in Astro, it requires a runtime adapter during deployment. I opted for Deno, considering its free tier sufficient for my project’s needs. The adapter calls an API written in .NET Core to retrieve data for server-side rendering. My intention was to deploy both these projects on my VPS. However, with just a static IP address (Lightsail provides a static IP address without TLS), accessing the page became inconvenient without a domain.

Getting started

Initially, I intended to deploy my AstroJS to Deno and a .NET Core API to my LightSail instance. Things started off smoothly as I got the front-end template from their official website, needing only a few tweaks to match my preferences. Similarly, I had some boilerplate code for my .NET Core API project from a previous project. My estimation was about 2 or 3 days to complete the project, working around 3 hours a day. However, things took an unexpected turn from this point onward.

Nginx and Firewall

I chose Nginx as a reverse proxy due to its strong reputation. However, mastering it involved overcoming a steep learning curve. I had to extensively study its documentation to effectively set it up, dealing with elements like conf.d, directives, blocks, servers, and more. In contrast, configuring the firewall was comparatively straightforward.

Code deployment

I aimed to configure my VPS to function with Github Action, but considering my small project and the infrequency of my git commits, I anticipated a substantial effort. Consequently, I opted to establish an FTP server on my VPS instead. This allowed me to compile my code locally and transfer the resulting binaries to my VPS over the internet using the FTP protocol. Although I couldn’t gauge the time saved compared to setting up my machine for Github, configuring the FTP server proved to be a time-consuming endeavor, given my unfamiliarity with both options.

Writing Systemd service

After experiencing a reboot and crash, I needed a solution to automatically initiate my .NET Core app upon system startup, eliminating the need for manual SSH access to my VPS instance for command-line startup. My resolution involved crafting a systemd service. Delving into the documentation and implementing it was a time-consuming process. The permissions aspect posed a significant challenge — I could run “dotnet myapp.dll” flawlessly via the command line, but encountered issues replicating this command within the systemd service. Hence, I had to resort to analyzing journalctl logs to comprehend the issues my systemd service was encountering. Through numerous reboots and meticulous examination of journalctl logs, I eventually managed to resolve the discrepancies and successfully execute the application through systemd.

Serving images

Delivering images through Nginx was straightforward — I simply followed a clear guide and got it up and running smoothly. However, compatibility issues arose when I tried to integrate it with Astro, leading to complications that I’ll elaborate on in the following section.

Static IP with no domain and TLS

I desired a domain for my project because even a playful one feels more substantial than a simple IP address, right? However, acquiring a free domain entailed extensive setup. Consequently, I decided to deploy my AstroJS project to Deno, a process that required just a few clicks to integrate with Github. This resulted in my AstroJS project running smoothly, now accessible via a whimsical domain that boasted default TLS encryption.

My usage of AstroJS to invoke my .NET Core project initially led me to believe that a domain and TLS capabilities were unnecessary. However, I soon discovered my mistake. While Deno worked flawlessly for server-side rendering within the Astro project and invoked the API endpoint using HTTP without any issues, my website contained dynamic content that required AJAX calls directly from the browser to the .NET Core project.

This is where the problem arose: the browser protested, indicating something along the lines of “the site was initially loaded with HTTPS (provided by Deno), but then called an HTTP endpoint, so I (the browser) rejected it.” Despite hours spent grappling with this issue, attempting to set up free TLS proved overly complex and often led to dead ends.

My avoidance of TLS wasn’t due to the difficulty but rather the considerable time it consumed in comparison to the entire process.

Once more, another issue emerged when I attempted to serve images from my previously mentioned VPS. The browser expressed its dissatisfaction as the site loaded initially through HTTPS, yet the images were being loaded via HTTP only, leading to yet another misstep on my part.

I contemplated deploying Astro directly on the VPS itself, but this approach contradicted the purpose of utilizing Deno. As I lacked a domain and preferred not to access my page through an IP address, this option seemed less feasible. Furthermore, shifting Deno to the VPS would necessitate configuring Nginx to serve all the assets for the site, adding another layer of complexity to the process.

I ended up using Cloud services

After grappling with endless issues for numerous days, I had an epiphany that my project could have been completed much earlier. The only factors hindering its progress were the infrastructure and deployment procedures — not the coding aspect. Surprisingly, my website was exceptionally simple, taking merely around 3–4 hours of typing to develop. This realization felt counterintuitive, as my focus should ideally be on coding and the business rather than getting entangled in unrelated technicalities.

What struck me was the operational expense (OpEx) nature of cloud services, where you solely pay for what you use. Given that my project wasn’t extensive and wouldn’t generate substantial traffic, the cost estimation hovered around a mere $1 — $2 per month.

Deno

Deno provides a free tier that accommodates up to 1 million operations per month, which significantly exceeds the needs of my small-scale project. As mentioned earlier, my project doesn’t generate substantial traffic, making this ample allocation far beyond what I actually require.

AWS App Runner

I opted to run my .NET Core project using AWS App Runner, which meant that if my website didn’t handle any requests, there would be no associated costs. The process took approximately 2 hours, involving the creation of a Docker Image for my project, followed by its upload to the AWS Container Registry. Using AWS App Runner required minimal additional configuration, primarily specifying the port my app listens on. Moreover, App Runner conveniently provided a straightforward domain name with SSL capabilities, marking a clear victory for my deployment process.

AWS S3

Utilizing S3 for image storage, AWS demonstrates its generosity by providing 5GB of free storage. I managed to upload all my images to the S3 Bucket with just approximately 10 lines of C# code. Consequently, all the images became readily available for serving via URL with SSL encryption right away.

The most challenging aspect of this approach, in my experience, was acquainting myself with how AWS functions and operates, particularly concerning roles, users, and IAM-related functionalities. While I encountered some difficulty configuring credentials for my AWS CLI, it paled in comparison to the complexity involved in setting up the entire Ubuntu machine for project deployment using a VPS.

Summary

The lesson learned from this experience is that VPS hosting can be arduous and time-consuming when configuring everything. As evident from my own journey, I reached a point of giving up due to the substantial time and effort it demanded compared to actual coding. Despite this, utilizing cloud services proved more cost-effective for my small-scale project, which didn’t require extensive computing power.

For startups, leveraging pre-existing services to swiftly create an MVP is advisable. In summary, while on-premises and VPS hosting might present challenges, they offer valuable opportunities to acquire new concepts and knowledge along the way.