Audit: selfh.st survey 2023-2025 read against Felhom; R-989 filed
gates / gates (push) Successful in 6m24s

Read-only. fetch.py + tables.py (stdlib), TABLE.md (45 charts, % of
respondents), FELHOM.md (apps, features, reasons, hardware, trends, ten
ranked recommendations), METHOD.md (hashes, counts, matching, MIT licence,
limits). Survey files are not committed. One register row (R-989) and one
STATUS line ask the operator to rule on the recommendations.
This commit is contained in:
2026-10-12 22:09:10 +02:00
parent c4d1b0ff98
commit a7f897a7dd
7 changed files with 1175 additions and 2 deletions
@@ -0,0 +1,122 @@
# selfh.st survey 2023–2025 — what it means for Felhom
Read-only, 2026-10-12. Numbers are **% of the year's respondents** (2023: 1,869 · 2024: 3,655 · 2025: 4,081),
from `TABLE.md` section **T*n***; each section cites its year's URL. The crowd is hobby users, not households
(`METHOD.md` limits). Catalog = the 62 template folders of `app-catalog-felhom.eu/templates/` at `cb4ececf1f`.
## 1. Apps (2025 Software charts + Favorite Software)
`✓ name` = we ship that template · `✗` = we do not · `n/a` = not a server app a household box would run.
| App | 2025 % | Source | Catalog |
|---|---|---|---|
| Home Assistant | 62.7 deploy · 14.4 fav | T30, T33 | ✓ home-assistant |
| Jellyfin | 60.9 · 12.8 fav | T25, T33 | ✓ jellyfin |
| Nextcloud (+ Nextcloud Notes 7.9) | 30.3 · 2.9 fav | T26, T28, T33 | ✓ nextcloud |
| Plex | 29.9 · 5.1 fav | T25, T33 | ✓ plex |
| Syncthing | 25.9 · 0.8 fav | T26, T33 | ✗ |
| Obsidian | 25.5 | T28 | n/a (desktop app; its sync server is a separate project) |
| Minecraft (game server) | 25.5 | T32 | ✓ crafty-controller |
| Gitea | 20.8 · 0.7 fav | T29, T33 | ✓ gitea |
| AudioBookshelf | 20.6 · 2.3 fav | T25, T33 | ✓ audiobookshelf |
| Navidrome | 16.1 · 1.0 fav | T25, T33 | ✓ navidrome |
| Forgejo | 15.0 | T29 | ✗ |
| Synology | 13.5 | T26 | n/a (NAS product) |
| FileBrowser / FileBrowser Quantum | 11.8 / 5.1 | T26 | built into every box (not a template; R-917 row text) |
| Immich | 10.5 fav | T33 | ✓ immich |
| Authelia | 9.6 | T27 | ✗ |
| Pocket ID | 7.6 | T27 | ✗ |
| BookStack | 7.4 | T28 | ✓ bookstack |
| GitLab | 7.4 | T29 | ✗ |
| authentik | 6.6 | T27 | ✗ |
| Joplin | 6.6 | T28 | ✗ |
| Pangolin | 5.9 auth · 7.1 proxy · 0.6 fav | T27, T19, T33 | ✗ (tunnel/proxy; Felhom has its own) |
| Kodi | 5.8 | T25 | n/a (player, not a server) |
| Trilium · Memos · Wiki.js | 4.5 · 4.1 · 3.8 | T28 | ✗ |
| Keycloak · Tinyauth · Zitadel | 3.9 · 2.7 · 1.1 | T27 | ✗ |
| Emby | 3.7 · 0.6 fav | T25, T33 | ✓ emby |
| Docmost · Outline | 3.5 · 3.2 | T28 | ✓ docmost · ✓ outline |
| Vaultwarden | 3.7 fav | T33 | ✓ vaultwarden |
| Seafile · AFFiNE · SilverBullet · DokuWiki · Logseq | 2.9 · 2.5 · 2.4 · 2.3 · 2.2 | T26, T28 | ✗ |
| SFTPGo · ownCloud · Subsonic · PeerTube · MkDocs · Resilio · Gogs | 2.2 · 2.0 · 2.0 · 1.8 · 1.8 · 1.3 · 0.4 | T25, T26, T28, T29 | ✗ |
| Paperless-ngx | 2.1 fav | T33 | ✓ paperless-ngx |
| *arr | 2.0 fav | T33 | ✓ radarr, ✓ sonarr (others ✗) |
| FreshRSS · Pi-hole · Miniflux · AdGuard Home | 1.3 · 1.2 · 0.7 · 0.5 fav | T33 | ✗ |
| Actual Budget · RomM · Mealie · n8n · Homepage · Glance · Karakeep | 1.0 · 1.0 · 0.7 · 0.7 · 0.7 · 0.6 · 0.5 fav | T33 | ✓ actualbudget, romm, mealie, n8n, homepage, glance, karakeep |
| Proxmox · Komodo | 0.6 · 0.5 fav | T33 | n/a (host OS / container manager — Felhom's own role) |
**Catalog gap — above 5 %, not shipped, a server app:** Syncthing 25.9 (T26) · Forgejo 15.0 (T29) ·
Authelia 9.6, Pocket ID 7.6, authentik 6.6 (T27, sign-on layers) · GitLab 7.4 (T29) · Joplin server 6.6 (T28) ·
Pangolin 5.9 (T27; a tunnel, overlaps Felhom's own). Nothing was added.
## 2. Our features against what people do
| Area | Crowd, 2025 | Felhom | Source |
|---|---|---|---|
| Backup | Onsite 74.8, cloud 51.5, **offsite 25.2, none 13.3** | three automatic nightly tiers (on-drive, second drive, encrypted off-site to a Hetzner Storage Box) + whole-guest copies, local daily and off-site weekly — **a selling point: only 1 in 4 does off-site** | T8; `07-backup-architecture.md` :373-382 |
| Backup cloud | Backblaze 15.0, Google Drive 12.6, **Hetzner 9.1 (rising)** | Hetzner Storage Box | T9; `07` :379 |
| Remote access | VPN 71.1, reverse proxy 66.6, forwarded ports 17.7 | Cloudflare Tunnel, no open port, no customer VPN; TLS ends at Cloudflare (R-904 deferred) | T18; `01-topology-and-trust.md` :202-210 |
| Reverse proxy | NPM 20.6, **Traefik 18.4**, Caddy 14.7, CF Tunnels 13.8 | Traefik behind the tunnel | T19; `01` :113, :143-145 |
| Domain | 87.9 have their own | every customer has their own domain | T20; `01` :210 |
| OS | Linux 81.3, **Proxmox 44.9**, HA OS 29.4 | Proxmox host + one Debian LXC + Docker | T7; `01` :56-59 |
| Containers | 96.3 use them, Docker 89.1 | Docker inside the guest | T10, T13 |
| App updates | **manual 45.1**, both 40.3, automatic 14.6 | automatic, watched — **a selling point** | T16; `website/index.html` :156 |
| Container manager | 51.5 use one (Portainer 32.7) | the Felhom dashboard does this job | T14, T15 |
| NAS | 67.8 run one | NAS as extra storage (FAQ), SMB shares on the box | T5; `website/gyik.html` :77-80 |
## 3. Reasons for self-hosting
| Rank 2025 | Reason | 2023 → 2025 | Our pitch names it? |
|---|---|---|---|
| 1 | Hobby | 95.5 → 94.6 | no — fine: a household that buys care is not tinkering |
| 2 | Privacy | 79.7 → 86.3 | **yes** (`index.html` :233-234) |
| 3 | Cost | 54.6 → 63.6 | **yes** (:229-230, no subscription) |
| 4 | Convenience | – → 63.3 (2024: 55.0) | **yes** (:114-116, :213, :221) |
| 5 | Education | 83.0 → 60.6 | no — fine, same reason as Hobby |
| 6 | Flexibility | 69.7 → 58.2 | **yes** (:237-242, capacity and control) |
| 7 | Business | 12.5 → 12.9 | home page no; `technologiak.html` rack setup only |
Source T3. The pitch already names the four reasons a buyer can share. Caution: Convenience was added as an
option in 2024; some of the 2023 Education/Flexibility votes may have moved to it.
## 4. Hardware
**No document states Felhom's supported hardware** (finding). What exists: FAQ advice (N100 mini PC, 16–32 GB,
`website/gyik.html` :77-80; an old PC "often yes", :85-88), three example setups (`technologiak.html` :375-447),
BYO Proxmox (`documentation/runbooks/day0-install.md` :18-21), and the installer ISO, which is x86_64-only
(`scripts/iso/iso-repack.sh` :31; inferred — no document says it).
| Crowd class (T4) | 2025 % | Felhom |
|---|---|---|
| Consumer hardware | 57.8 | in (mini PC is the recommendation) |
| Low-powered device | 50.0 | **partly out** — x86 N100-class in; ARM boards out (Raspberry Pi OS is 24.0, T7) |
| Custom build | 37.8 | in (tower example) |
| Cloud VPS | 34.2 | **out** — not offered |
| Desktop PC | 31.7 | in, case by case (FAQ :85-88) |
| Enterprise hardware | 20.0 | in (rack example, "business") |
| Server cluster | 13.7 | **out** — not documented; one host per household |
| Laptop | 10.5 | not documented (a tester laptop exists, STATUS) |
| Mobile device | 2.3 | **out** |
## 5. Trends 2023 → 2025 that matter to Felhom (≥ 5 points)
| Change | Points | Table |
|---|---|---|
| Cloud VPS as hosting hardware 2.7 → 34.2 (2024 → 2025; not asked 2023) — a 13× jump, may be a change in how the option was offered | +31.5 | T4 |
| Home Assistant OS 0.4 → 29.4 (2024 → 2025); Home Assistant use 56.5 → 62.7 | +29.0 | T7, T30 |
| Reasons move from learning to practical: Education −22.4, Flexibility −11.5, Cost +9.0, Privacy +6.6 | −22.4 … +9.0 | T3 |
| Jellyfin 46.1 → 60.9, Plex 39.5 → 29.9; Immich as favourite 2.2 → 10.5 | +14.8 / −9.6 / +8.3 | T25, T33 |
| Backup "None" 6.2 → 13.3 (2024 → 2025) | +7.1 | T8 |
## 6. Recommendations (ranked; none acted on)
1. Write one supported-hardware page: x86_64 only, minimum RAM and disk, no ARM, no cloud VPS, BYO Proxmox status — §4, T4.
2. Put "off-site, encrypted, automatic backup" in the home page's *why* list, with the 1-in-4 figure — T8 Offsite 25.2.
3. Consider Syncthing for the catalog (the largest gap, 25.9) — T26.
4. Say in the FAQ what the catalog Home Assistant (container) lacks against Home Assistant OS (add-ons) — T7 HA OS 29.4, T30.
5. Name automatic, watched app updates as a reason in the pitch — T16 Automatic 14.6.
6. Use the privacy figure as input to R-904 (TLS ends at Cloudflare): privacy is reason 2 and rising — T3 Privacy 86.3, T18 VPN 71.1.
7. Consider a Joplin server template for notes (Obsidian users need a separate sync) — T28 Joplin 6.6.
8. Consider Forgejo beside Gitea (Forgejo 15.0 is the second git server after Gitea 20.8) — T29.
9. Keep single sign-on (Authelia / Pocket ID / authentik) as a later idea; low household value today — T27.
10. Do not chase Cloud VPS until the T4 jump is confirmed by the 2026 survey — T4 Cloud VPS 34.2.
@@ -0,0 +1,40 @@
# Method — selfh.st survey read, 2026-10-12
**Fetched** 2026-10-12 by `fetch.py` to `~/research-cache/selfhst-survey/` (outside every repo; not committed).
| Year | Data file | Bytes | SHA-256 | Matches the spec |
|---|---|---|---|---|
| 2023 | `https://raw.githubusercontent.com/selfhst/cdn/main/assets/surveys/annual/2023-results.json` | 10362 | `714350e5e4b8f3ccbc7ce8c1348122a9b7a4ae8167abaf2c051d3dcc9911f03f` | yes |
| 2024 | `…/2024-results.json` | 14290 | `289c5161e5b6b5684ac6c751c16e73dd0dc42490c5c0d24517a6c1f891da4832` | yes |
| 2025 | `…/2025-results.json` | 20428 | `e083094ad7f8cb6721f7059c6c73e8779273717bb3d4c1f14dbd744dc0d0229e` | yes |
**Respondents.** 2025: **4,081**, stated as completed responses on `https://selfh.st/survey/2025-results/`.
2023 and 2024 pages give only rounded numbers ("just shy of 1,900", "~3,700" — `https://selfh.st/survey/2023-results/`,
`…/2024-results/`). The exact figures used, **1,869** and **3,655**, are the column sum of the first, mandatory,
single-choice question (personal / enterprise / both). The same method gives exactly 4,081 for 2025.
**Shapes.** 2023/2024: top-level `charts`, each with an `id`. 2025: `categories[].charts[]`, no `id`, five
categories, 39 charts. Some 2025 charts carry a `commentary` field (not copied).
**Percentages.** Multi-select charts: % of the year's respondents. Single-choice charts (marked in `TABLE.md`):
% of the column sum, because the question went to a subset or not everyone answered. Favourite software is one
answer per person but the chart drops small counts, so it is % of respondents.
**Matching across years.** 2023/2024 by `id` (same titles). 2025 by question meaning, a hand-written map in
`tables.py` (`MATCH_2025`); both titles are printed in every section. Answer labels mapped by hand where the
wording changed (`ALIAS`, e.g. Yes → Deploy, Generic Linux → Linux, Docker - Individual → Container (Separate),
2023 Paperless → Paperless-ngx, 2024 "Trilum" → Trilium); labels that differ only in case are merged and listed.
**Unmatched (no same question in 2025):** 2023 offsite-backup yes/no (T40), 2023 Kubernetes (T41 — 2024/2025
ask about any orchestration), 2023 media-streaming yes/no (T42), 2024 smart-home protocols (T43), 2024
ActivityPub yes/no and platforms (T44, T45). 2025-only: Management Platforms, Authentication, Git.
**Licence.** The results page shows no licence. The data repository `github.com/selfhst/cdn` is **MIT**
(GitHub API `license.spdx_id = MIT`; `LICENSE` reads "MIT License, Copyright (c) 2025 selfh.st"). We commit no
survey file and no survey prose; only our own counts derived from it, each cited to its URL.
**Limits.** Hobby users, mostly technical (Technology career 80.6 %, T36), self-selected through the selfh.st
newsletter and community, not households buying a service. Multi-select counts: a column passes 100 %. Open-text
charts (favourites) omit small answers. **Country:** the JSON files carry no country data, but the **2024 results
page has a country table** (HTML only, not in `tables.py`): United States 964, Germany 430 … **Hungary 19**.
2023 and 2025 pages have none.
@@ -0,0 +1,718 @@
# selfh.st self-hosting survey 2023–2025 — tables
Generated by `tables.py` from the files `fetch.py` downloads. Do not edit by hand.
| Year | Respondents | Results page | Data file |
|---|---|---|---|
| 2023 | 1,869 | https://selfh.st/survey/2023-results/ | https://raw.githubusercontent.com/selfhst/cdn/main/assets/surveys/annual/2023-results.json |
| 2024 | 3,655 | https://selfh.st/survey/2024-results/ | https://raw.githubusercontent.com/selfhst/cdn/main/assets/surveys/annual/2024-results.json |
| 2025 | 4,081 | https://selfh.st/survey/2025-results/ | https://raw.githubusercontent.com/selfhst/cdn/main/assets/surveys/annual/2025-results.json |
Values are **% of the year's respondents** (multi-select: a column can pass 100 %).
Charts marked *single-choice* show **% of the column sum** instead.
`2025 n` is the raw 2025 response count. `–` = not asked that year (or not matched).
## T1. Primary Purpose
Titles — 2023: Do you self-host for personal or enterprise purposes? · 2024: Do you self-host for personal or enterprise purposes? · 2025: Primary Purpose
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Personal | 83.4 | 83.1 | 83.7 | 3,417 |
| Both | 16.2 | 16.6 | 16.1 | 656 |
| Enterprise | 0.4 | 0.4 | 0.2 | 8 |
## T2. Unique Users
Titles — 2023: Approximately how many unique users are registered across your applications? · 2024: Approximately how many unique users are registered across your applications? · 2025: Unique Users
*Single-choice: % of the column sum.*
Note: 2023 buckets 11-15 and 16-20 are kept apart; 2024/2025 ask 11-20.
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| 1 user | 22.1 | 20.9 | 27.6 | 1,123 |
| 2 users | 23.1 | 25.5 | 21.0 | 854 |
| 3 users | 12.5 | 13.7 | 12.6 | 513 |
| 4 users | 10.3 | 10.0 | 9.9 | 401 |
| 5 users | 10.7 | 10.3 | 8.5 | 346 |
| 6 users | 4.3 | 4.3 | 4.9 | 200 |
| 7 users | 1.6 | 1.8 | 4.2 | 171 |
| 8 users | 2.2 | 2.3 | 4.2 | 170 |
| 9 users | 0.5 | 0.6 | 2.7 | 109 |
| 10 users | 6.2 | 2.8 | 2.1 | 86 |
| 11-20 users | – | 4.9 | 1.9 | 77 |
| 21+ users | 2.1 | 2.8 | 0.5 | 19 |
| 11-15 users | 2.6 | – | – | – |
| 16-20 users | 2.0 | – | – | – |
## T3. Reasons for Self-Hosting
Titles — 2023: Why do you self-host? · 2024: Why do you self-host? · 2025: Reasons for Self-Hosting
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Hobby | 95.5 | 94.8 | 94.6 | 3,862 |
| Privacy | 79.7 | 84.0 | 86.3 | 3,520 |
| Cost | 54.6 | 56.7 | 63.6 | 2,596 |
| Convenience | – | 55.0 | 63.3 | 2,584 |
| Education | 83.0 | 63.9 | 60.6 | 2,474 |
| Flexibility | 69.7 | 59.8 | 58.2 | 2,374 |
| Business | 12.5 | 8.7 | 12.9 | 525 |
| Other | 3.7 | 2.2 | 2.6 | 106 |
## T4. Hardware Options
Titles — 2023: Select the hardware option(s) used to deploy your self-hosted software. · 2024: Select the hardware option(s) used to deploy your self-hosted software. · 2025: Hardware Options
Note: 2023 'Custom-built', 'Enterprise', 'Mobile phone' matched to the 2024/2025 wording.
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Consumer hardware | – | 45.8 | 57.8 | 2,358 |
| Low-powered device | 52.7 | 54.7 | 50.0 | 2,039 |
| Custom build | 43.8 | 35.5 | 37.8 | 1,542 |
| Cloud VPS | – | 2.7 | 34.2 | 1,394 |
| Desktop PC | 44.6 | 35.9 | 31.7 | 1,295 |
| Enterprise hardware | 26.1 | 22.5 | 20.0 | 817 |
| Server cluster | 15.8 | 14.7 | 13.7 | 560 |
| Laptop | 12.8 | 12.0 | 10.5 | 429 |
| Mobile device | 1.8 | 2.5 | 2.3 | 94 |
| Other | 7.7 | 1.3 | 1.9 | 78 |
## T5. Network-Attached Storage
Titles — 2023: Do you deploy a network-attached storage device? · 2024: Do you deploy a network-attached storage device? · 2025: Network-Attached Storage
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Deploy | 63.2 | 63.0 | 67.8 | 2,768 |
| Don't Deploy | 36.8 | 37.0 | 32.2 | 1,313 |
## T6. Sensitive Data
Titles — 2023: Do you avoid self-hosting sensitive or important data? · 2024: Do you avoid self-hosting sensitive or important data? · 2025: Sensitive Data
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Don't Avoid | 77.2 | 82.8 | 82.7 | 3,373 |
| Avoid | 22.8 | 17.2 | 17.3 | 708 |
## T7. Operating Systems
Titles — 2023: Which operating system(s) are installed on your server(s)? · 2024: Which operating system(s) are installed on your server(s)? · 2025: Operating Systems
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Linux | 79.5 | 86.7 | 81.3 | 3,317 |
| Proxmox | 38.2 | 43.2 | 44.9 | 1,832 |
| Home Assistant OS | – | 0.4 | 29.4 | 1,199 |
| Raspberry Pi OS | 29.6 | 27.1 | 24.0 | 979 |
| TrueNAS | 15.6 | 14.5 | 19.0 | 774 |
| Unraid | 10.3 | 10.7 | 10.1 | 412 |
| DiskStation Manager | – | 2.9 | 9.8 | 398 |
| Windows Desktop | 11.6 | 9.3 | 9.1 | 373 |
| Windows Server | 9.6 | 6.3 | 5.7 | 232 |
| BSD | 4.9 | 6.2 | 5.6 | 229 |
| Open Media Vault | – | 1.1 | 5.0 | 204 |
| macOS | 3.2 | 3.5 | 4.9 | 199 |
| ESXi | 5.4 | 3.1 | – | – |
| XCP-ng | – | 0.5 | – | – |
| Other | 9.4 | 2.0 | 8.3 | 340 |
## T8. Backup Strategies
Titles — 2024: Which backup strategies have you deployed for your self-hosted data? · 2025: Backup Strategies
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Onsite | – | 79.2 | 74.8 | 3,054 |
| Cloud | – | 52.7 | 51.5 | 2,100 |
| Offsite | – | 23.6 | 25.2 | 1,029 |
| None | – | 6.2 | 13.3 | 542 |
| Other | – | – | 1.2 | 51 |
## T9. Cloud Storage Providers
Titles — 2023: Which cloud storage service(s) are you subscribed to for backups? · 2024: Which cloud storage service(s) are you subscribed to for backups? · 2025: Cloud Storage Providers
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Backblaze | 15.4 | 16.2 | 15.0 | 613 |
| Google Drive | 11.7 | 13.7 | 12.6 | 514 |
| Hetzner | – | 5.1 | 9.1 | 371 |
| Microsoft OneDrive | 6.5 | 8.5 | 6.8 | 277 |
| iCloud | – | 4.8 | 5.3 | 215 |
| Proton Drive | – | 1.3 | 4.0 | 165 |
| Dropbox | 2.8 | 3.0 | 2.4 | 96 |
| Cloudflare R2 | – | 0.6 | 1.7 | 70 |
| BorgBase | – | 2.1 | 1.7 | 68 |
| Mega | 1.4 | 1.9 | 1.5 | 63 |
| pCloud | – | 0.8 | 1.2 | 51 |
| iDrive | – | 0.9 | – | – |
| Storj | – | 0.7 | – | – |
| Veeam | – | 0.7 | – | – |
| Box | – | 0.5 | – | – |
| CrashPlan | – | 0.5 | – | – |
| Wasabi | – | 0.5 | – | – |
| Amazon S3 | 4.5 | – | – | – |
| Other | 18.0 | 6.3 | 23.1 | 942 |
## T10. Container Population
Titles — 2023: Do you use containers to deploy self-hosted software? · 2024: Do you use containers to deploy self-hosted software? · 2025: Container Population
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Deploy | 94.3 | 95.8 | 96.3 | 3,932 |
| Don't Deploy | 5.7 | 4.2 | 3.7 | 149 |
## T11. Containers vs Bare Metal
Titles — 2023: Do you prefer container installations to bare metal? · 2024: Do you prefer container installations to bare metal? · 2025: Containers vs Bare Metal
*Single-choice: % of the column sum.*
Note: 2025 added a third answer (Depends); 2024 and 2025 asked only container users.
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Containers | 79.5 | 88.1 | 69.1 | 2,720 |
| Depends | – | – | 26.5 | 1,041 |
| Bare Metal | 20.5 | 11.9 | 4.4 | 174 |
## T12. Container Orchestration
Titles — 2024: Do you use an orchestration platform to deploy containers? · 2025: Container Orchestration
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| No | – | 75.0 | 76.9 | 3,024 |
| Yes | – | 25.0 | 23.1 | 910 |
## T13. Container Platforms
Titles — 2023: Which container platform(s) do you use for deployment? · 2024: Which container platform(s) do you use for deployment? · 2025: Container Platforms
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Docker | 89.8 | 89.5 | 89.1 | 3,637 |
| LXC | 27.3 | 29.1 | 30.8 | 1,256 |
| Podman | 9.8 | 10.1 | 10.9 | 446 |
| Other | 4.8 | 2.6 | 3.5 | 142 |
## T14. Management Software
Titles — 2023: Do you deploy management software to administer your containers? · 2024: Do you deploy management software to administer your containers? · 2025: Management Software
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Use | 49.7 | 54.6 | 51.5 | 2,022 |
| Don't Use | 50.3 | 45.4 | 48.5 | 1,908 |
## T15. Management Platforms
Titles — 2025: Management Platforms
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Portainer | – | – | 32.7 | 1,336 |
| Dockge | – | – | 10.4 | 425 |
| Komodo | – | – | 7.5 | 308 |
| Coolify | – | – | 1.5 | 60 |
| Other | – | – | 9.4 | 385 |
## T16. Update Methodology
Titles — 2023: How do you manage container updates? · 2024: How do you manage container updates? · 2025: Update Methodology
*Single-choice: % of the column sum.*
Note: 2025 was deployed as multi-select by mistake (column sum 4,113 > 3,930 asked).
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Manual | 58.1 | 54.1 | 45.1 | 1,855 |
| Depends / Both | – | 29.2 | 40.3 | 1,658 |
| Automatic | 41.9 | 16.7 | 14.6 | 600 |
## T17. Firewalls
Titles — 2023: Do you deploy a firewall aside from the router provided by your ISP? · 2024: Do you deploy a firewall aside from the router provided by your ISP? · 2025: Firewalls
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Non-ISP | 55.9 | 51.7 | 58.7 | 2,393 |
| ISP | 44.1 | 48.3 | 41.3 | 1,686 |
## T18. Remote Access
Titles — 2023: How do you access your self-hosted software remotely? · 2024: How do you access your self-hosted software remotely? · 2025: Remote Access
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| VPN | 68.2 | 77.0 | 71.1 | 2,902 |
| Reverse Proxy | 65.9 | 62.9 | 66.6 | 2,716 |
| Forwarded Ports | – | 23.0 | 17.7 | 721 |
| Only via LAN | 17.5 | 14.2 | 12.5 | 510 |
| Other | – | 0.5 | 6.7 | 273 |
## T19. Reverse Proxies
Titles — 2023: Which web server(s) do you deploy for proxied services? · 2024: Which web server(s) do you deploy for proxied services? · 2025: Reverse Proxies
Merged spelling/case variants: 2023 'Nginx' = 'NGINX'
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| NGINX Proxy Manager | – | 31.1 | 20.6 | 842 |
| Traefik | 21.3 | 24.1 | 18.4 | 751 |
| Caddy | 10.9 | 18.1 | 14.7 | 598 |
| Cloudflare Tunnels | 17.1 | 19.3 | 13.8 | 563 |
| NGINX | 50.1 | 28.3 | 12.9 | 528 |
| Pangolin | – | – | 7.1 | 290 |
| HAProxy | 5.4 | 5.0 | 3.1 | 128 |
| Apache | 6.6 | 8.5 | 2.4 | 97 |
| SWAG | – | 4.2 | 2.3 | 95 |
| Zoraxy | – | 0.7 | – | – |
| Other | 4.0 | 1.6 | 3.2 | 131 |
## T20. Registered Domains
Titles — 2023: Do you utilize a custom domain for your self-hosted applications? · 2024: Do you utilize a custom domain for your self-hosted applications? · 2025: Registered Domains
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Custom Domain | 85.7 | 88.8 | 87.9 | 3,586 |
| No Domain | 14.3 | 11.2 | 12.1 | 495 |
## T21. Domain Registrars
Titles — 2023: Which registrar(s) do you purchase or lease your custom domains from? · 2024: Which registrar(s) do you purchase or lease your custom domains from? · 2025: Domain Registrars
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Cloudflare | 19.1 | 30.3 | 34.9 | 1,423 |
| Namecheap | 13.1 | 18.5 | 16.0 | 651 |
| Porkbun | 6.8 | 12.9 | 12.8 | 523 |
| OVH | 3.0 | 4.9 | 6.0 | 246 |
| GoDaddy | 2.5 | 3.6 | 3.0 | 124 |
| DuckDNS | 1.4 | 5.3 | 2.8 | 113 |
| Squarespace | – | – | 2.4 | 97 |
| Netcup | – | 1.8 | 2.1 | 85 |
| IONOS | 1.1 | 2.0 | 2.1 | 84 |
| Hetzner | 0.8 | 2.0 | 2.0 | 82 |
| Gandi | 2.0 | 2.5 | 1.5 | 62 |
| Strato | 1.2 | 1.4 | 1.4 | 57 |
| Google Domains | 4.3 | 3.3 | – | – |
| Hover | 0.9 | 1.2 | – | – |
| INWX | 0.7 | 1.1 | – | – |
| NameSilo | 0.8 | 1.0 | – | – |
| DreamHost | – | 0.7 | – | – |
| Other | 15.3 | 17.8 | 21.1 | 863 |
## T22. E-mail
Titles — 2023: Are you self-hosting any components of an e-mail server? · 2024: Are you self-hosting any components of an e-mail server? · 2025: E-mail
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Don't Self-Host | 85.7 | 83.8 | 86.2 | 3,516 |
| Self-Host | 14.3 | 16.2 | 13.8 | 562 |
## T23. E-mail Components
Titles — 2024: Which e-mail server components are you self-hosting? · 2025: E-mail Components
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Sending | – | 14.6 | 11.8 | 482 |
| Receiving | – | 13.4 | 11.5 | 469 |
| Client | – | 9.7 | 8.1 | 331 |
## T24. Database Deployment
Titles — 2023: How do you deploy databases for self-hosted applications? · 2024: How do you deploy databases for self-hosted applications? · 2025: Database Deployment
Note: Single-choice in 2023/2024, multi-select in 2025 (sum 5,004): % of respondents.
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Container (Separate) | 62.1 | 70.8 | 84.0 | 3,430 |
| Container (Shared) | 15.7 | 15.1 | 21.6 | 883 |
| Bare Metal (Shared) | 6.7 | 7.6 | 8.6 | 349 |
| Bare Metal (Separate) | 9.3 | 6.5 | 8.4 | 342 |
## T25. Media Streaming
Titles — 2023: Which of the platforms below do you deploy to stream media content? · 2024: Which of the platforms below do you deploy to stream media content? · 2025: Media Streaming
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Jellyfin | 46.1 | 53.9 | 60.9 | 2,487 |
| Plex | 39.5 | 35.0 | 29.9 | 1,219 |
| AudioBookshelf | – | 18.4 | 20.6 | 840 |
| Navidrome | – | 13.6 | 16.1 | 656 |
| Kodi | 6.5 | 7.6 | 5.8 | 237 |
| Emby | 4.1 | 3.6 | 3.7 | 149 |
| Subsonic | – | 2.7 | 2.0 | 81 |
| PeerTube | – | 2.1 | 1.8 | 74 |
| Lightweight Music Server | – | 0.8 | – | – |
| Ampache | – | 0.6 | – | – |
| Other | 6.7 | 6.0 | 6.6 | 268 |
## T26. File Storage and Syncing
Titles — 2024: Which of the platforms below do you deploy for remote file storage, access, or syncing? · 2025: File Storage and Syncing
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Nextcloud | – | 35.8 | 30.3 | 1,236 |
| Syncthing | – | 29.5 | 25.9 | 1,058 |
| Synology | – | 2.8 | 13.5 | 550 |
| FileBrowser | – | 13.5 | 11.8 | 481 |
| FileBrowser Quantum | – | – | 5.1 | 207 |
| Seafile | – | 3.2 | 2.9 | 118 |
| SFTPGo | – | 0.6 | 2.2 | 90 |
| ownCloud | – | 2.3 | 2.0 | 83 |
| Resilio | – | 0.5 | 1.3 | 53 |
| FileStash | – | 0.8 | – | – |
| FileRun | – | 0.5 | – | – |
| Other | – | 5.3 | 12.0 | 488 |
## T27. Authentication
Titles — 2025: Authentication
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Authelia | – | – | 9.6 | 390 |
| Pocket ID | – | – | 7.6 | 310 |
| authentik | – | – | 6.6 | 268 |
| Pangolin | – | – | 5.9 | 241 |
| Keycloak | – | – | 3.9 | 158 |
| Tinyauth | – | – | 2.7 | 111 |
| Zitadel | – | – | 1.1 | 46 |
| Other | – | – | 18.1 | 737 |
## T28. Note-Taking / Documentation
Titles — 2024: Which of the platforms below do you deploy for documentation or note-taking? · 2025: Note-Taking / Documentation
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Obsidian | – | 18.8 | 25.5 | 1,041 |
| Nextcloud Notes | – | 10.4 | 7.9 | 321 |
| BookStack | – | 11.1 | 7.4 | 303 |
| Joplin | – | 8.2 | 6.6 | 268 |
| Trilium | – | 5.1 | 4.5 | 182 |
| Memos | – | 4.9 | 4.1 | 167 |
| Wiki.js | – | 5.8 | 3.8 | 156 |
| Docmost | – | 2.1 | 3.5 | 141 |
| Outline | – | 3.1 | 3.2 | 132 |
| AFFiNE | – | 1.5 | 2.5 | 101 |
| SilverBullet | – | 1.7 | 2.4 | 99 |
| DokuWiki | – | 3.5 | 2.3 | 93 |
| Logseq | – | 3.1 | 2.2 | 89 |
| MkDocs | – | 0.5 | 1.8 | 75 |
| HedgeDoc | – | 1.9 | – | – |
| StandardNotes | – | 1.0 | – | – |
| AppFlowy | – | 0.9 | – | – |
| AnyType | – | 0.9 | – | – |
| Docusaurus | – | 0.9 | – | – |
| TiddlyWiki | – | 0.8 | – | – |
| MediaWiki | – | 0.6 | – | – |
| SiYuan | – | 0.5 | – | – |
| FlatNotes | – | 0.4 | – | – |
| XWiki | – | 0.3 | – | – |
| Other | – | 3.4 | 13.9 | 568 |
## T29. Git
Titles — 2025: Git
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Gitea | – | – | 20.8 | 849 |
| Forgejo | – | – | 15.0 | 613 |
| GitLab | – | – | 7.4 | 302 |
| Gogs | – | – | 0.4 | 18 |
| Other | – | – | 2.5 | 101 |
## T30. Home Automation
Titles — 2023: Do you deploy any self-hosted software for smart home or home automation purposes? · 2024: Do you deploy any self-hosted software for smart home or home automation purposes? · 2025: Home Automation
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Deploy | 56.5 | 59.8 | 62.7 | 2,548 |
| Don't Deploy | 43.5 | 40.2 | 37.3 | 1,516 |
## T31. Game Servers
Titles — 2023: Are you self-hosting any game servers? · 2024: Are you self-hosting any game servers? · 2025: Game Servers
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Don't Host | 62.8 | 67.6 | 67.8 | 2,765 |
| Host | 37.2 | 32.4 | 32.2 | 1,316 |
## T32. Games
Titles — 2024: List the games you're deploying a game server for. · 2025: Games
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Minecraft | – | 24.9 | 25.5 | 1,041 |
| Valheim | – | 3.3 | 5.2 | 214 |
| Satisfactory | – | 4.3 | 5.2 | 212 |
| Factorio | – | 3.2 | 5.1 | 208 |
| Terraria | – | 2.1 | 4.0 | 163 |
| Palworld | – | 2.1 | 3.2 | 131 |
| Foundry VTT | – | 0.4 | 2.6 | 107 |
| Project Zomboid | – | 1.1 | 2.1 | 84 |
| 7 Days to Die | – | 1.1 | 1.7 | 69 |
| ARK | – | 0.9 | 1.7 | 68 |
| V Rising | – | 0.8 | 1.6 | 66 |
| Counter-Strike | – | 0.9 | 1.5 | 60 |
| Space Engineers | – | 0.5 | 1.3 | 53 |
| Arma | – | 0.9 | – | – |
| Minetest | – | 0.6 | – | – |
| Enshrouded | – | 0.5 | – | – |
| Assetto Corsa | – | 0.5 | – | – |
| Vintage Story | – | 0.3 | – | – |
| Other | – | 7.9 | 7.7 | 315 |
## T33. Favorite Software
Titles — 2023: What is your favorite self-hosted application? · 2024: What is your favorite self-hosted application? · 2025: Favorite Software
Note: One answer per person, open text; each year omits small counts, so % is of all respondents. 2023 'Paperless' matched to 'Paperless-ngx'; 2024 'Trilum' read as Trilium.
Merged spelling/case variants: 2024 'AudioBookshelf' = 'Audiobookshelf'; 2024 'Pi-Hole' = 'Pi-hole'
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Home Assistant | 12.4 | 12.6 | 14.4 | 588 |
| Jellyfin | 10.4 | 13.5 | 12.8 | 522 |
| Immich | 2.2 | 7.5 | 10.5 | 429 |
| Plex | 8.9 | 8.8 | 5.1 | 208 |
| Vaultwarden | 7.9 | 5.8 | 3.7 | 152 |
| Nextcloud | 7.7 | 4.6 | 2.9 | 118 |
| Audiobookshelf | 1.1 | 2.4 | 2.3 | 94 |
| Paperless-ngx | 2.0 | 2.5 | 2.1 | 85 |
| *arr | 2.4 | 3.0 | 2.0 | 81 |
| FreshRSS | 1.2 | 0.9 | 1.3 | 52 |
| Pi-hole | 2.4 | 1.2 | 1.2 | 50 |
| Actual Budget | – | 0.8 | 1.0 | 42 |
| Navidrome | 1.3 | 1.0 | 1.0 | 41 |
| RomM | – | – | 1.0 | 41 |
| Syncthing | 0.9 | 1.0 | 0.8 | 32 |
| Homepage | – | 0.9 | 0.7 | 29 |
| Mealie | – | 0.6 | 0.7 | 29 |
| n8n | – | 0.4 | 0.7 | 29 |
| Gitea | 2.0 | 1.5 | 0.7 | 28 |
| Miniflux | 0.9 | 1.0 | 0.7 | 27 |
| Emby | – | 0.7 | 0.6 | 26 |
| Proxmox | 0.9 | 1.0 | 0.6 | 25 |
| Pangolin | – | – | 0.6 | 25 |
| Glance | – | – | 0.6 | 24 |
| Karakeep | – | – | 0.5 | 22 |
| Komodo | – | – | 0.5 | 22 |
| AdGuard Home | 0.7 | 0.6 | 0.5 | 20 |
| Forgejo | – | 0.9 | – | – |
| Authentik | – | 0.5 | – | – |
| GitLab | – | 0.5 | – | – |
| Matrix | – | 0.5 | – | – |
| Firefly III | – | 0.5 | – | – |
| Uptime Kuma | – | 0.4 | – | – |
| Frigate NVR | – | 0.4 | – | – |
| Tiny Tiny RSS | – | 0.4 | – | – |
| Trilium | – | 0.4 | – | – |
| BookStack | 0.8 | – | – | – |
| Portainer | 0.7 | – | – | – |
| Grafana | 0.6 | – | – | – |
| Authelia | 0.6 | – | – | – |
## T34. Age Range
Titles — 2023: What is your age range? · 2024: What is your age range? · 2025: Age Range
*Single-choice: % of the column sum.*
Note: 2023/2024 bucket 10-19 matched to 2025 12-19.
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| 30-39 years-old | 37.5 | 38.3 | 37.8 | 1,532 |
| 20-29 years-old | 34.8 | 31.5 | 29.9 | 1,210 |
| 40-49 years-old | 16.7 | 19.5 | 20.8 | 844 |
| 50-59 years-old | 4.6 | 6.4 | 7.1 | 287 |
| 12-19 years-old | 5.8 | 3.4 | 3.1 | 125 |
| 60-69 years-old | 0.6 | 0.9 | 1.3 | 53 |
## T35. Gender
Titles — 2023: What is your gender? · 2024: What is your gender? · 2025: Gender
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Male | 96.2 | 95.6 | 95.6 | 3,839 |
| Female | 2.5 | 2.4 | 1.9 | 77 |
| Non-Binary | – | 1.8 | 1.9 | 77 |
| Other | 1.4 | 0.2 | 0.5 | 21 |
## T36. Career Field
Titles — 2023: If employed, do you work in a technical field? · 2024: If employed, do you work in a technical field? · 2025: Career Field
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Technology | 79.2 | 81.8 | 80.6 | 3,227 |
| Non-Technology | 20.8 | 18.2 | 19.4 | 778 |
## T37. Mobile OS
Titles — 2023: What is your primary mobile device's operating system? · 2024: What is your primary mobile device's operating system? · 2025: Mobile OS
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Android | 70.5 | 67.0 | 66.7 | 2,715 |
| iOS | 28.1 | 31.6 | 31.8 | 1,295 |
| GrapheneOS | – | 0.7 | – | – |
| Other | 1.4 | 0.7 | 1.5 | 62 |
## T38. Donations
Titles — 2023: Have you donated to a self-hosted project in the last year? · 2024: Have you donated to a self-hosted project in the last year? · 2025: Donations
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Haven't Donated | 61.8 | 60.0 | 59.1 | 2,393 |
| Donated | 31.9 | 40.0 | 40.9 | 1,657 |
| Prefer not to answer | 6.3 | – | – | – |
## T39. Education
Titles — 2023: What is the highest level of education you have completed? · 2024: What is the highest level of education you have completed? · 2025: Education
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Bachelor's Degree | 40.3 | 39.0 | 40.7 | 1,607 |
| Master's Degree | 19.7 | 21.6 | 22.6 | 893 |
| Some college | 17.3 | 15.9 | 16.6 | 655 |
| HS Diploma / GED | 12.3 | 11.7 | 7.4 | 293 |
| Associate's Degree | 1.5 | 5.4 | 5.6 | 221 |
| Doctorate | 2.1 | 3.2 | 3.1 | 122 |
| No formal schooling | 1.8 | 1.8 | 2.8 | 109 |
| VoTech Program | 5.0 | 1.3 | 1.2 | 46 |
# Unmatched charts (asked in 2023 and/or 2024, no same question in 2025)
## T40. Are you utilizing hosted cloud storage for offsite backups?
Titles — 2023: Are you utilizing hosted cloud storage for offsite backups?
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| No | 53.9 | – | – | – |
| Yes | 46.1 | – | – | – |
## T41. Do you deploy Kubernetes to manage your self-hosted software?
Titles — 2023: Do you deploy Kubernetes to manage your self-hosted software?
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| No | 89.6 | – | – | – |
| Yes | 10.4 | – | – | – |
## T42. Do you deploy any self-hosted software for purposes related to media streaming?
Titles — 2023: Do you deploy any self-hosted software for purposes related to media streaming?
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Yes | 81.6 | – | – | – |
| No | 18.4 | – | – | – |
## T43. Which smart home communication standards and protocols are you utilizing in your self-hosted ecosystem?
Titles — 2024: Which smart home communication standards and protocols are you utilizing in your self-hosted ecosystem?
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| WiFi | – | 52.1 | – | – |
| Zigbee | – | 38.5 | – | – |
| MQTT | – | 32.7 | – | – |
| Bluetooth | – | 22.7 | – | – |
| ESP | – | 19.5 | – | – |
| Z-Wave | – | 12.0 | – | – |
| Matter | – | 9.9 | – | – |
| NFC | – | 7.4 | – | – |
| Thread | – | 6.2 | – | – |
| Other | – | 0.6 | – | – |
## T44. Do you deploy any self-hosted ActivityPub platforms?
Titles — 2024: Do you deploy any self-hosted ActivityPub platforms?
*Single-choice: % of the column sum.*
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| No | – | 90.2 | – | – |
| Yes | – | 9.8 | – | – |
## T45. Which ActivityPub platform(s) do you deploy?
Titles — 2024: Which ActivityPub platform(s) do you deploy?
| label | 2023 | 2024 | 2025 | 2025 n |
|---|---|---|---|---|
| Mastodon | – | 3.2 | – | – |
| PeerTube | – | 1.1 | – | – |
| Lemmy | – | 1.0 | – | – |
| GoToSocial | – | 0.7 | – | – |
| Pixelfed | – | 0.5 | – | – |
| Akkoma | – | 0.2 | – | – |
| Other | – | 1.3 | – | – |
@@ -0,0 +1,42 @@
#!/usr/bin/env python3
"""Download the selfh.st annual survey results (2023-2025) to a cache outside every repo.
Prints bytes and SHA-256 for each file. The files are not committed (spec §9.2).
"""
import hashlib
import os
import sys
import urllib.request
BASE = "https://raw.githubusercontent.com/selfhst/cdn/main/assets/surveys/annual/{}-results.json"
CACHE = os.path.expanduser("~/research-cache/selfhst-survey")
# Prefixes/suffixes of the hashes the reviewer recorded on 2026-10-12.
EXPECTED = {
2023: ("714350e5e4b8f3cc", "f03f", 10362),
2024: ("289c5161e5b6b568", "4832", 14290),
2025: ("e083094ad7f8cb67", "229e", 20428),
}
def main():
os.makedirs(CACHE, exist_ok=True)
rc = 0
for year, (pre, suf, size) in EXPECTED.items():
url = BASE.format(year)
try:
with urllib.request.urlopen(url, timeout=30) as r:
data = r.read()
except Exception as e: # continue with the years that can be read (spec §13)
print(f"{year} FETCH FAILED {url}: {e}")
rc = 1
continue
with open(os.path.join(CACHE, f"{year}-results.json"), "wb") as f:
f.write(data)
h = hashlib.sha256(data).hexdigest()
ok = h.startswith(pre) and h.endswith(suf) and len(data) == size
print(f"{year} bytes={len(data)} sha256={h} {'MATCH' if ok else 'DIFFERS from spec'}")
return rc
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,246 @@
#!/usr/bin/env python3
"""Build TABLE.md from the cached selfh.st survey results (2023-2025).
Reads ~/research-cache/selfhst-survey/<year>-results.json (written by fetch.py).
Writes TABLE.md next to this script. Stdlib only.
Percent rule (spec §1, §8):
- multi-select charts: % of the year's respondent count;
- single-choice charts (marked "single"): % of the column sum, because the
question was asked to a subset or answered by fewer than all respondents.
"""
import json
import os
CACHE = os.path.expanduser("~/research-cache/selfhst-survey")
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "TABLE.md")
YEARS = (2023, 2024, 2025)
PAGE = "https://selfh.st/survey/{}-results/"
DATA = "https://raw.githubusercontent.com/selfhst/cdn/main/assets/surveys/annual/{}-results.json"
# Respondent count per year. 2025: the results page states 4,081 completed responses.
# 2023/2024: the pages give only rounded numbers ("just shy of 1,900", "~3,700"); the exact
# figure is the column sum of the first, mandatory, single-choice question (Primary Purpose),
# which is 4,081 in 2025 too - the same method reproduces the stated 2025 number.
RESPONDENTS = {2023: 1869, 2024: 3655, 2025: 4081}
# 2025 title -> the 2023/2024 chart id it asks the same question as. None = 2025 only.
MATCH_2025 = {
"Primary Purpose": "personal-enterprise",
"Unique Users": "unique-users",
"Reasons for Self-Hosting": "why-self-host",
"Hardware Options": "hardware",
"Network-Attached Storage": "deploy-nas",
"Sensitive Data": "sensitive-data",
"Operating Systems": "operating-system",
"Backup Strategies": "backup-strategy",
"Cloud Storage Providers": "backup-cloud",
"Container Population": "deploy-containers",
"Containers vs Bare Metal": "prefer-containers",
# 2023 asked about Kubernetes only, 2024/2025 about any orchestration: 2023 kept separate.
"Container Orchestration": "orchestration-platform",
"Container Platforms": "container-platform",
"Management Software": "container-manager",
"Management Platforms": None,
"Update Methodology": "container-updates",
"Firewalls": "deploy-firewall",
"Remote Access": "remote-access",
"Reverse Proxies": "web-server",
"Registered Domains": "custom-domain",
"Domain Registrars": "domain-registrar",
"E-mail": "email-server",
"E-mail Components": "email-components",
"Database Deployment": "deploy-database",
"Media Streaming": "media-server",
"File Storage and Syncing": "file-sync",
"Authentication": None,
"Note-Taking / Documentation": "documentation-notes",
"Git": None,
"Home Automation": "smart-home",
"Game Servers": "deploy-game",
"Games": "game-server",
"Favorite Software": "favorite-app",
"Age Range": "age-range",
"Gender": "gender",
"Career Field": "employment-field",
"Mobile OS": "mobile-os",
"Donations": "donate-project",
"Education": "education-level",
}
# 2023's Kubernetes question differs from the later orchestration question.
NOT_MATCHED_2023 = {"orchestration-platform"}
# Single-choice charts -> % of column sum. Keyed by 2023/2024 id (or 2025 title when 2025-only).
SINGLE = {
"personal-enterprise", "unique-users", "deploy-nas", "sensitive-data", "offsite-backups",
"deploy-containers", "prefer-containers", "orchestration-platform", "container-manager",
"container-updates", "deploy-firewall", "custom-domain", "email-server", "stream-media",
"smart-home", "deploy-game", "host-activitypub", "age-range", "gender", "employment-field",
"mobile-os", "donate-project", "education-level",
}
# Old label -> 2025 label, per chart, where the wording changed but the answer is the same.
# Labels that differ only in case are merged automatically (and listed in the output).
ALIAS = {
"deploy-nas": {"Yes": "Deploy", "No": "Don't Deploy"},
"sensitive-data": {"Yes": "Avoid", "No": "Don't Avoid"},
"deploy-containers": {"Yes": "Deploy", "No": "Don't Deploy"},
"prefer-containers": {"Yes": "Containers", "No": "Bare Metal"},
"container-manager": {"Yes": "Use", "No": "Don't Use"},
"container-updates": {"Both": "Depends / Both"},
"deploy-firewall": {"Yes": "Non-ISP", "No": "ISP"},
"custom-domain": {"Yes": "Custom Domain", "No": "No Domain"},
"email-server": {"Yes": "Self-Host", "No": "Don't Self-Host"},
"smart-home": {"Yes": "Deploy", "No": "Don't Deploy"},
"deploy-game": {"Yes": "Host", "No": "Don't Host"},
"employment-field": {"Yes": "Technology", "No": "Non-Technology"},
"donate-project": {"Yes": "Donated", "No": "Haven't Donated"},
"gender": {"Man": "Male", "Woman": "Female"},
"education-level": {"Some college, no degree": "Some college"},
"age-range": {"10-19 years-old": "12-19 years-old"},
"operating-system": {"Generic Linux": "Linux"},
"hardware": {"Custom-built": "Custom build", "Enterprise": "Enterprise hardware",
"Mobile phone": "Mobile device"},
"deploy-database": {"Docker - Individual": "Container (Separate)",
"Docker - Shared": "Container (Shared)",
"Bare Metal - Individual": "Bare Metal (Separate)",
"Bare Metal - Shared": "Bare Metal (Shared)"},
"favorite-app": {"Paperless": "Paperless-ngx", "*arr Stack": "*arr", "Trilum": "Trilium"},
}
# Notes printed under a section heading.
NOTES = {
"unique-users": "2023 buckets 11-15 and 16-20 are kept apart; 2024/2025 ask 11-20.",
"prefer-containers": "2025 added a third answer (Depends); 2024 and 2025 asked only container users.",
"container-updates": "2025 was deployed as multi-select by mistake (column sum 4,113 > 3,930 asked).",
"age-range": "2023/2024 bucket 10-19 matched to 2025 12-19.",
"favorite-app": ("One answer per person, open text; each year omits small counts, so % is of all "
"respondents. 2023 'Paperless' matched to 'Paperless-ngx'; 2024 'Trilum' read as Trilium."),
"deploy-database": "Single-choice in 2023/2024, multi-select in 2025 (sum 5,004): % of respondents.",
"hardware": "2023 'Custom-built', 'Enterprise', 'Mobile phone' matched to the 2024/2025 wording.",
}
def charts_of(year):
path = os.path.join(CACHE, f"{year}-results.json")
if not os.path.exists(path):
return None
with open(path, encoding="utf-8") as f:
d = json.load(f)
if "charts" in d:
return d["charts"]
return [c for cat in d["categories"] for c in cat["charts"]]
def series(chart, alias):
out, merged = {}, []
canon = {}
for lab, val in zip(chart["labels"], chart["datasets"][0]["data"]):
lab = alias.get(lab, lab)
key = lab.lower()
if key in canon and canon[key] != lab:
merged.append(f"{lab} = {canon[key]}")
canon.setdefault(key, lab)
out[key] = out.get(key, 0) + val
return out, canon, merged
def pct(v, denom):
return f"{100.0 * v / denom:.1f}" if denom else "–"
def main():
per_year = {y: charts_of(y) for y in YEARS}
missing = [y for y in YEARS if per_year[y] is None]
by_id = {y: {c["id"]: c for c in (per_year[y] or [])} for y in (2023, 2024)}
# Build the section list: 2025 order first, then the 2023/2024 charts nobody matched.
sections = []
used = {2023: set(), 2024: set()}
for c25 in per_year[2025] or []:
cid = MATCH_2025.get(c25["title"])
c23 = by_id[2023].get(cid) if cid and cid not in NOT_MATCHED_2023 else None
c24 = by_id[2024].get(cid) if cid else None
for y, c in ((2023, c23), (2024, c24)):
if c:
used[y].add(c["id"])
sections.append({"key": cid or c25["title"], "charts": {2023: c23, 2024: c24, 2025: c25}})
unmatched = []
for cid in list(by_id[2023]) + [i for i in by_id[2024] if i not in by_id[2023]]:
c23 = by_id[2023].get(cid) if cid not in used[2023] else None
c24 = by_id[2024].get(cid) if cid not in used[2024] else None
if c23 or c24:
unmatched.append({"key": cid, "charts": {2023: c23, 2024: c24, 2025: None}})
L = ["# selfh.st self-hosting survey 2023–2025 — tables", "",
"Generated by `tables.py` from the files `fetch.py` downloads. Do not edit by hand.", "",
"| Year | Respondents | Results page | Data file |", "|---|---|---|---|"]
for y in YEARS:
L.append(f"| {y} | {RESPONDENTS[y]:,} | {PAGE.format(y)} | {DATA.format(y)} |")
L += ["", "Values are **% of the year's respondents** (multi-select: a column can pass 100 %).",
"Charts marked *single-choice* show **% of the column sum** instead.",
"`2025 n` is the raw 2025 response count. `–` = not asked that year (or not matched).", ""]
if missing:
L.append(f"**Missing years (download failed): {missing}.**\n")
def emit(num, sec, section_list_name):
ch = sec["charts"]
key = sec["key"]
single = key in SINGLE
alias = ALIAS.get(key, {})
titles = [f"{y}: {ch[y]['title']}" for y in YEARS if ch[y]]
rows, canon_all, merged_all, denom = {}, {}, [], {}
for y in reversed(YEARS): # 2025 first, so its spelling is the shown label
if not ch[y]:
continue
s, canon, merged = series(ch[y], alias)
merged_all += merged
for k, v in canon.items():
if k in canon_all and canon_all[k] != v:
merged_all.append(f"{y} '{v}' = '{canon_all[k]}'")
canon_all.setdefault(k, v)
denom[y] = sum(s.values()) if single else RESPONDENTS[y]
for k, v in s.items():
rows.setdefault(k, {})[y] = v
L.append(f"## T{num}. {ch[2025]['title'] if ch[2025] else ch[2024]['title'] if ch[2024] else ch[2023]['title']}")
L.append("")
L.append("Titles — " + " · ".join(titles))
L.append("")
if single:
L.append("*Single-choice: % of the column sum.*")
L.append("")
if key in NOTES:
L.append(f"Note: {NOTES[key]}")
L.append("")
if merged_all:
L.append("Merged spelling/case variants: " + "; ".join(sorted(set(merged_all))))
L.append("")
L.append("| label | 2023 | 2024 | 2025 | 2025 n |")
L.append("|---|---|---|---|---|")
def sortkey(k):
r = rows[k]
return (k == "other", -r.get(2025, -1), -r.get(2024, -1), -r.get(2023, -1))
for k in sorted(rows, key=sortkey):
r = rows[k]
cells = [pct(r[y], denom[y]) if y in r else "–" for y in YEARS]
n = f"{r[2025]:,}" if 2025 in r else "–"
L.append(f"| {canon_all[k]} | {' | '.join(cells)} | {n} |")
L.append("")
n = 0
for sec in sections:
n += 1
emit(n, sec, "matched")
L += ["# Unmatched charts (asked in 2023 and/or 2024, no same question in 2025)", ""]
for sec in unmatched:
n += 1
emit(n, sec, "unmatched")
with open(OUT, "w", encoding="utf-8") as f:
f.write("\n".join(L).rstrip() + "\n")
print(f"wrote {OUT}: {n} sections ({len(sections)} from 2025, {len(unmatched)} unmatched)")
return 1 if missing else 0
if __name__ == "__main__":
raise SystemExit(main())