First principles
Nine ideas behind how we build and ship. Every rule in engineering rules comes from one of them. Argue with the principle, not the rule.
1. One fixed version, built once, promoted unchanged
Build an artefact once. Move it between environments by reference. Roll back by re-pointing to the previous reference.
Rebuild per environment, or float on a moving tag, and you no longer know what is running, nor how
to put back what ran before. Hence: digest pins, no :latest in deploy configuration, promotion
as a commit that moves a pin.
2. Every input is pinned
A build that resolves a version at build time depends on the day it ran.
- Base images by digest.
- Third-party actions by commit.
- Tools by
mise.tomlandmise.lock. - Interpreters by exact version.
- No
curl | shin a build path. An installer script is an unpinned input over the network.
A pin needs something that moves it deliberately, or it rots.
3. Logic lives once, and both sides run it
- If CI can do something you cannot run locally, then that thing is untested.
- If a Dockerfile spells out its own build command, then the image ships an artefact that no test ever built.
So build logic lives in shared scripts, called through one task runner. Laptop, workflow, and Docker stage run the same task. The pipeline can then leave GitHub Actions without moving a line of build logic.
4. Parallel change: expand, migrate, contract
Introduce every change beside what it replaces.
- Expand. Add the new path. The old one stays authoritative.
- Migrate. Move callers one at a time. Each move is harmless alone.
- Contract. Delete the old path once nothing uses it.
The migration then runs on its own schedule and reverses at every step. It binds tooling, database schemas, and internal APIs alike. A destructive migration shipped with the code that needs it cannot be rolled back, which forfeits principle 1.
It also makes big changes reviewable. Nobody holds a whole switch in their head, so a change that flips everything gets approved on trust.
5. Every environment is a full, independent stack
Each environment owns everything it needs, data plane and control plane. Its workload project runs the product, and its platform project holds everything long-lived. No cross-environment wiring, no shared identity store, no shared secrets. Borrow a piece of another environment and you prove nothing about a change to that piece, and inherit its faults.
Parity is shape, not capacity. Same services, same topology, same region, sized down. A different shape rehearses nothing.
6. Dependencies point up, never down
An environment MAY depend on what a higher-tier environment shares with it. Never the reverse.
production is the highest tier and depends on no other environment. staging's platform project
is shared with every environment below it.
A shared component removes a boundary, so the design MUST replace what it removed: scoped IAM, per-repository retention, immutability. Sharing is never free.
7. Keep families symmetric, do not manage divergence
Two things that SHOULD match drift apart. Each difference then grows machinery: a pin held equal to another pin, a check for the pin, a hook for the check. That machinery solves nothing. It administers a difference that SHOULD NOT exist.
Before adding a task, check or hook, ask what it guards. If it guards a real invariant, then keep it. If it holds two divergent paths in step, then close the gap and the check deletes itself. Worked example: container images.
8. Credentials are short-lived, scoped, and never shared
- No long-lived key where a federated identity works.
- No standing admin where time-boxed elevation works.
- No secret crossing an environment boundary.
A permanent credential eventually leaks. One spanning two environments turns a small incident into a large one.
9. Documentation is part of the change, not a follow-up
A page describing how something used to work is worse than no page. It gets read, believed, and acted on.
So the page changes in the same commit as the thing. Writing it then is cheap and falls on the person who understands the change. Correcting it later falls on whoever was misled, and they do not know they were.
Where these come from
The Platform Engineering programme, generalised to the whole repository. What it built and what is still open: environments, container images, current state of the architecture.