Skip to main content
Version: Next

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 updates
  • docker-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:

  1. It uses immutable image references from release-manifest.json instead of local builds.
  2. It includes a first-class install and update flow that validates .env.school, pulls images, runs migrations, and verifies health.
  3. 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.json
  • deploy/school-bundle/.env.school.example
  • deploy/school-bundle/docker-compose.yml
  • deploy/school-bundle/install.sh
  • deploy/school-bundle/install.ps1
  • deploy/school-bundle/update.sh
  • deploy/school-bundle/update.ps1
  • deploy/school-bundle/validate-env.sh
  • deploy/school-bundle/health-check.sh
  • deploy/school-bundle/verify-release.sh
  • deploy/school-bundle/preflight-check.sh
  • deploy/school-bundle/preflight-check.ps1
  • deploy/school-bundle/import-airgap.sh
  • deploy/school-bundle/release-checksums.json
  • deploy/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 Kubernetes
  • Publish School Release: assembles the school bundle artifact and manages school rollout orchestration
  • School 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.

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.gz
  • school-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:

  1. Create or confirm the tenant that owns the school.
  2. Create the school record under that tenant.
  3. Generate or assign the deployment license when license enforcement is enabled.
  4. Prepare the school release bundle with the approved frontend and backend image references.
  5. Record the issued setup values for the appliance:
    • TENANT_ID
    • LOCAL_SCHOOL_ID
    • SITE_ID
    • CLOUD_SYNC_URL
    • CLOUD_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:

  1. Install and start Docker Desktop, Docker Engine, or the supported container runtime.
  2. Copy or extract the school release bundle onto the machine.
  3. Create deploy/school-bundle/.env.school from .env.school.example.
  4. Fill in the issued tenant, school, site, cloud sync, and license values.
  5. Run the bundle installer with install.sh, install.ps1, or npm 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.

Copy These From The Cloud Handoff Form

Before install, support should hand the operator a deployment handoff containing these exact values:

  • TENANT_ID
  • LOCAL_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 tenant
  • LOCAL_SCHOOL_ID: the UUID of the school record this appliance is being deployed for
  • SITE_ID: a stable identifier for this physical server or appliance

Where they come from:

  1. TENANT_ID comes from the cloud-side provisioning or onboarding handoff.
  2. LOCAL_SCHOOL_ID comes from the cloud-side school record created for that school.
  3. SITE_ID is 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_ID or LOCAL_SCHOOL_ID locally. They are issued values.
  • Generate SITE_ID once and keep it stable across updates, restores, and reboots.
  • On a fresh install, set TENANT_ID explicitly 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 sync
  • CLOUD_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:

  1. tool readiness is checked
  2. .env.school is validated
  3. bundle checksum and optional signature are verified
  4. pinned image tags are read from release-manifest.json
  5. images are pulled
  6. PostgreSQL, Redis, MinIO, and initialization services start
  7. backend migrations run from the released backend image
  8. backend, worker, frontend, and nginx start
  9. 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:

  1. Confirms the bundle is running in local deployment mode.
  2. Reads TENANT_ID, LOCAL_SCHOOL_ID, SITE_ID, CLOUD_SYNC_URL, and CLOUD_SYNC_API_KEY.
  3. Builds a deterministic local device ID from TENANT_ID and SITE_ID.
  4. Pulls cloud sync_changes for the tenant and school in batches.
  5. Applies those changes to the local database through the sync domain applier.
  6. 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:

  1. It verifies that the local tenant record exists.
  2. It runs the tenant bootstrap runner to materialize tenant-scoped defaults.
  3. It verifies that the local school record exists.
  4. 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.

This is the school deployment document

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.