Skip to content

About

Open dataset: self-hostable apps whose published install instructions were run on a real, fresh server — with measured idle RAM and disk. CC BY 4.0.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

SelfHost Atlas — install-verification dataset

DOI

An open dataset of self-hostable applications whose published install instructions have been run on a real, fresh server — recording which OS the run happened on and when.


What is measured, and what is not

This is the part that matters. The dataset deliberately contains only what the project actually observed, plus values that are clearly labelled as documented rather than observed.

Status Fields Meaning
Measured install_verified_os, install_verified_date A real install run happened: the app's published install snippet was executed on a cloud VM running that OS, on that date, and every step exited successfully.
Measured measured_ram_mb, measured_disk_mb, measured_box, measured_on Observed footprint at idle, immediately after first boot, on the machine named in measured_box. Taken on a box pruned of every prior app, so the disk figure is this app's alone. Never under load, and never a capacity recommendation.
Documented, not measured documented_min_ram_mb The minimum RAM the upstream project documents, or SelfHost Atlas's sizing recommendation where upstream documents none. Values sit on standard instance tiers (256/512/1024/2048…). This is a recommendation, not an observation. Do not cite it as measured memory usage.
Catalog metadata slug, title, category, license, repo_url, website_url, is_docker_ready, content_updated Facts about the project and about our page, not measurements of a running system.
Not present CPU usage, setup time, maintenance time These are not yet measured, so they are not in the export at all — not even as null columns. They will appear only once real measurements exist.

documented_min_ram_mb and measured_ram_mb answer different questions and routinely disagree by an order of magnitude. Vaultwarden documents 256 MB and idles at 7 MB; PhotoPrism documents 2048 MB and idles at 102 MB. Neither figure corrects the other: size a server with the documented minimum, budget for the real world with the measured one. A row can carry one, both, or neither.

scripts/export-dataset.ts refuses to publish a field listed as measured if that column is empty in the database, and tests/export-dataset.test.ts fails if an empty column is ever added to the export. That guard exists because publishing a documented value as a measured one would be a false claim.

Field reference

Field Type Nullable Description
slug string no Stable identifier. Page: https://selfhostatlas.com/self-host/<slug>
title string no Project name as commonly written
category string yes Category slug (e.g. photos, password-managers)
license string no SPDX-style short identifier (e.g. AGPL-3.0, MIT)
repo_url string yes Upstream source repository
website_url string yes Upstream project website
is_docker_ready boolean no Upstream publishes an official container image or Compose file
documented_min_ram_mb integer no Documented/recommended minimum RAM in MB — see the table above
install_verified_os string yes OS the install run happened on (e.g. Ubuntu 26.04). Null = not yet verified
install_verified_date date (YYYY-MM-DD) yes Date of that run. Null = not yet verified
measured_ram_mb integer yes Observed idle container memory (sum of docker stats usage) 30 s after first boot, in MB. Null = not measured
measured_disk_mb integer yes Observed image + volume footprint, in MB. Null = not measured. May be null while measured_ram_mb is set, when a run's disk reading was not isolated
measured_box string yes The machine the figures were taken on, e.g. GCP e2-standard-2 · Ubuntu 26.04 LTS · Docker 29.7.2. Non-null whenever either figure is — an unattributed measurement is not published
measured_on date (YYYY-MM-DD) yes Date of the measurement run
content_updated date (YYYY-MM-DD) yes Date the SelfHost Atlas page for this app was last editorially reviewed

Judge freshness from install_verified_date: an install verified a year ago says little about today's upstream instructions. Every record carries its own date precisely so you can filter on it rather than trust the dataset as a whole.

Records with a null install_verified_os / install_verified_date are included so the catalog is complete and the unverified share is visible. Filter them out if you only want verified rows.

How the numbers are taken

Everything measured comes from one harness, scripts/verify-install.ts, run by scripts/sweep-installs.sh against a throwaway cloud VM:

  1. Clean host per app. One throwaway VM, reset between apps — containers, volumes, networks, images and build cache pruned — so nothing from a previous app is counted.
  2. Install verification. The app's own documented install commands are executed end to end — placeholder hostnames swapped for a real one, recorded in the transcript — under a hard timeout. If every step exits successfully, install_verified_os / install_verified_date record the host OS and the run date. A failure normally leaves both null; interactive installers are driven through a terminal.
  3. RAM. After the install completes the harness waits 30 seconds, then sums the memory usage docker stats reports for every running container. That is container memory at idle, immediately after first boot — not RSS of a single process, not peak, not under load.
  4. Disk. The sum of docker system df sizes (images, containers, volumes, build cache) on the pruned box, so the figure is this app's footprint alone.
  5. No number without its machine. If either figure is recorded, so are measured_box and measured_on. If nothing parses, the harness records nothing rather than a zero.

Anything not produced by that process — CPU, install wall-clock time, steady state usage — is not in the file.

Load it

import pandas as pd
df = pd.read_csv("install-verification.csv", parse_dates=["install_verified_date", "measured_on"])
df[df.measured_ram_mb.notna()][["title", "documented_min_ram_mb", "measured_ram_mb", "measured_box"]]
-- DuckDB
select title, documented_min_ram_mb, measured_ram_mb
from read_csv_auto('install-verification.csv')
where measured_ram_mb is not null
order by documented_min_ram_mb / measured_ram_mb desc;
jq '.records[] | select(.measured_ram_mb != null) | {slug, measured_ram_mb, measured_disk_mb}' install-verification.json

Or open it in the browser with no install, via Datasette Lite: https://lite.datasette.io/?csv=https://raw.githubusercontent.com/usualdesigner/selfhost-atlas-data/main/install-verification.csv.

Cite this dataset

Cite the copy you used: quote schema_version and generated_at from the JSON, because rows change between exports.

SelfHost Atlas (2026). SelfHost Atlas install-verification dataset (schema 1.1.0, generated YYYY-MM-DD) [Data set]. https://doi.org/10.5281/zenodo.22983606. Licensed CC BY 4.0.

@misc{selfhostatlas_install_verification,
  author       = {{SelfHost Atlas}},
  title        = {SelfHost Atlas install-verification dataset},
  year         = {2026},
  doi          = {10.5281/zenodo.22983606},
  howpublished = {\url{https://doi.org/10.5281/zenodo.22983606}},
  note         = {Schema 1.1.0, generated YYYY-MM-DD. CC BY 4.0.}
}

GitHub's Cite this repository button (from CITATION.cff) gives the same in APA and BibTeX. Each GitHub release is archived on Zenodo; the DOI above always resolves to the latest version.

JSON envelope

{
  "schema_version": "1.1.0",
  "generated_at": "2026-09-26T21:32:47.328Z",
  "license": "CC-BY-4.0",
  "attribution": "SelfHost Atlas — https://selfhostatlas.com",
  "methodology": "https://selfhostatlas.com/how-we-test",
  "record_count": 118,
  "records": [ /* … sorted by slug … */ ]
}

schema_version follows semver: a major bump means a field was removed or its meaning changed; a minor bump means a field was added. Records are always sorted by slug, so a re-export diffs cleanly.

The CSV carries the record fields only, with a header row, RFC 4180 quoting and the same field order. It has no envelope — take generated_at and schema_version from the JSON.

Update cadence

Regenerated when the catalog changes — in practice after each install-sweep run and each content wave, roughly monthly. generated_at in the JSON is the authoritative export timestamp; the per-record install_verified_date is what tells you how old any individual observation is.

Catalog dataset

A second, independent export: catalog.json / catalog.csv, generated by scripts/export-catalog.ts (also run by npm run data:export). Same directory, same CC BY 4.0 licence, same attribution requirement as the install-verification dataset above — but its own contract: catalog metadata, not "measured install verification".

Field Type Nullable Description
slug string no Stable identifier. Page: https://selfhostatlas.com/self-host/<slug>
title string no Project name as commonly written
category string yes Category slug (e.g. photos, password-managers)
license string no SPDX-style short identifier (e.g. AGPL-3.0, MIT)
stack string[] no Runtime/language tags (e.g. ["node", "postgres"])
is_self_hostable boolean no The app can be run entirely on your own infrastructure
is_docker_ready boolean no Upstream publishes an official container image or Compose file
difficulty integer no Editorial setup-difficulty rating
documented_min_ram_mb integer no Documented/recommended minimum RAM in MB — a recommendation, not a measurement (see the install-verification table above)
alternative_to_saas string[] no Slugs of the SaaS products this app is pitched as an alternative to, in editorial order. Empty array if none

Not present: is_externally_dependent (whether the app depends on a third-party hosted service it doesn't control) and sso_support (none / built-in / plugin / enterprise-only) are catalog facts the site tracks in apps, but they are 0/N populated in the database as of this export — the classification backfill hasn't run yet — so, matching the install-verification table's own omission precedent above, they are left out entirely rather than published as an always-null "not yet classified" field. They'll appear in a future export once real classification data exists.

How to attribute

Required by CC BY 4.0. Any of these satisfies it:

Prose

Install-verification data by SelfHost Atlas, licensed under CC BY 4.0.

Markdown

Data: [SelfHost Atlas install-verification dataset](https://selfhostatlas.com) ([CC BY 4.0](https://creativecommons.org/licenses/by/4.0/))

HTML

<p>Data: <a href="https://selfhostatlas.com">SelfHost Atlas</a>,
<a href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a></p>

If you modified the data, say so ("adapted from…"). A link back to https://selfhostatlas.com is the credit; there is no other condition.

Corrections

If a record is wrong — an install that no longer works, a licence change, a dead repo URL — open an issue or email hello@selfhostatlas.com. Corrections land in the catalog and appear in the next export.


The data files are generated by SelfHost Atlas and copied here verbatim on each export — please report problems as issues rather than editing the files, since a PR would be overwritten by the next export.

About

Open dataset: self-hostable apps whose published install instructions were run on a real, fresh server — with measured idle RAM and disk. CC BY 4.0.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors