Self-hosting my web app has been an incredible adventure—filled with learning experiences, moments of frustration, and the satisfaction of building something truly my own. In this post, I want to share my journey: why I decided to self-host, how I set it all up, and the important lesson I learned about knowing when to stop.
Why Self-Hosting?
For me, self-hosting was about more than just running my app on my own infrastructure; it was about understanding the systems behind the scenes and taking control of my stack. Here’s what motivated me:
A Learning Experience
There’s no better way to understand how web systems work than to build and manage them yourself. Self-hosting pushed me to dive deep into networking, system administration, and containerization. It’s not just about running code; it’s about understanding where and how it runs.
Freedom from Cloud Vendors
Platforms like Vercel and AWS Lambda are great, but they often come with limitations and hidden complexities. For example:
- Vercel uses Lambda functions, which are excellent for stateless operations but tricky when you need persistent connections.
- Want to add a job queue? You’ll probably need a third-party service like Upstash (Redis as a service).
- Need real-time functionality with WebSocket? That’s another service to integrate (though you can get away with Server-Sent Events, it’s not ideal).
With every dependency on a managed service, you’re adding costs, potential bottlenecks, and more moving parts to your stack. Self-hosting gave me the freedom to simplify and control all these features myself.
Removing Pain Points
By hosting everything myself, I could avoid the frustration of stitching together multiple services. Instead of managing a web of dependencies, I’ve built a cohesive system that works well for my needs.
My Setup
After years of trial and error, I’ve settled on a self-hosted setup that’s versatile, scalable, and—most importantly—works for me. Here’s how it’s structured:
Infrastructure
I run everything on three VPSs (Virtual Private Servers) hosted on Hetzner, but DigitalOcean or OVH would work just as well. To distribute traffic and ensure reliability, I use a managed load balancer in front of the VPS cluster. This provides a good balance of simplicity and scalability.
Docker Swarm
To orchestrate my services, I use Docker Swarm. While Kubernetes is powerful, I found Swarm to be simpler and perfectly capable for my needs.
Services
Each VPS runs several Docker containers, which form the backbone of my self-hosted setup:
- Reverse Proxy (Traefik): This allows me to manage subdomains easily and route traffic to the right service.
- Postgres: My relational database of choice, rock-solid and reliable.
- Node.js tRPC Service: This handles real-time events via WebSocket for apps requiring live updates.
- Redis: Keeps WebSocket connections in sync across all three VPSs.
- Email Service: A Node.js service that integrates with Resend and React Email to handle transactional emails.
- Job Queue Service: Another Node.js service using Postgres with
SKIP LOCKED
for efficient job management. (I’ve written about this setup in more detail in another blog post.)
This stack allows me to run everything I need, from real-time apps to background jobs, with minimal reliance on external services.
Knowing When to Stop
One of the biggest challenges of self-hosting isn’t the technical work—it’s knowing where to draw the line. It’s all too easy to fall into the trap of trying to self-host everything, but at some point, the inconvenience outweighs the benefits. Here’s how I’ve defined my boundaries:
Email Hosting
Self-hosting email is a nightmare. Between configuring DNS records, managing spam filters, and dealing with blacklists, it’s virtually impossible to do reliably without significant effort. Even long-time advocates for self-hosted email, like Carlos Fenollosa, have given up. I use a service like Resend for this, and it’s worth every penny.
Monitoring
When things go wrong, you need to know immediately. Application monitoring must live outside your infrastructure to be effective. For this, I use Sentry. It’s reliable, feature-rich, and saves me from spending hours debugging in the dark.
Other Considerations
There are some services that are simply not worth self-hosting due to their complexity or the risk of downtime. For example:
- Payment gateways
- Video encoding
- Global CDN services
Final Thoughts
Self-hosting is a rewarding journey, but it’s not for everyone. It takes time, patience, and a willingness to embrace complexity. For me, the benefits of learning and having full control over my infrastructure far outweigh the challenges. That said, it’s essential to know when to stop and let third-party services handle the things they’re good at.
If you’re thinking about self-hosting your app, start small, define your boundaries, and enjoy the process. It’s a journey worth taking.