Deploy with the Backend School Bundle
This is the standard production workflow for deploying the full Makronexus system to a school site.
The backend school bundle is the supportable on-prem school artifact in deploy/school-bundle. It is the release operators should use for installs, updates, and verified handoff to a school laptop or local appliance.
What changed
The school deployment path now has one standard production artifact and one separate developer-only compose path:
deploy/school-bundle/docker-compose.yml: the standard supportable school appliance bundle for installs and updatesdocker-compose.local.yml: a developer or local environment file, not the school production artifact
The appliance bundle is the school deployment artifact. It differs from local compose workflows in three important ways:
- It uses immutable image references from
release-manifest.jsoninstead of local builds. - It includes a first-class install and update flow that validates
.env.school, pulls images, runs migrations, and verifies health. - It includes runtime deployment license enforcement for local and hybrid deployments.
What the bundle deploys
The backend school bundle runs the full school stack:
- frontend
- backend
- PostgreSQL
- Redis
- MinIO
- migration container
- notification worker
- nginx reverse proxy
Bundle files
Important files in the deployment artifact:
deploy/school-bundle/release-manifest.jsondeploy/school-bundle/.env.school.exampledeploy/school-bundle/docker-compose.ymldeploy/school-bundle/install.shdeploy/school-bundle/install.ps1deploy/school-bundle/update.shdeploy/school-bundle/update.ps1deploy/school-bundle/validate-env.shdeploy/school-bundle/health-check.shdeploy/school-bundle/verify-release.shdeploy/school-bundle/preflight-check.shdeploy/school-bundle/preflight-check.ps1deploy/school-bundle/import-airgap.shdeploy/school-bundle/release-checksums.jsondeploy/school-bundle/nginx/default.conf
Prepare the release manifest
Support should stamp bundle releases with image references that are ready for school deployment. For production releases, prefer immutable digest-pinned frontend and backend references.
npm run bundle:manifest -- --release-version <releaseVersion> --registry-owner <registryOwner>
To stamp the bundle for Docker Hub instead of GHCR:
npm run bundle:manifest -- --release-version <releaseVersion> --registry-provider dockerhub --registry-namespace <dockerHubNamespace>
If frontend and backend use different refs, override the image directly:
npm run bundle:manifest -- \
--release-version <releaseVersion> \
--backend-image docker.io/<dockerHubNamespace>/mn-school-be:<backendReleaseTag> \
--frontend-image docker.io/<dockerHubNamespace>/mn-school-fe:<frontendReleaseTag>
GitHub Actions workflows involved in release preparation:
Publish Backend Image: publishes the backend image and can deploy it to KubernetesPublish School Release: assembles the school bundle artifact and manages school rollout orchestrationSchool Fleet Operations: reuses an existing release for school fleet operations without rebuilding the backend image
For stable or all-wave releases, Publish School Release should use digest-pinned frontend and backend image references by default.
Recommended handoff format
Support should preferably give the school an already assembled release directory and, when needed, the matching archive plus checksum for transfer verification.
The working directory typically looks like this:
school-bundle-release-<releaseVersion>/
school-bundle/
docs/
scripts/
RELEASE-BUNDLE.json
The assembled package can also include a transfer archive and checksum manifest:
school-bundle-release-<releaseVersion>.tar.gzschool-bundle-release-<releaseVersion>.tar.gz.sha256.json
The packaged extraction helpers verify the checksum manifest before unpacking the archive.
End-to-end school setup
This is the field workflow from cloud preparation through a working school laptop or local appliance install.
1. Prepare the school in the cloud
Before visiting the school, the cloud environment remains the source of truth for the school identity and deployment authorization:
- Create or confirm the tenant that owns the school.
- Create the school record under that tenant.
- Generate or assign the deployment license when license enforcement is enabled.
- Prepare the school release bundle with the approved frontend and backend image references.
- Record the issued setup values for the appliance:
TENANT_IDLOCAL_SCHOOL_IDSITE_IDCLOUD_SYNC_URLCLOUD_SYNC_API_KEY
At this point, the cloud knows which tenant and school the appliance belongs to, and which site is allowed to perform the first bootstrap pull.
Step 1: Verify transfer if an archive was provided
If support provided the tarball and checksum manifest, verify and extract it first.
Windows PowerShell:
powershell -ExecutionPolicy Bypass -File .\scripts\extract-school-bundle-release.ps1 `
-Archive .\school-bundle-release-<releaseVersion>.tar.gz `
-ChecksumFile .\school-bundle-release-<releaseVersion>.tar.gz.sha256.json `
-OutputDir .
If the release is signed, place trusted-release-signing-public.pem beside the bundle before install.
2. Configure the laptop at the school
On the school laptop or appliance:
- Install and start Docker Desktop, Docker Engine, or the supported container runtime.
- Copy or extract the school release bundle onto the machine.
- Create
deploy/school-bundle/.env.schoolfrom.env.school.example. - Fill in the issued tenant, school, site, cloud sync, and license values.
- Run the bundle installer with
install.sh,install.ps1, ornpm run bundle:install:ps.
A fresh cloud bootstrap requires internet access unless this is an offline restore or manually seeded local database using CLOUD_BOOTSTRAP_MODE=skip.
Step 2: Enter the bundle directory
cd .\school-bundle-release-<releaseVersion>\school-bundle
Step 3: Prepare configuration
Create the school configuration file if it does not already exist:
copy .env.school.example .env.school
Fill in the school-specific values support provided.
Before install, support should hand the operator a deployment handoff containing these exact values:
TENANT_IDLOCAL_SCHOOL_ID- any school-specific licensing values
The operator should generate or confirm SITE_ID for the physical appliance once, write it into .env.school, and keep it unchanged for the lifetime of that installation.
Typical values to confirm:
- tenant or site-specific licensing values
- image overrides when support uses a local registry mirror
- deployment secrets or credentials that must not remain placeholders
Required identity values in .env.school:
TENANT_ID: the tenant UUID assigned to the school's owning tenantLOCAL_SCHOOL_ID: the UUID of the school record this appliance is being deployed forSITE_ID: a stable identifier for this physical server or appliance
Where they come from:
TENANT_IDcomes from the cloud-side provisioning or onboarding handoff.LOCAL_SCHOOL_IDcomes from the cloud-side school record created for that school.SITE_IDis created for the appliance during first install and then kept unchanged for the lifetime of that installation.
Operator rules:
- Do not invent or regenerate
TENANT_IDorLOCAL_SCHOOL_IDlocally. They are issued values. - Generate
SITE_IDonce and keep it stable across updates, restores, and reboots. - On a fresh install, set
TENANT_IDexplicitly in.env.school. Do not rely on the local database to infer it before bootstrap has completed.
Example:
TENANT_ID=872880db-b6e6-4ebc-a3e3-078f023cb041
LOCAL_SCHOOL_ID=5db0c8d4-c9f6-4f1a-b56d-8cb3d8dd6f2a
SITE_ID=school-laptop-eg2fltlv
Additional bootstrap values commonly required in .env.school:
CLOUD_SYNC_URL: the cloud API base used for initial bootstrap pull and syncCLOUD_SYNC_API_KEY: the issued credential that authorizes the local appliance to pull the school's bootstrap data
Step 4: Run preflight
Windows PowerShell:
powershell -ExecutionPolicy Bypass -File .\preflight-check.ps1
Preflight must succeed before install or update.
Step 5: Install
Windows PowerShell:
powershell -ExecutionPolicy Bypass -File .\install.ps1
For Linux or macOS operators:
./install.sh
What happens during install:
- tool readiness is checked
.env.schoolis validated- bundle checksum and optional signature are verified
- pinned image tags are read from
release-manifest.json - images are pulled
- PostgreSQL, Redis, MinIO, and initialization services start
- backend migrations run from the released backend image
- backend, worker, frontend, and nginx start
- health checks run through the reverse proxy
After health checks pass, the bundle also runs the backend bootstrap sync CLI. In local or hybrid mode, it uses TENANT_ID, LOCAL_SCHOOL_ID, and SITE_ID to pull the school's cloud data into the local database before the deployment is treated as complete.
That bootstrap pull is scoped by these effective values:
x-tenant-id: <TENANT_ID>
x-school-id: <LOCAL_SCHOOL_ID>
x-site-id: <SITE_ID>
If deployment licensing is enabled, any tenant_id, school_id, and site_id claims in the signed license token must also match these configured values.
What bootstrap does
When CLOUD_BOOTSTRAP_MODE allows bootstrap, the backend image runs the cloud bootstrap CLI. The CLI:
- Confirms the bundle is running in local deployment mode.
- Reads
TENANT_ID,LOCAL_SCHOOL_ID,SITE_ID,CLOUD_SYNC_URL, andCLOUD_SYNC_API_KEY. - Builds a deterministic local device ID from
TENANT_IDandSITE_ID. - Pulls cloud
sync_changesfor the tenant and school in batches. - Applies those changes to the local database through the sync domain applier.
- Saves progress locally so an interrupted bootstrap can resume safely.
The pull is expected to create the local tenant and school skeleton records. The CLI does not invent those records locally because they must come from cloud-side provisioning.
What local defaults are materialized
After the remote pull completes, the CLI automatically runs both idempotent local bootstrap runners:
- It verifies that the local tenant record exists.
- It runs the tenant bootstrap runner to materialize tenant-scoped defaults.
- It verifies that the local school record exists.
- It runs the school bootstrap runner to materialize school-scoped defaults.
Tenant-scoped defaults include artifacts such as subject templates, default role wiring, search configuration, notification defaults, communication topics, department blueprints, and GL accounts.
School-scoped defaults include artifacts such as school years, grade levels, school-wide streams, class divisions, grade categories, curricula, and admission policies.
This makes the first local install deterministic even when some defaults were created in cloud bootstrap tables and were never emitted as sync_changes rows.
Step 6: Verify deployment
Confirm these before handing the system to end users:
- the frontend loads through the school URL or LAN address
- backend health succeeds
- migrations completed without error
- the bundle install did not report checksum, signature, or license failures
- the expected tenant and school data were pulled successfully from cloud bootstrap
- the local appliance identity values remain aligned with the cloud handoff
Step 7: Update later releases
Use the packaged update command, not raw compose commands:
powershell -ExecutionPolicy Bypass -File .\update.ps1
The update flow reuses the same verification and health-check model as install.
For Linux or macOS operators:
./update.sh
Step 8: Roll back only with support guidance
If a release fails after update:
- stop and collect logs
- use the previously approved release directory
- rerun the packaged update or reinstall path from that known-good bundle
Do not improvise by mixing files from different bundle versions.
If you need to explain how to deploy the Makronexus system in a school, this is the deployment path to document and follow. Do not replace it with source checkout steps, ad hoc compose commands, or school-side npm install workflows.