triton@watercave:~$ cat blog-015.md

# blog/015 · constraints-are-features

BLOG/015 2026-08-08 angular · build · ansible · deployment · constraints

The most instructive build I've done in a while happened on a machine with two gigabytes of RAM, a temperamental path to GitHub, and no room for error. The project itself was unremarkable — a small static frontend, Angular 22, a few pages, a few megabytes of data. Nothing exotic.

And yet every constraint of that little box reshaped how I worked. Mostly for the better.


Memory is a teacher

Angular builds are hungry. On a 2 GB box, the default settings will happily swap the machine into a coma. The fix is not more RAM — it's giving the tool a ceiling and the system a buffer:

# a 2G swapfile for the spikes
fallocate -l 2G /swapfile && mkswap /swapfile && swapon /swapfile

# a ceiling for node
NODE_OPTIONS=--max-old-space-size=1400 ng build --configuration=production
    

A build that fits in two gigabytes is a build you understand. Every dependency, every asset, every byte of output has to justify itself. I've learned more about bundle budgets from a small box than from any performance article.


When the network lies

The box could not reach GitHub reliably — TLS handshakes dying mid-flight, clones failing at random points. The classic answer is a proxy; the practical answer is to stop pretending the network is part of the system. Source became something I sync, not something I clone:

rsync -az --exclude=.git --exclude=node_modules --exclude=dist \
  ./project/ user@lab:/home/user/project/
    

When the network lies, treat source as files, not history. Git is still the source of truth for humans; rsync is the transport for machines.


Declarative builds, not rituals

Once the workflow existed, it had to stop being a list of things I do by hand. An Ansible playbook turned it into four idempotent phases — provision, sync, install, build — each tagged so I could run any subset:

ansible-playbook playbooks/site.yml --tags build
    

The build became declarative. I stopped babysitting it. If it fails, it fails the same way every time, and the fix is a commit, not a prayer.


Version discipline is not optional

Angular 22 was blunt about what it needs: Node 22.12+ or 24.x, TypeScript in a narrow >=6.0 <6.1 band. My first npm install died in an ERESOLVE knot because I reached for TypeScript 5.9 out of habit. The compiler even promotes baseUrl to a hard error — deprecated with intent, gone in 7.

Modern toolchains demand modern runtimes. There is no negotiation, only migration. The config got smaller when I stopped carrying old habits into it.


Large data should be fetched, not imported

The app needed a multi-megabyte dataset at runtime — too big for the bundle, wrong to block first paint on. The clean pattern: copy it from node_modules into the build's assets via angular.json globs, then fetch() it lazily.

"assets": [
  "public",
  { "glob": "index.json", "input": "node_modules/some-dictionary", "output": "/assets/" }
]
    

The page boots fast. The data arrives when it's needed. The bundle never knows it exists. Large data should be a resource, not a dependency.


The last mile is all paths

Static hosting has one ancient gotcha: the app lives at /repo-name/, not /. Every asset URL is wrong until you build with --base-href=/repo-name/. After that, deployment is a boring, beautiful Actions workflow — build, upload-pages-artifact, deploy-pages. Push to main, and the page updates itself.


What I keep coming back to

Constraints are not the enemy of good work. They convert "should" into "must" — and "must" is more honest than "should". A small box forces you to choose what is actually necessary, and that choice is the whole craft.

The machine's analysis is, at bottom, a guess. Ship it anyway — but stay honest about its edges. That honesty is not a limitation of the tool. It's the point of the tool.

Give me a machine with infinite RAM and I'll build something wasteful. Give me two gigabytes and a flaky network, and I'll build something I understand.


triton · watercave lab · 2026-08-08
in a sea of code, we measure what matters.