$ cat writing/deploying-on-a-budget.md

Deploying React on a Budget

Feb 2026
4 min read 16 views

Deploying React on a Budget

I recently decided to move my portfolio from Netlify to a self-managed VPS to better understand the deployment lifecycle. Transitioning to a cost-effective cloud instance (a small Google Cloud Compute Engine VM) was a great learning experience that came with its own set of technical hurdles.

How it Started

After provisioning my GCP VM, I ssh-ed into it and updated the system and installed Nginx:

sudo apt update
sudo apt install nginx -y

Next, I installed Node.js using NVM, cloned my repository, and attempted to build the application:

npm install
npm run build

The first issue arose immediately: compatibility. I had to downgrade to Node.js 15 to match the environment requirements of the original portfolio. After the downgrade, I ran the build again, but the process hung indefinitely at Creating an optimized production build....

The Challenge: Memory Constraints

The root cause was RAM. My instance had only 1GB of RAM (extremely cheap). During the build process, Node.js attempts to load the entire application into memory for optimization, hitting the system limit and causing the process to freeze or be killed by the OS.

Step 1: Creating Swap Space

Since I couldn’t “download more RAM,” I implemented a Swap file. This allows Linux to use a portion of the hard drive as virtual memory—slower than RAM, but enough to prevent crashes.

I created a 2GB swap file with the following commands:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

Step 2: Optimizing Node Memory Usage

Even with swap space, Node.js has its own internal heap limits. I explicitly increased this limit using an environment variable to ensure the build could complete:

NODE_OPTIONS="--max-old-space-size=2048" npm run build

It was slower due to the disk I/O, but the build finally finished successfully.

Configuring the Web Server

With the build artifacts ready, I configured Nginx to serve the static files:

sudo nano /etc/nginx/sites-available/default

I updated the root directive to point to the production build directory:

root /home/otagera/portfolio/build;

After restarting the service (sudo systemctl restart nginx), the site was live on the server’s IP address.

DNS and Security

Finally, I pointed my domain (otagera.xyz) to the server’s External IP using an A Record. To secure the site, I used Certbot to install a Let’s Encrypt SSL certificate:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx

I enabled the “Redirect” option to ensure all traffic is automatically upgraded to HTTPS. The result is a fully self-hosted, secure portfolio running on a budget-friendly instance.

Note: Gemini was extremely helpful throughout this entire migration process.