heff-shop - Saleor submodules, Docker, and the test build grind
heff-shop Saleor monorepo - platform + forked storefront submodules, Docker smoke tests, multi-channel GraphQL.
After opening the Stagea platform monorepo in late 2024, the shop problem split into two layers: community parts (catalogue, fitment, wiki cross-links) and actual checkout (inventory, channels, tax, payments). The second layer became heff0/heff-shop: a focused repo for a Saleor-powered, multi-channel storefront with the backend and frontend pinned as Git submodules instead of copy-pasted vendor trees.
Project dossier (submodule BOM, smoke-test timeline): heff-shop Saleor Monorepo.
This is a 2025 build log, not a launch announcement. The README is still one line. The value is in what the repo structure forces you to learn.
What heff-shop is
Per the repo:
Saleor powered storefront. Contains saleor/storefront + Saleor/core. Serves multi-channel.
In practice the layout is:
| Submodule | Upstream | Role |
|---|---|---|
saleor-platform/ | saleor/saleor-platform | Dockerized Saleor core: API, worker, Postgres, Redis, dashboard |
saleor-storefront/ | heff0/saleor-storefront (fork) | Next.js storefront talking to Saleor over GraphQL |
The fork matters. Upstream Saleor moves fast; the build needed a storefront submodule that could be bumped on its own schedule, patched for Heff channels, and pinned without surprise main breakage. Early commits in heff-shop reflect that pivot: initial submodules, then retargeting the storefront remote to the fork and bumping the pinned SHA.
Multi-channel is the business reason, not resume padding. stagea-stuff might sell Stagea-specific parts; heff.world might sell engineering services or generic merch; future Animal Garage inventory might be another channel entirely. Saleor's channel model maps cleanly onto "same platform, different catalogues and checkout rules", if you can keep the GraphQL API and storefront env vars honest.
Submodule subcontainers: the part that hurts
Calling them "subcontainers via repo" is almost polite. What you actually get is containers defined inside repos defined inside your repo:
- Clone
heff-shopwithgit clone --recurse-submodules saleor-platform/brings its owndocker-compose.yml: API on:8000, dashboard, worker, databasessaleor-storefront/expectsNEXT_PUBLIC_SALEOR_API_URLpointing at that GraphQL endpoint and runs its own Node dev server on:3000
The pain points, repeated every time you onboard a machine:
Submodule drift
A - in git submodule status means someone cloned without --recurse-submodules and wonders why saleor-platform/ is empty. A + means the child repo moved but the parent never committed the SHA bump. Both look like "Docker is broken" until you realize Git never fetched the child.
Double Compose namespaces
saleor-platform already binds host ports for Postgres, Redis, Mailpit, and the API. Spin up Ghost or another stack from stagea-stuff on the same laptop and you collide on 6379, 1025, or 8025. The fix is overrides and discipline (not running every submodule's default compose blindly), but that discipline has to be documented or every test build regresses.
Forked storefront wiring
The storefront is not vendored source in the parent repo; it is a submodule pointer. Changing a product card component means:
- Commit in
heff0/saleor-storefront - Bump submodule SHA in
heff-shop - Re-run the integration smoke test against the pinned platform stack
Skip step 2 and production deploys a storefront SHA that does not match what you tested.
Env var fragility
Saleor's contract is GraphQL. One wrong URL (http://localhost:8000 without /graphql/, or https against a dev HTTP API) and the Next storefront fails in opaque urql errors. Multi-channel adds NEXT_PUBLIC_DEFAULT_CHANNEL; seed data must actually contain that channel or the storefront renders empty shelves successfully, which is worse than a hard crash.
Packaging in Docker
heff-shop does not rebuild Saleor from scratch. The packaging model is orchestration, not authorship:
- Platform submodule = upstream-maintained images and compose services (the "core" in README terms)
- Storefront submodule = Next.js app containerized for dev; production packaging TBD behind the same GraphQL boundary
- Parent repo = glue:
.gitmodules, ignore rules, eventually a root compose that pins compatible SHAs and documents port maps
The challenge is version coupling. Saleor API schema changes can break storefront queries. Pinning both submodules in one parent commit is the closest thing to a reproducible "stack version" without a monolithic image. Docker gives you process isolation; Git submodules give you version isolation, when you respect the pointers.
What Docker does not solve: waiting for five services to become healthy before the storefront's first GraphQL introspection. Test builds need an explicit "API ready" gate, not docker compose up and immediate pnpm dev.
Test build-out
The 2025 goal was not feature completeness. It was a repeatable smoke path:
- Initialise submodules at pinned commits
docker compose upinsaleor-platform/: confirm GraphQL athttp://localhost:8000/graphql/- Seed or verify
default-channel(saleor-platform's sample data path) - Configure storefront
.envwith API URL, storefront URL, default channel pnpm install --frozen-lockfile && pnpm devinsaleor-storefront/- Load homepage: products resolve, cart mutation succeeds, checkout does not 500 on channel mismatch
That sounds trivial. It ate evenings. Typical failures:
- Stale volume data from an older Saleor major version
- Storefront pointing at API before migrations finished
- Channel slug typos (
default-channelvsdefault_channel) - Submodule not updated after a fork fix: "works in the child repo, fails in heff-shop"
heff-shop also stayed separate from the broader stagea-stuff monorepo on purpose. Stagea-stuff's shop/ submodule tracks upstream Saleor storefront for the community site; heff-shop is the Heff commerce lab: multi-channel experiments, fork workflow, and Docker pinning without dragging NodeBB and MediaWiki into every shop debug session.
GraphQL as the real interface
Saleor is not REST-with-extra-steps. The storefront's urql client, channel queries, and cart mutations are all GraphQL. Once submodules and Compose are aligned, most debugging is:
- Inspecting the schema in Dashboard β API playground
- Comparing storefront query documents to the pinned API version
- Tracing multi-channel context headers
That made GraphQL a practical boundary skill here, not trivia: the line where backend and frontend actually meet in this stack.
Lineage and what is next
| When | What |
|---|---|
| Dec 2024 | stageastuff-origin: why a parts catalogue exists |
| 2025 | heff-shop: Saleor platform + forked storefront, Docker test builds |
| 2022 | Django-Auto-Forum: first owned marketplace-of-ideas attempt |
Next steps for heff-shop: root-level compose overrides, CI that fails if submodule SHAs drift without a changelog entry, and channel definitions that match real inventory, not demo products.
Repository: github.com/heff0/heff-shop.
Related Posts
Local agentic AI β Mac Mini harnesses, a 4090 worker, and one Obsidian skill vault
JJ Heffernan
•Jul 6, 2026
jheff.media-site, from boilerplate fork to a modern Rust media stack
JJ Heffernan
•Jun 19, 2026
Animal Garage: building a headless automotive storefront from scratch
JJ Heffernan
•Jun 7, 2026
