← all posts

11 Oct 2026

Designing NORSTEC's Server Architecture

platform·server·norstec·infrastructure

Field notes — still taking shape, written down as we go rather than after the fact. Last reviewed 2026-10-11.

NORSTEC runs its infrastructure on OpenStack, hosted by NTNU, with a small rotating cast of IT members doing the administration. That combination; few hands, shared institutional hosting, people who'll move on before long — shaped almost every decision below. We're optimizing for a system the next person can understand, not one only this year's team can operate.

Starting from the Twelve-Factor App#

Rather than invent our own conventions, we adopted the Twelve-Factor App methodology as the backbone for how services get built and run. A few of its rules did the most work for us:

  • Codebase & config are separate. Nothing that changes between a dev and prod deploy lives in code — it's env vars or an uncommitted config file. In principle, the codebase could go open source tomorrow without exposing anything.
  • Backing services are swappable resources. A datastore or SMTP service is addressed by URL and credentials in config, never hard-wired — so swapping providers, or even local for third-party, is a config change, not a rewrite.
  • Processes are disposable. Fast startup, graceful shutdown, and tolerance for being killed outright. That's what lets us scale out instead of up, and recover from failure without ceremony.

Port binding follows the same logic: every service runs in its own container and exposes a local port, which traefik proxies out. The service itself never needs to know it's public.

Five kinds of server, not one#

The topology splits by role rather than by convenience. Each server type exists because something specific needed isolating:

Server Role
prod (external) Public-facing services
prod (internal) Dashboards, schedulers
backing Databases, data stores
logging SIEM, log collection
dev Staging, config-only diff from prod

The dev server is deliberately a clone of prod in every way except config — the one axis the Twelve-Factor rules say is allowed to differ. That means a feature that works in staging should behave identically in prod, which is the entire point of having a staging environment at all.

Why split external from internal#

Because NTNU is the OpenStack provider, we're required to keep separate OpenStack projects for externally-reachable and internally-only services. We leaned into that requirement rather than working around it: it buys zone-level security almost for free, since a compromise of a public-facing service doesn't automatically put it in the same network namespace as internal tooling.

Why logging gets its own line item#

Centralizing logs onto one server costs us a bit of compute we technically don't need at our current scale. We're paying that cost anyway, for three reasons: it keeps control and separation of concerns clean, it keeps clutter off the prod server, and — the main one — it lets us enforce least-privilege access on who can read logs at all.

Access rule: only high-level sysadmins, plus the specific people handling compliance or security work, touch the logging server. Application admins, end users, and the help desk have no reason to be there — these systems don't serve the daily business, only auditability.

Network rules, stated plainly#

The rules we've settled on so far are short on purpose:

  • Only the prod server can reach backing services — never the reverse.
  • Every server can send to logging.
  • Access to logs is itself logged.

That second rule is the one we're still least happy with. "Everything can send to logging" is the right default for not missing anything, but it's also the widest-open rule in the whole document, and we haven't yet scoped it to specific senders or protocols. It's on the list.

One filesystem convention, everywhere#

Every server keeps OS files and application files apart by routing anything server-related through /srv, rather than letting it sprawl across /etc, /opt, or /home. On a plain prod box that's just srv/services and srv/scripts, with services sitting flat — no further nesting by team or language.

The backing server earns one more layer, because it actually holds state: a service like services/postgres keeps its data in the matching data/postgres, and backups land in backups/postgres alongside it. Same shape, same names, no surprises when you're standing on a different box than usual.