# Self-hosting ThreadCount (Community edition) The Community edition is the hosted product with `EDITION=community` in its environment. This page says what that changes, how to run it, how to keep it, and what is not included. ## What `EDITION=community` changes | | Hosted (threadcount.tech) | Community | |---|---|---| | Features | All | All | | Plans, staff ceiling, read-only for non-payment | Yes | None. Every facility behaves as a grandfathered one. | | Public website at `/` | The marketing site | Redirects to sign-in | | Demo facility | Yes | Off | | Operations console (`ops.` hostname) | Yes | Off unless `OPS_SESSION_SECRET` and the ops database roles are set up | | Cloudflare Turnstile on sign-in and sign-up | Required | Optional. Set both keys to enforce it; without it the per-address rate limits stand alone. | | Error reports | To ThreadCount's own GlitchTip, scrubbed | Nowhere, unless you set `NEXT_PUBLIC_GLITCHTIP_DSN` to your own | | Usage statistics | To ThreadCount's own Umami, scrubbed | Nowhere. The tracker only mounts on `*.threadcount.tech`. | | Single sign-on for a facility | Via ThreadCount's Jackson broker | Run your own [BoxyHQ Jackson](https://boxyhq.com/docs/jackson/overview) and set `JACKSON_URL` and `JACKSON_API_KEY`; otherwise no SSO | | Transactional email | ThreadCount's SMTP | Your SMTP, or none | Everything the legal pages on threadcount.tech say applies to the hosted service. Your instance is yours: publish your own privacy statement for your staff. The pages under `/privacy`, `/terms` and so on still render on a Community instance, but they describe threadcount.tech. ## Running it `docker-compose.yml` runs three services: `db` (Postgres 16, in a named volume), `migrate` (a one-shot `prisma migrate deploy` that runs before the app on every `up`), and `app` (Next.js's standalone server, port 3000, photographs in a second named volume). Put TLS in front of port 3000. A minimal Caddyfile: ``` uniforms.example.health { reverse_proxy 127.0.0.1:3000 } ``` The image is built on your machine, not pulled, because `NEXT_PUBLIC_SITE_URL` and the optional Turnstile site key are compiled into the browser bundle. If you change either, rebuild: `docker compose up -d --build`. ### The first facility Open the hostname. Create an account; that creates a facility and makes you its administrator. Under Settings, name your staff groups first (nothing works until a facility has them), then load the catalogue, departments, staff register, barcodes and opening stock from CSV under Settings › Data. Add a second administrator under Settings › Account › Users before you sign out — a forgotten password with one admin and no SMTP locks the facility. Then set `SIGNUPS_DISABLED=1` in `.env` and `docker compose up -d`. Your instance is now one facility, and nobody else can create another. ## Upgrading ```sh git pull docker compose up -d --build ``` `migrate` applies any new schema migrations before the new app starts; the old app keeps serving until then. Take a backup first (below). Releases that need more than that say so in their notes. ## Backups `docker/backup.sh ` writes one directory per run: the database as a custom-format `pg_dump` and the photographs as a tarball, keeping the last fourteen. Run it nightly from cron: ``` 30 3 * * * cd /srv/threadcount && docker/backup.sh /srv/backups/threadcount >> /var/log/threadcount-backup.log 2>&1 ``` Copy that directory somewhere off the machine. A backup on the same disk as the database is not a backup. Separately, an administrator can download the whole facility as one JSON file from Settings › Data at any time, and restore it into a fresh instance from the same screen. That file is the portable form; the `pg_dump` is the fast one. ### Restoring ```sh docker compose down docker volume rm threadcount_db threadcount_photos # only if you mean to replace everything docker compose up -d db docker compose exec -T db pg_restore -U threadcount -d threadcount --clean --if-exists < 2026-09-13-033001/threadcount.dump docker compose up -d docker compose cp 2026-09-13-033001/photos app:/data/ # after extracting photos.tgz ``` ## The Android apps The two Play apps (ThreadCount, the counter; ThreadCount Staff) load their screens from `https://threadcount.tech` and cannot be pointed at another host. On a self-hosted instance use the same screens in a phone's browser: `https://your-host/m` for the counter (camera scanning works in Chrome and Edge) and `https://your-host/my` for staff. Add either to the home screen. ## Environment reference `.env.example` lists every variable the application reads, with a comment on each. The ones that matter for a Community instance are `SESSION_SECRET`, `POSTGRES_PASSWORD`, `NEXT_PUBLIC_SITE_URL`, `EDITION`, the `SMTP_*` group, and `SIGNUPS_DISABLED`. Leave the operations console, Cloudflare Access, Listmonk, Umami and GlitchTip variables empty unless you run those yourself. ## Getting help Open an issue on the repository. If you would rather someone else ran it, that is what [threadcount.tech/pricing](https://threadcount.tech/pricing) is for.