Skip to content

DatasetteHostingConstraints

Dennis Lee edited this page Jul 23, 2026 · 1 revision

title: Datasette Live-Plus-Static Hosting radar_quadrant: Techniques radar_ring: Assess radar_position: inner

Choosing a Hosting Approach for a Live-Plus-Static Datasette Setup

Summary

This document evaluates the hosting options for a single SQLite dataset that needs to serve two audiences at once: a private, login-gated live query interface, and a public, static browsable copy. The lens used throughout is each hosting platform's own published limits and terms, since these apply regardless of how good the application code is — a platform ceiling is a business constraint, not a bug to fix.

The setup has three hosting decisions, not one: where the live query server runs, where the public static copy is served from, and what happens with the underlying SQLite file itself, since some platforms impose limits on the file independent of where it's hosted.

Four hosting approaches are compared for the live-query layer, one option covers the static layer, and one shared constraint applies to the SQLite file no matter where any of this runs.

Approach What it hosts Ongoing cost Persistent write access Platform ceiling to watch
Approach A (Serverless — Vercel) Live query Usage-based, has a free tier No — stateless per request Deployment bundle size, execution timeout
Approach B (Persistent VM — Fly.io) Live query ~$2-3/month minimum, no free tier Yes — real attached disk None significant at small scale
Approach C (Self-hosted VPS) Live query, and optionally everything else ~$4-6/month minimum Yes — full control Everything becomes your responsibility
Approach D (Static hosting — GitHub Pages) Public static copy Free Not applicable — read-only Site size, bandwidth, no commercial use
Shared constraint The SQLite file itself N/A N/A Per-file size limit wherever it's committed to Git

Approach A: Serverless hosting for the live query layer (Vercel)

The live-query Datasette server runs as a cloud function that starts up fresh for each request rather than staying running continuously.

Platform limits: the deployment package is capped at 500 MB for this kind of workload (larger sizes exist on a newer opt-in tier, up to 5 GB), and each request has to finish within 300 seconds by default, extendable on paid plans. The bigger structural point is that this hosting model has no normal persistent disk — every request runs in a fresh, temporary environment. The SQLite file either has to be bundled into the deployment itself, counting against that size limit, or fetched fresh on each cold start.

Where the real cost shows up: this model is well suited to serving read-heavy queries against a dataset that doesn't change from request to request, but it is an awkward fit if anything is meant to write into the same file the server is reading from — there's no durable place for those writes to land between requests.

Approach B: Persistent virtual machine hosting for the live query layer (Fly.io)

The live-query Datasette server runs on a small, continuously-running machine with its own attached storage, rather than starting fresh per request, via datasette-publish-fly.

Platform limits: no meaningful ceiling at the scale this project would run at — a small machine plus a modest attached disk covers this comfortably. The provider no longer offers a free allowance to new accounts; expect a genuine, if small, recurring bill (roughly two to three dollars a month for the smallest machine, plus a small per-gigabyte storage charge) rather than the "free until you outgrow it" pattern the serverless and static options offer.

Where the real cost shows up: the appeal here is that the SQLite file can live on real, persistent disk and be both read and written by the same running process — this is the natural home for the live layer if a data pipeline is meant to write into the same file the login-gated interface serves from, rather than a separate copy.

Approach C: Self-hosted virtual private server

Instead of using a managed service purpose-built for hosting Datasette, a general-purpose rented server is provisioned and configured entirely by hand — installing the operating system's packages, the Python environment, the web server, and the login wall, all from scratch.

Platform limits: effectively none imposed by the provider — a basic virtual server of this kind (DigitalOcean's cheapest droplet, for reference) typically comes with a genuinely generous monthly bandwidth allowance and full control over what runs on it, for a price in a similar range to the persistent-VM option above. There is no equivalent of Apple's App Review, GitHub's commercial-use restriction, or a serverless bundle-size cap to work around.

Where the real cost shows up: removing the platform's guardrails also removes the platform's help. Every piece of operational responsibility that a managed service quietly handles — security patching, backups, uptime monitoring, restarting the process if it crashes, renewing the login wall's certificates — becomes the operator's job instead. This is the most flexible option and also the one with the least safety net; it suits a comfort level with ongoing system administration more than it suits a "set it up once and forget it" preference.

Approach D: Static hosting for the public copy (GitHub Pages, serving a browser-side Datasette)

The public, no-login copy runs entirely inside the visitor's own browser, loading the SQLite file directly rather than talking to any server the operator runs, via datasette-lite.

Platform limits, confirmed directly from the provider's own published limits: the published site is capped at 1 GB total, with a soft bandwidth allowance of 100 GB per month and a soft limit of 10 site rebuilds per hour, each of which must finish within 10 minutes. None of these are likely to bind at the scale of a personal dataset. The one restriction worth deliberately noting: this hosting is explicitly not licensed for commercial or e-commerce use — if the trading-card project were ever to become a storefront rather than a browsing tool, this specific hosting choice would stop being appropriate.

Where the real cost shows up: this option is free and has no ongoing hosting bill, but the tradeoff is that the public copy is a snapshot of the file as of whenever it was last published here — not a live mirror of whatever the login-gated server (Approach A, B, or C) is currently serving. Keeping the two in sync is a deliberate step, not something that happens automatically.

The constraint that applies no matter which live-hosting approach is chosen

This comes from GitHub's own file-handling rules and is unrelated to which live-hosting approach is picked, because it applies the moment the SQLite file itself is committed to a Git-based repository (which the static-hosting approach above requires, and which is a common way to move the file to other approaches too):

  • 50 MB triggers a warning but still succeeds.
  • 100 MB is a hard block — anything larger requires a specialized large-file storage add-on rather than a normal commit.
  • 25 MB is the cap specifically for files uploaded through GitHub's web interface, not relevant if the file is pushed through other means.
  • 1 GB is the informally recommended ceiling for an entire repository's size, with 5 GB described as a strong upper bound before it becomes a problem.

A trading-card dataset that includes images or other large embedded content could cross the 100 MB hard block sooner than expected — this is worth checking against the actual file size before assuming any Git-based hosting path is available at all.

The pipeline this hosting decision sits inside

The hosting choices above are the last step of a longer pipeline, not the whole picture. The full shape is: transform, explore, build, test, deploy. Naming each stage matters because two of the hosting approaches above (Approach A, stateless, versus Approach B or C, persistent) only make sense once it's clear what each stage actually produces and hands to the next one.

  • Transform. Raw data, including anything pulled from external sources, is reshaped into the SQLite file's tables. This stage is fully automated and repeatable.
  • Explore. This is the one stage that is deliberately manual and out-of-band from the rest of the pipeline — a person sits down on a Mac with a tool like Datasette (or a comparable data-exploration tool) and looks at the transformed data directly: slicing by column, checking groupings, spotting anything that looks wrong. Nothing here is scripted, and that's intentional; this stage exists specifically to put human judgment between raw transformed data and anything that gets shipped.
  • Build. Once exploration confirms the data is in good shape, this stage takes that confirmed version and produces the actual artifacts that will be tested — the difference from "transform" is that transform produces data to be reviewed, while build produces something meant to already be correct, ready for verification rather than inspection. Because the public static copy and the private live copy are not allowed to hold the same fields (see tensions below), build is also where the confirmed data has to split into two separate outputs: a full version for the live host, and a version with sensitive fields removed for the public static host.
  • Test. The built artifact is checked automatically before anything is deployed — this is where the confirmation from the manual exploration stage gets backed up by repeatable checks, so a later re-run of the pipeline doesn't silently drift away from what a person actually looked at and approved.
  • Deploy. The tested artifact goes to whichever hosting approach fits what it is. A live, queryable artifact goes to Approach B or C (the two options with persistent, writable storage); a static, browsable artifact goes to Approach D. This is also why Approach A (serverless, no persistent disk) sits awkwardly in this pipeline specifically — if the pipeline's job is to keep writing fresh, tested data into the live host on every run, the live host needs somewhere durable to receive it.

Other major tensions

These are flagged here as decisions someone will need to make, not resolved here — this document establishes what's at stake, not how to build the pipeline.

  • The gated copy holds genuinely sensitive fields the public copy must not — and the public file is fully downloadable, not just queryable. The live, login-gated copy is expected to include fields like cost or owner that have no business being public. This collides directly with how the static layer works: datasette-lite doesn't just let visitors run read-only queries against a remote server, it downloads the entire SQLite file into the visitor's browser. That means anyone who can reach the public static page can also just download the whole database file directly — there is no partial-access mechanism at that layer, no query restriction, nothing short of the file itself not containing the sensitive fields in the first place. Access control cannot be bolted on after the fact here; it has to be a property of the file. This means the pipeline cannot deploy one shared SQLite file to both destinations — it needs a dedicated stage, before deploy, that splits the confirmed data into two separate artifacts: a full version (with cost, owner, and anything else sensitive) that only ever reaches the persistent-storage live host, and a redacted version, stripped of those fields, that is the only one ever allowed to reach the public static host. Skipping this stage doesn't create a smaller privacy risk, it creates a full data leak the moment the static page goes live.
  • Whether external data sources can legally end up on the public copy at all. The pipeline's transform stage is described as pulling data from external sources into the same SQLite file that ultimately feeds both the private live copy and the public static one. Those two destinations carry very different exposure: a source's terms might permit private, authenticated querying but prohibit public redistribution outright. Nothing in this pipeline currently checks that distinction before data reaches the public artifact — this is worth resolving before the first external source is added, not after the static copy is already public.
  • A manual stage inside an otherwise automatable pipeline. Exploration is deliberately a human-in-the-loop checkpoint, not a bottleneck to engineer away. Automating it — skipping straight from transform to build — would remove the exact judgment call the stage exists for. The tension is between pipeline speed and the value of a person actually looking at the data before it ships; resolving it by removing the human step defeats its purpose.
  • What "confirmed" means, and whether it's captured anywhere. If a person approves the explored data on their Mac, that approval currently lives in their head, not in the pipeline. Nothing described so far records which specific transformed dataset was actually looked at and approved before it moved on to build. Without that record, a future run could quietly build and deploy a dataset that was never actually reviewed by anyone, and there would be no way to tell the difference from the outside.
  • Two different environments doing the same kind of exploration. The Mac is used for manual exploration; if Approach B or C's login-gated live query interface is also used for ad hoc exploration after deployment, there are now two separate places data gets inspected, potentially on different versions of the dataset. Worth deciding whether the Mac-based exploration is the only trusted checkpoint, or whether the live hosted copy is also expected to be explored directly.
  • The static copy's staleness compounds with the pipeline's cadence. Section on Approach D already noted the public static copy is a snapshot, not a live mirror. Layered onto this pipeline, that means the public copy is only as fresh as the last time deploy ran for the static artifact specifically — if deploy to the live host and deploy to the static host don't happen on the same schedule, the two public-facing surfaces (login-gated live, public static) can show different versions of the data at the same time, which is a confusing state to be in without meaning to.
  • Build now produces two artifacts, not one, and deploy has to route each correctly. A live-hosted deployment (Approach B or C) receives the full SQLite file, written directly into persistent storage. A static deployment (Approach D) receives only the redacted file, published with an open CORS header. The risk this introduces is procedural rather than technical: if deploy is ever pointed at the wrong artifact — the full file reaching the static host, even once, even by accident — the sensitive fields it was supposed to protect become permanently downloadable to the public. This is worth treating as the single highest-stakes step in the entire pipeline.

Conclusion

There is no rejection-style gate in this comparison the way Apple's App Store review has one — every approach here is technically available, and the real differences are cost, operational responsibility, and how well each option matches "the pipeline writes into the same file the live server reads from" versus "the live server just needs to answer read queries."

Ranked against the stated requirements (a login-gated live query layer that a pipeline may write into, plus a free public static copy):

  1. Approach D (GitHub Pages, static) is the clear and only real choice for the public layer — it's free, has generous headroom for a personal dataset, and the one restriction that matters (no commercial use) is easy to plan around given the project's current shape.
  2. Approach B (Fly.io, persistent VM) is the best fit for the live-query layer if the data pipeline needs to write directly into the same file the login-gated interface serves. Its small recurring cost buys a real, durable disk and none of the operational burden of full self-management.
  3. Approach C (self-hosted VPS) ranks close behind Approach B on capability — it can do everything Approach B can, with more flexibility and typically a larger bandwidth allowance for a similar price — but ranks below it because it trades a managed service's guardrails for full personal responsibility for security, backups, and uptime. Worth choosing only if that ongoing operational work is genuinely wanted, not just tolerated.
  4. Approach A (Vercel, serverless) ranks last for the live-query layer specifically because of the pipeline-writes-into-the-same-file requirement — its stateless, no-persistent-disk model is a structural mismatch for that use case. It would rank competitively if the live layer only ever needed to serve read queries against a file updated through some other channel.

Radar Assessment

This entry is placed in Techniques because its subject is a hosting-decision method — evaluating where to run a live-plus-static data application by reading each platform's own published constraints — rather than any single tool. It is placed in Assess because no first-person production deployment has occurred yet; this document reflects research ahead of a hosting decision. Placement is inner, reflecting that every constraint cited traces directly to a platform's own documentation and is independently verifiable without requiring production experience to validate.

References

Clone this wiki locally