I Moved My Site Off Managed Hosting. Here's What I Found.
Why I moved my personal site to a self-hosted Linode VPS with Dokploy, and what I found when I finally had access to the full stack.
There’s a specific kind of comfort that comes with managed hosting.
You push code. Something happens somewhere. The site is live. You don’t know the details and, more importantly, you don’t need to. The abstraction is the product.
I was fine with this for a long time. My personal site ran on an edge hosting platform, auto-deployed from GitHub, globally distributed, free tier, zero maintenance. It worked. I had no complaints that felt urgent enough to do anything about.
That’s the thing about comfortable abstractions. They don’t break dramatically. They just make you a little blind.
The decision to move wasn’t dramatic either. I already had a Linode VPS (Akamai Cloud) running other things: Dokploy, a few Docker containers, some personal tooling. The question that started nagging at me was: why is my own website the one thing not on infrastructure I actually control?
I couldn’t give myself a good answer. So I moved it.
The first thing I discovered: the move was mostly already done.
The repo had a Dockerfile and an nginx.conf sitting in it, written months earlier and forgotten (back when I’d thought about this and not followed through). A multi-stage build, Node for the Astro build step, then nginx Alpine to serve the static output.
The nginx.conf had everything. Gzip. Cache headers. Security headers: Content-Security-Policy, Strict-Transport-Security, X-Frame-Options, the full set. It even had a CSP that referenced my Linode Object Storage bucket by name, which tells you something about how far along this had gotten and then stalled.
I hadn’t touched those files in months. They just sat there, quietly being correct.
Switching the DNS was the actual moment of commitment. One DNS record deleted, one A record created pointing to 192.46.210.163, with the CDN proxy layer still sitting in front of the origin. The site didn’t go down. The transition was instant.
What I hadn’t accounted for: I’d never verified the nginx config was actually working. The site was live. But when I checked the response headers, the security headers weren’t there.
Turns out there’s a non-obvious nginx behaviour: if a location block defines any add_header directives, it silently overrides every add_header directive in the parent server block. My location / had cache-control headers. That was enough to wipe out all six security headers sitting above it in the config.
They were there. They just weren’t being applied. Had been sitting like that since June.
The fix was straightforward: move the security headers into each location block directly. But the failure mode was quiet enough that I’d never have caught it without checking, which I’d never have thought to do on managed hosting where you don’t see the raw response.
Owning your infrastructure means you find the things that were always broken.
Dokploy handles the deployment layer: Traefik for routing and SSL, Docker for the container, a GitHub webhook so git push still triggers a deploy exactly like it did before. The operational experience is nearly identical to managed hosting. Except the machine is mine, the logs are mine, and I know what’s running on it.
Let’s Encrypt handles the certificate now. Traefik requests it automatically when the domain is configured, and the certificate lives on the origin server rather than at an edge somewhere I’ll never see.
The CDN layer is still in front. Performance globally is unchanged. Static assets still serve from edge nodes, TTFB is still low. What changed is that Linode is the origin instead of a managed platform. Everything between the CDN and the user stayed the same.
I don’t think managed hosting is wrong. For a team, for a site that needs a CMS, for anyone who doesn’t want to think about servers, it earns its place.
But I’d been treating my personal site like something that should require as little thought as possible. Which meant I’d been avoiding understanding it. The nginx bug is a good example: it had been quietly wrong for months. On managed hosting, that either wouldn’t have happened, or I’d have never had the surface area to find it.
There’s something useful in seeing the full stack. Not because self-hosting is inherently better, but because the things that break teach you something. And things that break silently on infrastructure you can’t inspect just stay broken.
The site is files now. I know what serves them, where the cert comes from, what headers are on the response. That’s a small thing. But it’s a satisfying one.