Why Self-Host Instead of Using Vercel
Managed platforms like Vercel and Heroku offer excellent developer experience, but they come with trade-offs that compound over time. Pricing gets unpredictable at scale. Background processes and databases either don’t fit the model or become expensive add-ons. Infrastructure tuning is either impossible or locked behind enterprise plans. For a portfolio with multiple live interactive demos — some of which run Docker containers, databases, and AI models — a managed platform would have been prohibitively expensive.
A raw VPS, on the other hand, has its own set of problems. Managing Docker Compose files, configuring reverse proxies, setting up Let’s Encrypt, and exposing the right ports while keeping everything else locked down is error-prone and tedious to maintain. I wanted the push-to-deploy convenience of a managed platform with the control and economics of running my own server. Dokploy gets most of the way there.
What the Platform Does
Every application and demo on this portfolio is deployed here. A push to the main branch triggers an automated build, containerization, and deployment. Dokploy handles the Traefik reverse proxy configuration and TLS certificate provisioning and renewal automatically — no manual proxy configuration, no cert management scripts.
The entire administrative surface of the server is invisible to the public internet. No SSH accessible on the public IP. No web admin interfaces on exposed ports. To anything scanning the internet, the server looks like a web host and nothing else. Administrative access goes through Tailscale.
The Stack
- Infrastructure: Hostinger VPS, Ubuntu Linux
- Deployment & Orchestration: Dokploy, Docker
- Networking & Security: Tailscale (WireGuard mesh VPN), UFW (Uncomplicated Firewall)
- Routing & Proxy: Traefik (integrated via Dokploy)
Dokploy over manual Docker/Traefik configuration was the right call. Manually wiring Docker Compose files, reverse proxies, and certificate renewal is tedious and failure-prone. Dokploy abstracts that complexity while preserving access to the underlying Traefik and Docker primitives when I need them. Understanding what’s happening underneath makes debugging straightforward; not having to configure it manually every time saves hours.
Tailscale over traditional SSH exposure: I configured the SSH daemon to bind exclusively to the private Tailscale IP address and used UFW to deny all inbound SSH from the public interface. Access is now cryptographically verified through Tailscale’s identity system, independent of my physical location or current IP address. Adding a new device to the tailnet takes two minutes.
Making the Server Invisible
The most important architectural decision was not “how do I secure this port” but “how do I make this port not exist from the outside.”
Botnet scanning is constant and automated. Exposing an SSH port — even on a non-standard port — means accepting a continuous stream of brute-force attempts. IP whitelisting is brittle when you’re traveling or working from different locations. The Tailscale approach eliminates the attack surface entirely rather than hardening it. The server simply doesn’t respond to public administrative requests.
The practical result is that I can SSH into the server from anywhere with an authenticated device and never worry about the firewall rules I set up six months ago.
What I’d Tell Someone Starting From Scratch
Security is an enabler, not a blocker. The setup process for Tailscale and UFW takes maybe an hour. What you get in return is the ability to deploy and iterate quickly without the background anxiety of “did I misconfigure something that’s now exposed?” That peace of mind has real value on day-to-day development velocity.
Abstractions are only useful when you understand what’s underneath. Dokploy is genuinely useful, but its value is fully unlocked when you understand the Traefik routing and Docker containerization it abstracts. When something breaks — and eventually something breaks — knowing the primitives is what makes debugging possible rather than guesswork.
The investment in a proper deployment pipeline pays off continuously. Time that would otherwise go to “how do I ship this code?” goes to “what should I build next?” That shift in focus compounds.
Where This Goes
As traffic to the portfolio and live demos grows, a single-node VPS becomes a single point of failure. Transitioning to a multi-node cluster setup for critical services would ensure fault tolerance and zero-downtime during hardware maintenance or unexpected traffic spikes — the natural next step once the current setup starts showing resource pressure.
Automated pull-request preview environments would mirror modern enterprise DevOps workflows and make it easier to validate changes before merging. The CI/CD integration is already in place; spinning up temporary, isolated versions of applications per PR is primarily a Dokploy configuration problem at this point.
A comprehensive monitoring stack — Prometheus and Grafana, or something lighter — would enable proactive infrastructure scaling before bottlenecks affect the user experience. Right now I’m flying mostly blind on resource utilization between deployments.