Stack, install and hosting
1. Stack
Section titled “1. Stack”Rule. Use one of the two web stacks, or a native stack for a phone app. Do not introduce a third. Every surface draws its colours, type and motion from the shared design system.
Why. One small set of stacks means any engineer can move between products, installers and CI are shared, and a fix in one place can be copied to its siblings.
How to apply.
| Stack | Pieces | Use it when |
|---|---|---|
| A. Django | Django + DRF API, React + Vite + TypeScript, TanStack Query, PostgreSQL 16, Redis, Celery worker and beat, S3-compatible private storage, drf-spectacular (OpenAPI) | The product is the platform itself, or needs a long-lived API that web and native apps share |
| B. Next.js | Next.js (App Router) + React + TypeScript, Prisma, PostgreSQL, Redis (rate limits, BullMQ jobs), S3-compatible private storage, Auth.js, Tailwind CSS 4 with Radix primitives, Vitest and Playwright | A standalone app that one organisation can self-host on its own |
| C. Android | Kotlin, Jetpack Compose, Material 3, generated OpenAPI client | Native Android app |
| D. iOS | Swift, SwiftUI (iOS 17+), generated OpenAPI client | Native iPhone and iPad app |
- Native apps never hand-write API models. They use a client generated from
api/openapi.jsonand never call a route that is not in it. - Colours, fonts, spacing and motion come from this repository (
tokens/,motion/), consumed as a git submodule or a synced copy. Never hard-code a hex colour in a component. Seetheming.md. - Write the product name as “Space Works” in copy; “SpaceWorks” is for repository and code names.
Across the org. SpaceWorks: stack A. Workspace, Events, Machines, Inventory, Bookings, Organization: stack B (all with PostgreSQL 16 and Redis; Machines, Inventory, Bookings and Organization add a BullMQ worker and beat). Android: C. iOS: D. Website: plain static HTML with a small Python build step. Docs: Astro Starlight. SpaceWorks-Design-System: tokens, fonts, brand masters and a Starlight site.
2. Install and hosting
Section titled “2. Install and hosting”2.1 One-line install is the default
Section titled “2.1 One-line install is the default”Rule. Every server product installs with one line, from a published release, and asks every question before it changes anything.
Why. Makerspaces do not have a platform team. A guided installer that shows a summary, waits for a confirmation and can roll back is the safest thing we can hand them.
How to apply.
- The command is
curl -fsSL <release or raw install.sh URL> | sh(or| bash). SpaceWorks serves it from GitHub Releases (releases/latest/download/install.sh); the Next.js apps serve it from the repository’smainbranch and download the release images. - The script is wrapped in one function so a cut-off download runs nothing, checks the release checksum (and the signature where the tool is present), and prints one final block: the address to open, how to sign in, and where the log is.
- Running the same command again on an existing install shows a menu: update, back up now, check health, backups schedule, change settings, server security, automatic deploys, recover the super admin, support bundle (secrets redacted), uninstall (data kept unless the person types a confirmation).
- Updates take a backup first, pull the new images while the site keeps running, migrate, and roll back automatically if the new release is not healthy.
- Images are built by CI and published; nothing is compiled on the customer’s server. One image serves every installation, so nothing installation-specific is baked in; public origins are placeholders swapped at start.
- Secrets are generated by the installer, never printed, and never regenerated on an update.
- Provide an inspect-first path (download, read, run) for people who will not pipe to a shell.
- Windows: Linux servers are the target. SpaceWorks supports Windows through WSL2 or a Linux VM.
Across the org. SpaceWorks, Workspace, Events, Machines, Inventory, Bookings, Organization: one-line installer present (README). Events documents the fullest menu (ten options). Android and iOS are not server products: they ship through GitHub Releases (CI builds are unsigned; owner signing is needed for the stores). Website and Docs deploy as static sites.
2.2 VPS default setup
Section titled “2.2 VPS default setup”Rule. The default shape is one Linux VPS running everything in Docker Compose behind a reverse proxy with automatic HTTPS.
Why. It is the one shape every product tests end to end, and the only one with the complete backup, restore and update-rollback contract.
How to apply.
- Services: backend (API, plus migrate and operator-tool roles), frontend, PostgreSQL 16, Redis 7, private S3-compatible object storage (bundled Silo, the maintained MinIO-compatible fork, or an external bucket), a worker and a beat (timed jobs), and a reverse proxy (Caddy: automatic Let’s Encrypt, a LAN CA for trials, or the person’s own proxy).
- Run Compose with
compose.production.yml(ordocker-compose.prod.yml). Development and staging Compose files are never production setups. - Migrations are a separate release step, never part of app start-up.
- A small instance needs about 2 vCPU, 2 GB RAM and 30 GB SSD. The installer checks DNS, ports 80 and 443, and an authenticated SMTP relay before starting.
- Offer server hardening in the installer: a firewall allowing only SSH, HTTP and HTTPS, automatic security updates, fail2ban.
- Logs rotate (for example 10 MB per file, five files).
2.3 Self-host, managed and hybrid
Section titled “2.3 Self-host, managed and hybrid”Rule. There is one product and one deployment mode: the owner runs it. “Managed” is an optional setting on that same install, and “hybrid” is a way of placing its components. Nothing managed is active by default.
Why. Two products (self-hosted and cloud) would drift apart. One product with dormant managed features cannot.
How to apply.
- Self-host: everything on the owner’s server. The default. Fully works with no outside service.
- Managed / hosted spaces: the owner may switch on “hosting other makerspaces” to run spaces for other people (plans, per-space limits, trial, hosting invoices, subdomains, custom domains). When it is off every space is an “own” space with no limits, and every managed screen and route is inert. In SpaceWorks this is keyed off whether a platform domain suffix is set.
- Hybrid (required for every server product): a per-part choice. The database, Redis, file storage, email and the website each run on this server or outside (managed database, S3-compatible storage, SMTP or an email API, a CDN or static host), independently of each other, and every part defaults to local. The installer asks per part and probes each outside part before anything starts. This is the SpaceWorks model, and it is the rule for every product. In SpaceWorks the choices are website (on this server, elsewhere, or none: API-only), database (bundled PostgreSQL 16, or any outside PostgreSQL 16 or 17 such as Supabase, Neon, RDS), Redis (bundled, outside, or none), file storage (bundled Silo, or any S3-compatible service such as R2, B2, S3), and email (SMTP or an API provider). The Next.js split layout (website on Netlify or another static host, everything else on the VPS) is only one case of this rule, not the whole of it. Moving a part later (for example Redis none, bundled, outside) is one supported command.
- Every outside service is tested before anything starts (reachability, an empty database on first
install, a signed upload). A database behind a transaction pooler is refused (advisory locks, migrations and
pg_dumpcannot run through one). The running services never receive the database owner login. - Declare a storage capability profile (
limitedorfull) and never treat a limited store as the stronger kind. - Both sides of a split must be HTTPS (sign-in uses cross-site cookies). Deploy the same source revision to both halves.
- No-Redis (lite) mode: background jobs run in the request, scheduled jobs run from a once-a-minute scheduler loop, the cache lives in the database and live screens poll. Suits a small space. Moving between none, bundled and outside Redis later must be one command.
- Other shapes (cloud blueprints, a free profile with hard caps, Helm) are documented as packaging of the same product, each stating what it gives up.
2.4 Backups, restore, upgrades
Section titled “2.4 Backups, restore, upgrades”Rule. A backup exists before every change, and a restore is a deliberate, documented step.
How to apply.
- Nightly database, config and uploaded-file backups, optionally encrypted to a key kept off the server
(
age) and copied off site. A failed or stale backup shows in the operations screen and is emailed. - Restore is an explicit script, never automatic. Backups, restore and tenant migration must work against an outside database with a client whose major version equals the server’s.
- A tenant (one space) can be exported as a portable dump and moved to another server; publication refuses if money moved after the freeze.
- Upgrades only move forward; a re-run over an existing install is an update.
Across the org 2.2 to 2.4. SpaceWorks: full VPS, split (API-only), hybrid database/Redis/storage/email,
no-Redis mode, cloud and free profiles, Helm, tenant migration. Events: full VPS, split layout with Netlify,
external S3, existing proxy, age-encrypted backups with off-site copy, Render and Helm. Workspace, Inventory:
VPS install, split frontend (Netlify) and external-storage and external-proxy Compose overlays. Machines,
Bookings: VPS install, Netlify split, no-Redis development mode (JOBS_INPROCESS). Organization: VPS install,
Netlify split. For the Next.js apps other than Events, an outside-database or per-component installer
question is not checked; their READMEs describe split and external storage/proxy, not the full
SpaceWorks questionnaire. Android and iOS: n/a, but they take any server URL and validate it against the
health endpoint.
2.5 Release images: frontend and backend only
Section titled “2.5 Release images: frontend and backend only”Rule. A product release publishes exactly two container images: <product>-frontend and <product>-backend
(multi-architecture: amd64 and arm64), verified after publishing. Everything else a deployment runs is NOT a product
image: the database (Postgres), Redis, object storage (Silo/S3-compatible), the reverse proxy, and any other
infrastructure come from their upstream images at a pinned version, pulled by the install script according to the
choices made in setup. Background workers and schedulers (Celery worker and beat, migrations, one-off jobs) run from
the backend image with a different command — never a third image. No all-in-one image.
Why. Two images keep releases small, cacheable and auditable, and let every deployment shape (one VPS, split hosting, outside database or storage, no-Redis mode) be assembled from the same artefacts. An all-in-one image hides version drift of bundled services, cannot use a managed database or storage, and doubles what has to be built, scanned and supported. (SpaceWorks retired its all-in-one image in 0.10 and removed it from the registry for 0.12.0.)
How to apply.
- CI builds and publishes only the two images; a release check fails if any other image is pushed.
- The install script asks which parts run locally or outside (§2.3), tests every outside service first, then pulls
and starts only what is needed (
compose pull+up -d); pinned upstream tags live in the compose file and are bumped deliberately. - Update scripts pull the new frontend/backend tags plus any changed infrastructure pins, then migrate.
- Documentation lists the two images and every pinned upstream image with its version.
- Object storage is Silo. The bundled object store is Silo (
pgsty/silo), pinned bySILO_IMAGE_TAGin SpaceWorks. An outside store must be S3-compatible (AWS S3, Cloudflare R2, Backblaze B2, …). In SpaceWorks the compose service and its volume keep the historical namesminio/minio_dataon purpose: renaming them would detach the existing evidence data on update.
Part of Space Works product rules, published from docs/product-rules.md. Edit it there.