PlatformCLOUD-SPECIFICSCALE-SPECIFIC

Self-Service Infrastructure

Letting teams provision what they need without a ticket, by constraining what can be asked for rather than by reviewing every request.

The question, the obvious approach, and why it breaks

Every lesson starts where the work starts: an operational problem, a first attempt that is entirely reasonable, and the way production disagrees with it.

The production question

How does a team get a database, a queue or a bucket without waiting for someone, and without being able to create anything at all?

The problem

Infrastructure requests are frequent, mostly routine, and answered by a small number of people who hold the credentials — so a routine need becomes a queue, and the queue becomes the reason people build workarounds.

What teams do first

Give teams direct access to the cloud console. They know what they need better than a central team does, and it removes the bottleneck immediately.

How it breaks

Console-created resources exist outside any plan, so the infrastructure definition and reality diverge from the first click (Drift).

How it breaks in production
  • Console-created resources exist outside any plan, so the infrastructure definition and reality diverge from the first click (Drift).
  • Nothing carries the defaults that matter and nobody remembers: encryption, backups, private networking, retention, ownership tags.
  • A resource created by a person leaves with that person. Six months later nobody knows what it is for, and deleting it is a risk nobody wants to take.
  • Broad console access is broad production access, and the blast radius of a mistake is the whole account (Least Privilege in Production).
  • Spend rises with no way to attribute it, because the resources carry no owner (Cost Per Request).
CodeBuildTestArtifactReleaseDeployRunObserveOperateIncidentRecoverLearnImprove

What is actually happening

Underneath the tooling, which is the part that survives a change of tool.

  • Self-service is not "everyone can do anything". It is a *narrowed interface*: teams can ask for the small number of things the platform models, with the parameters the platform exposes, and nothing else.
  • The narrowing is what makes review unnecessary. If the only thing that can be requested is a database from a reviewed module, with private networking and backups fixed and only size and version as parameters, then approving each request adds latency and no safety (Guardrails, Not Gates).
  • The request must be a *declaration* rather than an action. A team declares that this service needs a database of this size; the platform reconciles reality towards that declaration, so re-running is safe and drift is detectable (Declarative vs Imperative Infrastructure).
  • The identity performing the provisioning is the platform's, not the team's. Teams never hold credentials broad enough to create infrastructure directly — which is what lets the interface be the only control point.
  • Every provisioned resource must be traceable to a declaration, an owner and a reason, or day two is impossible.

The request is a declaration, not an action

The structural choice that decides everything else: does the team perform an action against the cloud, or declare a desired state that the platform reconciles? The first cannot be reviewed, cannot be re-run and leaves no record. The second is code, and inherits everything you already know how to do with code.

This is also what makes the interface narrow enough to skip approvals. The team is not asking for arbitrary infrastructure; it is filling in the parameters of a shape someone already reviewed.

A resource claim in the team's repository — tool-neutral shape
1apiVersion: platform/v1
2kind: ManagedPostgres
3metadata:
4 name: orders-primary
5 owner: team-orders # required: no owner, no resource
6 costCentre: retail
7spec:
8 environment: production
9 size: small # small | medium | large, from the catalogue
10 version: "16"
11 retentionDays: 14
12
13# Not exposed as parameters, because they are not decisions:
14# encryption at rest, private networking only, automated backups,
15# deletion protection, audit logging, maintenance window policy.

What is absent from spec is the design. Every field the platform does not expose is a decision it has already made on the team's behalf, and every field it does expose is one it accepts will vary. Getting that split wrong in either direction is how a catalogue becomes either useless or pointless.

What happens between the declaration and the resource

The steps below are what an approval used to be standing in for. Each one is faster than a human, runs on every request, and — unlike an approver — does not get tired at the fortieth request of the week.

A self-service request, end to end
  1. 1
    Declare

    Team commits the claim to its repository and opens a change.

    fails by Provisioning done out of band, leaving no reviewable artifact.

    evidence The claim exists in version control with an author.

  2. 2
    Validate

    Schema and catalogue check: is this a shape we model, with parameters in range?

    fails by Accepting free-form configuration, which is console access in YAML.

    evidence Invalid claims are rejected in seconds, with the reason.

  3. 3
    Evaluate policy

    Required tags, no public exposure, size within envelope, environment rules (Policy as Code).

    fails by Policy checked only after creation, when the violation already exists.

    evidence A deliberately non-compliant claim is refused before anything is created.

  4. 4
    Plan

    Compute what will actually change, including what will be replaced or destroyed (The Plan: Desired vs Current).

    fails by Applying without reading the plan, which is how a rename deletes a database.

    evidence The plan is attached to the change and read by the reviewer.

  5. 5
    Apply

    Platform identity provisions; the team's identity never could.

    fails by Teams holding provisioning credentials directly, so the interface is not the only path.

    evidence Cloud audit log shows the platform identity, referencing the change.

  6. 6
    Record

    Write owner, cost centre, module version and the originating change into the inventory.

    fails by Resource exists, provenance does not.

    evidence Every live resource resolves to a claim and an owner.

  7. 7
    Hand back

    Return connection details through workload identity or the secret store — never as a copied credential (Workload Identity).

    fails by Credentials pasted into a chat message and then into CI variables.

    evidence No static credential exists for the new resource.

  8. 8
    Reconcile

    Detect drift between declaration and reality on a schedule.

    fails by Nobody notices someone changed it in the console until an incident.

    evidence Scheduled plan reports no unexplained differences (Drift).

The failures that arrive on day two hundred

TOOL-SPECIFICThe destroy-on-rename behaviour is a property of tools that key resources by an address in state, such as Terraform. A tool that reconciles against live resource identity behaves differently, but every such tool has an equivalent operation that quietly means "delete and recreate" — find out which one it is before you meet it in production.

Self-service infrastructure rarely fails at provisioning. It fails months later, in ways that all trace back to something the request did not capture.

TriggerSymptomCauseResponse
A team is reorganisedResources with an owner tag that names a team that no longer existsOwnership captured once, never reconciledReconcile owner tags against the current org record; unowned means decommission or adopt
A misconfigured loop in a provisioning changeDozens of instances created in minutesNo quota on the shape or the environmentQuota per environment and per shape; the request fails loudly instead of succeeding expensively (The Automation Trap)
Someone fixes something in the console during an incidentThe next apply reverts the fix, at the worst possible timeEmergency change made outside the declarationExpect it, detect it, and require the change to be back-filled into the declaration (Break-Glass Access)
A module default is changedThe next unrelated apply on every consuming service proposes a large changeModules not versioned, so consumers float onto new defaultsPin module versions per consumer; upgrade deliberately (Dependency Pinning)
A service is decommissionedDatabase still running, still backed up, still billedDeletion never modelled as part of the interfaceModel decommission as a first-class claim removal with a retention window
A stateful resource is renamed in the declarationPlan proposes destroy and createIdentity of the resource is its name in stateRead the plan; move state deliberately rather than applying (Destructive Changes: What a Rename Really Does)

How to do it properly

Most important first.

  • Model a small catalogue of shapes with opinionated defaults, and expose only the parameters that genuinely vary between teams (Modules: Reuse Without Hiding).
  • Make the request a versioned artifact in the team's repository, reviewed the way code is, applied by the platform's identity (Infrastructure as Code).
  • Enforce the non-negotiables as policy evaluated on the declaration, before anything is created (Policy as Code).
  • Attach ownership, cost centre and environment at creation time as required fields; a resource that cannot be attributed should not be creatable.
  • Give every request a budget and a quota, so a mistake is bounded by a number rather than by whether anyone noticed.
  • Make deletion self-service too, with the same narrowing — and make it the platform's job to know what deleting will take with it (Destructive Changes: What a Rename Really Does).

How much can this affect

Every production change has a blast radius. Stated as a scale so it is comparable between changes rather than adjectival — and paired with what actually contains it, because a wide scope with a real containment mechanism is a different situation from a wide scope with none.

Blast radius if this is wrongOne region
One testEveryone
What contains it

A bad module or a runaway request is contained by quotas, per-environment credential scoping and policy evaluated before creation. Without those, the containment is the size of the account the platform identity can reach.

What can go wrong

Failure modes, including of the mitigation
  • The catalogue is too narrow, so most real needs fall outside it and the ticket queue reappears for everything interesting.
  • The catalogue is too wide, so it is effectively console access with extra steps and no defaults worth having.
  • The platform identity is over-privileged, making the provisioning pipeline the most dangerous credential in the organisation (Identity and Access Management (IAM)).
  • Resources are created and never reconciled, so declarations and reality diverge and nobody trusts either.
  • No quota, so one misconfigured loop provisions a hundred instances and the containment is a billing alert three days later (Cost Drivers).
  • Deletion is not modelled, so decommissioning is manual and therefore incomplete.
Misreads this invites
  • "Self-service means teams have cloud access." It means teams have a narrowed interface. Direct access is the thing self-service exists to avoid, because it is the version with no defaults and no record.
  • "If nobody approves the request, nothing is being checked." Policy, module defaults and quotas are all checks. They run in milliseconds, on every request, without a queue.
  • "We can add ownership tags later." Later means a fleet of resources you cannot attribute, and a manual archaeology project to fix it.

Operating it

Evidence is the signal, not the intention. Rollback is sometimes 'you cannot, and that is the point'.

How you know it worked
  • A team provisions a routine dependency without a human in the path, and the resource comes up with encryption, backups and private networking without anyone having chosen them.
  • A plan run against the live environment reports no unexplained differences.
  • Every resource can be traced to a declaration in a repository, with an owner.
  • A deliberate over-request hits a quota rather than succeeding.
How you get back
  • Provisioning must be reversible as a unit: a failed request removes exactly what it created rather than leaving fragments.
  • For stateful resources, reversal is not symmetric — destroying a database is not the inverse of creating one. Model deletion as a two-step with a retention window and a deliberate second action (Destructive Changes: What a Rename Really Does).
  • A bad module version can be rolled back for new requests immediately; existing resources created from it need migration, not rollback.
What to automate, and what stays human
  • Automate provisioning, defaults, tagging, quota enforcement and drift detection.
  • Automate the paperwork that used to justify the ticket: the ownership record, the cost attribution, the audit trail of who declared what (The Audit Trail).
  • Do not automate away the review of the *declaration* — it is code, and it gets reviewed like code. What is removed is the approval of each apply, not the reading of what is being asked for.
  • Keep destructive actions on stateful resources deliberately manual at the final step, and make that one of the very few places a human confirmation genuinely buys something.
What this costs
  • Modelling a shape properly is real work, so the catalogue grows slowly and there is always something not in it.
  • Opinionated defaults are wrong for someone, and that someone is usually the team with the most demanding workload.
  • The platform now holds the credentials that matter, concentrating risk in one place — which is easier to protect and worse to lose.

Where this applies

This domain is unusually tool- and organisation-dependent. These labels say what each claim is specific to, and what a different platform, provider or organisation does instead.

  • CLOUD-SPECIFICThe quota, tagging and account-boundary mechanisms differ by provider, and so does what a single over-privileged identity can reach. On a provider where account boundaries are cheap, per-team accounts are the strongest containment available; where they are expensive, the containment has to come from policy and scoped roles instead.
  • SCALE-SPECIFICWith one team and a handful of resources, a shared IaC repository and a person who runs the apply is self-service enough. The catalogue starts paying for itself when requests arrive faster than the credential holder can answer them without becoming the bottleneck.

Where the depth lives

This domain teaches delivery and operation, and hands the mechanism off to the domain that owns it.