BuildHub

BuildHub

Docker app from allornothing's Repository

Overview

Offline parts search and AI-assisted BOM generation for electronics and 3D-printing (RepRap) builds. Crawls electronics/maker marketplaces and component distributors into a local, offline-searchable catalog; paste or AI-generate a parts list and get the cheapest source per line with links, using landed cost (unit price x MOQ + shipping), never unit price alone. Set BUILDHUB_ADMIN_TOKEN before exposing this beyond your LAN -- without it the admin pages (which mutate the catalog) refuse to serve from a non-loopback address, which every container connection is. AI BOM generation is optional and off by default -- set AI_PROVIDER to enable it with a cloud API key or a local Ollama instance. Requires --shm-size=1g (below in Extra Parameters) -- several adapters render pages in a real Chromium, and Docker's default 64MB /dev/shm makes that crash silently.

Running BuildHub on Unraid

Everything below was built and verified against a real Docker daemon: the image builds, runs as nobody:users, launches Chromium unprivileged, crawls live sites, and its data survives container recreation.


What you get

One container running the web UI and the crawl scheduler. It stores everything -- database, downloaded images, browser login profile, and the editable YAML config -- under a single /config mount you point at appdata.

Image size ~4.2 GB
Memory ~400 MB idle, ~1.2 GB while a browser-rendered site is crawling
Appdata ~16 MB after a small crawl; grows mostly with downloaded images
Ports one, default 8000
Volumes one, /config -> /mnt/user/appdata/buildhub
Database SQLite, inside /config -- no second container needed

The image is large because four of the working adapters (Banggood, eBay, AliExpress detail pages, Chinavasion) render their listings in JavaScript and need a real Chromium. It is built on Microsoft's official Playwright image rather than python:slim -- Chromium pulls in around 90 system libraries, and hand-assembling those on a slim base is a brittle apt incantation that breaks on Debian point releases.


Get the image

The image is published, so you do not need to build anything:

docker pull allornothing/build-hub:latest

It is around 4.2 GB, so give the pull a few minutes on a normal connection.

Building it yourself instead

Clone the repo onto a share and build on the Unraid box:

git clone https://github.com/aon082910/build-hub.git /mnt/user/isos/build-hub
cd /mnt/user/isos/build-hub && docker build -t allornothing/build-hub:latest .

Roughly 3-5 minutes, most of it pulling the Playwright base layer. Tagging it with the same name means the template and compose file work unchanged.


Add the container

Easiest: Community Applications. Open the Apps tab and search for build-hub. Everything below is already filled in; just set the admin token. The manual routes are below for anyone not using CA.

Option A -- the template (recommended). On the Unraid box:

wget -O /boot/config/plugins/dockerMan/templates-user/my-build-hub.xml \
  https://raw.githubusercontent.com/aon082910/AoN-Unraid-Apps/main/Build-Hub/build-hub.xml

Then Docker -> Add Container and pick build-hub from the template dropdown. Every setting below is already filled in and described in the form.

Option B -- by hand. Docker -> Add Container, then:

Field Value
Name BuildHub
Repository allornothing/build-hub:latest
Network Type Bridge
Port Container 8000 -> Host 8000
Path Container /config -> Host /mnt/user/appdata/buildhub (Read/Write)
Extra Parameters --shm-size=1g

Add these variables:

Variable Value Why
BUILDHUB_ADMIN_TOKEN a long random string Read the next section. Without it the admin pages refuse to serve
TZ America/New_York The crawl schedule is interpreted in this zone
PUID 99 Unraid's nobody -- keeps appdata editable from the host
PGID 100 Unraid's users
BUILDHUB_MODE serve Web UI and scheduler in one process

Start it, then open http://<tower-ip>:8000.


The two settings that will bite you

1. BUILDHUB_ADMIN_TOKEN -- admin is 403 without it

This is deliberate and it will look like a bug the first time. The admin pages mutate your catalog (merging products, recording permanent match rejections), so they refuse to serve when they are reachable from a non-loopback address and no token is set. In a container the client is never loopback -- traffic arrives from the Docker bridge -- so this fires on every containerised deployment, always.

Set the variable to any strong random string, restart, and sign in with it as the password (any username works). Browsing, search, the API and crawling are unaffected either way; only the pages that can modify data are gated.

Failing closed is the right default here: silently serving an open admin panel to your whole LAN is the outcome nobody wants.

2. --shm-size=1g -- or browser-rendered sites go blank

Docker gives /dev/shm 64 MB by default. Chromium needs far more on heavy pages and crashes when it runs out. The symptom is nasty because it is not an error: Banggood and eBay simply return zero listings, which looks exactly like a broken selector. Put --shm-size=1g in Extra Parameters and it goes away.


First run

The container seeds /config on first start and says so:

[buildhub] seeded config.yaml into /config (edit it there; the image copy is ignored)
[buildhub] running as buildhub (99:100), TZ=America/New_York
Database ready. 16 sites seeded.
[buildhub] mode=serve (web UI + scheduler) on port 8000
[buildhub] scheduled full_crawl       0 3 * * *
[buildhub] scheduled refresh_prices   0 */6 * * *

(The useradd warning: uid 99 outside of the UID_MIN 1000 line above it is normal -- Unraid's nobody is genuinely below the usual range.)

Your appdata now looks like this:

/mnt/user/appdata/buildhub/
├── config.yaml          which sites to crawl, keywords, schedule, matching weights
├── providers.yaml       API presets for Taobao / Tmall / 1688
├── duty.yaml            import duty rates (shipped disabled -- see below)
├── freight.yaml         shipping weight and volumetric rates
├── .env                 optional; container variables override anything here
├── db/buildhub.db      the catalog
├── media/               downloaded product images
├── fixtures/            saved HTML snapshots used by the offline tests
└── browser_profile/     saved logins for the domestic-China sites

Edit the YAML files here, not in the image. They survive rebuilds and upgrades.

To see the UI populated without waiting for a crawl:

docker exec -u 99:100 BuildHub python -m buildhub.cli demo-seed

Then a real crawl:

docker exec -u 99:100 BuildHub python -m buildhub.cli crawl --sites dhgate,banggood --keyword "usb c hub" --pages 2

That exact command was run inside the container during testing: 81 listings, zero errors, Chromium included.


Running the CLI

Everything works through docker exec. Use -u 99:100 so anything it writes stays owned correctly:

docker exec -u 99:100 BuildHub python -m buildhub.cli health
docker exec -u 99:100 BuildHub python -m buildhub.cli selftest --site dhgate
docker exec -u 99:100 BuildHub python -m buildhub.cli stats

health is the one worth scheduling. It compares each site's recent yield against its own history, so it distinguishes broken (worked last week, zero today) from idle (never worked), and exits non-zero when something needs attention -- which makes it a clean User Scripts cron job.


Scheduled crawling

BUILDHUB_MODE=serve runs the scheduler inside the web container. Five jobs are registered at boot; timings live in config.yaml under schedule: and are read in your TZ:

schedule:
  full_crawl: "0 3 * * *"        # 03:00 daily
  refresh_prices: "0 */6 * * *"  # every 6 hours
  fx_rates: "30 2 * * *"
  rematch: "0 5 * * 0"           # Sundays
  health_check: "0 7 * * *"

Edit, then restart the container.

A full sweep of the default keyword list across all enabled sites takes roughly 20-40 minutes, and it is deliberately slow -- the per-host rate limiter spaces requests 2.5-6 s apart. Do not tighten it. You will get your home IP blocked by the same sites you are trying to buy from.

To split the UI and the crawler into separate containers (handy if you want to restart the UI without interrupting a crawl), run a second container from the same image with BUILDHUB_MODE=schedule and no port mapping, and set the first to BUILDHUB_MODE=web. Point both at the same /config.


Optional variables

None of these are required to boot; the app skips whatever capability is missing.

Variable Effect if unset
TRANSLATE_PROVIDER + ANTHROPIC_API_KEY Chinese listings are not translated, so they cannot be title-matched and mostly stay on their own product pages. claude, deepl, google_free or none
EBAY_CLIENT_ID / EBAY_CLIENT_SECRET eBay falls back to browser scraping -- works, just slower. Free keys at developer.ebay.com
BUILDHUB_PROXY Crawls come from your home IP. Fine for light use; a residential proxy is strongly recommended for sustained crawling. One URL, or several comma-separated for round-robin
CN_PROVIDER_KEY Taobao / Tmall / 1688 stay unavailable without a browser login. See below
OCTOPART_CLIENT_ID / SECRET Electronic-component part-number lookup (Mouser/Digi-Key/LCSC pricing via the Octopart aggregator) is skipped
MOUSER_API_KEY Direct Mouser search is skipped (Octopart may still surface Mouser pricing)
DIGIKEY_CLIENT_ID / SECRET Direct Digi-Key search is skipped (Octopart may still surface Digi-Key pricing)
AI_PROVIDER (+ AI_API_KEY or OLLAMA_BASE_URL) AI-generated BOMs (/bom/generate, generate-bom on the CLI) are disabled and report themselves as such. none (default), anthropic, or ollama

Translations are cached by content hash, so a repeat crawl of the same listings costs nothing.


Taobao, Tmall and 1688 in a container

These three need either a one-time interactive browser login or an API key, and the login is genuinely awkward in a container because it wants a visible browser window.

Recommended: get an API key and set CN_PROVIDER_KEY (presets are in providers.yaml). No login, no browser, works unattended -- which is what you want on a server anyway. Verify it before enabling the sites:

docker exec BuildHub python -m buildhub.cli provider-probe --preset otapi --keyword "usb hub"

Alternative: do the browser login on a desktop with the project checked out normally, then copy the resulting data/browser_profile/ into /mnt/user/appdata/buildhub/browser_profile/. The cookies are portable. Expect to redo it every few weeks.

Until you have done one or the other, leave those three enabled: false in config.yaml, otherwise every crawl wastes minutes failing on them.


Backup

Back up /mnt/user/appdata/buildhub -- that is the entire application state. The CA Appdata Backup plugin covers it with no special configuration. media/ is the bulk of it and is regenerable by re-crawling, so exclude it if you want a small backup.


Updating

docker pull allornothing/build-hub:latest

Then Docker -> BuildHub -> Force Update, or stop and start it.

Your YAML files in /config are never overwritten. A pristine copy of the defaults lives at /defaults inside the image, so after an upgrade you can see what changed:

docker exec BuildHub diff /defaults/config.yaml /config/config.yaml

Settings added by an upgrade show as missing from your copy. Add them by hand.


Troubleshooting

Admin returns 403. Set BUILDHUB_ADMIN_TOKEN. Expected in a container -- see above. With the token set you get a normal HTTP Basic prompt: any username, the token as the password.

Banggood or eBay return zero listings. Almost always a missing --shm-size=1g in Extra Parameters.

Permission denied writing to appdata. Check PUID=99 / PGID=100 and that the share is not read-only. The entrypoint chowns /config at start and warns in the log if it cannot.

A site that used to work now finds nothing. Run health (above) to confirm, then selftest --site <key> to see the live failure. These sites change their markup without notice; that is why both commands exist.

Everything fails with CERTIFICATE_VERIFY_FAILED. Something on your network is intercepting TLS -- antivirus HTTPS scanning is the usual culprit:

docker exec BuildHub python -m buildhub.cli trust-setup

It probes a live certificate and reports which authority actually signed it.

Container restarts in a loop. docker logs BuildHub. The entrypoint prints each step as it happens, so the failing stage is normally the last line.

Is it alive? http://<tower-ip>:8000/healthz returns 200 when it is. That is also the container's own HEALTHCHECK, so Unraid's Docker tab shows it as healthy.


Notes on what it will not do

Duty ships disabled with an empty rate table, because rates depend on HTS classification and change. Enable it in duty.yaml only if you have real numbers for the goods you actually import -- a plausible-looking wrong duty rate is worse than none.

Three adapters (LCSC, Tomtop, Global Sources) ship disabled with the reason recorded in config.yaml. Global Sources in particular was ingesting navigation links as products, which is worse than returning nothing.

And the tool will not make importing cheaper than it is. The eBay baseline exists precisely because buying domestically sometimes wins, and the break-even page will tell you when.

Install BuildHub on Unraid in a few clicks.

Find BuildHub in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.

Open the Apps tab on your Unraid server Search Community Apps for BuildHub Review the template variables and paths Click Install

Download Statistics

98
Total Downloads

Related apps

Explore more like this

Explore all

Details

Repository
allornothing/build-hub:latest
Last Updated2026-09-02
First Seen2026-09-03

Runtime arguments

Web UI
http://[IP]:[PORT:8000]/
Network
bridge
Privileged
false
Extra Params
--shm-size=1g

Template configuration

WebUI PortPorttcp

Web UI / API port

Target
8000
Default
8000
Value
8000
AppdataPathrw

Database, media, browser profile and editable YAML config

Target
/config
Default
/mnt/user/appdata/build-hub
Value
/mnt/user/appdata/build-hub
PUIDVariable

Unraid's nobody user -- keeps appdata editable from the host

Default
99
Value
99
PGIDVariable

Unraid's users group

Default
100
Value
100
TZVariable

Timezone the crawl schedule is interpreted in

Default
Etc/UTC
Value
Etc/UTC
ModeVariable

serve = web UI + scheduler; web = UI only; schedule = crawler only

Target
BUILDHUB_MODE
Default
serve
Value
serve
Admin TokenVariable

Required to reach /admin from a container. Any long random string; used as the HTTP Basic password.

Target
BUILDHUB_ADMIN_TOKEN
AI ProviderVariable

Optional: none | anthropic | ollama. Enables AI-generated BOMs from a plain-language build description.

Target
AI_PROVIDER
Default
none
Value
none
AI API KeyVariable

Only used when AI Provider = anthropic

Target
AI_API_KEY
Ollama Base URLVariable

Only used when AI Provider = ollama. Point at a local/LAN Ollama instance.

Target
OLLAMA_BASE_URL
Default
http://ollama:11434
Value
http://ollama:11434
Translate ProviderVariable

Optional: none | claude | deepl | google_free. Needed to merge non-English listings by title.

Target
TRANSLATE_PROVIDER
Default
none
Value
none
Anthropic API KeyVariable

Used by Translate Provider = claude

Target
ANTHROPIC_API_KEY
Octopart Client IDVariable

Free Nexar key: https://nexar.com -- aggregates Mouser/Digi-Key/LCSC pricing by part number

Target
OCTOPART_CLIENT_ID
Octopart Client SecretVariable
Target
OCTOPART_CLIENT_SECRET
Mouser API KeyVariable

Free key: https://www.mouser.com/api-hub/ -- direct Mouser search, alongside Octopart

Target
MOUSER_API_KEY
Digi-Key Client IDVariable

Requires an approved app: https://developer.digikey.com -- direct Digi-Key search, alongside Octopart

Target
DIGIKEY_CLIENT_ID
Digi-Key Client SecretVariable
Target
DIGIKEY_CLIENT_SECRET
eBay Client IDVariable

Optional -- without it the eBay baseline falls back to browser scraping

Target
EBAY_CLIENT_ID
eBay Client SecretVariable
Target
EBAY_CLIENT_SECRET
ProxyVariable

Optional residential proxy URL(s), comma-separated

Target
BUILDHUB_PROXY