Sebastiano Druda

← Back to writing

Field note / Tools, Infrastructure & Workflows

I Almost Used a VPS for a Static Website. I’m Glad I Didn’t.

A small visitor counter was pulling this portfolio toward a backend. Removing it made the hosting decision simpler without giving up the features that mattered.

The feature that nearly gave this website a production backend was a number in the footer.

The visitor counter was small from the reader’s perspective. Behind it sat a browser request, a Node service, persistent SQLite storage and deployment preparation involving Caddy and systemd. Those choices could form a reasonable system. They were a substantial amount of operating responsibility for a portfolio and publication that otherwise compiled into static files.

I had been preparing a VPS-oriented architecture. I changed that direction before treating it as the production requirement. The final target became static shared hosting on HostingPerTe, with builds and publishing handled through GitHub Actions.

This is not an argument that VPSs are bad. It is an account of why the additional control was unnecessary for this particular site, and why deleting a feature was more useful than optimizing the machinery around it.

List what the website actually has to do

The core product is straightforward: present professional information, show research, publish articles and help readers discover them through topics, archive pages and search. Navigation, responsive layouts and small visual enhancements run in the browser.

Nothing in that list requires a personalized server response. The article source lives in the repository. A build can create the pages and their metadata before anyone requests them.

Astro’s rendering documentation distinguishes its default prerendered output from routes rendered on demand. For this site, the useful choice was to keep the production output static. Node remains part of development and the build environment; it does not need to run on the shared host to serve the resulting pages.

Search did not force a backend either. Pagefind generates a static search bundle from built content, with queries handled in the reader’s browser. That matched the publication’s needs without introducing a separate search service.

Once I wrote those requirements down, the counter stood out. It was the feature asking production to remember something between visits.

A tiny interface can imply persistent operations

The counter’s value was modest: a rough aggregate indication of visits. Supporting it meant deciding how the number was stored, how requests were counted and what happened when the service failed.

A persistent service also needs an operating story. Who updates its runtime? How does it start after a restart? Where does its state live relative to deployed code? What needs a backup, and how is that backup restored? How do we know whether the service is healthy?

These are questions I would have had to answer for the proposed architecture. They are not a claim that this site had already suffered a production incident, or that SQLite itself was unsuitable. The issue was the relationship between the feature’s importance and the obligations it introduced.

I could have kept the metric and improved the implementation. Instead, I asked whether readers needed it. Removing it left the useful footer content intact: contact details, local browser time, timezone, UTC offset and the approved wordmark treatment.

The simplest infrastructure change was a product decision. That is easy to miss when the first instinct is to search for a smaller server or a more elegant deployment script.

Removal has to reach the repository

Hiding the number would not have simplified the system. The browser might still make a request. The backend might still run. The README might still tell a future maintainer to configure a database and start a service.

The cleanup therefore followed the feature through its dependencies. The footer element and fetch code were removed, along with the API integration, storage implementation, service entrypoint, dedicated tests, obsolete environment variables and VPS deployment artifacts.

Unrelated safeguards stayed. Ignoring database files in Git still makes sense as a general protection against accidental publication. A release guard can still reject private state even when the application no longer creates any. Removing a feature should not become an excuse to weaken the rest of the project.

The documentation also needed a single current answer. Historical decisions can remain useful when labelled as history, but the active deployment guide should not describe two incompatible production architectures.

That was a useful test of the change: could someone clone the repository and understand the current operating model without knowing the conversation that led to it?

Shared hosting still has a contract

Moving to static files does not eliminate deployment work. It changes the work that needs attention.

The host must serve the generated routes, handle HTTPS and the canonical domain, return a real 404 for missing pages and apply appropriate caching. The upload process must target the correct document root and preserve files managed by the provider.

For this site, the prepared transfer process uploads new or changed generated files and records ownership so that only known stale files can be removed later. It does not empty the hosting account before copying a build. That distinction matters on shared hosting, where the account may contain things the site does not own.

I also distinguish per-file replacement from a fully atomic release. Uploading a temporary file and renaming it can protect an individual replacement. It does not make an entire collection of pages and assets switch at one instant. A failed transfer can leave mixed versions and needs a documented retry or rollback path.

These are concrete limitations I can understand and test. They are preferable to assuming that “static” means deployment is automatically safe.

The useful tests followed the new boundary

A static migration should preserve the product rather than merely reduce the process count. The local audit compared the approved pages before and after the counter removal, exercised navigation and search, and verified the footer’s remaining behavior.

The server configuration was tested separately using local Apache. The transfer code was exercised against a temporary FTPS endpoint, including repeat uploads and preservation of unrelated files. Those checks addressed behavior that an Astro build alone could not establish.

They did not prove that the final provider account, certificates or DNS were ready. At the time this launch was being prepared, public hosting activation and destination verification remained separate from the completed local migration. I would not use a passing local test as evidence that traffic was reaching the intended server.

That distinction is part of the appeal of the simpler architecture: fewer kinds of runtime state, with the remaining boundaries made explicit. There is still plenty to verify, but the checks are tied to the actual delivery path.

Control is useful when there is something to control

A VPS would make sense for a different set of requirements. If I needed a persistent application process, custom system software or operating-system-level configuration, shared static hosting might be the awkward choice.

The point is to identify that need before taking on the environment. I do not want to run a server merely because I am capable of administering one, any more than I want to add a database merely because the hosting plan includes it.

Learning infrastructure can be a legitimate goal on its own. If that were the purpose of the project, the operating work would be part of the value. For this publication, I wanted a reliable place to write and present work. The infrastructure should support that activity without becoming another ongoing project by default.

I also wanted the decision to remain reversible. Keeping article source in Git and producing ordinary static output gives the project a path to another suitable host. That does not mean migration has zero work; redirects, certificates, caching and transfer access still need checking. It means the content is not dependent on an application database running inside the chosen hosting account.

There are still updates and backups

Static production does not mean a frozen toolchain. The repository still has dependencies, CI configuration and content that need maintenance. Builds should remain reproducible enough to diagnose a change, and credentials still need protection.

The recovery model is simpler, however. Source comes from Git. Public pages and search can be rebuilt. Provider backups remain useful for hosting-level mistakes, but there is no visitor database whose live state must be preserved across releases.

I would keep a record of a known-good revision and know how to restore it through the current pipeline. I would also avoid treating a source rollback as time travel: a scheduled publication gate evaluates the build time, so rebuilding an old revision later can legitimately include content that was future-dated at its original build.

That is another reason to understand the publishing workflow instead of viewing deployment as an opaque final command.

The website did not become less capable when the counter disappeared. It became more closely matched to its purpose. I still like the control a VPS offers. For this site, I prefer having fewer things that need my control in the first place.

Sources

  1. Astro: On-demand rendering — checked 2026-10-11
  2. Pagefind: Static search — checked 2026-10-11

Topics

37 / Essay

Own the Source, Rent the Infrastructure

Control can live in source, content and a portable build. A practical look at static hosting, independent recovery and the limits of infrastructure ownership.

self hosting · hosting · 7 min