How I Use Tailscale to Host a Public App From My Laptop

I live in India, and getting a static IP at home isn’t straightforward. You usually have to go through customer care, sometimes upgrade to a more expensive plan, and even then it’s not always guaranteed. I wanted a simple setup to make one of my apps publicly accessible without paying a lot or dealing with all that.

So here’s what I did. I used Tailscale, AWS Lightsail, Docker, and Nginx Proxy Manager to expose my laptop to the internet, safely and securely. And right now, I’m running https://easyclientlog.com - next.js application and https://api.easyclientlog.com - ruby on rails application, using this exact setup.

Getting a Static IP

I signed up on AWS and went to Lightsail. Picked Mumbai as the region and selected the cheapest Ubuntu server. AWS gives you a static IP for free (in lightsail) as long as it’s attached to a running instance, and the server itself is free for the first three months.

This server acts like a relay. It’s the public-facing machine. I don’t run the actual app on it, it just receives the request and forwards it to my laptop at home.

Ruby `Data` Class – A Convenient Way to Create Value Objects

Ruby’s Data class was introduced in Ruby 3.2, offering a convenient way to define value objects. The concept of value objects was popularized by Martin Fowler and Eric Evans through their books and articles. In the real world, we often represent properties like coordinates on a map (x, y) or the speed of a car—using primitives such as integers or strings. While these work, they lack the semantic clarity and behavior of custom types. This is where value objects shine. They encapsulate meaning and behavior around a set of values.

Ruby’s Data class was created to provide a native way to represent such concepts. Struct does fullfil this requirement, except the part of immutability. Ruby Structs are mutable. Some example of use case for value objects are to represent money, email address, co-ordinates, etc.

Lets now explore ruby Data, keeping in my the characteristics of a value object as shared by Marin Fowler. A value object should have:

  • No Identity
  • Immutable
  • Equality by Value
  • Small and Simple
  • Reusable

Let’s explore how Ruby’s Data class supports these characteristics.

class Data

Data is a core Ruby class, so no external gems are needed.

Here’s a simple example:

1
MarsRover = Data.define(:name, :x, :y)

Understanding Ruby’s `tap` — A Powerful Debugging and Configuration Tool

Ruby’s Object#tap is a small but powerful method that often goes unnoticed. It allows you to “tap into” a method chain, perform some operation, and return the original object—regardless of what the block inside returns. This makes it particularly useful for debugging, configuration, or inserting side effects without breaking the flow of your code.

A Simple Use Case: Debugging

Take the following example:

1
n = [1,2,3].map { _1 % 2 }.map { _1 * 10 }

This returns [10, 0, 10]. But what if that’s not what you expected? You might want to inspect the result of the first map:

1
2
3
4
n = [1,2,3].map { _1 % 2 }
puts n.inspect
n = n.map { _1 * 10 }
puts n.inspect

Using tap, we can make this more elegant:

1
2
3
4
n = [1, 2, 3].map { _1 % 2 }
             .tap { puts _1.inspect }
             .map { _1 * 10 }
             .tap { puts _1.inspect }

Storing PostgreSQL Data in a Different Partition for Performance

What I am suggesting is a method of optimization not just for PostgreSQL but for other similar databases as well. This is a more primitive, hardware-level performance optimization where an exclusive partition or hard disk is dedicated just for the actual data. By doing so, all the read and write operations related to your data happen on that disk, while OS-level and software-level read/write operations happen on another disk. This approach also makes it easier to handle failure, monitor disk health, and isolate workloads effectively.

Why Store PostgreSQL Data in a Separate Partition?

Separating the data directory from the root filesystem has several advantages:

  1. Improved Disk I/O Performance: Storing data on a dedicated partition ensures that database read/write operations are isolated from the OS and application processes, avoiding competition for disk I/O.
  2. Efficient Disk Usage Management: By placing the data in a separate partition, you can allocate specific disk space for the database and prevent it from filling up the root partition.
  3. Better Backup and Restore Control: Storing data in a dedicated location simplifies backup processes and makes restoring data more efficient.
  4. Optimized Disk Types: Different types of storage media (e.g., SSD for faster reads/writes or HDD for archival data) can be used based on database needs.
  5. Isolation for Security and Resilience: A dedicated partition reduces the risk of system failure in case of database corruption or storage issues.

Tailscale for Single User Single Host

I often see articles discussing Tailscale in multi-device setups, but I wanted to share my experience of using Tailscale primarily with a single machine. To clarify, it’s not technically a single device, but a combination of my work computer, phone, and a 10-inch Android tablet.

The Problem: Inefficient Remote Access

I work from home, and my computer serves as both my personal and work machine. There have been many times when I’ve needed to assist colleagues or push changes while away from my desk. In emergencies, I used to rely on AnyDesk to remote into my computer via my phone or tablet. While AnyDesk and similar tools like TeamViewer provide visual control over the desktop, they often suffer from lag, disconnections, and poor responsiveness on weaker connections, making simple tasks like opening a terminal or editing a file feel like a chore. The lag becomes especially pronounced when navigating large projects or deploying code, turning a few-minute task into a frustrating, time-consuming ordeal.