Deployment

One host, thirty minutes, your infrastructure

CampusLink is a single-tenant application that runs on a server you control. No container orchestration to learn, no cloud account to open, and no vendor holding a copy of your student data.

Architecture

Deliberately unremarkable

nginx terminates TLS and proxies to a Next.js process on localhost. A second PM2 process ticks every five minutes for recurring supply orders, footage retention scrubbing and webhook retries. MariaDB sits on the same host.

This is a stack a district technology department can already operate. Horizontal scaling is a later problem — for the size of district CampusLink is built for, one properly specified host is the correct answer, and adding a cluster would add failure modes without adding capacity anyone needs.

Attachments — permit PDFs, insurance certificates, footage extracts, safety plans — live on local disk with metadata in the database, or in an S3 bucket if you would rather. Everything else in the deployment is reproducible from a git pull and a build.

Requirements

What to provision

Hand this section to whoever builds your servers. There is nothing unusual on it.

Operating system Ubuntu 22.04 LTS or 24.04 LTS, x86_64
Minimum 2 vCPU · 4 GB RAM · 50 GB disk
Recommended 4 vCPU · 8 GB RAM — sized for a district of about 20,000 students
Runtime Node.js 20 · Next.js 14 · PM2 (web process + background worker)
Database MariaDB or MySQL 8, local to the host
Web nginx terminating TLS, reverse-proxying to 127.0.0.1
Network 80/443 inbound; outbound 443 to your SMTP relay and SAML IdP
Attachments Local disk at /var/lib/campuslink/attachments, or an S3 bucket

A SAML 2.0 identity provider — required

It must issue an SP-initiated response, sign with RSA-SHA256, use the email NameID format, and emit email, displayName and groups. Known good: Entra ID, Okta, Google Workspace, OneLogin, JumpCloud.

An SMTP relay — strongly recommended

Approvals, receipts and notifications flow through it. Without one the platform still boots, and messages are recorded in the email log as skipped rather than silently lost — but nobody receives them.

Installing

Three ways in, one result

01 ~30 minutes

Scripted

One idempotent shell script on a clean Ubuntu host. Installs Node, the database, nginx and certbot; creates the database and a random password; generates the environment file with a fresh secret; sets up both PM2 processes; runs migrations and seeds.

02 45–60 minutes

Manual

Every step run by hand, for locked-down environments, non-Ubuntu distributions or VM templates. Same result. Districts whose security team wants to watch each step usually choose this one — and the script is heavily commented precisely so it can be read first.

03 ~30 minutes

Proxmox

A script on the PVE host creates an LXC container, picks the next free cluster-wide ID, and runs the manual install inside it.

Upgrades

A customer is a profile, never a branch

Every district runs the same code, unmodified. What makes an install yours is a small profile file and an environment file — your name, your buildings, your branding, your SAML details.

This is the single most important thing about the upgrade path. The moment district-specific behaviour is committed into a module, that district needs a fork, and a fork stops receiving upstream fixes without a merge somebody has to pay for. If a requirement cannot be expressed in the profile, the profile is extended — for every district at once.

The practical result: a security fix released on a Tuesday reaches every district on the same Tuesday, and no district is quietly stranded three versions back because of a customisation made two years ago.

profile.ts
// everything that makes an install yours
export const profile: CustomerProfile = {
  key: 'riverview',
  districtName: 'Riverview Community Schools',
  emailDomain: 'riverview.k12.in.us',
  publicHost: 'campuslink.riverview.k12.in.us',
  sites: [
    { code: 'DIST', name: 'District Office', kind: 'DISTRICT' },
    { code: 'RHS',  name: 'Riverview High School', kind: 'SCHOOL' }
  ],
  branding: { primaryColor: '#1d4ed8' }
};

Managed operation

Or let us run it

Self-hosting means the district owns the server, the backups, the certificates and the uptime. Plenty of technology departments are glad to. For those that would rather not add another system to the on-call list, Melton Electronics operates it on your infrastructure.

  • Install and initial configuration on your hardware or VM
  • SAML wiring against your identity provider, with the claim-to-role rules written for your org structure
  • Site, room and building import from whatever export you can produce
  • Version upgrades scheduled outside school hours
  • Backup verification and periodic restore drills — the part everyone plans and nobody tests
  • Named contact with a response commitment in writing

What the district still owns

Even under a managed agreement, these stay with you — and any vendor who tells you otherwise is describing something they cannot deliver.

  • The server or VM, and the network it sits on
  • The data, and the retention policy applied to it
  • Identity provider configuration and account lifecycle
  • Staff training and the directory-information policy

Have your technology director on the call

The deployment conversation goes faster with the person who will run it in the room. Bring your IdP, your MDM and your backup arrangement, and we will map it out live.