344b1701dd
Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
109 lines
5.0 KiB
Markdown
109 lines
5.0 KiB
Markdown
# 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 <directory>` 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.
|