Skip to content

Colophon

How Image Factory is built.

The argument for buying this is that the assembly is finished, not that the parts are secret. So here are the parts, the ones we chose not to build, and the decisions we got wrong the first time.

The stack

What it is made of.

Nothing here is unusual, and that is the point — every one of these is a tool you could pick up yourself this afternoon.

Build

  • HashiCorp Packer

    Builds the machine image. One parameterised source block and the provisioner scripts are hand-written; the build block is generated per image from its pipeline.

  • Ansible

    Runs playbooks against the build VM over WinRM, for configuration better expressed declaratively than as another script.

  • Chocolatey

    Installs applications, from the community feed or from a private source of your own.

  • PowerShell

    The hardening and Sysprep provisioners. Windows work, in the language Windows answers to.

  • AaronLocker, OSConfig, ACSC, WDOT

    Vendored hardening toolkits, pinned and shipped with the runner so a build resolves nothing from the internet.

Platform

  • Azure Compute Gallery

    Where versions are published and replicated — and the source of truth for what versions exist, which is why the definition does not carry one.

  • PostgreSQL

    Definitions, revisions, change requests, builds and the audit trail. One schema per tenant, so a scoping bug cannot cross a customer boundary.

  • Docker and Compose

    One set of container images for every deployment profile. The profiles differ by configuration, never by fork.

Console

  • Next.js

    Server components and server actions. The page you are reading is the same application as the console behind sign-in.

  • TypeScript and Zod

    The manifest schema is a Zod schema. It is the definition of what an image is, and it validates the form you type into and the job a runner executes from one source.

  • Tailwind CSS and HeroUI

    Re-themed entirely through CSS custom properties — no fork, no ejected components, one token file that both this page and the console read.

  • kawarp

    The ambient WebGL backdrop behind this page. Purely decorative: it degrades to nothing at all without WebGL.

Delivery

  • GitHub Actions

    Manifest validation, image builds and container publishing. A workstation build and a CI build take an identical path.

  • GitHub Container Registry

    Where the control plane and runner images are published for every profile to pull.

  • Portainer

    One supported way to stand the stack up on a customer host without handing anybody a shell script.

Restraint

What we deliberately did not build.

Every one of these was a real temptation, and saying no to it is the reason the thing ships.

  • An image builderPacker is one, and a better one than we would write. What was missing around it was everything else on this site.
  • A JWT libraryVerifying RS256 is a single node:crypto call once the matching key is imported from the issuer's JWKS. That is one less dependency to keep patched in a security-critical path.
  • An embedded git repository for historyIt was a heavier way to get version history and a second system to operate, back up and reason about. Append-only revision rows do the same job in the database that was already there.
  • A fork of our component libraryHeroUI reads its entire look from CSS custom properties, so re-theming it is a stylesheet rather than a maintenance burden that grows with every upstream release.
  • A separate marketing stackNo CMS, no second framework, no third-party landing page builder. This page shares one set of design tokens with the console, which is why they look like the same product.
  • An icon dependencyThe icon set is about twenty hand-drawn paths on a 24-pixel canvas. It weighs nothing and it matches.

Corrections

What we changed our mind about.

A product this age has a history of being wrong about things. Hiding that would make the rest of this page less believable, not more.

  • Manifests were gitignored variable filesWhich meant the most important state in the system lived outside version control. They are validated documents with history now.
  • Review was a pull requestIt is a row now — the same optimistic-concurrency check, against a revision id instead of a git blob sha, and no GitHub account required to approve a change to your own image.
  • Versions lived in the definitionWhere they drifted from what the gallery actually held. The gallery answers that question now, at build time.
  • Change authorship was a configuration valueHonestly labelled as self-reported and unverified, because a server action is a public endpoint and an author name from the browser is forgeable. It comes from the session now.

Still want to build it yourself?

Genuinely fine — the reading list is above. If you would rather have it running this week, that is the other option.